#include "ParameterOptimizer.h"

ParameterOptimizer::ParameterOptimizer(std::default_random_engine& randomizer) : m_randomizer{randomizer}, m_saving_in_csvfile{false} {
    // ctor
}

ParameterOptimizer::~ParameterOptimizer() {
    //dtor
}

std::string ParameterOptimizer::display_context(unsigned int num_context) {
   std::stringstream ss;
   ss << "num_context=" << num_context;
   return ss.str();
}

void ParameterOptimizer::set_nb_contexts(unsigned int nb_contexts) {
    m_nb_contexts = nb_contexts;
}

void ParameterOptimizer::set_study_interval(double initial_parameter, double final_parameter, double parameter_step) {
    m_initial_parameter = initial_parameter;
    m_final_parameter = final_parameter;
    m_parameter_step = parameter_step;
}

void ParameterOptimizer::add_test_wave(int nb_tests, double student_law_quartile, int depth) {
    m_waves.push_back(TestWave(nb_tests, student_law_quartile, depth));
}

std::string ParameterOptimizer::get_context_for_csvfile(unsigned int num_context) {
   return std::to_string(num_context);
}

void ParameterOptimizer::save_run(std::string filename) {
    m_csvfile_name = filename;
    m_saving_in_csvfile = true;
}

std::vector<ConfidenceInterval> ParameterOptimizer::run_step(int num_step, bool displaying) {
    return run_step(num_step, 0, m_nb_contexts, m_initial_parameter, m_final_parameter, m_parameter_step, displaying);
}

std::vector<ConfidenceInterval> ParameterOptimizer::run_step(int num_step, int first_context, int nb_explored_contexts, double initial_parameter, double final_parameter, double parameter_step, bool displaying) {
    int nb_different_parameters = floor((final_parameter-initial_parameter)/parameter_step);

    std::vector<ConfidenceInterval> res;
    for(unsigned int num_context=first_context; num_context<first_context+nb_explored_contexts; num_context++) {
        auto start_context = start_chrono();

        Statistic stats[nb_different_parameters];
        double thresold = 0;
        double thresold_ind = 0;
        // Realize the experiences
        for(int ind=0; ind<nb_different_parameters; ind++) {
            double parameter = initial_parameter+ind*parameter_step;

            SearchHeuristic* algo = get_context(num_context, parameter, m_randomizer);
            stats[ind] = avg_calls(algo, m_waves[num_step].nb_tests, -1, m_waves[num_step].slq);
            double potential_thresold = stats[ind].average + stats[ind].alea;
            if(ind == 0 || thresold > potential_thresold) {
                thresold = potential_thresold;
                thresold_ind = ind;
            }
            delete algo->get_problem();
            delete algo;

            auto stop_loop = stop_chrono();
            int elapsed_milliseconds_loop = interval_chrono(start_context, stop_loop);
            if(displaying) std::cout << '\r' << display_context(num_context) << ", parameter=" << parameter << "... executed in " << elapsed_milliseconds_loop/1000. << " s";
        }

        // Calculate the confidence interval of 95%
        if(displaying) std::cout << '\r' << display_context(num_context) << ". Thresold=" << thresold << ". Keep best... ";
        int min_possible = -1;
        int max_possible = -1;
        for(int i=0; i<nb_different_parameters; i++) {
            double min_bound = stats[i].average - stats[i].alea;

            if(min_possible < 0 && min_bound < thresold)
                min_possible = i;
            if(min_bound < thresold)
                max_possible = i;
        }
        double min_parameter = initial_parameter+min_possible*parameter_step;
        double max_parameter = initial_parameter+max_possible*parameter_step;
        double thresold_parameter = initial_parameter+thresold_ind*parameter_step;

        auto stop_context = stop_chrono();
        int elapsed_milliseconds_n = interval_chrono(start_context, stop_context);
        if(displaying) std::cout << "Interval=[" << min_parameter << ", " << max_parameter << "]" << std::endl;
        if(displaying) std::cout << "... executed in " << elapsed_milliseconds_n/1000. << " seconds ";
        if(displaying) std::cout << "with " << m_waves[num_step].nb_tests*nb_different_parameters << " resolutions" << std::endl;

        ConfidenceInterval ci(min_parameter, thresold_parameter, max_parameter, thresold);
        res.push_back(ci);
    }

    return res;
}

std::vector<ConfidenceInterval> ParameterOptimizer::run(bool displaying) {
    unsigned int nb_waves = m_waves.size();

    std::vector<ConfidenceInterval> res;
    for(unsigned int num_context=0; num_context<m_nb_contexts; num_context++) {
        if(displaying) std::cout << display_context(num_context) << ": ";
        if(displaying && nb_waves>1) std::cout << std::endl;

        std::vector<ConfidenceInterval> cis;
        int nb_tests = 0;
        for(unsigned int num_wave=0; num_wave<nb_waves; num_wave++) {
            int depth = m_waves[num_wave].depth;
            double delta = (num_wave==0) ? 0 : m_parameter_step/pow(2, m_waves[num_wave-1].depth);

            double initial_parameter = (num_wave == 0) ? m_initial_parameter : cis[0].down - delta;
            double final_parameter = (num_wave == 0) ? m_final_parameter : cis[0].up + delta;
            if(initial_parameter < m_initial_parameter)
                initial_parameter = m_initial_parameter;
            if(final_parameter > m_final_parameter)
                final_parameter = m_final_parameter;


            if(displaying)
                if(nb_waves>1)  std::cout << " - wave=" << num_wave+1 << ": ";
            cis = run_step(num_wave, num_context, 1, initial_parameter, final_parameter, m_parameter_step/pow(2, depth), false);
            nb_tests += m_waves[num_wave].nb_tests;
            if(displaying) {
                if(nb_waves>1)  std::cout << "["<< cis[0].down <<","<< cis[0].up <<"]" << std::endl;
                else            std::cout << "Interval=["<< cis[0].down <<","<< cis[0].up <<"]" << std::endl;
            }
        }
        res.push_back(cis[0]);

        if(m_saving_in_csvfile) {
            std::ofstream probafile(m_csvfile_name, std::ios::app);
            probafile << get_context_for_csvfile(num_context) << ";" << cis[0].down << ";" << cis[0].mean << ";" << cis[0].up << ";" << nb_tests << std::endl;
        }
    }

    return res;
}
