#include "analysis.h"

std::chrono::time_point<std::chrono::system_clock> start_chrono() {
    return std::chrono::system_clock::now();
}
std::chrono::time_point<std::chrono::system_clock> stop_chrono() {
    return std::chrono::system_clock::now();
}
int interval_chrono(std::chrono::time_point<std::chrono::system_clock> start, std::chrono::time_point<std::chrono::system_clock> stop) {
    return std::chrono::duration_cast<std::chrono::milliseconds>(stop-start).count();
}

double student_law_quartiles[21] = {
    0.,
    12.7062047362,
    4.3026527297,
    3.1824463053,
    2.7764451052,
    2.5705818356,
    2.4469118511,
    2.3646242516,
    2.3060041352,
    2.2621571628,
    2.228138852,
    2.2009851601,
    2.1788128297,
    2.1603686565,
    2.1447866879,
    2.1314495456,
    2.1199052992,
    2.1098155778,
    2.1009220402,
    2.0930240544,
    2.0859634473
};

Statistic avg_calls(SearchHeuristic* algo, int nb, double abortion_limit, double student_law_quartile) {
    std::vector<double> data;
    Statistic stat;
    for(int i=0; i<nb; i++) {
        // Initialize algorithm
        if(abortion_limit > 0) {
            double lim = nb*abortion_limit-stat.sum + 1;
            if(lim <= 0)  lim = 1;
            algo->set_abortion_limit(lim);
        }
        // Execute algorithm on the problem
        Result res = algo->run();
        delete[] res.solution;
        int nb_calls = res.nb_calls;

        // Update statistics
        data.push_back(nb_calls);
        stat.sum += nb_calls;
        stat.deviation += pow(nb_calls, 2);

        if(i == 0 || nb_calls > stat.maximal)
            stat.maximal = nb_calls;
        if(i == 0 || nb_calls < stat.minimal)
            stat.minimal = nb_calls;

        // Verify if it was aborted
        if(res.has_been_aborted) {
            nb = i+1;
            break;
        }
    }

    // Calculate some statistics
    stat.nb = nb;
    stat.average = stat.sum / nb;
    stat.deviation = sqrt(stat.deviation/nb - pow(stat.average, 2));
    stat.no_bias_deviation = sqrt((double) nb/(nb-1)) * stat.deviation;
    if(student_law_quartile > 0)
        stat.alea = stat.no_bias_deviation * student_law_quartile / sqrt(nb);
    else
        stat.alea = (nb-1 <= 20) ? stat.no_bias_deviation * student_law_quartiles[nb-1] / sqrt(nb) : -1;
    return stat;
}

Statistic* avg_evolution(SearchHeuristic* algo, int max_evolution, int nb_tests) {
    Statistic* values = new Statistic[max_evolution];

    std::vector<Point> record;
    algo->set_record(&record);

    for(int i=0; i<nb_tests; i++) {
        Result res = algo->run();
        delete[] res.solution;

        Point p;
        int previous_nb_calls = -1;
        double previous_value = 0;
        for(int j=0, l=record.size(); j<=l; j++) {
            if(j < l)   p = record[j];
            else        p.nb_calls = max_evolution-1;

            int nb_calls = std::min(p.nb_calls, max_evolution-1);
            for(int k=previous_nb_calls+1; k<=nb_calls; k++) {
                if(k == nb_calls) {
                    previous_nb_calls = nb_calls;
                    previous_value = p.value;
                }
                values[k].nb++;
                values[k].sum += previous_value;
                values[k].deviation += pow(previous_value, 2);
            }
        }
    }
    for(int i=0; i<max_evolution; i++) {
        values[i].average = values[i].sum / values[i].nb;
        values[i].deviation = sqrt(values[i].deviation/values[i].nb - pow(values[i].average, 2));
        values[i].no_bias_deviation = sqrt((double) values[i].nb/(values[i].nb-1)) * values[i].deviation;
        values[i].alea = values[i].no_bias_deviation * 2. / sqrt(values[i].nb);
    }
    return values;
}

Statistic* derivate(Statistic* curve, unsigned int length) {
    Statistic* dcurve = new Statistic[length-1];
    for(int i=0; i<length-1; i++) {
        Statistic p1 = curve[i];
        Statistic p2 = curve[i+1];

        dcurve[i].nb = p2.nb - p1.nb;
        dcurve[i].sum = p2.sum - p1.sum;
        dcurve[i].average = p2.average - p1.average;
        dcurve[i].alea = p2.alea - p1.alea;
        dcurve[i].maximal = p2.maximal - p1.maximal;
        dcurve[i].median = p2.median - p1.median;
        dcurve[i].minimal = p2.minimal - p1.minimal;
        dcurve[i].deviation = p2.deviation - p1.deviation;
        dcurve[i].no_bias_deviation = p2.no_bias_deviation - p1.no_bias_deviation;
    }
    return dcurve;
}

Statistic* compress(Statistic* curve, unsigned int length) {
    Statistic* ccurve = new Statistic[(int) ceil(length/2.)-1];
    for(int i=0; 2*i+2<length; i++) {
        Statistic p1 = curve[2*i];
        Statistic p2 = curve[2*i+1];
        Statistic p3 = curve[2*i+2];

        ccurve[i].nb = p1.nb/2. + p2.nb + p3.nb/2.;
        ccurve[i].sum = p1.sum/2. + p2.sum + p3.sum/2.;
        ccurve[i].average = p1.average/2. + p2.average + p3.average/2.;
        ccurve[i].alea = p1.alea/2. + p2.alea + p3.alea/2.;
        ccurve[i].maximal = p1.maximal/2. + p2.maximal + p3.maximal/2.;
        ccurve[i].median = p1.median/2. + p2.median + p3.median/2.;
        ccurve[i].minimal = p1.minimal/2. + p2.minimal + p3.minimal/2.;
        ccurve[i].deviation = p1.deviation/2. + p2.deviation + p3.deviation/2.;
        ccurve[i].no_bias_deviation = p1.no_bias_deviation/2. + p2.no_bias_deviation + p3.no_bias_deviation/2.;
    }
    return ccurve;
}

Statistic* smooth(Statistic* curve, unsigned int length) {
    Statistic* scurve = new Statistic[length];
    for(int i=1; i<length-1; i++) {
        Statistic p1 = curve[i-1];
        Statistic p2 = curve[i];
        Statistic p3 = curve[i+1];

        scurve[i].nb = (p1.nb + 2*p2.nb + p3.nb)/4.;
        scurve[i].sum = (p1.sum + 2*p2.sum + p3.sum)/4.;
        scurve[i].average = (p1.average + 2*p2.average + p3.average)/4.;
        scurve[i].alea = (p1.alea + 2*p2.alea + p3.alea)/4.;
        scurve[i].maximal = (p1.maximal + 2*p2.maximal + p3.maximal)/4.;
        scurve[i].median = (p1.median + 2*p2.median + p3.median)/4.;
        scurve[i].minimal = (p1.minimal + 2*p2.minimal + p3.minimal)/4.;
        scurve[i].deviation = (p1.deviation + 2*p2.deviation + p3.deviation)/4.;
        scurve[i].no_bias_deviation = (p1.no_bias_deviation + 2*p2.no_bias_deviation + p3.no_bias_deviation)/4.;
    }
    return scurve;
}

