#include <gtest/gtest.h>
#include <json.hpp>
#include <chrono>
#include <httplib.h>
#include "Registration.h"
#include "OptimizerEvolutionary.h"
#include "ProcessorStandard.h"
#include <math.h>
#include <thread>

#include <windows.h>
#include <string>
#include <iostream>

using namespace mdd;
namespace TEST_ANSYS { 
    auto regi = Registration();


    std::string ExePath() {
        TCHAR buffer[MAX_PATH] = { 0 };
        GetModuleFileName(NULL, buffer, MAX_PATH);
        std::string::size_type pos = std::string(buffer).find_last_of("\\/");
        return std::string(buffer).substr(0, pos);
    }

    void evaluateIndividuum(OptimizerEvolutionary& optimizer, IModule::Ptr module_ptr, OptimizerEvolutionary::Permutation& ind) {
        auto start = std::chrono::steady_clock::now();
        json j;
        j = ind.dna[0];
        std::cout << j.dump() << ": " << optimizer.evaluateFitness(ind);
        std::cout << "\t| " << module_ptr->getOutput(0)->getValue()[0] << "\t| " << module_ptr->getOutput(1)->getValue()[0] << "\t| " << module_ptr->getOutput(2)->getValue()[0] << "\t| " << module_ptr->getOutput(3)->getValue()[0] << std::endl;
        std::cout << "\t| " << module_ptr->getOutput(4)->getValue()[0] << "\t| " << module_ptr->getOutput(5)->getValue()[0] << std::endl;
        auto end = std::chrono::steady_clock::now();
        std::cout << "\t| " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << "ms" << std::endl;
        std::cout << std::endl;
    }
    
    
    /*
    TEST(ModuleHTTP, test_ansys_sql_server) {
        std::cout << ExePath() << std::endl;
        auto sql = regi.generateModule("ModuleSQL");
        json config = sql->getConfiguration();
        config["configure"]["database"]["value"] = "../../../../../lib/test/db/materials.db";
        sql->configure(config);

        auto http = regi.generateModule("ModuleHTTP");// ("", "localhost", 8888);
        config = http->getConfiguration())["configure"];
        config["file"]["value"] = "";
        config["url"]["value"] = "localhost";
        config["port"]["value"] = 8888;
        http->configure(config.dump());
        //../../../lib/test/server/server-ansys.py

        sql->getInput(0)->setValue() = { 1 };
        
        for (size_t i = 0; i < http->getNumInputs(); i++)
        {
            std::cout << http->getInput(i)->getValue()[0] << std::endl;
        }
        for (size_t i = 2; i < sql->getNumOutputs(); i++)//0: ID 1: Name
        {
            http->getInput(i - 2)->connect(sql->getOutput(i));
        }
        http->getInput(http->getNumInputs() - 1)->setOptimizability(true);
        limits limit;
        limit.min = { 0, 0, 0, 0, 0, 0 };
        limit.max = { 90, 90, 90, 90, 90, 90 };
        limit.rule = "val[0] != val[5] || val[1] != val[4] || val[2] != val[3]";
        http->getInput(http->getNumInputs() - 1)->setLimits() = limit;
        http->getInput(http->getNumInputs() - 1)->setValue() = { 0,0,0,0,0,90 };

        http->getOutput(0)->setOptimizability(true);

        sql->update();
        //test SQL
        std::cout << "SQL-TEST" << std::endl;
        for (size_t i = 2; i < sql->getNumOutputs(); i++)
        {
            std::cout << sql->getOutput(i)->getValue()[0] << std::endl;
        }

        http->update();
        EXPECT_FLOAT_EQ(http->getOutput(3)->getValue()[0], 37.154861);
    }

    TEST(ModuleHTTP, test_configurations) {
        auto sql = regi.generateModule("ModuleSQL");
        json config = sql->getConfiguration();
        config["configure"]["database"]["value"] = "../../../../../lib/test/db/materials.db";
        sql->configure(config);

        auto http = regi.generateModule("ModuleHTTP");// ("", "localhost", 8888);
        config = http->getConfiguration();
        config["configure"]["file"]["value"] = "";
        config["configure"]["url"]["value"] = "localhost";
        config["configure"]["port"]["value"] = 8888;
        http->configure(config);
        //../../../lib/test/server/server-ansys.py

        sql->getInput(0)->setValue() = { 1 };

        for (size_t i = 0; i < http->getNumInputs(); i++)
        {
            std::cout << http->getInput(i)->getValue()[0] << std::endl;
        }
        for (size_t i = 2; i < sql->getNumOutputs(); i++)//0: ID 1: Name
        {
            http->getInput(i - 2)->connect(sql->getOutput(i));
        }
        http->getInput(http->getNumInputs() - 1)->setOptimizability(true);
        limits limit;
        limit.min = { 0, 0, 0, 0, 0, 0 };
        limit.max = { 90, 90, 90, 90, 90, 90 };
        limit.rule = "val[0] != val[5] || val[1] != val[4] || val[2] != val[3]";
        http->getInput(http->getNumInputs() - 1)->setLimits() = limit;

        http->getOutput(0)->setOptimizability(true);

        std::shared_ptr<ProcessorStandard> processor = std::make_shared<ProcessorStandard>();;
        processor->addModule(sql);
        processor->addModule(http);

        OptimizerEvolutionary optimizer;
        config = optimizer.getConfiguration());
        config["configure"]["converges"]["value"] = 3;
        config["configure"]["grow generation"]["value"] = 20;
        config["configure"]["max fitness"]["value"] = 0.13;
        config["configure"]["min generations"]["value"] = 5;
        optimizer.configure(config.dump());
        optimizer.connect(processor);
        optimizer.setEvaluation("out0");

        //auto res = optimizer.update();
        OptimizerEvolutionary::Individual ind;
        auto start = std::chrono::steady_clock::now();
        auto end = std::chrono::steady_clock::now();

        ind.dna = { { 0,0,0,0,0,0 } };
        evaluateIndividuum(optimizer, http, ind);
        
        ind.dna = { { 90,90,90,90,90,90 } };
        evaluateIndividuum(optimizer, http, ind);

        ind.dna = { { 90,0,0,0,0,0 } };
        evaluateIndividuum(optimizer, http, ind);

        ind.dna = { { 0,0,0,0,0,90 } };
        evaluateIndividuum(optimizer, http, ind);

        ind.dna = { { 0,90,90,90,90,90 } };
        evaluateIndividuum(optimizer, http, ind);

        ind.dna = { { 90,90,90,90,90,0 } };
        evaluateIndividuum(optimizer, http, ind);

        ind.dna = { { 90,0,0,0,90,0 } };
        evaluateIndividuum(optimizer, http, ind);

        ind.dna = { { 90,0,0,0,90,0 } };
        evaluateIndividuum(optimizer, http, ind);

        ind.dna = { { 90,0,0,0,90,0 } };
        evaluateIndividuum(optimizer, http, ind);

        ind.dna = { { 0,0,90,90,90,90 } };
        evaluateIndividuum(optimizer, http, ind);

        ind.dna = { { 0,0,90,90,90,90 } };
        evaluateIndividuum(optimizer, http, ind);

        EXPECT_TRUE(ind.fitness <= 100.0);
    }
    

    TEST(ModuleHTTP, test_ansys_server) {
        auto sql = regi.generateModule("ModuleSQL");
        json config = sql->getConfiguration();
        config["configure"]["database"]["value"] = "../../../../../lib/test/db/materials.db";
        sql->configure(config);

        auto http = regi.generateModule("ModuleHTTP");// ("", "localhost", 8888);
        config = http->getConfiguration();
        config["configure"]["file"]["value"] = "";
        config["configure"]["url"]["value"] = "localhost";
        config["configure"]["port"]["value"] = 8888;
        http->configure(config);
        //../../../lib/test/server/server-ansys.py

        sql->getInput(0)->setValue() = { 1 };

        for (size_t i = 0; i < http->getNumInputs(); i++)
        {
            std::cout << http->getInput(i)->getValue()[0] << std::endl;
        }
        for (size_t i = 2; i < sql->getNumOutputs(); i++)//0: ID 1: Name
        {
            http->getInput(i - 2)->connect(sql->getOutput(i));
        }
        http->getInput(http->getNumInputs() - 1)->setOptimizability(true);
        limits limit;
        limit.min = { 0, 0, 0, 0, 0, 0 };
        limit.max = { 90, 90, 90, 90, 90, 90 };
        limit.rule = "val[0] != val[5] or val[1] != val[4] or val[2] != val[3]";
        http->getInput(http->getNumInputs() - 1)->setLimits() = limit;

        http->getOutput(0)->setOptimizability(true);

        std::shared_ptr<ProcessorStandard> processor = std::make_shared<ProcessorStandard>();;
        processor->addModule(sql);
        processor->addModule(http);

        OptimizerEvolutionary optimizer;
        config = optimizer.getConfiguration();
        config["configure"]["converges"]["value"] = 3;
        config["configure"]["grow generation"]["value"] = 20;
        config["configure"]["max fitness"]["value"] = 0.13;
        config["configure"]["min generations"]["value"] = 5;
        optimizer.configure(config);
        optimizer.connect(processor);
        optimizer.setEvaluation("out0");
        auto start = std::chrono::steady_clock::now();
        auto res = optimizer.update();
        auto end = std::chrono::steady_clock::now();
        std::cout << optimizer.getBests()[0].fitness;
        std::cout << " | " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << "ms" << std::endl;
        std::cout << optimizer.evaluateFitness(optimizer.getBests()[0]) << std::endl;
        EXPECT_TRUE(optimizer.getBests()[0].fitness <= 100.0);
    }
    

    TEST(ModuleHTTP, test_configurations) {
        auto sql = regi.generateModule("ModuleSQL");
        json config = sql->getConfiguration();
        config["configure"]["database"]["value"] = "../../../../../lib/test/db/materials.db";
        sql->configure(config);
    
        auto http = regi.generateModule("ModuleHTTP");// ("", "localhost", 8888);
        config = http->getConfiguration();
        config["configure"]["file"]["value"] = "";
        config["configure"]["url"]["value"] = "localhost";
        config["configure"]["port"]["value"] = 8888;
        http->configure(config);
        //../../../lib/test/server/server-ansys.py
    
        sql->getInput(0)->setValue() = { 1 };
    
        for (size_t i = 0; i < http->getNumInputs(); i++)
        {
            std::cout << http->getInput(i)->getValue()[0] << std::endl;
        }
        for (size_t i = 2; i < sql->getNumOutputs(); i++)//0: ID 1: Name
        {
            http->getInput(i - 2)->connect(sql->getOutput(i));
        }
        http->getInput(http->getNumInputs() - 1)->setOptimizability(true);
        limits limit;
        limit.min = { 0, 0, 0, 0, 0, 0 };
        limit.max = { 90, 90, 90, 90, 90, 90 };
        limit.rule = "val[0] != val[5] || val[1] != val[4] || val[2] != val[3]";
        http->getInput(http->getNumInputs() - 1)->setLimits() = limit;
    
        http->getOutput(0)->setOptimizability(true);
        http->getOutput(4)->setOptimizability(true);
    
        std::shared_ptr<ProcessorStandard> processor = std::make_shared<ProcessorStandard>();;
        processor->addModule(sql);
        processor->addModule(http);
    
        OptimizerEvolutionary optimizer;
        config = optimizer.getConfiguration();
        config["configure"]["converges"]["value"] = 3;
        config["configure"]["grow generation"]["value"] = 20;
        config["configure"]["max fitness"]["value"] = 0.13;
        config["configure"]["min generations"]["value"] = 5;
        optimizer.configure(config);
        optimizer.connect(processor);
        optimizer.setEvaluation("2^(-35+out0)+4.2233*10^(-11)*exp(-out1^5/643634300000)*out1^5");
    
        //auto res = optimizer.update();
        OptimizerEvolutionary::Individual ind;
    
        ind.dna = { { 0,0,0,0,0,0 } };
        evaluateIndividuum(optimizer, http, ind);
    
        ind.dna = { { 90,90,90,90,90,90 } };
        evaluateIndividuum(optimizer, http, ind);
    
        ind.dna = { { 90,0,0,0,0,0 } };
        evaluateIndividuum(optimizer, http, ind);
    
        ind.dna = { { 0,0,0,0,0,90 } };
        evaluateIndividuum(optimizer, http, ind);
    
        ind.dna = { { 0,90,90,90,90,90 } };
        evaluateIndividuum(optimizer, http, ind);
    
        ind.dna = { { 0,0,90,90,90,90 } };
        evaluateIndividuum(optimizer, http, ind);
    
        ind.dna = { { 90,90,90,0,90,90 } };
        evaluateIndividuum(optimizer, http, ind);
    
        ind.dna = { { 0,0,0,90,0,0 } };
        evaluateIndividuum(optimizer, http, ind);
    
        ind.dna = { { 0,0,90,0,90,0 } };
        evaluateIndividuum(optimizer, http, ind);
    
        EXPECT_TRUE(ind.fitness <= 100.0);
    }

    TEST(ModuleHTTP, test_ansys_server) {
        auto sql = regi.generateModule("ModuleSQL");
        json config = sql->getConfiguration();
        config["configure"]["database"]["value"] = "../../../../../lib/test/db/materials.db";
        sql->configure(config);
    
        auto http = regi.generateModule("ModuleHTTP");// ("", "localhost", 8888);
        config = http->getConfiguration();
        config["configure"]["file"]["value"] = "";
        config["configure"]["url"]["value"] = "localhost";
        config["configure"]["port"]["value"] = 8888;
        http->configure(config);
        //../../../lib/test/server/server-ansys.py

        sql->getInput(0)->setValue() = { 1 };

        for (size_t i = 0; i < http->getNumInputs(); i++)
        {
            std::cout << http->getInput(i)->getValue()[0] << std::endl;
        }
        for (size_t i = 2; i < sql->getNumOutputs(); i++)//0: ID 1: Name
        {
            http->getInput(i - 2)->connect(sql->getOutput(i));
        }
        http->getInput(http->getNumInputs() - 1)->setOptimizability(true);
        limits limit;
        limit.min = { 0, 0, 0, 0, 0, 0 };
        limit.max = { 90, 90, 90, 90, 90, 90 };
        limit.rule = "val[0] != val[5] or val[1] != val[4] or val[2] != val[3]";
        http->getInput(http->getNumInputs() - 1)->setLimits() = limit;

        http->getOutput(0)->setOptimizability(true);
        http->getOutput(4)->setOptimizability(true);

        std::shared_ptr<ProcessorStandard> processor = std::make_shared<ProcessorStandard>();;
        processor->addModule(sql);
        processor->addModule(http);

        OptimizerEvolutionary optimizer;
        config = optimizer.getConfiguration();
        config["configure"]["converges"]["value"] = 3;
        config["configure"]["grow generation"]["value"] = 20;
        config["configure"]["max fitness"]["value"] = 0.13;
        config["configure"]["min generations"]["value"] = 5;
        optimizer.configure(config);
        optimizer.connect(processor);
        optimizer.setEvaluation("2^(-35+out0)+4.2233*10^(-11)*exp(-out1^5/643634300000)*out1^5");
        auto start = std::chrono::steady_clock::now();
        auto res = optimizer.update();
        auto end = std::chrono::steady_clock::now();
        std::cout << optimizer.getBests()[0].fitness;
        std::cout << " | " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << "ms" << std::endl;
        std::cout << optimizer.evaluateFitness(optimizer.getBests()[0]) << std::endl;
        EXPECT_TRUE(optimizer.getBests()[0].fitness <= 10.0);
    }
    //*/
}