#include <vector>
#include <list>
#include <unordered_map>
#include <set>
#include <stack>
#include <memory>
#include <thread>
#include <condition_variable>
#include <mutex>
#include <chrono>

#include "matrice.h"
#include "linearExtension.h"
#include "functionLinearExtension.h"
#include "poset.h"
#include "linearExtensionGenerator.h"
#include "tranformExtension.h"
#include "score.h"


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

std::shared_ptr<POSet> POSet::Build(POSet::BEC p, bool transitiveClosure) {
    std::shared_ptr<POSet> r(new POSet());
    //std::shared_ptr<POSet> r = std::make_shared<POSet>();
    r->FillBaseAttribute(*std::get<1>(p), *std::get<2>(p), nullptr, transitiveClosure);
    return r;
}

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

std::shared_ptr<POSet> POSet::Build(std::shared_ptr<std::vector<std::string>> elements,
             std::list<std::pair<std::string, std::string>>& comparabilities, bool transitiveClosure) {
    std::shared_ptr<POSet> r(new POSet());
    //std::shared_ptr<POSet> r = std::make_shared<POSet>();
    r->FillBaseAttribute(elements, comparabilities, transitiveClosure);
    return r;
}

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

void POSet::FillBaseAttribute(std::vector<std::string>& elements,
                              std::list<std::pair<std::string, std::string>>& comparabilities,
                              Score* score,
                              bool transitiveClosure) {
    __tree_of_ideals = nullptr;
    __lattice_of_ideals = nullptr;
    elementi = std::make_shared<DATASTORE>(elements.size(), nullptr);
    eid_vs_ename = std::make_shared<std::vector<std::string>>(elements.size());
    ename_vs_eid = std::make_shared<std::map<std::string, std::uint_fast64_t>>();

    // elements
    for (auto elemento:elements) {
        auto rinsert = ename_vs_eid->insert(std::pair(elemento, 0));
        if (rinsert.second != true)
        {
            std::string err_str = "POSet error: " + elemento + " duplicated ";
            throw_line(err_str);
        }
    }
    if (score == nullptr) {
        for (auto  [it_name, eid] = std::tuple<std::map<std::string, std::uint_fast64_t>::iterator, std::uint_fast64_t>{ename_vs_eid->begin(), 0};
             it_name != ename_vs_eid->end();
             ++it_name, ++eid) {
            auto elemento = it_name->first;
            it_name->second = eid;
            eid_vs_ename->at(eid) = elemento;
            bool r = AddToDatastore(eid);
            if (r != true)
            {
                std::string err_str = "POSet error: " + elemento + " duplicated ";
                throw_line(err_str);
            }
        }
    } else {
        for (auto  it_name = ename_vs_eid->begin(); it_name != ename_vs_eid->end(); ++it_name) {
            auto elemento = it_name->first;
            auto eid = score->getEID(elemento);
            it_name->second = eid;
            eid_vs_ename->at(eid) = elemento;
            bool r = AddToDatastore(eid);
            if (r != true)
            {
                std::string err_str = "POSet error: " + elemento + " duplicated ";
                throw_line(err_str);
            }
        }
    }

    // comparabilities
    for (auto comp:comparabilities)
    {
        std::uint_fast64_t lower = getEID(comp.first);
        std::uint_fast64_t upper = getEID(comp.second);
        if (lower == upper)
            continue;

        auto lower_set = elementi->at(lower);
        lower_set->insert(upper);
        if (!CheckAntisymmetric(lower, upper))
        {
            std::string lower_str = comp.first;
            std::string second_str = comp.second;
            std::string err_str = "The binary relation is not antisymmetric due to the comparability " + lower_str + " <= " + second_str;
            throw_line(err_str);
        }
        if (transitiveClosure)
            TransitiveClosure(*this, lower, upper);
    }
}

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

void POSet::FillBaseAttribute(std::shared_ptr<std::vector<std::string>> elements,
                              std::list<std::pair<std::string, std::string>>& comparabilities,
                              bool transitiveClosure) {
    __tree_of_ideals = nullptr;
    __lattice_of_ideals = nullptr;
    elementi = std::make_shared<DATASTORE>(elements->size(), nullptr);
    eid_vs_ename = elements;
    ename_vs_eid = std::make_shared<std::map<std::string, std::uint_fast64_t>>();
    // elements
    for (auto  [it_name, eid] = std::tuple<std::vector<std::string>::iterator, std::uint_fast64_t>{eid_vs_ename->begin(), 0}; it_name != eid_vs_ename->end(); ++it_name, ++eid) {
        auto rinsert = ename_vs_eid->insert(std::pair(*it_name, eid));
        if (rinsert.second != true) {
            std::string err_str = "POSet error: " + *it_name + " duplicated ";
            throw_line(err_str);
        }
        bool r = AddToDatastore(eid);
        if (r != true) {
            std::string err_str = "POSet error: " + *it_name + " duplicated ";
            throw_line(err_str);
        }
    }
    // comparabilities
    for (auto comp:comparabilities)
    {
        std::uint_fast64_t lower = getEID(comp.first);
        std::uint_fast64_t upper = getEID(comp.second);
        if (lower == upper)
            continue;

        auto lower_set = elementi->at(lower);
        lower_set->insert(upper);
        if (!CheckAntisymmetric(lower, upper))
        {
            std::string lower_str = comp.first;
            std::string second_str = comp.second;
            std::string err_str = "The binary relation is not antisymmetric due to the comparability " + lower_str + " <= " + second_str;
            throw_line(err_str);
        }
        if (transitiveClosure)
            TransitiveClosure(*this, lower, upper);
    }
    return;
}


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

std::shared_ptr<BucketPOSet> POSet::BucketPOSetFromMinimal() const {
    std::set<std::uint_fast64_t> not_used;
    std::set<std::uint_fast64_t> used;
    std::vector<std::shared_ptr<std::set<std::uint_fast64_t>>> buckets;
    auto elements = std::make_shared<std::vector<std::string>>();
    std::list<std::pair<std::string, std::string>> comparabilities;

    for(std::uint_fast64_t it = 0; it < elementi->size(); ++it) {
        not_used.insert(it);
        elements->push_back(eid_vs_ename->at(it));
    }

    while (not_used.size() > 0) {
        auto current_bucket = std::make_shared<std::set<std::uint_fast64_t>>();
        for (auto check_el : not_used) {
            bool not_minimal = false;
            for(std::uint_fast64_t poset_el = 0; poset_el < elementi->size(); ++poset_el) {
                if (poset_el != check_el && used.find(poset_el) == used.end() && elementi->at(poset_el)->find(check_el) != elementi->at(poset_el)->end()) {
                    not_minimal = true;
                    break;
                }
            }
            if (!not_minimal) {
                current_bucket->insert(check_el);
            }
        }
        used.insert(current_bucket->begin(), current_bucket->end());
        buckets.push_back(current_bucket);
        for (auto e : *current_bucket) {
            not_used.erase(e);
        }
    }

    for (std::uint_fast64_t k = 1; k < buckets.size(); ++k) {
        for (auto down_el_idx : *(buckets.at(k-1))) {
            for (auto up_el_idx : *(buckets.at(k))) {
                auto down_el = eid_vs_ename->at(down_el_idx);
                auto up_el = eid_vs_ename->at(up_el_idx);
                comparabilities.push_back(std::make_pair(down_el, up_el));
            }
        }
    }

    auto result = BucketPOSet::Build(elements, comparabilities);
    return result;
}

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

std::shared_ptr<BucketPOSet> POSet::BucketPOSetFromMaximal() const {
    auto remove = [](POSet::DATASTORE& store, std::set<std::uint_fast64_t>& elements) {
        bool ancora_dati = true;
        for (auto element_to_be_removed: elements) {
            store.at(element_to_be_removed) = nullptr;
            ancora_dati = false;
            for (auto d : store) {
                if (d != nullptr) {
                    d->erase(element_to_be_removed);
                    ancora_dati = true;
                }
            }
            if (ancora_dati == false) {
                break;
            }
        }
        return ancora_dati;
    };

    POSet::DATASTORE data_store_clone(size(), nullptr);
    for (std::uint_fast64_t d = 0; d < elementi->size(); ++d) {
        auto upset_d = elementi->at(d);
        data_store_clone.at(d) = std::make_shared<std::set<std::uint_fast64_t>>(upset_d->begin(), upset_d->end());
    }



    std::vector<std::shared_ptr<std::set<std::uint_fast64_t>>> buckets;
    std::list<std::pair<std::string, std::string>> comparabilities;

    bool ancora_dati = true;
    while (ancora_dati) {
        auto current_bucket = std::make_shared<std::set<std::uint_fast64_t>>();
        for (std::uint_fast64_t check_el = 0; check_el < data_store_clone.size(); ++check_el) {
            if (data_store_clone.at(check_el) != nullptr && data_store_clone.at(check_el)->size() == 0) {
                current_bucket->insert(check_el);
            }
        }
        buckets.insert(buckets.begin(), current_bucket);
        ancora_dati = remove(data_store_clone, *current_bucket);
    }

    auto elements = eid_vs_ename;

    for (std::uint_fast64_t k = 1; k < buckets.size(); ++k) {
        for (auto down_el_idx : *(buckets.at(k-1))) {
            for (auto up_el_idx : *(buckets.at(k))) {
                auto down_el = eid_vs_ename->at(down_el_idx);
                auto up_el = eid_vs_ename->at(up_el_idx);
                comparabilities.push_back(std::make_pair(down_el, up_el));
            }
        }
    }

    auto result = BucketPOSet::Build(elements, comparabilities);
    return result;
}

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

POSet::~POSet() {

}

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

std::uint_fast64_t POSet::getEID(std::string element) const {
    std::map<std::string, std::uint_fast64_t>::const_iterator starting_iterator = ename_vs_eid->find(element);
    if (starting_iterator == ename_vs_eid->end()) {
        std::string err_str = "Element " + element + " not found!";
        throw_line(err_str);
    }

    std::uint_fast64_t element_idx = starting_iterator->second;
    return element_idx;
}

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

std::string POSet::GetEName(std::uint_fast64_t idx) const {
    return eid_vs_ename->at(idx);
}


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

std::uint_fast64_t POSet::size() const {
    return eid_vs_ename->size();
}

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

std::string POSet::to_string(char DELIMETER) const {
    std::string result = "";
    for (std::uint_fast64_t eid = 0; eid < size(); ++eid) {
        result += DELIMETER + GetEName(eid);
    }
    result += "\n";
    for (std::uint_fast64_t out_eid = 0; out_eid < size(); ++out_eid) {
        result += GetEName(out_eid);
        for (std::uint_fast64_t in_eid = 0; in_eid < size(); ++in_eid) {
            if (in_eid == out_eid) {
                result += ";T";
                continue;
            }

            if ((elementi->at(out_eid)->find(in_eid) != elementi->at(out_eid)->end()))
                result += ";T";
            else
                result += DELIMETER;
        }
        result += "\n";
    }
    return result;
}

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

bool POSet::AddToDatastore(std::uint_fast64_t val)
{
    bool result = false;
    if (val < elementi->size() && elementi->at(val) == nullptr) {
        elementi->at(val) = std::make_shared<std::set<std::uint_fast64_t>>();
        result = true;
    }
    return result;
}

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

bool POSet::CheckAntisymmetric(std::uint_fast64_t a, std::uint_fast64_t b)
{
    if (a >= elementi->size())
    {
        std::string el_str = eid_vs_ename->at(a);
        std::string err_str = "Element " + el_str + " not found!";
        throw_line(err_str);
    }
    if (b >= elementi->size())
    {
        std::string el_str = eid_vs_ename->at(b);
        std::string err_str = "Element " + el_str + " not found!";
        throw_line(err_str);
    }

    auto upset_a = elementi->at(a);
    auto upset_b = elementi->at(b);

    bool b_in_successori_a = (upset_a->find(b) != upset_a->end());
    bool a_in_successori_b = (upset_b->find(a) != upset_b->end());

    return !(b_in_successori_a && a_in_successori_b);
}

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

void POSet::TransitiveClosure(POSet& poset, std::uint_fast64_t lower, std::uint_fast64_t upper)
{
    // insert successor of upper into successor of lower
    auto upset_upper = poset.elementi->at(upper);
    auto upset_lower = poset.elementi->at(lower);
    for (auto value_upset_upper : *upset_upper) {
        upset_lower->insert(value_upset_upper);
    }

    // insert upper and its successors into successor of element that contains lower
    for (std::uint_fast64_t el = 0; el < poset.elementi->size(); ++el) {
        if (el == lower)
            continue;
        auto upset_el = poset.elementi->at(el);
        if (upset_el->find(lower) != upset_el->end())
        {
            upset_el->insert(upper);
            for (auto elemento: *upset_upper) {
                upset_el->insert(elemento);
            }
        }
    }
}



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

void POSet::FirstLE(LinearExtension& le) const {
    std::shared_ptr<DATASTORE> downSets = DownSets();
    std::set<std::uint_fast64_t> setOne;

    for (std::uint_fast64_t el = 0; el < downSets->size(); ++el) {
        if (downSets->at(el)->size() == 0)
            setOne.insert(el);
    }

    for (std::uint_fast64_t k = 0; k < le.size(); ++k) {
        std::uint_fast64_t min_el = *(setOne.begin());
        POSet::UpdateForFirst(downSets, setOne, min_el);
        le.set(k, min_el);
    }
}

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

std::shared_ptr<LatticeOfIdeals>  POSet::latticeOfIdeals() {
    if (__lattice_of_ideals == nullptr) {
        auto toi = treeOfIdeals();
        __lattice_of_ideals = std::make_shared<LatticeOfIdeals>(toi, toi->root());
    }
    return __lattice_of_ideals;
}

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

std::shared_ptr<TreeOfIdeals>  POSet::treeOfIdeals() {
    if (__tree_of_ideals == nullptr) {
        auto immediate_predecessor = imPred();
        auto linearExtensionForTreeOfIdeals = std::make_shared<LinearExtension>(size());
        FirstLE(*linearExtensionForTreeOfIdeals);
        auto imPredConverted = std::make_shared<std::vector<std::set<std::uint_fast64_t>>>(immediate_predecessor->size() + 1, std::set<std::uint_fast64_t>());
        for (std::uint_fast64_t val = 0; val < immediate_predecessor->size(); ++val) {
            auto pos_val = linearExtensionForTreeOfIdeals->getPos(val);
            for (auto se : *immediate_predecessor->at(val)) {
                auto pos_se = linearExtensionForTreeOfIdeals->getPos(se);
                imPredConverted->at(pos_val + 1).insert(pos_se + 1);
            }
        }
        __tree_of_ideals = std::make_shared<TreeOfIdeals>(imPredConverted, linearExtensionForTreeOfIdeals);
    }

    return __tree_of_ideals;
}

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

std::shared_ptr<POSet::DATASTORE> POSet::imPred() const {
    std::shared_ptr<POSet::DATASTORE> result = std::make_shared<POSet::DATASTORE>(elementi->size(), nullptr);
    for (std::uint_fast64_t value = 0; value < elementi->size(); ++value) {
        std::shared_ptr<std::set<std::uint_fast64_t>> cs = imPred(value);
        result->at(value) = cs;

    }

    return result;
}

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

std::shared_ptr<POSet::DATASTORE> POSet::imPred(std::shared_ptr<std::vector<std::uint_fast64_t>> els) const {
    std::shared_ptr<POSet::DATASTORE> result = std::make_shared<POSet::DATASTORE>();
    for (std::uint_fast64_t k = 0; k < els->size(); ++k) {
        std::shared_ptr<std::set<std::uint_fast64_t>> cs = imPred(els->at(k));
        (*result)[els->at(k)] = (cs);
    }

    return result;
}

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

std::shared_ptr<std::set<std::uint_fast64_t>> POSet::imPred(std::uint_fast64_t el) const {
    std::shared_ptr<std::set<std::uint_fast64_t>> result = std::make_shared<std::set<std::uint_fast64_t>>();
    for (std::uint_fast64_t k = 0; k < elementi->size(); ++k) {
        auto in_set = elementi->at(k);
        if (in_set->find(el) != in_set->end()) {
            result->insert(k);
        }
    }

    for (auto it_out = result->begin(); it_out != result->end();++it_out) {
        for (auto it_in = result->begin(); it_in != result->end();) {
            if (*it_out != *it_in && elementi->at(*it_in)->find(*it_out) != elementi->at(*it_in)->end()) {
                it_in = result->erase(it_in);
            }
            else {
                ++it_in;
            }
        }
    }
    return result;
}

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

std::shared_ptr<std::vector<std::vector<bool>>> POSet::IncidenceMatrix() const {
    auto result = std::make_shared<std::vector<std::vector<bool>>>(this->size(), std::vector<bool>(this->size(), false));
    for (std::uint_fast64_t row = 0; row < elementi->size(); ++row) {
        result->at(row).at(row) =  true;
        auto set_out = elementi->at(row);
        for (auto cols : *set_out) {
            result->at(row).at(cols) =  true;
        }
    }
    return result;
}

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

std::shared_ptr<std::list<std::pair<std::string, std::string>>> POSet::OrderRelation() const {
    auto result = std::make_shared<std::list<std::pair<std::string, std::string>>>();
    for (std::uint_fast64_t row = 0; row < elementi->size(); ++row) {
        auto set_out = elementi->at(row);
        auto row_e = GetEName(row);
        result->push_back(std::make_pair(row_e, row_e));
        for (auto col : *set_out) {
            auto col_e = GetEName(col);
            result->push_back(std::make_pair(row_e, col_e));
        }
    }
    return result;
}

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

std::shared_ptr<std::vector<std::vector<bool>>> POSet::CoverMatrix() const {
    auto result = std::make_shared<std::vector<std::vector<bool>>>(size(), std::vector<bool>(size(), false));
    for (std::uint_fast64_t v1 = 0; v1 < elementi->size(); ++v1) {
        auto upset_v1 = elementi->at(v1);
        for (auto v2 : *upset_v1) {
            bool trovato = false;
            for (auto v3 : *upset_v1) {
                if (v2 == v3) continue;
                if (elementi->at(v3)->find(v2) != elementi->at(v3)->end()) {
                    trovato = true;
                    break;
                }
            }
            if (!trovato) {
                result->at(v1).at(v2) = true;
            }
        }
    }
    return result;
}

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

std::shared_ptr<std::uint_fast64_t> POSet::Meet(std::vector<std::uint_fast64_t>& insieme) {
    std::shared_ptr<std::uint_fast64_t> result = nullptr;

    auto upsets = UpSets();
    for (std::uint_fast64_t v1 = 0; v1 < upsets->size(); ++v1) {
        auto& upset_v1 = upsets->at(v1);
        bool tutto_ok = true;
        for (auto& v2 : insieme) {
            if (v1 != v2 && upset_v1->find(v2) == upset_v1->end()) {
                tutto_ok = false;
                break;
            }
        }
        if (tutto_ok) {
            if (result == nullptr) {
                result = std::make_shared<std::uint_fast64_t>(v1);
            } else {
                auto& upset_result = upsets->at(*result);
                if (upset_result->find(v1) != upset_result->end()) {
                    *result = v1;
                } else {
                    if (upset_v1->find(*result) == upset_v1->end()) {
                        result = nullptr;
                        break;
                    }
                }
            }
        }
    }
    return result;
}

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

std::shared_ptr<std::uint_fast64_t> POSet::Join(std::vector<std::uint_fast64_t>& insieme) {
    std::shared_ptr<std::uint_fast64_t> result = nullptr;

    auto upsets = UpSets();
    for (std::uint_fast64_t v1 = 0; v1 < upsets->size(); ++v1) {
        auto& upset_v1 = upsets->at(v1);
        bool tutto_ok = true;
        for (auto& v2 : insieme) {
            auto& upset_v2 = upsets->at(v2);
            if (v1 != v2 && upset_v2->find(v1) == upset_v2->end()) {
                tutto_ok = false;
                break;
            }
        }
        if (tutto_ok) {
            if (result == nullptr) {
                result = std::make_shared<std::uint_fast64_t>(v1);
            } else {
                auto& upset_result = upsets->at(*result);
                if (upset_v1->find(*result) != upset_v1->end()) {
                    *result = v1;
                } else {
                    if (upset_result->find(v1) == upset_result->end()) {
                        result = nullptr;
                        break;
                    }
                }
            }
        }
    }
    return result;
}

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

std::shared_ptr<POSet::DATASTORE> POSet::DownSets() const {
    std::shared_ptr<POSet::DATASTORE> result = std::make_shared<POSet::DATASTORE>(elementi->size(), nullptr);
    for (std::uint_fast64_t el1 = 0; el1 < elementi->size(); ++el1)
    {
        auto downset_el1 = std::make_shared<std::set<std::uint_fast64_t>>();
        result->at(el1) = downset_el1;
        for (std::uint_fast64_t el2 = 0; el2 < elementi->size(); ++el2)
        {
            auto upset_el2 = elementi->at(el2);
            if (upset_el2->find(el1) != upset_el2->end())
            {
                downset_el1->insert(el2);
            }
        }
    }

    return result;
}

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

std::shared_ptr<std::set<std::uint_fast64_t>> POSet::DownSet(std::set<std::uint_fast64_t>& els) const {
    auto result = std::make_shared<std::set<std::uint_fast64_t>>();

    for (auto el : els) {
        result->insert(el);
    }
    for (std::uint_fast64_t val = 0; val < elementi->size(); ++val) {
        bool at_least_one = false;
        auto key = elementi->at(val);
        for (auto el : els) {
            if (key->find(el) != key->end()) {
                at_least_one = true;
                break;
            }
        }
        if (at_least_one) {
            result->insert(val);
        }
    }
    return result;
}

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

bool POSet::IsDownSet(std::set<std::uint_fast64_t>& els) const {
    for (std::uint_fast64_t val = 0; val < elementi->size(); ++val) {
        bool at_least_one = false;
        auto key = elementi->at(val);
        for (auto el : els) {
            if (key->find(el) != key->end()) {
                at_least_one = true;
                break;
            }
        }
        if (at_least_one && els.find(val) == els.end()) {
            return false;
        }
    }
    return true;
}

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

std::shared_ptr<std::set<std::uint_fast64_t>> POSet::UpSet(std::set<std::uint_fast64_t>& els) const {
    auto result = std::make_shared<std::set<std::uint_fast64_t>>();

    for (auto el : els) {
        result->insert(el);
    }

    for (auto el_ext : els) {
        for (auto el : *elementi->at(el_ext)) {
            result->insert(el);
        }
    }

    return result;
}

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

bool POSet::IsUpSet(std::set<std::uint_fast64_t>& els) const {
    for (auto el_ext : els) {
        for (auto el : *elementi->at(el_ext)) {
            if (els.find(el) == els.end()) {
                return false;
            }
        }
    }
    return true;
}

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

std::shared_ptr<std::set<std::uint_fast64_t>> POSet::ComparabilitySetOf(std::uint_fast64_t e) const {
    std::set<std::uint_fast64_t> eset;
    eset.insert(e);
    auto r1 = UpSet(eset);
    auto r2 = DownSet(eset);

    r1->insert(r2->begin(), r2->end());
    return r1;
}

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

std::shared_ptr<std::set<std::uint_fast64_t>> POSet::IncomparabilitySetOf(std::uint_fast64_t e) const {
    auto result = std::make_shared<std::set<std::uint_fast64_t>>();

    for (std::uint_fast64_t val = 0; val < elementi->size(); ++val) {
        if (val == e) continue;
        auto upset = elementi->at(val);
        if (upset->find(e) == upset->end()) {
            result->insert(val);
        }
    }
    return result;
}

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

std::shared_ptr<std::set<std::uint_fast64_t>> POSet::Maximals() const {
    auto result = std::make_shared<std::set<std::uint_fast64_t>>();
    for (std::uint_fast64_t v = 0; v < elementi->size(); ++v) {
        auto upset = elementi->at(v);
        if (upset->empty()) {
            result->insert(v);
        }
    }
    return result;
}

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

std::shared_ptr<std::set<std::uint_fast64_t>> POSet::Minimals() const {
    auto result = std::make_shared<std::set<std::uint_fast64_t>>();

    for (std::uint_fast64_t v1 = 0; v1 < elementi->size(); ++v1) {
        bool trovato = false;
        for (std::uint_fast64_t v2 = 0; v2 < elementi->size(); ++v2) {
            if (v1 == v2) continue;
            auto upset_v2 = elementi->at(v2);
            if (upset_v2->find(v1) != upset_v2->end()) {
                trovato = true;
                break;
            }
        }
        if (!trovato) {
            result->insert(v1);
        }
    }
    return result;
}
// ***********************************************
// ***********************************************
// ***********************************************

bool POSet::IsMaximal(std::uint_fast64_t e) const {
    auto upset = elementi->at(e);
    if (upset->size() == 0) {
        return true;
    }
    return false;
}

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

bool POSet::IsMinimal(std::uint_fast64_t e) const {
    for (std::uint_fast64_t v = 0; v < elementi->size(); ++v) {
        if (v == e) continue;
        auto upset = elementi->at(v);
        if (upset->find(e) != upset->end()) {
            return false;
        }
    }
    return true;
}

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

bool POSet::GreaterThan(std::uint_fast64_t e1, std::uint_fast64_t e2) const {
    // e2 < e1 true
    // otherwise false

    if (e1 >= elementi->size()) {
        std::string el_str = eid_vs_ename->at(e1);
        std::string err_str = "POSet error: " + el_str + " not found!";
        throw_line(err_str);
    }
    if (e2 >= elementi->size()) {
        std::string el_str = eid_vs_ename->at(e2);
        std::string err_str = "POSet error: " + el_str + " not found!";
        throw_line(err_str);
    }

    std::shared_ptr<std::set<std::uint_fast64_t>> upset_e2 = elementi->at(e2);
    bool result = upset_e2->find(e1) != upset_e2->end();
    return result;
}

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

std::shared_ptr<std::list<std::pair<std::uint_fast64_t, std::uint_fast64_t>>> POSet::CoverRelation() const {
    auto result = std::make_shared<std::list<std::pair<std::uint_fast64_t, std::uint_fast64_t>>>();
    for (std::uint_fast64_t v1 = 0; v1 < elementi->size(); ++v1) {
        auto upset_v1 = elementi->at(v1);
        for (auto v2 : *upset_v1) {
            bool trovato = false;
            for (auto v3 : *upset_v1) {
                if (v2 == v3) continue;
                if (elementi->at(v3)->find(v2) != elementi->at(v3)->end()) {
                    trovato = true;
                    break;
                }
            }
            if (!trovato) {
                result->push_back(std::make_pair(v1, v2));
            }
        }
    }
    return result;
}

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

std::shared_ptr<std::list<std::pair<std::uint_fast64_t, std::uint_fast64_t>>> POSet::Incomparabilities() const {
    auto result = std::make_shared<std::list<std::pair<std::uint_fast64_t, std::uint_fast64_t>>>();

    for (std::uint_fast64_t out = 0; out < elementi->size(); ++out) {
        auto upset_out = elementi->at(out);
        for (std::uint_fast64_t in = out + 1; in < elementi->size(); ++in) {
            auto upset_in = elementi->at(in);
            if (upset_out->find(in) == upset_out->end() && upset_in->find(out) == upset_in->end()) {
                result->push_back(std::make_pair(out, in));
            }
        }
    }
    return result;
}

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

bool POSet::IsExtensionOf(std::shared_ptr<POSet> p) const {
    if (ename_vs_eid->size() != p->ename_vs_eid->size()) {
        return false;
    }

    for (auto  [it_this, it_param] = std::tuple{ename_vs_eid->begin(), p->ename_vs_eid->begin()};
         it_this != ename_vs_eid->end();
         ++it_this, ++it_param) {
        if (!(it_this->first == it_param->first)) {
            return false;
        }
    }

    for (auto  [it_this, it_param] = std::tuple{elementi->begin(), p->elementi->begin()};
         it_this != elementi->end();
         ++it_this, ++it_param) {
        auto upset_this = *it_this;
        auto upset_param = *it_param;
        for (auto v : *upset_param) {
            if (upset_this->find(v) == upset_this->end()) {
                return false;
            }
        }
    }

    return true;
}

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

std::shared_ptr<std::list<std::pair<std::uint_fast64_t, std::uint_fast64_t>>> POSet::Comparabilities() {
    auto result = std::make_shared<std::list<std::pair<std::uint_fast64_t, std::uint_fast64_t>>>();

    for (std::uint_fast64_t out = 0; out < elementi->size(); ++out) {
        auto upset_out = elementi->at(out);
        for (auto el : *upset_out) {
            result->push_back(std::make_pair(out, el));
        }
    }
    return result;
}

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

void POSet::evaluation(std::vector<std::shared_ptr<FunctionLinearExtension>>& fles,
                       LinearExtensionGenerator& leg,
                       TranformExtension& te,
                       std::vector<std::shared_ptr<Matrice<double>>>& eval_results,
                       std::uint_fast64_t& le_count,
                       bool& end_process,
                       std::shared_ptr<DisplayMessage> displayMessage) {

    displayMessage->Start();
    for (le_count = 1; true ; ++le_count) {
        std::shared_ptr<LinearExtension> leval = leg.get();
        std::shared_ptr<LinearExtension> letrasnsf = te(leval);
        for (std::uint_fast64_t k = 0; k < fles.size(); ++k) {
            std::shared_ptr<FunctionLinearExtension> fle = fles.at(k);
            (*fle)(letrasnsf);
            auto eval_result = eval_results.at(k);
            AverageUpdate(eval_result, fle, leg.currentNumberOfLE());
        }
        displayMessage->Display();
        if (!leg.hasNext())
            break;
        leg.next();
    }
    displayMessage->Stop();
    end_process = true;
}

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

void POSet::AverageUpdate(std::shared_ptr<Matrice<double>> ris,
                          std::shared_ptr<FunctionLinearExtension> fle,
                          std::uint_fast64_t le_number) {
    // se le_number viene incrementato dopo: ris <- ris * (iter / (iter + 1)) + (val / (iter + 1)))
    // se le_number viene incrementato prima: ris <- ris * ((iter - 1) / iter) + (val / iter))

    for (std::uint_fast64_t k = 0; k < fle->resSize(); ++k) {
        std::uint_fast64_t first = fle->at0(k);
        std::uint_fast64_t second = fle->at1(k);
        double add_val = fle->at2(k);

        double old_val = ris->at(first, second);
        //double new_val = old_val * (le_number / (le_number + 1.0)) + add_val / (le_number + 1.0); // se le_number viene incrementato dopo
        double new_val = old_val * ((le_number - 1.0) / ((double) le_number)) + add_val / ((double) le_number); // se le_number viene incrementatoprima
        (*ris)(first, second) = new_val;
    }
}

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

void POSet::SumUpdate(std::shared_ptr<Matrice<double>> ris,
                          std::shared_ptr<FunctionLinearExtension> fle,
                          std::uint_fast64_t le_number) {

    for (std::uint_fast64_t k = 0; k < fle->resSize(); ++k) {
        std::uint_fast64_t first = fle->at0(k);
        std::uint_fast64_t second = fle->at1(k);
        double add_val = fle->at2(k);

        double old_val = ris->at(first, second);
        double new_val = old_val + add_val;
        (*ris)(first, second) = new_val;
    }
}



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

std::shared_ptr<POSet> POSet::clone() const {
    auto elements = std::make_shared<std::vector<std::string>>();
    for (auto v:*eid_vs_ename)
        elements->push_back(v);

    std::list<std::pair<std::string, std::string>> comparabilities;
    for (std::uint_fast64_t d1 = 0; d1 < elementi->size(); ++d1) {
        std::string v1 = eid_vs_ename->at(d1);
        auto upset_d1 = elementi->at(d1);
        for (auto d2 : *upset_d1) {
            std::string v2 = eid_vs_ename->at(d2);
            comparabilities.push_back(std::pair<std::string, std::string>(v1, v2));
        }
    }

    std::shared_ptr<POSet> result = POSet::Build(elements, comparabilities, true);

    return result;
}

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

template <typename SHSet>
bool set_compare (SHSet const &lhs, SHSet const &rhs) {
    return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin());
}

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

void POSet::UpdateForFirst(std::shared_ptr<POSet::DATASTORE> downSets, std::set<std::uint_fast64_t>& setOne, std::uint_fast64_t min_el)
{
    setOne.erase(min_el);
    downSets->at(min_el) = nullptr;
    for (std::uint_fast64_t el = 0; el < downSets->size(); ++el) {
        auto upset_el = downSets->at(el);
        if (upset_el != nullptr && upset_el->find(min_el) != upset_el->end()) {
            upset_el->erase(min_el);
            if (upset_el->size() == 0) {
                setOne.insert(el);
            }
        }
    }
}

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

bool POSet::IsTotalOrder() const {
    std::string err_str = "TO DO....";
    throw_line(err_str);

    return false;
}

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

std::shared_ptr<Matrice<double>> POSet::ComputeMRP() {
    auto mrp = std::make_shared<Matrice<double>>(elementi->size());
    for (std::uint_fast64_t k = 0; k < mrp->Rows(); ++k) {
        (*mrp)(k, k) = 1.0;
    }
    latticeOfIdeals();
    auto e = __lattice_of_ideals->ComputeFiltersCount();
    __lattice_of_ideals->ComputeIdealsCount();


    std::vector<bool> lattice_elements_used(__lattice_of_ideals->ImPred().size(), false);
    std::vector<bool> poset_elements_used(elementi->size(), false);
    RecursiveComputeMRP(__lattice_of_ideals->Bottom(), lattice_elements_used, poset_elements_used, e, mrp);

    return mrp;
}

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


std::shared_ptr<Matrice<double>> POSet::BruggemannLercheSorensenDominance() const {
    auto matrice = std::make_shared<Matrice<double>>(elementi->size());
    for (std::uint_fast64_t k = 0; k < matrice->Rows(); ++k) {
        (*matrice)(k, k) = 1.0;
    }
    for (std::uint_fast64_t v1 = 0; v1 < matrice->Rows(); ++v1) {
        auto up_v1 = elementi->at(v1);
        for (std::uint_fast64_t v2 = v1 + 1; v2 < matrice->Rows(); ++v2) {
            auto up_v2 = elementi->at(v2);
            if (up_v1->find(v2) != up_v1->end()) {
                (*matrice)(v1, v2) = 1.0;
                (*matrice)(v2, v1) = 0.0;
            } else if (up_v2->find(v1) != up_v2->end()) {
                (*matrice)(v2, v1) = 1.0;
                (*matrice)(v1, v2) = 0.0;
            } else {
                std::uint_fast64_t upij = 0;
                std::uint_fast64_t upji = 0;
                std::uint_fast64_t downij = 0;
                std::uint_fast64_t downji = 0;
                for (std::uint_fast64_t v3 = 0; v3 < matrice->Rows(); ++v3) {
                    if (v3 == v1 || v3 == v2) continue;
                    if (up_v1->find(v3) != up_v1->end()) {
                        if (up_v2->find(v3) == up_v2->end()) {
                            ++upij;
                        }
                    }
                    else {
                        if (up_v2->find(v3) != up_v2->end()) {
                            ++upji;
                        }
                    }
                    auto up_v3 = elementi->at(v3);
                    if (up_v3->find(v1) != up_v3->end()) {
                        if (up_v3->find(v2) == up_v3->end()) {
                            ++downij;
                        }
                    }
                    else {
                        if (up_v3->find(v2) != up_v3->end()) {
                            ++downji;
                        }
                    }
                }
                auto aij = (upij + 1.0) / (downij + 1.0);
                auto aji = (upji + 1.0) / (downji + 1.0);
                (*matrice)(v2, v1) = aij / (aij + aji);
                (*matrice)(v1, v2) = aji / (aji + aij);
            }
        }
    }

    return matrice;
}

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

void POSet::RecursiveComputeMRP(
                                std::uint_fast64_t v,
                                std::vector<bool>& lattice_elements_used,
                                std::vector<bool>& poset_elements_used,
                                std::uint_fast64_t e,
                                std::shared_ptr<Matrice<double>>& mrp) {
    lattice_elements_used[v] = true;
    for (auto v_primo : __lattice_of_ideals->ImSucc().at(v)) {
        for (std::uint_fast64_t y = 0; y < elementi->size(); ++y) {
            if (poset_elements_used[y]) {
                std::vector<std::uint_fast64_t> diff;
                auto ideal1 = __lattice_of_ideals->getTreeOfIdeals().ideals().at(v_primo);
                auto ideal2 = __lattice_of_ideals->getTreeOfIdeals().ideals().at(v);
                std::set_difference(ideal1->begin(), ideal1->end(), ideal2->begin(), ideal2->end(), std::inserter(diff, diff.begin()));
                std::uint_fast64_t x = diff.front();
                auto x_converted = __tree_of_ideals->leForConversion()->getVal(x - 1);
                auto nLEIv = __lattice_of_ideals->IdealsCount().at(v);
                auto nLEFvprimo = __lattice_of_ideals->FiltersCount().at(v_primo);
                (*mrp)(y, x_converted) += (nLEIv * nLEFvprimo) / (1.0 * e);
            }
        }
        if ((v_primo != __lattice_of_ideals->Top()) && !lattice_elements_used[v_primo]) {
            std::vector<std::uint_fast64_t> diff;
            auto ideal1 = __lattice_of_ideals->getTreeOfIdeals().ideals().at(v_primo);
            auto ideal2 = __lattice_of_ideals->getTreeOfIdeals().ideals().at(v);
            std::set_difference(ideal1->begin(), ideal1->end(), ideal2->begin(), ideal2->end(), std::inserter(diff, diff.begin()));
            std::uint_fast64_t label = diff.front();
            auto label_converted = __tree_of_ideals->leForConversion()->getVal(label - 1);
            poset_elements_used[label_converted] = true;
            RecursiveComputeMRP(v_primo, lattice_elements_used, poset_elements_used, e, mrp);
            poset_elements_used[label_converted] = false;
        }
    }
}

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

std::shared_ptr<POSet> POSet::LinearSum(POSet& p1, POSet& p2) {
    auto p1_elements = p1.Elements();
    auto p2_elements = p2.Elements();

    std::set<std::string> elements_set(p1_elements->begin(), p1_elements->end());
    elements_set.insert(p2_elements->begin(), p2_elements->end());
    if (elements_set.size() != p1_elements->size() + p2_elements->size()) {
        std::string err_str = "POSets are not disjoint!";
        throw_line(err_str);
    }
    auto elements = std::make_shared<std::vector<std::string>>(elements_set.size());

    std::list<std::pair<std::string, std::string>> comparabilities;
    auto p1_upsets = p1.UpSets();
    auto p2_upsets = p2.UpSets();
    std::uint_fast64_t p = 0;
    for (std::uint_fast64_t v1 = 0; v1 < p1_upsets->size(); ++v1) {
        auto e1 = p1.GetEName(v1);
        elements->at(p++) = e1;

        auto e1_upset = p1_upsets->at(v1);
        for (auto v2 : *e1_upset) {
            auto e2 = p1.GetEName(v2);
            comparabilities.push_back(std::make_pair(e1, e2));
        }
        for (std::uint_fast64_t v3 = 0; v3 < p2_upsets->size(); ++v3) {
            auto e3 = p2.GetEName(v3);
            comparabilities.push_back(std::make_pair(e1, e3));
        }
    }
    for (std::uint_fast64_t v1 = 0; v1 < p2_upsets->size(); ++v1) {
        auto e1 = p2.GetEName(v1);
        elements->at(p++) = e1;

        auto e1_upset = p2_upsets->at(v1);
        for (auto v2 : *e1_upset) {
            auto e2 = p2.GetEName(v2);
            comparabilities.push_back(std::make_pair(e1, e2));
        }
    }

    auto risultato = POSet::Build(elements, comparabilities, true);
    return risultato;
}


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

std::shared_ptr<POSet> POSet::DisjointSum(POSet& p1, POSet& p2) {
    auto p1_elements = p1.Elements();
    auto p2_elements = p2.Elements();

    std::set<std::string> elements_set(p1_elements->begin(), p1_elements->end());
    elements_set.insert(p2_elements->begin(), p2_elements->end());
    if (elements_set.size() != p1_elements->size() + p2_elements->size()) {
        std::string err_str = "POSets are not disjoint!";
        throw_line(err_str);
    }
    auto elements = std::make_shared<std::vector<std::string>>(elements_set.size());

    std::uint_fast64_t p = 0;
    std::list<std::pair<std::string, std::string>> comparabilities;
    {
        auto p1_upsets = p1.UpSets();
        for (std::uint_fast64_t v1 = 0; v1 < p1_upsets->size(); ++v1) {
            auto e1 = p1.GetEName(v1);
            elements->at(p++) = e1;

            auto e1_upset = p1_upsets->at(v1);
            for (auto v2 : *e1_upset) {
                auto e2 = p1.GetEName(v2);
                comparabilities.push_back(std::make_pair(e1, e2));
            }
        }
    }
    {
        auto p2_upsets = p2.UpSets();
        for (std::uint_fast64_t v1 = 0; v1 < p2_upsets->size(); ++v1) {
            auto e1 = p2.GetEName(v1);
            elements->at(p++) = e1;

            auto e1_upset = p2_upsets->at(v1);
            for (auto v2 : *e1_upset) {
                auto e2 = p2.GetEName(v2);
                comparabilities.push_back(std::make_pair(e1, e2));
            }
        }
    }

    auto risultato = POSet::Build(elements, comparabilities, true);
    return risultato;
}

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

std::shared_ptr<POSet> POSet::Lifting(std::string new_element) {
    bool is_new = true;
    for (std::uint_fast64_t v = 0; v < size(); ++v) {
        auto e = GetEName(v);
        if (e == new_element) {
            is_new = false;
            break;
        }
    }
    if (!is_new) {
        std::string err_str = "POSets botton element is not new!";
        throw_line(err_str);
    }

    auto elements = std::make_shared<std::vector<std::string>>(size() + 1);
    std::list<std::pair<std::string, std::string>> comparabilities;

    elements->at(size()) = new_element;
    auto upsets = UpSets();
    for (std::uint_fast64_t v1 = 0; v1 < upsets->size(); ++v1) {
        auto e1 = GetEName(v1);
        elements->at(v1) = e1;
        comparabilities.push_back(std::make_pair(new_element, e1));

        auto e1_upset = upsets->at(v1);
        for (auto v2 : *e1_upset) {
            auto e2 = GetEName(v2);
            comparabilities.push_back(std::make_pair(e1, e2));
        }
    }
    auto risultato = POSet::Build(elements, comparabilities, true);
    return risultato;
}


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

std::shared_ptr<POSet> POSet::Dual() {
    auto els = Elements();
    auto elements = std::make_shared<std::vector<std::string>>(els->begin(), els->end());
    std::list<std::pair<std::string, std::string>> comparabilities;

    auto upsets = UpSets();
    for (std::uint_fast64_t v1 = 0; v1 < upsets->size(); ++v1) {
        auto e1 = GetEName(v1);
        for (auto v2 : *upsets->at(v1)) {
            auto e2 = GetEName(v2);
            comparabilities.push_back(std::make_pair(e2, e1));
        }
    }
    auto risultato = POSet::Build(elements, comparabilities, true);
    return risultato;
}
