#include "wasm/run.hpp"

#ifndef BUILD_OPTIONS_STRING
#define BUILD_OPTIONS_STRING "unknown"
#endif

std::string getBuildOptions() {
    return BUILD_OPTIONS_STRING;
}

void test(unsigned x) {
    Range<double> range(0, 1.0, 3, 2);

    std::println("test({})", x);
    std::println("range({})", range.getNext());
    std::println("range({})", range.getNext());
    std::println("range({})", range.getNext());
    std::println("range({})", range.getNext());
    std::println("range({})", range.getNext());
    std::println("range({})", range.getNext());
    std::println("range({})", range.getNext());
    std::println("range({})", range.getNext());
    std::println("range({})", range.getNext());
    std::println("range({})", range.getNext());

    std::println("{}", processType(0.5));
    std::println("{}", processType(range));

    // processDebug(range, 4.3, -3, range);
    // process2(range);
}

/**
 * @brief Function used to test performance in the browser
 */
int testPerformance() {
    e::Tensor<dtype, 4> t4(4, 16, 16, 4);
    auto data = t4.data();
    for (unsigned i = 0; i < t4.size(); i++) {
        data[i] = i;
    }
    auto [A, S, B] = split_truncate_theta<dtype>(t4, 20, 1e-10);
    e::Tensor<dtype, 4> t4reconstructedButNormalized = A.contract(diagonal(S).contract(B, e::array<iP, 1>{iP{1, 0}}), e::array<iP, 1>{iP{2, 0}});
    return t4reconstructedButNormalized.size();
}

#ifdef EMSCRIPTEN
EMSCRIPTEN_BINDINGS(run) {
    // see https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html#advanced-class-concepts
    // RESULTS
    ems::class_<Results<TfiModel>>("ResultsBaseTfi")
        .property("finalNorm", &Results<TfiModel>::finalNorm)
    ;
    ems::class_<Results<BhModel>>("ResultsBaseBh")
        .property("finalNorm", &Results<BhModel>::finalNorm)
    ;
    ems::class_<TfiResults, ems::base<Results<TfiModel>>>("ResultsTfi")
        .constructor<ems::val, double>()
        .property("finalStatesX", &TfiResults::finalStatesX, ems::return_value_policy::reference())
        .property("finalStatesZ", &TfiResults::finalStatesZ, ems::return_value_policy::reference())
    ;
    ems::class_<BoseHubbardResults, ems::base<Results<BhModel>>>("ResultsBh")
        .constructor<ems::val, double>()
        .property("finalStatesN", &BoseHubbardResults::finalStatesN, ems::return_value_policy::reference())
    ;
    // MODELS
    ems::class_<Model1D<dtype>>("Model1D")
        .smart_ptr<std::shared_ptr<Model1D<dtype>>>("Model1D")
    ;
    ems::class_<TfiModel, ems::base<Model1D<dtype>>>("ModelTfi")
        // .constructor<unsigned, double ,double>()
        .smart_ptr_constructor("ModelTfi", &std::make_shared<TfiModel, unsigned, double ,double>)
    ;
    ems::class_<BhModel, ems::base<Model1D<dtype>>>("ModelBh")
        // .constructor<unsigned, double, double, double, unsigned>()
        .smart_ptr_constructor("ModelBh", &std::make_shared<BhModel, unsigned, double, double, double, unsigned>)
    ;
    ems::class_<Range<double>>("DoubleRange")
        .constructor<double, double, unsigned, unsigned>()
    ;
    // MPS
    ems::class_<MPS<dtype>>("MPS");
    ems::function("initMpsSpinUp", &initMPS_spinUp<dtype>);
    ems::function("initMpsSpinRight", &initMPS_spinRight<dtype>);
    ems::function("initMpsNParticles", &initMPS_nParticles<dtype>);
    // TEBD
    ems::enum_<TEBD_TYPE>("TEBD_TYPE")
        .value("BRICKWALL", TEBD_TYPE::BRICKWALL)
        .value("SECOND_ORDER", TEBD_TYPE::SECOND_ORDER)
        ;
    ems::function("runTebdTfi", &runTEBD<TfiModel>);
    ems::function("runTebdEvolveTfi", &runTEBD_evolve<TfiModel, unsigned, Range<double>, Range<double>>);
    ems::function("runTebdPhaseDiagramTfi", &runTEBD_phaseDiagram<TfiModel, unsigned, Range<double>, Range<double>>);
    ems::function("runTebdBh", &runTEBD<BhModel>);
    ems::function("runTebdPhaseDiagramBh", &runTEBD_phaseDiagram<BhModel, unsigned, double, Range<double>, Range<double>, unsigned>);
    // DMRG
    ems::function("runDmrgTfi", &runDMRG<TfiModel>);
    ems::function("runDmrgPhaseDiagramTfi", &runDMRG_phaseDiagram<TfiModel, unsigned, Range<double>, Range<double>>);
    ems::function("runDmrgBh", &runDMRG<BhModel>);
    ems::function("runDmrgPhaseDiagramBh", &runDMRG_phaseDiagram<BhModel, unsigned, double, Range<double>, Range<double>, unsigned>);
    // OTHER
    ems::function("test", &test);
    ems::function("getBuildOptions", &getBuildOptions);
    ems::function("testPerformance", &testPerformance);
    // BASICS
    ems::register_vector<double>("stdVectorDouble");
    ems::register_vector<dtype>("stdVectorDType");
    ems::register_vector<std::vector<double>>("stdVectorDouble2D");
    ems::class_<dtype>("dtype")
        .function("real", ems::select_overload<double()const>(&dtype::real))
        .function("imag", ems::select_overload<double()const>(&dtype::imag))
    ;
};

#endif
