#include "runappcecoptimization.h"
#include "ext.h"
#include "../DE/ZADE/zade.h"
#include "../challenges/cec2013rpsoo/initcec2013.h"

#include <iostream>
#include <vector>
#include <iomanip>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <iostream>
using namespace std;

int currentNF;

int NFUNCS;
int NRUNS;
int scale;
int MAX_NFEVALS[6];
int funNr;

// check if some limiting parameters on CEC testbed is supplied
RunAppCECOptimization::RunAppCECOptimization(int &argc, char**&argv) {
    isCEC2013 = true;
    for (int i = 1; i < argc; i++) if (strcmp(argv[i], "CEC2013") == 0) isCEC2013 = true;

    if (isCEC2013) {
        NFUNCS = 28;
        NRUNS = 51;
        scale = 10000;
        MAX_NFEVALS[0] = cec2013_D[0]*scale;
        MAX_NFEVALS[1] = cec2013_D[1]*scale;
        MAX_NFEVALS[2] = cec2013_D[2]*scale;
        MAX_NFEVALS[3] = 200000; // fixed, for timing
        MAX_NFEVALS[4] = 200000; // fixed, for timing
        MAX_NFEVALS[5] = 200000; // fixed, for timing
    }

#ifdef EXT_RUNCECINIT_CUTPOINT
    ext_runCECInit_cutpoint(argc, argv); // include possible extensions  (none)
#endif


    // default running settings: run everything
    funStart = 1;
    funEnd = funStart+(NFUNCS-1);
    nfStart = 0;
    nfEnd = 2;
    rniStart = 1;
    rniEnd = NRUNS;
    DEstrategy = 0; // the default DE/rand/1/bin
    DEstrategyOptions = 0;

    for (int i = 1; i < argc; i++) {
       if (argc > i+2 && strcmp(argv[i], "funcs") == 0) {
            funStart = atoi(argv[i+1]);
            funEnd = atoi(argv[i+2]);
        }

        if (argc > i+2 && strcmp(argv[i], "fesn") == 0) {
            nfStart = atoi(argv[i+1]);
            nfEnd = atoi(argv[i+2]);
        }

        if (argc > i+2 && strcmp(argv[i], "runs") == 0) {
            rniStart = atoi(argv[i+1]);
            rniEnd = atoi(argv[i+2]);
        }

        if (argc > i+1 && strcmp(argv[i], "strat") == 0) {
            DEstrategy = atoi(argv[i+1]);
        }

        if (argc > i+1 && strcmp(argv[i], "stratOpts") == 0) {
            DEstrategyOptions = atoi(argv[i+1]);
        }
    }
}

RunAppCECOptimization::~RunAppCECOptimization() {
}

void RunAppCECOptimization::runOptimizationOnAllCECFunctions() {
#ifdef EXT_RUNCECALLFUNCS_CUTPOINT
    ext_runCECAllFunctions_cutpoint(isCEC2013); // include possible extensions  (none)
#endif

    using namespace std;
    if (isCEC2013) cout << "Optimizing CEC2013 F";

    cout << funStart << " to F" << funEnd
         << " with FESN = " << nfStart << " (" << MAX_NFEVALS[nfStart] << ")"
         << " to " << nfEnd  << " (" << MAX_NFEVALS[nfEnd]   << ")"
         << ", runs " << rniStart << " to " << rniEnd
         << " using strategy " << DEstrategy
         << " with parameters " << DEstrategyOptions << endl;

    for (int fun = funStart; fun <= funEnd; fun++)
        for (int NF = nfStart; NF <= nfEnd; NF++)
            reportCECAssessmentForOneFunction(fun, NF);
}

void RunAppCECOptimization::reportCECAssessmentForOneFunction(int funNr, int NF) {
    ::currentNF=NF; // signaling D to CEC2013
    using namespace std;
    vector<double> bestsAtTheEnd;

    clock_t begin=clock();
    optimizeOneCECFunction(funNr, MAX_NFEVALS[NF], bestsAtTheEnd);
    clock_t end = clock();
    double time = diffclock_min(end, begin);

    printFunctionStatistics(funNr, NF, bestsAtTheEnd, time);
}

void RunAppCECOptimization::optimizeOneCECFunction(int funNr, int max_nfevals, std::vector<double> &bestsAtTheEnd) {
    //if (funNr == 3 && !isCEC2013) max_nfevals = 0; // HACK!

    for (int RNi = rniStart; RNi <= rniEnd; RNi++) { // give work
        cerr << "fun " << funNr << " @ " << max_nfevals << " RNi " << RNi << " " << flush;
        bestsAtTheEnd.push_back(workerOptimizeCECFunction(funNr, max_nfevals, RNi));
        if (rniStart!=1 || rniEnd!=NRUNS)
            cout << endl << "current F" << funNr
                 << " RNi " << RNi
                 << "\tFES " << max_nfevals
                 << "\tb " << bestsAtTheEnd.back() << endl;
    }
}

int currRNi;
double RunAppCECOptimization::workerOptimizeCECFunction(int funNr, int max_nfevals, int RNi) {
    jDEtevc2006::EvaluateFun evaluate = &evaluateCEC2013;
    ::funNr = funNr; // for evaluate()
    ::currRNi = RNi;

    int D = nfStart <= 2 ? max_nfevals / 10000 : cec2013_D[nfStart-3]; // for timing
    int C = 0;

#ifdef EXT_RUNCECWORKERPREINIT_CUTPOINT
    ext_runCECworkerPreInit_cutpoint(isCEC2013, C, D); // include possible extensions  (none)
#endif


    double *x          = new double[D  ];
    double *xmin       = new double[D  ];
    double *xmax       = new double[D  ];
    double *evaluation = new double[1+C];

#ifdef EXT_RUNCECWORKERPOSTINIT_CUTPOINT
    ext_runCECworkerPreInit_cutpoint(isCEC2013, xmin, xmax, funNr, D); // include possible extensions  (none)
#endif

    if (isCEC2013)
        initXminXmaxCEC2013(xmin, xmax, D);

    if (RNi > 10000) { // timing measure only
        for (int i = 0; i < 200000; i++)
            evaluate(xmin, evaluation);
    } else { // run optimization algorithm and timing
#define USE_ZADE_OPT
#ifdef USE_ZADE_OPT
        ZADE *optimizer =  new ZADE(evaluate, D, C, max_nfevals, xmin, xmax, RNi,
                                    DEstrategy, DEstrategyOptions);
#else
        jDEtevc2006 *optimizer =
                new jDEtevc2006(evaluate, D, 100, max_nfevals, xmin, xmax, RNi);
#endif
        optimizer->optimize(x, evaluation);

        delete optimizer;
    }

    delete []x;
    delete []xmin;
    delete []xmax;


    double result = evaluation[0];
    delete []evaluation;

    double errReturn = result - getOffsetCEC2013(funNr);
    if (errReturn < 1.e-8) errReturn = 0;

    return errReturn;
}

void RunAppCECOptimization::printFunctionStatistics(int funNr, int NF, std::vector<double> &bestsAtTheEnd, double time) {
    sort(bestsAtTheEnd.begin(), bestsAtTheEnd.end());
    double avg = 0;
    for (int n = rniStart, i = 0; n <= rniEnd; i++, n++)
        avg = (avg * i + bestsAtTheEnd[i]) / (i + 1);

    double var = 0;
    for (int n = rniStart, i = 0; n <= rniEnd; i++, n++)
        var += (bestsAtTheEnd[i] - avg) * (bestsAtTheEnd[i] - avg);//now, this is variance

    int N = 1 + rniEnd - rniStart;
    double stddev = N == 1 ? 0 : sqrt(var / (N - 1 ) );

    cout << setprecision(4) << scientific;

    cout << "F" << funNr
            << "\t " << MAX_NFEVALS[NF] << " FES"
            << "\tb " << bestsAtTheEnd[0]
            << "\tw " << bestsAtTheEnd[N-1]
            << "\tm " << bestsAtTheEnd[N/2]
            << "\ta " << avg
            << "\ts " << stddev;

    cout << setprecision(4) << fixed
            << "\tt " << time
            << endl;
}

#include <stdio.h>
double RunAppCECOptimization::diffclock_min(const clock_t &clock1, const clock_t &clock2) {
        double diffticks = clock1 - clock2;
        double diffms = double(diffticks) / CLOCKS_PER_SEC;
        return diffms / 60.0;
}
