#ifndef OSICLPSOLVER_BUILDER_HPP
#define OSICLPSOLVER_BUILDER_HPP

#include<functional>
#include<vector>

#include "coin/OsiClpSolverInterface.hpp"
#include "coin/CoinPackedMatrix.hpp"

class OsiClpSolver_Builder{
    private:
        int nb_vars;
        struct VarType {
            int offset;
            int number;
            std::function<int (int[])> id;
            std::function<std::string (int)> id_toString;
            double lb;
            double ub;
        };
        std::vector<VarType> varTypes;

        double * objective;
        double * col_lb;
        double * col_ub;

        std::vector<int> row_indices_buffer;
        std::vector<double> row_coeffs_buffer;
        std::vector<double> row_lb;
        std::vector<double> row_ub;

        CoinPackedMatrix * matrix;
        
    public:
        static const int MIN = 1;
        static const int MAX = -1;


        OsiClpSolver_Builder();
        ~OsiClpSolver_Builder();

        OsiClpSolver_Builder & addVarType(int number, std::function<int (int[])> id, std::function<std::string (int)> id_toString, double lb, double ub);
        void init();
        double infty();
        OsiClpSolver_Builder & setObjective(int  var_id, double coef);
        OsiClpSolver_Builder & setBounds(int  var_id, double lb, double ub);
        OsiClpSolver_Builder & buffEntry(int  var_id, double coef);
        OsiClpSolver_Builder & pushRow(double lb, double ub);

        OsiClpSolverInterface * buildSolver(int sense);
};

/////////////////////////

OsiClpSolver_Builder::OsiClpSolver_Builder() {
    this->nb_vars = 0;
}
OsiClpSolver_Builder::~OsiClpSolver_Builder() {
    delete[] objective;
    delete[] col_lb;
    delete[] col_ub;
}

OsiClpSolver_Builder & OsiClpSolver_Builder::addVarType(int number, std::function<int (int[])> id, std::function<std::string (int)> id_toString, double lb, double ub) {
    varTypes.push_back({nb_vars, number, id, id_toString, lb, ub});
    this->nb_vars += number;
    return *this;
}
void OsiClpSolver_Builder::init() {
    objective = new double[nb_vars];
    col_lb = new double[nb_vars];
    col_ub = new double[nb_vars];

    for(VarType varType : varTypes) {
        for(int i=varType.offset; i<varType.offset + varType.number; i++) {
            setObjective(i, 0);
            setBounds(i, varType.lb, varType.ub);
        }
    }

    matrix =  new CoinPackedMatrix(false, 0, 0);
    matrix->setDimensions(0, nb_vars);
}
double OsiClpSolver_Builder::infty() {
    return OsiClpInfinity;
}
OsiClpSolver_Builder & OsiClpSolver_Builder::setObjective(int var_id, double coef) {
    objective[var_id] = coef;
    return *this;
}
OsiClpSolver_Builder & OsiClpSolver_Builder::setBounds(int var_id, double lb, double ub) {
    col_lb[var_id] = lb;
    col_ub[var_id] = ub;
    return *this;
}
OsiClpSolver_Builder & OsiClpSolver_Builder::buffEntry(int var_id, double coef) {
    row_indices_buffer.push_back(var_id);
    row_coeffs_buffer.push_back(coef);
    return *this;
}
OsiClpSolver_Builder & OsiClpSolver_Builder::pushRow(double lb, double ub) {
    matrix->appendRow(row_indices_buffer.size(), row_indices_buffer.data(), row_coeffs_buffer.data());
    row_lb.push_back(lb);
    row_ub.push_back(ub);

    row_indices_buffer.clear();
    row_coeffs_buffer.clear();
    return *this;
}
OsiClpSolverInterface * OsiClpSolver_Builder::buildSolver(int sense) {
    OsiClpSolverInterface * solver = new OsiClpSolverInterface();
    solver->loadProblem(*matrix, col_lb, col_ub, objective, row_lb.data(), row_ub.data());
    solver->setObjSense(sense);
    return solver;
}

#endif //OSICLPSOLVER_BUILDER_HPP