#pragma once

#include <map>

#include "gdconfig.hpp"
#include "gdtypes.hpp"
#include "result.hpp"
#include "basesolver.hpp"

/*!
 * \brief General Dichotomy Solver
 */
class GeneralDichotomy {

    public:

        /*!
         * \brief backend choice for polytope computation. Note that with PPL the weights must be scaled reasonably (parameter wnorm) as it uses rational arithmetics 
         */
        enum Backend {CDD=0, PPL=1};

        enum SearchScheme {DIVERSIFICATION=0, INTENSIFICATION=1};

        /*!
         * \brief Parameters class to store the algorithm parameters
         */
        class Parameters {

            public:
                Parameters() : verbose(0), backend(Backend::CDD), wnorm(10.),export_weight_graph(false), max_iter(0), max_depth(0), cost_scale(1.), search_scheme(DIVERSIFICATION) { }

                size_t verbose; // verbosity level
                Backend backend; // polytope computation backend (libcdd or libppl)
                dichotomy::WEIGHT wnorm; // normalizing value for the weights
                bool export_weight_graph;
                size_t max_iter; // limit on the number of iterations for the method, 0 meaning no limit
                size_t max_depth; // maximum depth of search, none if 0
                dichotomy::COST cost_scale; // scaling factor for objective costs in PPL polytope computation
                std::vector<dichotomy::Direction> directions; // optimization directions, 1 for each objective
                dichotomy::WeightVec initial_weight; // initial weight provided by the user to compute the first solution (optional)
                SearchScheme search_scheme; // search scheme
        };

    public:

        /*!
         * \brief constructor
         * \param solver the single-objective solver to use
         */
        GeneralDichotomy(BaseSolver& solver);

        /*!
         * \brief solve the problem
         * \param parameters the solver parameters
         */
        dichotomy::Result run(Parameters parameters);

        /*!
         * \brief return the number of call to the user single-objective solver
         */
        size_t getNSubSolve();

        /*!
         * \brief return the total number of weight vectors processed by the algorithm
         */
        size_t getNComputedWeights();

    private:

        /*!
         * \brief init the extreme weights
         */
        void initWeights();

        /*!
         * \brief check is the solution is present in the list and add it if not found
         * \param sol the solution to add
         */
        size_t addCheckSolution(dichotomy::CostVec& sol);

        /*!
         * \brief look for a new solution that would udate the weight set decomposition
         * \param new_sol_ind output index of the new solution
         * \param polytope_sol output list of solutions to consider to build the polytope
         * \param origin_weight_ind index of the weight from which a new solution is found, if true
         */
        bool findImprovingSolution(size_t& new_sol_ind, std::set<size_t>& polytope_sol, size_t& origin_weight_ind);

        /*!
         * \brief compute the polytope of the solution against all the others and output its extreme weights using cdd library
         * \param sol_ind index of the solution
         * \param other_sol index of the other solutions
         * \param output_weights extreme weights of the polytope returned
         * \param depth depth of the weight from which the new solution sol_ind was discovered 
 
         */
        void computePolytopeCDD(size_t sol_ind, std::vector<size_t>& other_sol, std::vector<Weight>& output_weights, size_t depth);

        /*!
         * \brief compute the polytope of the solution against all the others and output its extreme weights using ppl library
         * \param sol_ind index of the solution
         * \param other_sol index of the other solutions
         * \param output_weights extreme weights of the polytope returned
         * \param depth depth of the weight from which the new solution sol_ind was discovered 
         */
        void computePolytopePPL(size_t sol_ind, std::vector<size_t>& other_sol, std::vector<Weight>& output_weights, size_t depth);
    
        /*!
         * \brief compute the graph of the weight set decomposition
         */
        void computeGraph();

    private:

        // instance and solver objects
        BaseSolver& _solver;

        // number of time a sub problem has been solved
        size_t _nSubSolve;

        // extreme weights and graph of the weight set decomposition
        std::vector<Weight> _weights;
        dichotomy::WeightGraph _weightGraph;

        // list of all solutions found
        std::vector<dichotomy::CostVec> _solutions;

        // number of weights that have been removed
        size_t _nRemoved;

        // total number of weight vectors tested by the algorithm
        size_t _nTotalWeights;

        // number of time the single-objective solver has been called
        size_t n_solve;

        std::map<dichotomy::WeightVec, size_t, weight_compare> _weights_set;
        
        // parameters
        Parameters _params;
};