#include "experiences/complexity-ea.h"

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

void study_complexity_of_ea_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 mu=1; mu<50; mu+=2) {
            if(displaying) std::cout << " n=" << n << ": mu=" << mu << std::endl;
            for(int lambda=1; lambda<50; lambda+=2) {
                EA algo(mu, lambda, PLUS, 1./n, randomizer);
                algo.set_problem(&mm);
                Statistic stat = avg_calls(&algo, 1000, -1, 1.9623414611);
                outfile << n << ";" << mu << ";" << lambda << ";" << stat.average << ";" << stat.alea << std::endl;
            }
        }
    }
}

void study_complexity_of_ea_filter(std::string input, std::string output, int filtered_n, int filtered_mu, int filtered_lda) {
    std::ifstream infile(input);
    std::ofstream outfile(output);

    std::string line;
    while(getline(infile, line)) {
        int n, mu, lda;
        double avg, alea;
        sscanf(line.c_str(), "%d;%d;%d;%lf;%lf", &n, &mu, &lda, &avg, &alea);

        if((filtered_n==0 || n==filtered_n) && (filtered_mu==0 || mu==filtered_mu) && (filtered_lda==0 || lda==filtered_lda)) {
            outfile << line << std::endl;
        }
    }

    std::cout << "Filtering finished !" << std::endl;
}

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

    double r;
    double s;
    double t;
    double u;

    double a;
    double b;
    double c;
    double d;

    double alpha;
    double beta;
    double gamma;
    double lambda;

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

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

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

        if(3 <= mu && mu <= 7) {
            if(3 <= lda && lda <= 7)
                datas[n].r += avg;
            else if(43 <= lda && lda <= 47)
                datas[n].s += avg;
        } else if(43 <= mu && mu <= 47) {
            if(3 <= lda && lda <= 7)
                datas[n].t += avg;
            else if(43 <= lda && lda <= 47)
                datas[n].u += avg;
        }
    }

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

        Data dat = datas[n];
        datas[n].a = (dat.s - dat.r) / (45. - 5.);
        datas[n].b = dat.s - 45.*datas[n].a;
        datas[n].c = (dat.u - dat.t) / (45. - 5.);
        datas[n].d = dat.u - 45.*datas[n].c;
    }

    // Make models for n fixed
    for(int n=n_initial; n<n_final; n++) {
        Data dat = datas[n];
        datas[n].alpha = (dat.c - dat.a) / (45. - 5.);
        datas[n].gamma = dat.a - 5*datas[n].alpha;
        datas[n].beta = (dat.d - dat.b) / (45. - 5.);
        datas[n].lambda = dat.b - 5*datas[n].beta;
    }
    for(int i=0, s=sdatas.size(); i<s; i++) {
        StoredData sd = sdatas[i];
        Data d = datas[sd.n];
        double prev = d.alpha*sd.mu*sd.lda + d.beta*sd.mu + d.gamma*sd.lda + d.lambda;

        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++) {
        StoredData sd = sdatas[i];
        Data d = datas[sd.n];
        double prev = d.alpha*sd.mu*sd.lda + d.beta*sd.mu + d.gamma*sd.lda + d.lambda;

        if(sd.n==5 && sd.mu==25)
            outfile << sd.n << ";" << sd.mu << ";" << sd.lda << ";" << sd.avg - sd.alea << ";" << sd.avg + sd.alea << ";" << prev << ";" << (int) (abs(sd.avg-prev) <= sd.alea) << std::endl;
    }

    std::ofstream out2file("modeling.csv");
    for(int n=5; n<30; n++) {
        Data d = datas[n];
        std::cout << n << ": " << d.alpha << "*mu*lda + " << d.beta << "*mu + " << d.gamma << "*lda + " << d.lambda << " => " << 100*d.nb_goods/(625.) << "%" << std::endl;
        out2file << n << ";" << d.alpha << ";" << d.beta << ";" << d.gamma << ";" << d.lambda << std::endl;
    }

    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;
    // Determine the performance of the model
    int global_nb_goods = 0;
    for(int i=0, s=sdatas.size(); i<s; i++) {
        StoredData 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.mu*sd.lda + beta_n*sd.mu + gamma_n*sd.lda + lambda_n;

        if(abs(sd.avg-prev) <= sd.alea)
            global_nb_goods++;
    }
    std::cout << "GLOBAL: " << 100*global_nb_goods/(625.*(25.-4.*ignoreSmall)) << "%" << std::endl;

    // Apply a (1+1)-EA to improve the model
    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);
            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++) {
                StoredData 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 = alpha_n*sd.mu*sd.lda + beta_n*sd.mu + 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;
            }
        }
    }
}
