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

#include <iostream>

#ifdef BACKEND_CDD

// cdd library header
#include <cddlib/setoper.h>
#include <cddlib/cdd.h>

//---------------------------------------------------------------
void GeneralDichotomy::computePolytopeCDD(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;
    }

    dd_PolyhedraPtr poly;
    dd_MatrixPtr A, G;
    dd_rowrange m; 
    dd_colrange d;
    dd_ErrorType err;

    // creation of b and A : b-Ax >= 0 

    // \lambda * costs(selected) <= \lambda * costs(others)

    // <w,y¹> <= <w,y²> -> <w, (y^1-y^2) > <= 0
    // <w,y¹> <= <w,y³>
    // ...
    // w_1 >= 0
    // w_2 >= 0
    // w_3 >= 0
    // ...
    // w_1 + w_2 + w_3 + ... = 1

    m = other_sol.size()+_solver.getNObj()+1;
    d = _solver.getNObj()+1;
    A = dd_CreateMatrix(m,d);

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

    // construction of the input matrix

    // linear inequalities
    size_t row_ind = 0;
    for(size_t other_sol_ind: other_sol) {
        Utils::subtractCostVec(_solutions[other_sol_ind], _solutions[sol_ind], delta_cost);
        for(size_t col_ind = 0; col_ind < delta_cost.size(); col_ind ++) {
            dd_set_d(A->matrix[row_ind][col_ind+1],static_cast<double>(delta_cost[col_ind]));
        }
        row_ind ++;
    }
    // weights positivity
    for(size_t obj_ind = 0; obj_ind < _solver.getNObj(); obj_ind ++) {
        if(_params.directions.size() > 0 && _params.directions[obj_ind] == dichotomy::MAX) {
            dd_set_d(A->matrix[row_ind][obj_ind+1], -1.);
        } else {
            dd_set_d(A->matrix[row_ind][obj_ind+1], 1.);
        }
        row_ind += 1;
    }
    // weights sum = 1
    dd_set_d(A->matrix[row_ind][0], _params.wnorm);
    for(size_t obj_ind = 0; obj_ind < _solver.getNObj(); obj_ind ++) {
        if(_params.directions.size() > 0 && _params.directions[obj_ind] == dichotomy::MAX) {
            dd_set_d(A->matrix[row_ind][obj_ind+1], 1.);
        } else {
            dd_set_d(A->matrix[row_ind][obj_ind+1], -1.);
        }
    }
    set_addelem(A->linset,row_ind+1); // setting the last to be equality

    A->representation=dd_Inequality; // H representation
    
    poly=dd_DDMatrix2Poly(A, &err);  // compute the second (generator) representation
    
    // vertices extraction from the matrix
    G = dd_CopyGenerators(poly);
    
    if(G != NULL) {


        // debug output the matrix of vertices
        if(_params.verbose >= 2) {
            std::cout << std::endl;
            std::cout << "matrix size: " << std::endl;
            std::cout << G->rowsize << std::endl;
            std::cout << G->colsize << std::endl;
            for(size_t i = 0; i < G->rowsize; i ++) {
                // make sure this is not a ray
                if(dd_get_d(G->matrix[i][0]) >= 0.5) {
                    for(size_t j = 1; j < G->colsize; j ++) {
                        std::cout << dd_get_d(G->matrix[i][j]) << " ";
                    }
                    std::cout << std::endl;
                }
            }

            dd_WriteMatrix(stdout,A);
            printf("\n");
            dd_WriteMatrix(stdout,G);
        }


        // get input incidence
        dd_SetFamilyPtr input_incidence  = dd_CopyIncidence(poly);

        // debug output the input incidence    
        // std::cout << std::endl;
        // std::cout << "polytope incidence: " << std::endl;
        // std::cout << "fam size: " << input_incidence->famsize << std::endl;
        // std::cout << "set size: " << input_incidence->setsize << std::endl;
        // for(size_t i = 0; i < input_incidence->famsize; i ++) {
        //     std::cout << "set " << i << " :";
        //     for(size_t j = 0; j < input_incidence->setsize; j ++) {
        //         if(set_member(j+1, input_incidence->set[i])) {
        //             std::cout << j << " ";
        //         }
        //     }
        //     std::cout << std::endl;
        // }

        // if(_params.verbose >= 2) {
        //     dd_WriteSetFamily(stdout,input_incidence);
        // }
        
        // output the extreme weights of the polytope
        for(size_t i = 0; i < static_cast<size_t>(G->rowsize); i ++) {
            
            // make sure this is not a ray
            if(dd_get_d(G->matrix[i][0]) >= 0.8) {

                output_weights.emplace_back(Weight());
                output_weights.back().weight.resize(_solver.getNObj());
                for(size_t j = 1; j < static_cast<size_t>(G->colsize); j ++) {
                    output_weights.back().weight[j-1] = dd_get_d(G->matrix[i][j]);
                }
                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;
            }
        }

        // initialization of the adjacency
        for(int i = 0; i < input_incidence->famsize; i ++) {

            for(int j = 0; j < input_incidence->setsize; j ++) {
                if(set_member(j+1, input_incidence->set[i])) {                
                    if(static_cast<size_t>(j) < other_sol.size()) { // sol adj
                        output_weights[i].adj.emplace(other_sol[j]);
                    } else if(j < m-1) { // bnd
                        output_weights[i].bnd.emplace( -static_cast<int>(j)+static_cast<int>(other_sol.size())-1 );
                    }
                }
                if(output_weights[i].bnd.size() >= _solver.getNObj()-1) {
                    output_weights[i].initial = true;
                }
            }

            output_weights[i].adj.emplace(sol_ind);

            // std::cout << "extreme weight adj: ";
            // output_weights[i].print(std::cout);
        }


        // // extract the polytope adjacency to access the edges
        // std::cout << std::endl;
        // std::cout << "polytope adjacency: " << std::endl;
        // dd_SetFamilyPtr input_adj = dd_CopyAdjacency(poly);
        // dd_WriteSetFamily(stdout,input_adj);
        // dd_FreeSetFamily(input_adj);


        dd_FreeSetFamily(input_incidence);
        dd_FreeMatrix(G);

    }

    dd_FreePolyhedra(poly);
    dd_FreeMatrix(A);
    
}

#else

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

#endif // BACKEND_CDD