#include "experiences/complexity-ga.h"

void study_complexity_of_ga(std::default_random_engine& randomizer, int option, int argc, char** argv) {
    switch(option) {
    case 0:
        {
            int n_initial = (argc>=4) ? atoi(argv[3]) : 10;
            std::stringstream ss;
            ss << "complexity-ga-n-" << n_initial << ".csv";
            study_complexity_of_ga_sample(randomizer, ss.str(), n_initial, 200, 10, true);
            break;
        }
    case 1:
        {
            int n_initial = (argc>=4) ? atoi(argv[3]) : 3;
            std::stringstream ss;
            ss << "complexity-ga-n-" << n_initial << ".csv";
            study_complexity_of_ga_global_sample(randomizer, ss.str(), n_initial, 30, 1, true);
            break;
        }
    case 2:
        {
            study_complexity_of_ga_treat(randomizer, argv[3], atoi(argv[4]), atoi(argv[5]));
            break;
        }
    }
}

void study_complexity_of_ga_sample(std::default_random_engine& randomizer, std::string filename, int n_initial, int n_final, int n_step, bool displaying) {
    std::ofstream outfile(filename);
    for(int n=n_initial; n<=n_final; n+=n_step) {
        if(displaying) std::cout << " - n=" << n << std::endl;
        MasterMind mm(n, n, randomizer);
        GA algo(1, 1, PLUS, 1./n, cr(n, 1), UNIFORM_CROSSOVER, GA_V1, randomizer);
        algo.set_problem(&mm);
        Statistic stat = avg_calls(&algo, 100, -1, 1.9842169516);
        outfile << n << ";" << stat.average-stat.alea << ";" << stat.average+stat.alea << std::endl;
    }
}

void study_complexity_of_ga_global_sample(std::default_random_engine& randomizer, std::string filename, int n_initial, int n_final, int n_step, bool displaying) {
    std::ofstream outfile(filename);
    for(int n=n_initial; n<n_final; n+=n_step) {
        MasterMind mm(n, n, randomizer);
        for(int lambda=1; lambda<50; lambda++) {
            if(displaying) std::cout << " - n=" << n << ", lambda=" << lambda << std::endl;
            GA algo(lambda, lambda, PLUS, 1./n, cr(n, lambda), UNIFORM_CROSSOVER, GA_V1, randomizer);
            algo.set_problem(&mm);
            Statistic stat = avg_calls(&algo, 1000, -1, 1.9623414611);
            outfile << n << ";" << lambda << ";" << stat.average << ";" << stat.alea << std::endl;
        }
    }
}

struct Data {
    Data() : r{0}, s{0}, nb_goods{0} {};

    double r;
    double s;

    double a;
    double b;

    int nb_goods;
};
struct Stored2Data {
    Stored2Data(int n0, int lda0, double avg0, double alea0) :
        n{n0}, lda{lda0}, avg{avg0}, alea{alea0} {};
    int n, lda;
    double avg, alea;
};

void study_complexity_of_ga_treat(std::default_random_engine& randomizer, std::string filename, int n_initial, int n_final) {
    Data datas[n_final];
    std::vector<Stored2Data> sdatas;

    // Load datas
    std::ifstream infile(filename);
    std::string line;
    while(getline(infile, line)) {
        int n, lda;
        double avg, alea;
        sscanf(line.c_str(), "%d;%d;%lf;%lf", &n, &lda, &avg, &alea);
        sdatas.push_back(Stored2Data(n, lda, avg, alea));

        if(4 <= lda && lda <= 6)
            datas[n].r += avg;
        else if(44 <= lda && lda <= 46)
            datas[n].s += avg;
    }

    // Make simplified regression
    for(int n=n_initial; n<n_final; n++) {
        datas[n].r /= 3.;
        datas[n].s /= 3.;

        Data dat = datas[n];
        datas[n].a = (dat.s - dat.r) / (45. - 5.);
        datas[n].b = dat.s - 45.*datas[n].a;
    }
    for(int i=0, s=sdatas.size(); i<s; i++) {
        Stored2Data sd = sdatas[i];
        Data d = datas[sd.n];
        double prev = d.a*sd.lda + d.b;

        if(abs(sd.avg-prev) <= sd.alea)
            datas[sd.n].nb_goods++;
    }

    std::ofstream outfile("filtered.csv");
    for(int i=0, s=sdatas.size(); i<s; i++) {
        Stored2Data sd = sdatas[i];
        Data d = datas[sd.n];
        double prev = d.a*sd.lda + d.b;

        if(sd.n==21)
            outfile << sd.n << ";" << sd.lda << ";" << sd.avg - sd.alea << ";" << sd.avg + sd.alea << ";" << prev << std::endl;
    }

    std::ofstream out2file("modeling.csv");
    for(int n=n_initial; n<n_final; n++) {
        Data d = datas[n];
        std::cout << n << ": " << d.a << "*lda + " << d.b << " => " << 100*d.nb_goods/(50.) << "%" << std::endl;
        out2file << n << ";" << d.a << ";" << d.b << std::endl;
    }
    return;

    std::vector<double> coeffs({
        0.0045748,
        -0.0325175,
        1.4444,
        -3.83779,
        1.03815,
        -2.99155,
        2.52103,
        -6.58785,
        16.965
    });

    bool ignoreSmall = true;

    int global_nb_goods = 0;
    for(int i=0, s=sdatas.size(); i<s; i++) {
        Stored2Data sd = sdatas[i];
        int n = sd.n;

        if(n<9 && ignoreSmall) continue;

        double alpha_n =(coeffs[0]*n + coeffs[1])*log(n);
        double beta_n = (coeffs[2]*n+coeffs[3])*log(n);
        double gamma_n = coeffs[4]*n + coeffs[5];
        double lambda_n = (coeffs[6]*pow(n, 2) + coeffs[7]*n + coeffs[8])*log(n);

        double prev = alpha_n*sd.lda + lambda_n;

        if(abs(sd.avg-prev) <= sd.alea)
            global_nb_goods++;
//        else
//            cout << " - " << n << ", " << sd.mu << ", " << sd.lda << ": " << prev << " not in " << sd.avg << " +- " << sd.alea << " (" << (int) (sd.avg < prev) << ")" << endl;
    }
    std::cout << "GLOBAL: " << 100*global_nb_goods/(625.*(25.-4.*ignoreSmall)) << "%" << std::endl;

    std::binomial_distribution<int> mutation = std::binomial_distribution<int>(8, 3./8);
    std::vector<int> permutation(9);
    iota(permutation.begin(), permutation.end(), 0);
    std::bernoulli_distribution direction(1./2);

    std::ofstream savefile("coeffs.csv", std::ios::app);

    for(int depth=-5; depth<12; depth++) {
        std::cout << std::endl << " !!! DEPTH = " << depth;
        for(int stag=0; stag<10000; stag++) {
            int r = 1+mutation(randomizer);
            std::vector<double> coeffs2(coeffs);

            std::shuffle(std::begin(permutation), std::end(permutation), randomizer);
            //cout << "[" << permutation[0] << "," << permutation[1] << "]";
            for(int i=0; i<r; i++)
                coeffs2[permutation[i]] *= 1 + pow(-1, (int) direction(randomizer))*(0.2/pow(2, abs(depth)));

            // Evaluate
            int nb_goods = 0;
            for(int i=0, s=sdatas.size(); i<s; i++) {
                Stored2Data sd = sdatas[i];
                int n = sd.n;

                if(n<9 && ignoreSmall) continue;

                double alpha_n = (coeffs2[0]*n + coeffs2[1])*log(n);
                double beta_n = (coeffs2[2]*n+coeffs2[3])*log(n);
                double gamma_n = coeffs2[4]*n + coeffs2[5];
                double lambda_n = (coeffs2[6]*pow(n, 2) + coeffs2[7]*n + coeffs2[8])*log(n);

                double prev = gamma_n*sd.lda + lambda_n;

                if(abs(sd.avg-prev) <= sd.alea)
                    nb_goods++;
            }

            // Selection
            if(nb_goods >= global_nb_goods) {
                coeffs = coeffs2;
                if(nb_goods > global_nb_goods) {
                    stag = 0;
                    double pourcent = 100*nb_goods/(625.*(25.-4.*ignoreSmall));
                    std::cout << std::endl << "Improvement: " << pourcent << " ";
                    // Saving
                    savefile << pourcent << ";";
                    for(int i=0, s=coeffs.size(); i<s; i++)
                        savefile << coeffs[i] << ";";
                    savefile << std::endl;
                }
                global_nb_goods = nb_goods;
            }
        }
    }
}
