#include <iostream>
#include <cstdlib>
#include <ctime>
#include <numeric>
#include <algorithm>
#include <vector>
#include <random>
#include <chrono>
#include <cmath>
#include <windows.h>

#include <limits>

#include "EA.h"
#include "RLS.h"
#include "analysis.h"

#include "scripts.h"

using namespace std;

int main() {
    int nb_colors = 16;
    int nb_cases = 16;

    srand(time(NULL));
    std::default_random_engine randomizer{static_cast<long unsigned int>(time(0))};

    auto start2 = start_chrono();
    RLS algo1;
    Statistic stat1 = avg_calls(algo1, MasterMind(nb_colors, nb_cases, randomizer), 4500);
    cout << "Average RLS: " << stat1.average << endl;
    EA algo2(1./nb_cases, 1, 1, randomizer);
    Statistic stat2 = avg_calls(algo2, MasterMind(nb_colors, nb_cases, randomizer), 4500);
    cout << "Average EA: " << stat2.average << endl;
    auto stop2 = stop_chrono();
    int elapsed_milliseconds2 = interval_chrono(start2, stop2);
    cout << " ... executed in " << elapsed_milliseconds2/1000. << " seconds" << endl;

    for(int u=0; u<3; u++)
        Beep(1568, 200);

    cout << "=========================" << endl;
    //optimal_proba(5,15, randomizer);
    double p_init=0.05;
    double p_final=0.5;
    double p_step=0.01;
    int nb_diff_proba = floor((p_final-p_init)/p_step);
    int exploration_phase = 1000;
    int exploitation_phase = 10000;
    double initial_value = 50;
    int nb_test = 20;

    for(int n=2; n<15; n++) {
        auto start_n = start_chrono();

        double* tab = new double[nb_diff_proba];
        for(int i=0; i<nb_diff_proba; i++)
            tab[i] = initial_value;
        double max_weight = initial_value;
        int max_weight_ind_proba = 0;
        double second_weight = initial_value;
        double second_weight_ind_proba = 1;

        for(int num_loop=1; second_weight + 2*initial_value > max_weight; num_loop++) {
            auto start_loop = start_chrono();
            int nb_solved_problems = 0;
            for(int k=0; k<20; k++) {
                double min_avg = -1;
                double min_proba;
                for(double p=p_init; p<p_final; p+=p_step) {
                    int ind = round((p - p_init)/p_step);
                    double execution_proba = 0;
                    if(ind > 0 && ind < nb_diff_proba-1)
                        execution_proba = ((tab[ind-1]+tab[ind]+tab[ind+1])/3)/max_weight;
                    else if(ind > 0)
                        execution_proba = ((tab[ind-1] + tab[ind])/2)/max_weight;
                    else if(ind < nb_diff_proba-1)
                        execution_proba = ((tab[ind] + tab[ind+1])/2)/max_weight;

                    execution_proba = (2*execution_proba > 1) ? -4*pow(execution_proba, 2)+8*execution_proba-3 : 0;
                    std::bernoulli_distribution distribution(execution_proba);

                    if(distribution(randomizer)) {
                        EA algo(p, 1, 1, randomizer);
                        Statistic stat = avg_calls(algo, MasterMind(n, n, randomizer), nb_test);
                        double avg = stat.average;

                        if(min_avg < 0 || min_avg > avg) {
                            min_avg = avg;
                            min_proba = p;
                        }

                        nb_solved_problems += nb_test;
                    }
                }
                int ind_p = round((min_proba - p_init)/p_step);
                tab[ind_p]++;

                if(tab[ind_p] > max_weight) {
                    max_weight = tab[ind_p];
                    max_weight_ind_proba = ind_p;
                }
            }
            auto stop_loop = stop_chrono();
            int elapsed_milliseconds_loop = interval_chrono(start_n, stop_loop);

            second_weight = -1;
            for(int i=0; i<nb_diff_proba; i++)
                if(i != max_weight_ind_proba)
                    if(second_weight < 0 || second_weight < tab[i]) {
                        second_weight = tab[i];
                        second_weight_ind_proba = i;
                    }

            cout << "\r";
            cout << "n=" << n << ", num_loop=" << num_loop << ": ";
            cout << p_init+max_weight_ind_proba*p_step << " (" << max_weight << "), ";
            cout << p_init+second_weight_ind_proba*p_step << " (" << second_weight << "), ";
            cout << "executed in " <<elapsed_milliseconds_loop/1000. << " seconds [" << nb_solved_problems << "]";
            cout << "    ";
        }

        //cout << endl;
        //for(int i=0; i<nb_diff_proba; i++)
        //    cout << p_init+i*p_step << ": " << tab[i]-initial_value << endl;

        auto stop_n = stop_chrono();
        int elapsed_milliseconds_n = interval_chrono(start_n, stop_n);
        cout << '\r' << "n=" << n << ", p_best=" << p_init+max_weight_ind_proba*p_step << ", executed in " << elapsed_milliseconds_n/1000. << " seconds";
        cout << "                                       " << endl;

        for(int u=0; u<3; u++)
            Beep(1568, 200);
    }

    cout << "FINISHED !" << endl;
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    std::cin.get();

    return 0;

    cout << "=========================" << endl;
    for(int n=5; n<6; n++) {
        vector<double> avg_list;
        vector<double> lambdas;

        int lambda_init = 1;
        int lambda_final = 20;
        int nb_tests = 10000;

        auto start = start_chrono();
        for(int lambda=lambda_init; lambda<lambda_final; lambda++) {
            EA algo(1./n, lambda, 1, randomizer);
            Statistic stat = avg_calls(algo, MasterMind(n, n, randomizer), nb_tests);
            double avg = stat.average;
            avg_list.push_back(avg);
            lambdas.push_back(lambda);
            cout << " -> lambda=" << lambda << ", nbcalls=" << avg << endl;
        }
        auto stop = stop_chrono();
        int elapsed_milliseconds = interval_chrono(start, stop);

        int min_ind = 0;
        for(int i=1; i<avg_list.size(); i++)
            if(avg_list[min_ind] > avg_list[i])
                min_ind = i;
        double lambda_best = lambdas[min_ind];
        cout << "n=" << n << ": " << lambda_best << " in " << elapsed_milliseconds/1000. << " seconds " << "(" << nb_tests*(lambda_final-lambda_init) << " problems)" << endl;
    }

    return 0;
}
