/*****************************************************************//**
 * \file   CyclesInitializer.h
 * \brief  Initializes the restricted master porblem with all the cycle exchanges.
 *
 * \author Matteo Petris
 * \date   February 2023
 *********************************************************************/
#ifndef CYCLESINITIALIZER_H
#define CYCLESINITIALIZER_H

#include "RestrictedMasterProblemInitializerAbstract.h"
#include "RestrictedMasterProblem.h"
#include "Label.h"

 /**
  * \class CyclesInitializer
  * \brief  Initializes the restricted master problem with all the cycles exchanges.
  */
class CyclesInitializer : public RestrictedMasterProblemInitializerAbstract<Instance, vector<Exchange>> {

protected:

    const vector<Vertex>& vertices;
    const vector<int>& incompatiblePairs;
    const vector<vector<vector<int>>>& arcsPerPairToProcess;
    int cycleLength;
    int wordsNumber;
    vector<Exchange> solution;

public:
    /**
     * \brief Constructor.
     *
     */
    CyclesInitializer(Instance& instance) : RestrictedMasterProblemInitializerAbstract(instance), vertices(instance.getVertices()), incompatiblePairs(instance.getPairsToProcess()),
        arcsPerPairToProcess(instance.getArcsPerPairToProcess()) {
        this->cycleLength = min(instance.getCyclesLength(), ::CYCLESINITIALISATIONLENGTH);
        this->wordsNumber = instance.getWordsNumber();
        this->exchangesPerVertex = instance.getExchangesPerVertexInitialisation();
    }


    /**
     * \brief Evaluates if the handler (the algorithm) can handle (initlize) the restricted master problem based on the code of request.
     *
     * \param request : string
     * \return True if the concrete algorithm can solve the pricing sub-problem, false otherwise.
     */
    bool can_initialize(const string& request) const {
        return request == "CYCLESINITIALIZER" || "WHATEVER";
    }

    /**
     * Initializes the restrcted master problem.
     * \details This method has to be implemented in the inherent classes.
     * \param request : string
     */
    vector<Exchange> initialize(const string& request) override {
        if (can_initialize(request) && this->cycleLength > 0) {

            //double tt = 0.0;
            for (int i = 0; i < this->incompatiblePairs.size(); i++) {
                int source = this->incompatiblePairs.at(i);
                //cout << source << endl;
                /*clock_t cc, ff;
                cc = clock();*/
                vector<vector<Label>> labels = dynamicProgram(source, this->cycleLength - 1, this->instance.getNodesPerPairToProcess().at(source), this->arcsPerPairToProcess.at(source));
                /*ff = clock();
                tt += (float)(ff - cc) / CLOCKS_PER_SEC;*/

                //build exchange objects
                for (int l = 0; l < labels.size(); l++) {
                    for (int h = 0; h < labels.at(l).size(); h++) {
                        const Label* label = &labels.at(l).at(h);
                        int lastInCycle = label->getSequence().back();
                        //cout << label->toString() << "\n";
                        unordered_map<int, int>::const_iterator finder = this->vertices.at(lastInCycle).getOutGoingArcs().find(source);
                        if (finder != this->vertices.at(lastInCycle).getOutGoingArcs().end()) {
                            vector<int> seq = label->getSequence();
                            seq.push_back(source);

                            Exchange exchange("cycle", static_cast<int>(label->getWeight()) + finder->second, label->getLength() + 1, seq, this->instance.getVertexNumber());
                            this->solution.push_back(exchange);
                            /*if (label->getLength() + 1 == 4) {
                                cout << exchange.toString() << endl;
                            }*/
                        }
                    }
                }

                /*if (this->solution.size() > 10000000) {
                    ofstream mystream;
                    mystream.open(::solutionPath + "Solutions_PICEF_OUT_" + ::thread + ".csv", ios::app);
                    mystream << ::instanceName << ",OUT,\n";
                    mystream.close();
                    exit(EXIT_FAILURE);
                    return this->solution;
                }*/

            }

            //displayCycles();
            assert(check_solution());

            //cout << "dyn prog "/* << tt */<< "\t" << this->solution.size() << endl;
            return this->solution;

        }
        else
            return RestrictedMasterProblemInitializerAbstract::initialize(request);
    }

    vector<vector<Label>> dynamicProgram(const int& source, const int& maxLength, const unordered_set<int>& nodes, const vector<vector<int>>& arcs) {

        //INITIALIZATION
        Label sourceLabel(source, 0, 0, 0.0);
        sourceLabel.buildSequence({});
        vector<int> ngMemory(this->wordsNumber, 0);
        sourceLabel.buildNgSetFullMemory(ngMemory, this->vertices.at(source).getWord(), this->vertices.at(source).getBitKey());
        //cout << "INITI\n" << sourceLabel.toString() << endl;

        size_t vSize = this->vertices.size();
        vector<vector<Label>> labels(vSize);
        labels.at(source).push_back(sourceLabel);

        //ITERATIONS
        bool termination = false;
        int counter = 0;

        while (termination != true) {

            termination = true;

            for (unordered_set<int>::const_iterator it = nodes.begin(); it != nodes.end(); it++) {

                int id = *it;
                const Vertex* v = &this->vertices.at(id);
                //cout << "VERTEX\n" << v->toString(false) << endl;

                for (int h = 0; h < labels.at(id).size(); h++) {

                    const Label* l = &labels.at(id).at(h);

                    if (!l->getIsExtended() && l->getLength() < maxLength) {

                        /*const Vertex* v = &this->vertices.at(this->incompatiblePairs.at(i));
                        cout << "VERTEX\n" << v->toString(false) << endl;*/
                        //cout << "LABEL\n" << l->toString() << endl;

                        //for (unordered_map<int, int>::const_iterator it = v->getOutGoingArcs().begin(); it != v->getOutGoingArcs().end(); it++) {
                        for (int j = 0; j < arcs.at(v->getId()).size(); j++) {

                            const Vertex* w = &this->vertices.at(arcs.at(v->getId()).at(j));
                            /*cout << "TO REACH\n" << w->toString(false) << endl;
                            cout << "IS VISITED? " << l->isVisited(w->getWord(), w->getBitKey()) << endl;*/

                            if (!l->isVisited(w->getWord(), w->getBitKey())) {

                                int weight = v->getOutGoingArcs().at(w->getId());
                                Label extension(w->getId(), l->getLength() + 1, l->getWeight() + weight, 0.0);
                                extension.buildSequence(l->getSequence());
                                extension.buildNgSetFullMemory(l->getNgMemory(), w->getWord(), w->getBitKey());
                                labels.at(w->getId()).push_back(extension);
                                //cout << "EXT\n" << extension.toString() << endl;

                                unordered_map<int, int>::const_iterator finder = w->getOutGoingArcs().find(source);
                                if (finder != w->getOutGoingArcs().end()) {
                                    counter++;
                                }

                                if (/*this->solution.size() + */counter > this->exchangesPerVertex) {
                                    return labels;
                                }

                                termination = false;

                            }

                        }

                    }

                    labels.at(id).at(h).setIsExtended(true);

                }

            }

        }

        return labels;

    }

    /**
     * Displays the cycles.
     */
    void displayCycles() {
        cout << "----Cycles initialisation :" << endl;
        for (int i = 0; i < this->solution.size(); i++)
            cout << this->solution.at(i).toString() << endl;
    }

    bool check_solution() {

        map<int, int> occurences;
        string str = "";

        for (const Exchange& exchange : this->solution) {
            if (exchange.getLength() > this->cycleLength) {
                str.append(::instanceName + "\nERROR LENGTH\n" + exchange.toString() + "\n");
                ::appendToFile(::solutionPath + "Cycles_" + thread + ".log", str);
                return false;
            }
        }

        for (const Exchange& exchange : this->solution) {
            if (exchange.getSequence().front() != exchange.getSequence().back()) {
                str.append(::instanceName + "\nERROR NOT A CYCLE\n" + exchange.toString() + "\n");
                ::appendToFile(::solutionPath + "Cycles_" + thread + ".log", str);
                return false;
            }
        }

        for (const Exchange& exchange : this->solution) {
            for (int i = 1; i < exchange.getSequence().size() - 1; i++) {
                if (exchange.getVisit(exchange.getSequence().at(i)) > 1) {
                    str.append(::instanceName + "\nERROR SAME PAIR MORE THAN ONCE\n" + exchange.toString() + "\n");
                    ::appendToFile(::solutionPath + "Cycles_" + thread + ".log", str);
                    return false;
                }
            }
        }

        for (const Exchange& exchange : this->solution) {
            int weight = 0;
            for (int i = 0; i < exchange.getSequence().size() - 1; i++) {
                const Vertex* v = &this->vertices.at(exchange.getSequence().at(i));
                unordered_map<int, int>::const_iterator finder = v->getOutGoingArcs().find(exchange.getSequence().at(i + 1));
                if (finder == v->getOutGoingArcs().end()) {
                    str.append(::instanceName + "\nERROR VERTICES NOT COMPATIBLE\n" + exchange.toString() + "\n");
                    ::appendToFile(::solutionPath + "Cycles_" + thread + ".log", str);
                    return false;
                }
                else {
                    weight += finder->second;
                }
            }
            if (weight != exchange.getWeight()) {
                str.append(::instanceName + "\nERROR WRONG WEIGHT\t" + to_string(weight) + "\n" + exchange.toString() + "\n");
                ::appendToFile(::solutionPath + "Cycles_" + thread + ".log", str);
                return false;
            }
        }

        return true;
    }
};


#endif //CYCLESINITIALIZER_H
