#include "gdconfig.hpp"
#include "gdtypes.hpp"
#include "utils.hpp"
#include "general_dichotomy.hpp"
#include "gdexception.hpp"

#include <iostream>

#ifdef BACKEND_PPL

// ppl library header
#include <ppl.hh>


//---------------------------------------------------------------
void GeneralDichotomy::computePolytopePPL(size_t sol_ind, std::vector<size_t>& other_sol, std::vector<Weight>& output_weights, size_t depth) {

    if(_params.verbose >= 2) {
        std::cout << "compute polytope for solution y^" << sol_ind << " against ";
        for(auto& other_sol_ind: other_sol) {
            std::cout << "y^" << other_sol_ind << ", "; 
        }
        std::cout << std::endl;
    }

    std::vector<Parma_Polyhedra_Library::Variable> var;
    Parma_Polyhedra_Library::Constraint_System cs;

    std::vector<dichotomy::COST> delta_cost(_solver.getNObj());

    // variables
    for(size_t var_ind = 0; var_ind < _solver.getNObj(); var_ind ++) {
      var.emplace_back(Parma_Polyhedra_Library::Variable(var_ind));
    }

    // points
   for(auto& other_sol_ind: other_sol) {

        Utils::subtractCostVec(_solutions[other_sol_ind], _solutions[sol_ind], delta_cost);
        
        auto expr = var[0]*delta_cost[0];
        for(size_t var_ind = 1; var_ind < var.size(); var_ind ++) {
            expr += var[var_ind]*static_cast<int>(delta_cost[var_ind]*_params.cost_scale);
        }
        cs.insert(expr >= 0);
   
   }

    // positivity
    for(size_t vind = 0; vind < var.size(); vind ++) {
        cs.insert(var[vind] >= 0); 
    }
    
    
    // sum=1
    auto expr = var[0]*1.;
    for(size_t ind = 1; ind < var.size(); ind ++) {
        expr += var[ind];
    }
    cs.insert(expr == _params.wnorm);

    Parma_Polyhedra_Library::C_Polyhedron poly(cs);

    if(_params.verbose >= 2) {
        using Parma_Polyhedra_Library::IO_Operators::operator<<;
        std::cout << poly << std::endl;
    }

    auto result = poly.minimized_generators();

    if(_params.verbose >= 2) {
        std::cout << "generators: " << std::endl;
        using Parma_Polyhedra_Library::IO_Operators::operator<<;
        for(auto it = result.begin(); it != result.end(); it ++) {
            if(!it->is_point()) {
                continue;
            }
            for(size_t i = 0; i < var.size(); i ++) {
                std::cout << Parma_Polyhedra_Library::raw_value(it->coefficient(var[i])).get_d() / Parma_Polyhedra_Library::raw_value(it->divisor()).get_d() << " ";
            }
            std::cout << std::endl;
        }
    }

    // extreme weights extraction
    using Parma_Polyhedra_Library::IO_Operators::operator<<;
    for(auto it = result.begin(); it != result.end(); it ++) {
        if(!it->is_point()) {
            continue;
        }
        output_weights.emplace_back(Weight());
        output_weights.back().weight.resize(_solver.getNObj());  
         for(size_t i = 0; i < var.size(); i ++) {
            output_weights.back().weight[i] = Parma_Polyhedra_Library::raw_value(it->coefficient(var[i])).get_d() / Parma_Polyhedra_Library::raw_value(it->divisor()).get_d();
        }
        output_weights.back().z = Utils::dot_product(output_weights.back().weight, _solutions[sol_ind]);

        output_weights.back().iteration = sol_ind;
        output_weights.back().depth = depth+1;

        // weight adjacency
        output_weights.back().adj.emplace(sol_ind);
        for(size_t col_ind = 0; col_ind < _solver.getNObj(); col_ind ++) {
            if(abs(output_weights.back().weight[col_ind]) <= dichotomy::epsilon2) {
                output_weights.back().bnd.emplace(-static_cast<int>(col_ind)-1);
            }
        } 
        for(size_t other_sol_ind: other_sol) {
            Utils::subtractCostVec(_solutions[other_sol_ind], _solutions[sol_ind], delta_cost);
            dichotomy::WEIGHT wsum = Utils::dot_product(output_weights.back().weight, delta_cost);
            if(abs(wsum) <= dichotomy::epsilon1) {
                output_weights.back().adj.emplace(other_sol_ind);  
            }
        }

    }

}

#else

//---------------------------------------------------------------
void GeneralDichotomy::computePolytopePPL(size_t sol_ind, std::vector<size_t>& other_sol, std::vector<Weight>& output_weights, size_t depth) {
    throw Dichotomy::GDException("error, PPL backend not available in the build!");
}

#endif // PPL_BACKEND