#include "zade.h"
#include "../constraintsepsilontakahama2006.h"

#include <float.h>
#include <math.h>
#include <sstream>

#include "zade_init.cpp"
#include "zade_mutation.cpp"
#include "zade_selection.cpp"
#include "zade_popcntrl.cpp"

void ZADE::optimize(double *result, double *evaluation) {
    jDEapin2008reduceNP::optimize(result, evaluation);

    if ((DEstrategy == PSrandbin || DEstrategy == PSrandBESTbin))
       if (isBetterFirst(bestBestPop, best)) // if IS BETTER :
           replaceFirstVectorIntoSecond(bestBestPop, best);

    for (int i = 0; i < 1 + C; i++) evaluation[i] = best[idxFE + i];
}

void ZADE::onBeforeOptimizeOneGeneration(int &NFevals, int g) {
    // pre-test: is in this generation POP. REDUCTION
    bool doAdjustNPbest = false;
    if (DEstrategyOptions >= PSrandbinNPbestPerc6_NP100_diffBHalways && DEstrategyOptions <= PSrandbinNPbestPerc5_NP400_diffBHalways) {
        int genp = maxNFEVALs / (pmax * NP);
        if (!(g <= genp || (NP+1)/2 < NPmin)) doAdjustNPbest = true;
    }


    // possibly, do POP. REDUCTION
    if (DEstrategy == NP_MMrandbestPopCNTRL)
        controlPopulationSizeBrestCEC12(g); // instead of APIN reduction, use CEC12 control NP
    else reducePopulationAPIN08(g);


    // if POP. REDUCTION, adjust NPbest size
    if (doAdjustNPbest) { //only for NPbest-percentage
        switch (DEstrategyOptions) {
            case PSrandbinNPbestPerc6_NP100_diffBHalways: NPbest = int(0.06 * NP); break;
            case PSrandbinNPbestPerc6_NP200_diffBHalways: NPbest = int(0.06 * NP); break;
            case PSrandbinNPbestPerc6_NP400_diffBHalways: NPbest = int(0.06 * NP); break;
            case PSrandbinNPbestPerc2_NP400_diffBHalways: NPbest = int(0.02 * NP); break;
            case PSrandbinNPbestPerc3_NP400_diffBHalways: NPbest = int(0.03 * NP); break;
            case PSrandbinNPbestPerc10_NP400_diffBHalways: NPbest = int(0.10 * NP); break;
            case PSrandbinNPbestPerc5_NP400_diffBHalways: NPbest = int(0.05 * NP); break;
        }
        if (NPbest < 4) NPbest = 4;
    }

    //store the best vector into bestit, saving it for /best/ strategies
    replaceFirstVectorIntoSecond(best, bestit);
}

void ZADE::onAfterOptimizeOneGeneration(int &NFevals, int g) {
   if (DEstrategy == PSrandbin || DEstrategy == PSrandBESTbin) { // NPbest -> NP back
       // migrate best from bestpop in randpop, if it differs significantly -> replacing random, which is not best..
       if (isBestBestPopImprovementAndDiffersSignificantly()) {

           //now, find the idxBest for the new normalpop:
           int idxStartBestPop = getIdxStartBestPop();
           int idxBestNormalPop = 0;
           for (int i = 1; i < idxStartBestPop; i++)
               if (isBetterFirst(newPop+i*L, newPop+idxBestNormalPop*L)) idxBestNormalPop = i;

           double *replace_into = newPop + idxBestNormalPop*L;
           if (DEstrategyOptions == PSrandbinNPbest6_NP100_diffBHalwaysRndNobest &&
                   NP - 1 > NPbest) { // check: if NPbest > NP - 1, no replace goes back!
               int idx; do { idx = random()*idxStartBestPop; } while (idx == idxBestNormalPop);
               replace_into = newPop + idx*L;
           }
           replaceFirstVectorIntoSecond(bestBestPop, replace_into);
           replaceFirstVectorIntoSecond(bestBestPop, bestBestPopHxchg); // also update Xchg!?
       }
   }

    //UNUSED, BUT WARN: pop and newPop NOT SWITCHED YET!
    //WATCH OUT: warning: pop and newPop are already SWITCHED here!!
    // but am not using this feature anyway (C=0), so let it here be until needed fixing
    constraintsEpsilonTakahama2006->updateEpsilonConstraint(newPop, (double)NFevals/maxNFEVALs);

    // ageing after the generated newPop became pop
    if (DEstrategy == NP_MMrandWbestAgeingNoEval) {
        for (int i = 0; i < NP; i++) {
            ///if (i == idxBest) continue;
            if (++pop[i*L + idxAge]>30*D && random()>0.05) {
                pop[i*L + idxAge] = 0;

                //re-init
                pop[i*L + idxF] = 0.1 + 0.9 * random(); // F
                pop[i*L + idxCR] = random(); // CR

                for (int j = 0; j < D; j++)
                    pop[i*L + j] = xmin[j] + random() * (xmax[j] - xmin[j]); // uniform initialization

                // DO NOT RE-EVAL, BUT ONLY SET AS A COMPONENT-DONOR having bad eval
                pop[i*L + idxFE] = NAN; // mark as unfeasible!
            }
        }
    }

    printBestStatisticsOUT();
}

bool ZADE::isBestBestPopImprovementAndDiffersSignificantly() {
    //improvement to the curr normalpop?
    // if IS IMPROVED (improved even for the curr. generation of normalpop!):
    if (!isBetterFirst(bestBestPop, best)) return false;

    //differs?
    if (DEstrategyOptions == PSrandbinNPbest6_NP100_diffBHalways ||
            DEstrategyOptions == PSrandbinNPbest6_NP100_diffBHalwaysRndNobest
            ||
            (DEstrategyOptions >= PSrandbinNPbestPerc6_NP100_diffBHalways &&
             DEstrategyOptions <= PSrandbinNPbestPerc5_NP400_diffBHalways)) return true;

    double diffBH = 0;
    for (int j = 0; j < D; j++) {
        double interval = xmax[j] - xmin[j];
        double xBest_BestPop_normalized_j = (bestBestPop[j] - xmin[j]) / interval;
        double xXchg_normalized_j = (bestBestPopHxchg[j] - xmin[j]) / interval;

        if (xXchg_normalized_j > 0)
            diffBH += xBest_BestPop_normalized_j / xXchg_normalized_j;
    }

    diffBH /= D;
    double rDiffBH = DEstrategyOptions == PSrandbinNPbest6_NP100_diffBH1perc ? 0.01 :
      DEstrategyOptions == PSrandbinNPbest6_NP100_diffBH5perc ? 0.05 :
      DEstrategyOptions == PSrandbinNPbest6_NP100_diffBH10perc ? 0.10 :
      DEstrategyOptions == PSrandbinNPbest6_NP100_diffBH20perc ? 0.20 :
      DEstrategyOptions == PSrandbinNPbest6_NP100_diffBH50perc ? 0.50 :
      DEstrategyOptions == PSrandbinNPbest6_NP100_diffBH100perc ? 1.00 :
      DEstrategyOptions == PSrandbinNPbest6_NP100_diffBH99perc ? 0.99 :
      DEstrategyOptions == PSrandbinNPbest6_NP100_diffBH98perc ? 0.98 :
      0.01;

    if (diffBH < 1 - rDiffBH || diffBH > 1 + rDiffBH) {
        return true;
    }

    return false;
}

//takes the currently best vector (not the bestit!) and use it possibly for bestPop part
void ZADE::onBeforeOptimizeOneVector(int NFevals, int g, int i) {
    if (DEstrategy == PSrandbin || DEstrategy == PSrandBESTbin) {
        int idxStartBestPop = getIdxStartBestPop();

        if (i == idxStartBestPop) { // from NP into NPbest: always replace its best
            // insert best of rand-popopulation in best-population?
            //best was updated immediately, so it holds the current normpop best
            if (isBetterFirst(best, bestBestPop)) {
                // OK, replace its best, idxBestBestPop remains same
               replaceFirstVectorIntoSecond(best, bestBestPop); 
               replaceFirstVectorIntoSecond(bestBestPop, bestBestPopHxchg);
            }
        }
    }
}

int ZADE::getIdxStartBestPop() {
    return (NP - NPbest) < 0 ? 0 : NP - NPbest; // checked: if NPbest > NP
}

#include <fstream>
using namespace std;
extern int funNr;
extern int currRNi;
double getOffsetCEC2013(int func_num);
void ZADE::printBestStatisticsOUT() {
    static int prevI = -1;
    int nowI = int((double)NFevals / maxNFEVALs * 10);
    if (NFevals < maxNFEVALs / 100) nowI = -1; // 100.000 / 10 = 10.000, 100.000 / 100 = 1.000; 1.000 * 100 = 100.000
    if (prevI != nowI) {
        prevI = nowI;
        //if (nowI > 0) { //uncomment to omit 0.01 maxFES printout (only 10 prints)
            stringstream fname;
            fname << "out-" << "strat" << DEstrategy << "-opt" << DEstrategyOptions << "-RN" << RNi;
            ofstream out(fname.str().c_str(), ios::app);
            out << funNr << " " << D << " " << "run " << currRNi << " i=" << nowI << " " << NFevals << " "
                << best[idxFE] - getOffsetCEC2013(funNr) << "\n";
        //}
    }
}
