/*********************************************************************
 *  Author  : Tino Weinkauf and Wiebke Koepp
 *  Init    : Tuesday, March 27, 2018 - 16:33:19
 *
 *  Project : KTH Inviwo Modules
 *
 *  License : Follows the Inviwo BSD license model
 *********************************************************************
 */

#include "treeordercomputationsa.h"
#include <random>
#include <time.h>
#include <iostream>

TemporalTreeSimulatedAnnealing::TemporalTreeSimulatedAnnealing()
    : TemporalTreeOrderOptimization() {
    propInitialTemperature = 10;
    propMinimumTemperature = 0;
    propTemperatureDecay = 0.9;
    propIterationsPerTemp = 10;
    // Current State
    propCurrentTemperature = 0;
}

void TemporalTreeSimulatedAnnealing::swapNodes(std::vector<size_t>& nodes) {
    std::uniform_int_distribution<int> chooseSwapNodes(0, static_cast<int>(nodes.size()) - 1);
    // We have at least two nodes, so we can definately find a pair of nodes to swap
    int swapA = chooseSwapNodes(randomGen);
    int swapB = chooseSwapNodes(randomGen);

    while (swapA == swapB) {
        swapB = chooseSwapNodes(randomGen);
    }

    // LogInfo("Swapped positions " << swapA << " and " << swapB << ".");
    std::swap(nodes[swapA], nodes[swapB]);
}

void TemporalTreeSimulatedAnnealing::decayTemperature() {
    // Exponential multiplicative cooling: 0.8 <= temperatureDecay <= 0.9
    currentTemperature *= propTemperatureDecay;

    // Others:
    // Logarithmical multiplicative cooling: temperatureDecay > 1
    // currentTemperature = initialTemperature / 1 + temperatureDecay*(log(1 + currentIteration +
    // 1));
    // ... (7 or so others)
    propCurrentTemperature = currentTemperature;
}

bool TemporalTreeSimulatedAnnealing::acceptNeighbor(double deltaEnergy) const {
    // If the new Energy is better or equal we accept it (Boltzmann/Metropolis critera)
    if (!(deltaEnergy <= 0)) {
        // For positive \Delta E the following holds:
        // \lim_{T \rightarrow 0} \exp\left(\frac{-\Delta E}{T}\right) = \lim_{x \rightarrow
        // -\infty} \exp(x) = 0 and \lim_{T \rightarrow \infty} \exp\left(-\frac{\Delta E}{T}\right)
        // = \lim_{x \rightarrow 0} \exp(x) = 1 this means if we want the initial probability to
        // accept close to 1 then
        const double probabilityThreshold =
            currentTemperature > 0 ? std::exp(-(deltaEnergy) / currentTemperature) : -1.0;
        // Do not accept the solution
        std::uniform_real_distribution<double> uniformReal(0.0, 1.0);
        double probability = uniformReal(randomGen);
        if (probability > probabilityThreshold) {
            return false;
        }
    }

    // Others: Barker criterion

    return true;
}

void TemporalTreeSimulatedAnnealing::logProperties() {
    const std::vector<std::string> colHeaders{
        "propSeedOrder",          "propSeedOptimization",
        "propIterationsMax",      "propInitialTemperature",
        "propMinimumTemperature", "propTemperatureDecay",
        "propIterationsPerTemp",  "propWeightByTypeOnly",
        "propWeightTypeOnly",     "propBestIteration",
        "propObjectiveValue",     "propTimeUntilBest",
        "propTimeForLastAction"};

    const std::vector<std::string> exampleRow{
        std::to_string(propSeedOrder),          std::to_string(propSeedOptimization),
        std::to_string(propIterationsMax),      std::to_string(propInitialTemperature),
        std::to_string(propMinimumTemperature), std::to_string(propTemperatureDecay),
        std::to_string(propIterationsPerTemp),  std::to_string(propWeightByTypeOnly),
        std::to_string(propWeightTypeOnly),     std::to_string(bestState.iteration),
        std::to_string(bestState.value),        std::to_string(propTimeUntilBest),
        std::to_string(propTimeForLastAction)};

    // optimizationSettings = createDataFrame({exampleRow}, colHeaders);
    // optimizationSettings->addRow(exampleRow);
}

void TemporalTreeSimulatedAnnealing::initializeLog() {
    const std::vector<std::string> colHeaders{
        "Iteration",         "Temperature",           "Energy",
        "Delta Energy",      "Accept Propabilty",     "Accepted",
        "Unfulfilled Merge", "Unfulfilled Hierarchy", "Unfulfilled Total",
        "Unhappy leaves"};

    const std::vector<std::string> exampleRow{
        std::to_string(currentState.iteration),
        std::to_string(currentTemperature),
        std::to_string(currentState.value),
        std::to_string(currentState.value),
        std::to_string(currentState.value),
        std::to_string(true),
        std::to_string(numConstraintsMergeSplit),
        std::to_string(numConstraintsHierarchy),
        std::to_string(numConstraintsHierarchy),
        std::to_string(currentState.statistic.unhappyLeaves.size())};

    // optimizationStatistics = createDataFrame({exampleRow}, colHeaders);
}

void TemporalTreeSimulatedAnnealing::logStep() {
    bool firstRow = false;

    size_t numUnfulfilledMergeSplit =
        numConstraintsMergeSplit - currentState.statistic.numFulFilledMergeSplitConstraints();
    size_t numUnfulfilledHierarchy =
        numConstraintsHierarchy - currentState.statistic.numFulfilledHierarchyConstraints();

    std::vector<std::string> newRow{
        std::to_string(int(currentState.iteration) - 1),  //"Iteration"
        std::to_string(currentTemperature),               //"Current Temperature",
        std::to_string(currentState.value),               //"Current Value",
        std::to_string(lastDeltaEnergy),                  // "Delta Energy"
        std::to_string((currentTemperature > 0)
                           ? (std::exp(-(lastDeltaEnergy) / currentTemperature))
                           : -1.0),    //"Accept Propabilty"
        std::to_string(lastAccepted),  // State was accepted
        std::to_string(firstRow ? numConstraintsMergeSplit : numUnfulfilledMergeSplit),
        std::to_string(firstRow ? numConstraintsHierarchy : numUnfulfilledHierarchy),
        std::to_string(firstRow ? numConstraintsHierarchy + numConstraintsMergeSplit
                                : numUnfulfilledMergeSplit + numUnfulfilledHierarchy),
        std::to_string(currentState.statistic.unhappyLeaves.size())};

    //optimizationStatistics->addRow(newRow);
}

void TemporalTreeSimulatedAnnealing::restart() {

    TemporalTreeOrderOptimization::restart();

    currentTemperature = propInitialTemperature;
    propCurrentTemperature = currentTemperature;

    logStep();
}

bool TemporalTreeSimulatedAnnealing::isConverged() {
    // the iteration number is an index starting at 0, the max is a number >= -1
    if (currentState.iteration > propIterationsMax - 1) {
        clog<<"Converged by reaching maximum number of iterations."<<endl;
        return true;
    }
    if (currentTemperature < propMinimumTemperature) {
        clog<<"Converged by temperature falling below the minimum."<<endl;
        return true;
    }
    if (std::abs(currentState.value) < std::numeric_limits<float>::epsilon()) {
        clog<<"Converged by reaching a global optimum."<<endl;
        return true;
    }

    return false;
}

void TemporalTreeSimulatedAnnealing::singleStep() {
    if (isConverged()) {
        return;
    }

    setLastToCurrent();

    // Generate a neighbor state (Changes current State)
    neighborSolution();

    // Evaluate new state
    currentState.statistic.clear();
    currentState.value = evaluateOrder(currentState.order, &currentState.statistic);

    lastDeltaEnergy = currentState.value - lastState.value;

    // Check if we can accept the new solution
    if (!acceptNeighbor(currentState.value - lastState.value)) {
        // Go back to the previous state
        setCurrentToLast();
        lastAccepted = false;
    } else {
        // Prepare the next step
        prepareNextStep();

        // Update best
        if (currentState.value < bestState.value) {
            // propTimeUntilBest = performanceTimer.ElapsedTime());
            setBest();
        }

        lastAccepted = true;
    }

    // Update for the next step
    currentState.iteration++;
    logStep();

    // Decay temperature after we have done the specified number of iterations
    if (currentState.iteration % propIterationsPerTemp == 0) {
        decayTemperature();
    }
}

void TemporalTreeSimulatedAnnealing::runUntilConvergence() {
    while (!isConverged()) {
        singleStep();
    }
}

void TemporalTreeSimulatedAnnealing::initializeResources() {
    TemporalTreeOrderOptimization::initializeResources();
}
