#include <typeinfo>
#include <limits>
#include <fstream>
#include <list>
#include <tuple>
#include <vector>
#include <set>
#include <map>
#include <memory>
#include <ctime>
#include <chrono>
#include <variant>

#include "rwrapper.h"

#include "posetWrapper.h"
#include "myException.h"
#include "poset.h"
#include "tranformExtension.h"
#include "functionLinearExtension.h"
#include "linearExtension.h"
#include "linearExtensionGenerator.h"
#include "rSeparationUtils.h"
#include "rDisplayUtils.h"

//************************************
//************************************
//************************************
std::uint_fast64_t POSetWrap::conta = 0;

std::map<std::string, POSetWrap::FunctionLinearType> POSetWrap::functionLinearMapType = {
    { "MutualRankingProbability", POSetWrap::FunctionLinearType::FLETMutualRankingProbability},
    { "AverageHeight", POSetWrap::FunctionLinearType::FLETAverageHeight },
    { "symmetric", POSetWrap::FunctionLinearType::FLETSeparationSymmetric },
    { "asymmetricLower", POSetWrap::FunctionLinearType::FLETSeparationAsymmetricLower },
    { "asymmetricUpper", POSetWrap::FunctionLinearType::FLETSeparationAsymmetricUpper },
    { "RFunction", POSetWrap::FunctionLinearType::FLETRFunction },
};

//************************************
//************************************
//************************************

POSetWrap* POSetWrap::BuildGeneric(std::shared_ptr<std::vector<std::string>> elements, std::list<std::pair<std::string, std::string>>& comparabilities) {
    auto result = new POSetWrap();
    result->type = POSetTypes::generic;
    result->poset = POSet::Build(elements, comparabilities);
    return result;
}

//************************************
//************************************
//************************************

POSetWrap* POSetWrap::BuildLinear(std::shared_ptr<std::vector<std::string>> elements) {
    auto result = new POSetWrap();
    result->type = POSetTypes::lin;
    result->poset = LinearPOSet::Build(elements);
    return result;
}

//************************************
//************************************
//************************************

POSetWrap* POSetWrap::BuildProduct(std::vector<POSetWrap*>& posets) {
    auto result = new POSetWrap();
    result->type = POSetTypes::prod;

    std::shared_ptr<ProductPOSet> r = ProductPOSet::Build(posets.at(0)->poset, posets.at(1)->poset);
    for (std::uint_fast64_t k = 2; k < posets.size(); ++k) {
        r = ProductPOSet::Build(r, posets.at(k)->poset);
    }
    result->poset = r;
    return result;
}

//************************************
//************************************
//************************************

POSetWrap* POSetWrap::BuildLexicographicProduct(std::vector<POSetWrap*>& posets) {
    auto result = new POSetWrap();
    result->type = POSetTypes::prod;

    std::shared_ptr<LexicographicProductPOSet> r = LexicographicProductPOSet::Build(posets.at(0)->poset, posets.at(1)->poset);

    for (std::uint_fast64_t k = 2; k < posets.size(); ++k) {
        r = LexicographicProductPOSet::Build(r, posets.at(k)->poset);
    }
    result->poset = r;
    return result;
}

//************************************
//************************************
//************************************

POSetWrap* POSetWrap::BuildIntersection(std::vector<POSetWrap*>& posets) {
    auto result = new POSetWrap();
    result->type = POSetTypes::generic;

    std::shared_ptr<POSet> r = POSet::Intersection(*(posets.at(0)->poset), *(posets.at(1)->poset));
    for (std::uint_fast64_t k = 2; k < posets.size(); ++k) {
        r = POSet::Intersection(*(r), *(posets.at(k)->poset));
    }
    result->poset = r;
    return result;
}

//************************************
//************************************
//************************************

POSetWrap* POSetWrap::BuildLinearSum(std::vector<POSetWrap*>& posets) {
    auto result = new POSetWrap();
    result->type = POSetTypes::generic;

    std::shared_ptr<POSet> r = POSet::LinearSum(*(posets.at(0)->poset), *(posets.at(1)->poset));
    for (std::uint_fast64_t k = 2; k < posets.size(); ++k) {
        r = POSet::LinearSum(*(r), *(posets.at(k)->poset));
    }
    result->poset = r;
    return result;
}

//************************************
//************************************
//************************************

POSetWrap* POSetWrap::BuildDisjointSum(std::vector<POSetWrap*>& posets) {
    auto result = new POSetWrap();
    result->type = POSetTypes::generic;

    std::shared_ptr<POSet> r = POSet::DisjointSum(*(posets.at(0)->poset), *(posets.at(1)->poset));
    for (std::uint_fast64_t k = 2; k < posets.size(); ++k) {
        r = POSet::DisjointSum(*(r), *(posets.at(k)->poset));
    }
    result->poset = r;
    return result;
}

//************************************
//************************************
//************************************

POSetWrap* POSetWrap::BuildLiftingPOSet(POSetWrap* poset, std::string new_element) {
    auto result = new POSetWrap();
    result->type = POSetTypes::generic;
    std::shared_ptr<POSet> r = poset->poset->Lifting(new_element);
    result->poset = r;
    return result;
}


//************************************
//************************************
//************************************

POSetWrap* POSetWrap::BuildDualPOSet(POSetWrap* poset) {
    auto result = new POSetWrap();
    result->type = POSetTypes::generic;
    std::shared_ptr<POSet> r = poset->poset->Dual();
    result->poset = r;
    return result;
}

//************************************
//************************************
//************************************

POSetWrap* POSetWrap::BuildBucketPOSet(std::shared_ptr<std::vector<std::string>> elements, std::list<std::pair<std::string, std::string>>& comparabilities) {
    auto result = new POSetWrap();
    result->type = POSetTypes::bucket;
    result->poset = BucketPOSet::Build(elements, comparabilities);
    return result;
}

//************************************
//************************************
//************************************

POSetWrap* POSetWrap::BuildBinaryVariablePOSet(std::vector<std::string>& variables_names) {
    auto result = new POSetWrap();
    result->type = POSetTypes::binaryVariable;
    result->poset = BinaryVariablePOSet::Build(variables_names);
    return result;
}

// ***********************************************
// ***********************************************
// ***********************************************

POSetWrap* POSetWrap::BuildFencePOSet(std::shared_ptr<std::vector<std::string>> elements, bool orientation) {
    auto result = new POSetWrap();
    result->type = POSetTypes::generic;
    std::list<std::pair<std::string, std::string>> comparabilities;
    for (std::uint_fast64_t k = 0; k < elements->size(); ++k) {
        std::string e = elements->at(k);
        if (orientation) {
            if (k % 2 == 1) {
                comparabilities.push_back(std::make_pair(elements->at(k - 1), elements->at(k)));
            } else if (k != 0) {
                comparabilities.push_back(std::make_pair(elements->at(k), elements->at(k - 1)));
            }
        } else {
            if (k % 2 == 1) {
                comparabilities.push_back(std::make_pair(elements->at(k), elements->at(k - 1)));
            } else if (k != 0) {
                comparabilities.push_back(std::make_pair(elements->at(k - 1), elements->at(k)));
            }
        }
     }
    result->poset = POSet::Build(elements, comparabilities);
    return result;
}

// ***********************************************
// ***********************************************
// ***********************************************

std::shared_ptr<std::vector<std::string>> POSetWrap::Elements() const {
    return this->poset->Elements();
}

// ***********************************************
// ***********************************************
// ***********************************************

SEXP POSetWrap::IsDominatedBy(std::vector<std::string>& es1, std::vector<std::string>& es2, int& conta_proteggi) const {

    SEXP result = Proteggi(Rf_allocVector(LGLSXP, (long) es1.size()), conta_proteggi);
    for (std::uint_fast64_t k = 0; k < es1.size(); ++k) {
        auto v1 = this->poset->getEID(es1.at(k));
        auto v2 = this->poset->getEID(es2.at(k));
        auto r = this->poset->GreaterThan(v2, v1);
        LOGICAL(result)[k] = r;
    }
    return result;
}

// ***********************************************
// ***********************************************
// ***********************************************

SEXP POSetWrap::Dominates(std::vector<std::string>& es1, std::vector<std::string>& es2, int& conta_proteggi) const {
    SEXP result = Proteggi(Rf_allocVector(LGLSXP, (long) es1.size()), conta_proteggi);
    for (std::uint_fast64_t k = 0; k < es1.size(); ++k) {
        auto v1 = this->poset->getEID(es1.at(k));
        auto v2 = this->poset->getEID(es2.at(k));
        auto r = this->poset->GreaterThan(v1, v2);
        LOGICAL(result)[k] = r;
    }
    return result;
}

// ***********************************************
// ***********************************************
// ***********************************************

SEXP POSetWrap::IsComparableWith(std::vector<std::string>& es1, std::vector<std::string>& es2, int& conta_proteggi) const {
    SEXP result = Proteggi(Rf_allocVector(LGLSXP, (long) es1.size()), conta_proteggi);
    for (std::uint_fast64_t k = 0; k < es1.size(); ++k) {
        auto e1 = es1.at(k);
        auto e2 = es2.at(k);
        if (e1 == e2) {
            LOGICAL(result)[k] = true;
            continue;
        }
        auto v1 = this->poset->getEID(e1);
        auto v2 = this->poset->getEID(e2);
        LOGICAL(result)[k] = this->poset->GreaterThan(v1, v2) || this->poset->GreaterThan(v2, v1);
    }
    return result;
}

// ***********************************************
// ***********************************************
// ***********************************************

SEXP POSetWrap::IsIncomparableWith(std::vector<std::string>& es1, std::vector<std::string>& es2, int& conta_proteggi) const {
    SEXP result = Proteggi(Rf_allocVector(LGLSXP, (long) es1.size()), conta_proteggi);
    for (std::uint_fast64_t k = 0; k < es1.size(); ++k) {
        auto e1 = es1.at(k);
        auto e2 = es2.at(k);
        if (e1 == e2) {
            LOGICAL(result)[k] = false;
            continue;
        }
        auto v1 = this->poset->getEID(e1);
        auto v2 = this->poset->getEID(e2);
        LOGICAL(result)[k] = !(this->poset->GreaterThan(v1, v2) || this->poset->GreaterThan(v2, v1));
    }
    return result;
}

// ***********************************************
// ***********************************************
// ***********************************************

std::shared_ptr<std::set<std::string>> POSetWrap::UpsetOf(std::shared_ptr<std::vector<std::string>> elementi) const {
    std::set<std::uint_fast64_t> eset;
    for (auto vstr : *elementi) {
        auto vint = this->poset->getEID(vstr);
        eset.insert(vint);
    }

    auto r = this->poset->UpSet(eset);
    auto result = std::make_shared<std::set<std::string>>();
    for (auto vint : *r) {
        auto vstr = this->poset->GetEName(vint);
        result->insert(vstr);
    }
    return result;
}

// ***********************************************
// ***********************************************
// ***********************************************

bool POSetWrap::IsUpSet(std::shared_ptr<std::vector<std::string>> elementi) const {
    std::set<std::uint_fast64_t> eset;
    for (auto vstr : *elementi) {
        auto vint = this->poset->getEID(vstr);
        eset.insert(vint);
    }

    auto result = this->poset->IsUpSet(eset);
    return result;
}

// ***********************************************
// ***********************************************
// ***********************************************

bool POSetWrap::IsDownSet(std::shared_ptr<std::vector<std::string>> elementi) const {
    std::set<std::uint_fast64_t> eset;
    for (auto vstr : *elementi) {
        auto vint = this->poset->getEID(vstr);
        eset.insert(vint);
    }

    auto result = this->poset->IsDownSet(eset);
    return result;
}

// ***********************************************
// ***********************************************
// ***********************************************

std::shared_ptr<std::set<std::string>> POSetWrap::DownsetOf(std::shared_ptr<std::vector<std::string>> elementi) const {
    std::set<std::uint_fast64_t> eset;
    for (auto vstr : *elementi) {
        auto vint = this->poset->getEID(vstr);
        eset.insert(vint);
    }

    auto r = this->poset->DownSet(eset);
    auto result = std::make_shared<std::set<std::string>>();
    for (auto vint : *r) {
        auto vstr = this->poset->GetEName(vint);
        result->insert(vstr);
    }
    return result;
}

// ***********************************************
// ***********************************************
// ***********************************************

SEXP POSetWrap::ComparabilitySetOf(std::string e, int& conta_proteggi) const {
    auto r = this->poset->ComparabilitySetOf(this->poset->getEID(e));
    SEXP result = Proteggi(Rf_allocVector(STRSXP, (long) r->size()), conta_proteggi);
    std::uint_fast64_t pos = 0;
    for (auto vint : *r) {
        auto vstr = this->poset->GetEName(vint);
        SET_STRING_ELT(result, (long) pos++, Rf_mkChar(vstr.c_str()));
    }
    return result;
}

// ***********************************************
// ***********************************************
// ***********************************************

SEXP POSetWrap::IncomparabilitySetOf(std::string e, int& conta_proteggi) const {
    auto r = this->poset->IncomparabilitySetOf(this->poset->getEID(e));
    SEXP result = Proteggi(Rf_allocVector(STRSXP, (long) r->size()), conta_proteggi);
    std::uint_fast64_t pos = 0;
    for (auto vint : *r) {
        auto vstr = this->poset->GetEName(vint);
        SET_STRING_ELT(result, (long) pos++, Rf_mkChar(vstr.c_str()));
    }
    return result;
}

// ***********************************************
// ***********************************************
// ***********************************************

SEXP POSetWrap::Maximal(int& conta_proteggi) const {
    auto r = this->poset->Maximals();
    SEXP result = Proteggi(Rf_allocVector(STRSXP, (long) r->size()), conta_proteggi);
    std::uint_fast64_t pos = 0;
    for (auto vint : *r) {
        auto vstr = this->poset->GetEName(vint);
        SET_STRING_ELT(result, (long) pos++, Rf_mkChar(vstr.c_str()));
    }
    return result;
}

// ***********************************************
// ***********************************************
// ***********************************************

SEXP POSetWrap::Minimal(int& conta_proteggi) const {
    auto r = this->poset->Minimals();
    SEXP result = Proteggi(Rf_allocVector(STRSXP, (long) r->size()), conta_proteggi);
    std::uint_fast64_t pos = 0;
    for (auto vint : *r) {
        auto vstr = this->poset->GetEName(vint);
        SET_STRING_ELT(result, (long) pos++, Rf_mkChar(vstr.c_str()));
    }
    return result;
}

// ***********************************************
// ***********************************************
// ***********************************************

bool POSetWrap::IsMaximal(std::string e) const {
  auto vint = this->poset->getEID(e);
  auto result = this->poset->IsMaximal(vint);
  return result;
}

// ***********************************************
// ***********************************************
// ***********************************************

bool POSetWrap::IsMinimal(std::string e) const {
  auto vint = this->poset->getEID(e);
  auto result = this->poset->IsMinimal(vint);
  return result;
}

// ***********************************************
// ***********************************************
// ***********************************************

SEXP POSetWrap::CoverRelation(int& conta_proteggi) const {
    auto r = this->poset->CoverRelation();
    SEXP result = Proteggi(Rf_allocMatrix(STRSXP, (int) r->size(), 2), conta_proteggi);
    std::uint_fast64_t pos = 0;
    for (auto p : *r) {
        auto p1 = this->poset->GetEName(p.first);
        auto p2 = this->poset->GetEName(p.second);
        SET_STRING_ELT(result, (long) (pos + r->size() * 0), Rf_mkChar(p1.c_str()));
        SET_STRING_ELT(result, (long) (pos + r->size() * 1), Rf_mkChar(p2.c_str()));
        pos++;
    }
    return result;
}

// ***********************************************
// ***********************************************
// ***********************************************

SEXP POSetWrap::CoverMatrix(int& conta_proteggi) const {
    auto r1 = this->poset->CoverMatrix();
    auto r2 = this->poset->Elements();
    SEXP result = Proteggi(Rf_allocMatrix(LGLSXP, (int) r1->size(), (int) r1->size()), conta_proteggi);
    auto names = Proteggi(Rf_allocVector(VECSXP, (long) r1->size()), conta_proteggi);

    for (std::uint_fast64_t riga = 0; riga < r1->size(); ++riga) {
        SET_VECTOR_ELT(names, (long) riga, Rf_mkChar(r2->at(riga).c_str()));
        for (std::uint_fast64_t colonna = 0; colonna < r1->size(); ++colonna) {
            LOGICAL(result)[(long) (riga + r1->size() * colonna)] = r1->at(riga).at(colonna);
        }
    }
    SEXP dimnames = Proteggi(Rf_allocVector(VECSXP, 2), conta_proteggi);
    SET_VECTOR_ELT(dimnames, 0, names);
    SET_VECTOR_ELT(dimnames, 1, names);
    Rf_setAttrib(result, R_DimNamesSymbol, dimnames);

    return result;
}

// ***********************************************
// ***********************************************
// ***********************************************

SEXP POSetWrap::Incomparabilities(int& conta_proteggi) const {
    auto r = this->poset->Incomparabilities();
    SEXP result = Proteggi(Rf_allocMatrix(STRSXP, (int) r->size(), 2), conta_proteggi);
    std::uint_fast64_t pos = 0;
    for (auto p : *r) {
        auto p1 = this->poset->GetEName(p.first);
        auto p2 = this->poset->GetEName(p.second);
        SET_STRING_ELT(result, (long) (pos + r->size() * 0), Rf_mkChar(p1.c_str()));
        SET_STRING_ELT(result, (long) (pos + r->size() * 1), Rf_mkChar(p2.c_str()));
        pos++;
    }
    return result;
}

// ***********************************************
// ***********************************************
// ***********************************************

bool POSetWrap::IsExtensionOf(POSetWrap* p) const {
    auto result = this->poset->IsExtensionOf(p->poset);
    return result;
}

// ***********************************************
// ***********************************************
// ***********************************************

std::shared_ptr<std::vector<std::vector<bool>>> POSetWrap::IncidenceMatrix() const {
    return this->poset->IncidenceMatrix();
}

// ***********************************************
// ***********************************************
// ***********************************************

SEXP POSetWrap::Meet(std::vector<std::string>& insieme, int& conta_proteggi) const {
    std::vector<std::uint_fast64_t> i_insieme(insieme.size());
    for (std::uint_fast64_t k = 0; k < insieme.size(); ++k) {
        auto vint = poset->getEID(insieme.at(k));
        i_insieme.at(k) = vint;
    }
    auto r = poset->Meet(i_insieme);
    std::string v = "";
    if (r != nullptr) {
        v = poset->GetEName(*r);
    }
    SEXP result = Proteggi(Rf_allocVector(STRSXP, 1), conta_proteggi);
    SET_STRING_ELT(result, 0, Rf_mkChar(v.c_str()));
    return result;
}

// ***********************************************
// ***********************************************
// ***********************************************

SEXP POSetWrap::Join(std::vector<std::string>& insieme, int& conta_proteggi) const {
    std::vector<std::uint_fast64_t> i_insieme(insieme.size());
    for (std::uint_fast64_t k = 0; k < insieme.size(); ++k) {
        auto vint = poset->getEID(insieme.at(k));
        i_insieme.at(k) = vint;
    }
    auto r = poset->Join(i_insieme);
    std::string v = "";
    if (r != nullptr) {
        v = poset->GetEName(*r);
    }
    SEXP result = Proteggi(Rf_allocVector(STRSXP, 1), conta_proteggi);
    SET_STRING_ELT(result, 0, Rf_mkChar(v.c_str()));
    return result;
}

// ***********************************************
// ***********************************************
// ***********************************************

std::shared_ptr<std::list<std::pair<std::string, std::string>>> POSetWrap::OrderRelation() const {
    return this->poset->OrderRelation();
}

// ***********************************************
// ***********************************************
// ***********************************************

SEXP POSetWrap::ExactMRP(std::uint_fast64_t output_ogni_in_sec, int& conta_proteggi) const {
    auto poss = std::make_shared<std::vector<std::shared_ptr<POSet>>>();
    poss->push_back(poset);
    std::shared_ptr<LinearExtensionGenerator> legenerator = nullptr;
    if (dynamic_pointer_cast<BinaryVariablePOSet>(poset) == nullptr) {
        legenerator = std::make_shared<LEGTreeOfIdeals>(poss);
    } else {
        legenerator = std::make_shared<LEGBinaryVariable>(poss);
    }
    legenerator->start(0);

    std::vector<std::shared_ptr<Matrice<double>>> eval_results(1);
    std::uint_fast64_t nrow = poset->size();
    std::uint_fast64_t ncol = poset->size();
    auto mrp = std::make_shared<Matrice<double>>(nrow, ncol);
    eval_results.at(0) = mrp;

    std::vector<std::shared_ptr<FunctionLinearExtension>> fles(1);
    fles.at(0) = std::make_shared<FLEMutualRankingProbability>(poset);
    TEItentity tle(poset->Elements());

    std::uint_fast64_t le_count = 0;
    std::uint_fast64_t total_number_of_extension = std::numeric_limits<std::uint_fast64_t>::max();
    bool end_process = false;

    std::shared_ptr<DisplayMessage> displayMessage = nullptr;
    if (output_ogni_in_sec == std::numeric_limits<std::uint_fast64_t>::max()) {
        displayMessage = std::make_shared<DisplayMessageNull>();
    } else {
        displayMessage = std::make_shared<DisplayMessageEvaluationR>(total_number_of_extension, le_count, output_ogni_in_sec);
    }
    poset->evaluation(fles, *legenerator, tle, eval_results, le_count, end_process, displayMessage);

    SEXP mrp_r = Proteggi(Rf_allocMatrix(REALSXP, (int) nrow, (int) ncol), conta_proteggi);
    auto names = Proteggi(Rf_allocVector(VECSXP, (int) nrow), conta_proteggi);
    auto poset_elements = poset->Elements();

    for (std::uint_fast64_t riga = 0; riga < nrow; ++riga) {
        SET_VECTOR_ELT(names, (long) riga, Rf_mkChar(poset_elements->at(riga).c_str()));
        for (std::uint_fast64_t colonna = 0; colonna < ncol; ++colonna) {
            auto v = (*mrp)(riga, colonna);
            REAL(mrp_r)[(long) (riga + nrow * colonna)] = v;
        }
    }

    SEXP dimnames = Proteggi(Rf_allocVector(VECSXP, 2), conta_proteggi);
    SET_VECTOR_ELT(dimnames, 0, names);
    SET_VECTOR_ELT(dimnames, 1, names);
    Rf_setAttrib(mrp_r, R_DimNamesSymbol, dimnames);

    auto result_r = Proteggi(Rf_allocVector(VECSXP, (long) (eval_results.size() + 1)), conta_proteggi);
    auto result_r_names = Proteggi(Rf_allocVector(VECSXP, (long) (eval_results.size() + 1)), conta_proteggi);
    SET_VECTOR_ELT(result_r, 0, mrp_r);
    SET_VECTOR_ELT(result_r_names, 0, Rf_mkChar("MRP"));
    SEXP n_r = Proteggi(Rf_allocVector(INTSXP, 1), conta_proteggi);
    INTEGER(n_r)[0] = (int) le_count;
    SET_VECTOR_ELT(result_r, 1, n_r);
    SET_VECTOR_ELT(result_r_names, 1, Rf_mkChar("n"));
    Rf_setAttrib(result_r, R_NamesSymbol, result_r_names);

    return result_r;
}

// ***********************************************
// ***********************************************
// ***********************************************

SEXP POSetWrap::BruggemannLercheSorensenDominance(int& conta_proteggi) const {
    auto r = poset->BruggemannLercheSorensenDominance();

    std::uint_fast64_t nrow = r->Rows();
    std::uint_fast64_t ncol = r->Cols();
    SEXP result = Proteggi(Rf_allocMatrix(REALSXP, (int) nrow, (int) ncol), conta_proteggi);
    SEXP names = Proteggi(Rf_allocVector(VECSXP, (long) nrow), conta_proteggi);
    auto poset_elements = poset->Elements();

    for (std::uint_fast64_t riga = 0; riga < nrow; ++riga) {
        SET_VECTOR_ELT(names, (long) riga, Rf_mkChar(poset_elements->at(riga).c_str()));
        for (std::uint_fast64_t colonna = 0; colonna < nrow; ++colonna) {
            auto v = (*r)(riga, colonna);
            REAL(result)[riga + nrow * colonna] = v;
        }
    }

    SEXP dimnames = Proteggi(Rf_allocVector(VECSXP, 2), conta_proteggi);
    SET_VECTOR_ELT(dimnames, 0, names);
    SET_VECTOR_ELT(dimnames, 1, names);
    Rf_setAttrib(result, R_DimNamesSymbol, dimnames);

    return result;
}

// ***********************************************
// ***********************************************
// ***********************************************

