/*********************************************************************
 *  Author  : Tino Weinkauf and Wiebke Koepp
 *  Init    : Friday, March 23, 2018 - 15:58:29
 *
 *  Project : KTH Inviwo Modules
 *
 *  License : Follows the Inviwo BSD license model
 *********************************************************************
 */

#include "treeordercomputation.h"
#include <random>

TemporalTreeOrderOptimization::TemporalTreeOrderOptimization(){
    this->propIterationsMax = 1000;
    this-> propWeightByTypeOnly = true;
    this->propWeightTypeOnly = 0.5;
    this->propWeightByType = false;
    this->propWeightMergeSplit = 1;
    this->propWeightHierarchy = 1;
    this->propWeightBySize = false;
    this->propWeightByLevel = false;
    this->propWeightLevel = 1;
    this->propUseInputOrder = true;
    this->propRandomizeOrder = false;
    this->propRandomOrSeedOrder = true;
    this->propSeedOrder = 0;
    this->propRandomOrSeedOptimization = true;
    this->propSeedOptimization = 0;
    this->propCurrentIteration = 0;
    this->propBestIteration = 0;
    this->propOutputBestOrder = true;
    this->propObjectiveValue = 0;
    this->propTimeForLastAction = 0;
    this->propTimeUntilBest = 0;
    this->propSaveLog = false;
}

void TemporalTreeOrderOptimization::fillStatistics(const ConstraintsStatistic& statistic) {
    const size_t numFulfilled = statistic.numFulfilledHierarchyConstraints() +
                                statistic.numFulFilledMergeSplitConstraints();

    propFulfilledConstraintsTotal = std::to_string(numFulfilled) + " / " +
                                      std::to_string(constraints.size());

    std::string mergeSplit;

    for (size_t level = 1; level < numByLevelMergeSplit.size(); level++) {
        if (level + 1 <= statistic.fulfilledByLevelMergeSplit.size()) {
            mergeSplit += std::to_string(statistic.fulfilledByLevelMergeSplit[level]) + "/" +
                          std::to_string(numByLevelMergeSplit[level]);
        } else {
            mergeSplit += "0/" + std::to_string(numByLevelMergeSplit[level]);
        }

        if (level < numByLevelMergeSplit.size() - 1) {
            mergeSplit += " - ";
        }
    }

    std::string hierarchy;

    for (size_t level = 1; level < numByLevelHierarchy.size(); level++) {
        if (level + 1 <= statistic.fulfilledByLevelHierarchy.size()) {
            hierarchy += std::to_string(statistic.fulfilledByLevelHierarchy[level]) + "/" +
                         std::to_string(numByLevelHierarchy[level]);
        } else {
            hierarchy += "0/" + std::to_string(numByLevelHierarchy[level]);
        }

        if (level < numByLevelHierarchy.size() - 1) {
            hierarchy += " - ";
        }
    }

    propStatisticsMergeSplit = mergeSplit;
    propStatisticsHierarchy = hierarchy;
}

void TemporalTreeOrderOptimization::setInitialOrder() {

    currentState.order.clear();

    if (propUseInputOrder) {
        // We can use the input order only when it fits with the tree
        if (treeorder::fitsWithTree(*pInputTree, pInputTree->order)) {
            currentState.order = pInputTree->order;
        } else {
            clog<<
                "Inconsistent order given in the tree, falling back to depth-first ordering."<<endl;
            treeorder::orderAsDepthFirst(currentState.order, *pInputTree,
                                         pInputTree->edgesHierarchy);
        }
    } else {
        treeorder::orderAsDepthFirst(currentState.order, *pInputTree, pInputTree->edgesHierarchy);
    }

    if (propRandomizeOrder) {
        if (propUseInputOrder) {
            clog<<
                "Using the input order takes precedence over order randomization. Order is not "
                "randomized."<<endl;
        } else {
            std::mt19937 randomGenOrder;

            // Generate a random seed if so desired and set it
            if (propRandomOrSeedOrder) {
                propSeedOrder = rand();
            }
            randomGenOrder.seed(static_cast<std::mt19937::result_type>(propSeedOrder));
            std::shuffle(currentState.order.begin(), currentState.order.end(), randomGenOrder);
        }
    }
}

void TemporalTreeOrderOptimization::initializeResources() {
    // Get tree
    std::shared_ptr<const TemporalTree> pTreeIn = portInTree;
    if (!pTreeIn) return;

    // Copy tree to compute reverse edges
    auto pCopyTree = std::make_shared<TemporalTree>(TemporalTree(*pTreeIn));
    pCopyTree->computeReverseEdges();
    pInputTree = std::const_pointer_cast<const TemporalTree>(pCopyTree);

    constraints.clear();

    // Extract constraints from the tree
    numByLevelHierarchy.clear();
    extractHierarchyConstraints(pInputTree, constraints, numByLevelHierarchy);
    numConstraintsHierarchy = constraints.size();
    numByLevelMergeSplit.clear();
    extractMergeSplitConstraints(pInputTree, constraints, numByLevelMergeSplit);
    numConstraintsMergeSplit = constraints.size() - numConstraintsHierarchy;

    maxConstraintSize = 0;
    maxConstraintLevel = 0;
    for (auto& constraint : constraints) {
        if (constraint.leaves.size() > maxConstraintSize)
            maxConstraintSize = constraint.leaves.size();
        if (constraint.level > maxConstraintLevel) maxConstraintLevel = constraint.level;
    }
}

double TemporalTreeOrderOptimization::weighUnfulfilledConstraint(Constraint& constraint) {
    double constraintValue = 1.0;

    if (propWeightByTypeOnly) {
        constraintValue *= constraint.type == ConstraintType::Hierarchy
                               ? (propWeightTypeOnly) / numConstraintsHierarchy
                               : (1.0 - propWeightTypeOnly) / numConstraintsMergeSplit;
    } else {
        if (propWeightByType) {
            constraintValue *= constraint.type == ConstraintType::Hierarchy ? propWeightHierarchy
                                                                            : propWeightMergeSplit;
        }
        if (propWeightByLevel) {
            constraintValue *= propWeightLevel * (maxConstraintLevel - (constraint.level - 1)) /
                               maxConstraintLevel;
        }
        if (propWeightBySize) {
            constraintValue *= propWeightSize * constraint.leaves.size() / maxConstraintSize;
        }
        if (!propWeightByType && !propWeightBySize && !propWeightByLevel) {
            constraintValue *= 1.0 / (numConstraintsMergeSplit + numConstraintsHierarchy);
        }
    }

    return constraintValue;
}

double TemporalTreeOrderOptimization::evaluateOrder(const TemporalTree::TTreeOrder& order,
                                                    ConstraintsStatistic* statistic) {
    double value = 0.0;

    // If a ConstraintsStatistic is given
    if (statistic) {
        (*statistic).clear();
    }

    TemporalTree::TTreeOrderMap orderMap;
    treeorder::toOrderMap(orderMap, order);

    for (auto& constraint : constraints) {
        if (!isFulFilled(constraint, pInputTree, order, orderMap)) {
            value += weighUnfulfilledConstraint(constraint);
        }
        // If a ConstraintsStatistic is given
        if (statistic) {
            (*statistic).update(constraint);
        }
    }

    return value;
}

double TemporalTreeOrderOptimization::evaluateOrder(const TemporalTree::TTreeOrder& order) {
    return evaluateOrder(order, nullptr);
}

void TemporalTreeOrderOptimization::restart() {
    // Get tree

    if (!initialized || !pInputTree) {
        initializeResources();
    }

    // Generate a random seed if so desired and set it
    if (propRandomOrSeedOptimization) {
        propSeedOptimization = rand();
    }
    randomGen.seed(static_cast<std::mt19937::result_type>(propSeedOptimization));

    currentState.iteration = 0;
    currentState.statistic.clear();
    setInitialOrder();
    currentState.value = evaluateOrder(currentState.order, &currentState.statistic);

    setFileNames();
}

void TemporalTreeOrderOptimization::updateOutput() {
    std::shared_ptr<const TemporalTree> pTreeIn = portInTree;
    if (!pTreeIn) return;

    // Copy tree to output things
    std::shared_ptr<TemporalTree> pTreeOut = std::make_shared<TemporalTree>(TemporalTree(*pTreeIn));

    propCurrentIteration = int(currentState.iteration) - 1;
    propBestIteration = int(bestState.iteration) - 1;

    // Get statistics of how many constraints are fulfilled
    fillStatistics(propOutputBestOrder ? bestState.statistic : currentState.statistic);
    propObjectiveValue = propOutputBestOrder ? float(bestState.value)
                                               : float(currentState.value);

    // Set the order
    pTreeOut->order = propOutputBestOrder ? bestState.order : currentState.order;

    // Set data
    portOutTree = pTreeOut;
}

void TemporalTreeOrderOptimization::saveLog() {
    OptimizationState currentStateBackup = currentState;
    logProperties();
    // Log the best state in the very last row
    currentState = bestState;
    logStep();
    currentState = currentStateBackup;
}

void TemporalTreeOrderOptimization::logStep() {

    bool firstRow = true;

    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(currentState.value),               //"Current Value",
        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 TemporalTreeOrderOptimization::initializeLog() {
    const std::vector<std::string> colHeaders{"Iteration",         "Value",
                                              "Unfulfilled Merge", "Unfulfilled Hierarchy",
                                              "Unfulfilled Total", "Unhappy leaves"};

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

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

void TemporalTreeOrderOptimization::setFileNames() {
    initializeLog();

    time_t t = time(0);  // get time now
    struct tm* now = localtime(&t);
}
