#include "utilita.h"
#include "poset.h"
#include "linearExtension.h"
#include "linearExtensionGenerator.h"

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

std::shared_ptr<ProductPOSet> ProductPOSet::Build(std::shared_ptr<POSet> p1, std::shared_ptr<POSet> p2) {
    std::shared_ptr<ProductPOSet> result(new ProductPOSet());
    if (instanceof<FromPOSets>(&(*p1))) {
        auto pp1 = dynamic_cast<const FromPOSets*>(&(*p1));
        result->posets.insert(result->posets.end(), pp1->posets.begin(), pp1->posets.end());
    } else {
        result->posets.push_back(p1);
    }
    if (instanceof<FromPOSets>(&(*p2))) {
        auto pp2 = dynamic_cast<const FromPOSets*>(&(*p2));
        result->posets.insert(result->posets.end(), pp2->posets.begin(), pp2->posets.end());
    } else {
        result->posets.push_back(p2);
    }
    
    std::uint_fast64_t elements_size = p1->size() * p2->size();
    std::vector<std::uint_fast64_t> selezionato(2, 0);
    std::vector<std::vector<std::uint_fast64_t>> elements(elements_size, selezionato);
    
    
    auto buildString = [&](std::vector<std::uint_fast64_t>& s) {
        auto v1 = p1->GetEName(s.at(0));
        auto v2 = p2->GetEName(s.at(1));
        std::string results = v1 + FromPOSets::SEP + v2;
        return results;
    };
    
    auto builToOriginal = [&](std::string& estring, std::vector<std::uint_fast64_t>& eids) {
        auto estring_split = split(estring, FromPOSets::SEP);
        for (std::uint_fast64_t p = 0; p < estring_split.size(); ++p) {
            auto v1 = result->posets.at(p)->getEID(estring_split.at(p));
            eids.at(p) = v1;
        }
    };
    
    auto next = [&](std::vector<std::uint_fast64_t>& s) {
        if (s.at(1) < p2->size() - 1) {
            ++s.at(1);
            return true;
        } else if (s.at(0) < p1->size() - 1) {
            ++s.at(0);
            s.at(1) = 0;
            return true;
        }
        return false;
    };

    {
        std::uint_fast64_t k = 0;
        do {
            elements.at(k) = selezionato;
            ++k;
        } while (next(selezionato));
    }
    auto elements_to_string = std::make_shared<std::vector<std::string>>(elements.size(), "");
    auto elements_to_original = std::make_shared<std::vector<std::vector<std::uint_fast64_t>>>(elements.size(), std::vector<std::uint_fast64_t>(result->posets.size()));

    std::vector<std::uint_fast64_t> eids(result->posets.size());
    for (std::uint_fast64_t k = 0; k < elements_to_string->size(); ++k) {
        auto& v_p = elements.at(k);
        auto v = buildString(v_p);
        elements_to_string->at(k) = v;
        builToOriginal(v, elements_to_original->at(k));
    }
    
    auto comparabilities = std::make_shared<std::list<std::pair<std::string, std::string>>>();
    for (std::uint_fast64_t k = 0; k < elements_to_string->size(); ++k) {
        auto& v1_p = elements.at(k);
        auto& v1 = elements_to_string->at(k);
        for (std::uint_fast64_t h = k + 1; h < elements_to_string->size(); ++h) {
            auto& v2_p = elements.at(h);
            auto& v2 = elements_to_string->at(h);
            
            auto a1 = v1_p.at(0);
            auto b1 = v2_p.at(0);
            auto a2 = v1_p.at(1);
            auto b2 = v2_p.at(1);
            auto p1_upsets = p1->UpSets();
            auto p2_upsets = p2->UpSets();
            if ((a1 == b1 || p1_upsets->at(a1)->find(b1) != p1_upsets->at(a1)->end()) &&
                ((a2 == b2 || p2_upsets->at(a2)->find(b2) != p2_upsets->at(a2)->end()))) {
                comparabilities->push_back(std::make_pair(v1, v2));
            }else if ((a1 == b1 || p1_upsets->at(b1)->find(a1) != p1_upsets->at(b1)->end()) &&
                ((a2 == b2 || p2_upsets->at(b2)->find(a2) != p2_upsets->at(b2)->end()))) {
                comparabilities->push_back(std::make_pair(v2, v1));
            }
        }
    }
    
    result->elements_to_original = elements_to_original;
    result->FillBaseAttribute(*elements_to_string, *comparabilities, nullptr, false);
    
    return result;
}

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

std::shared_ptr<POSet> ProductPOSet::FilterStrong(std::vector<std::uint_fast64_t>& posets_linear_order) {
    auto r = this->clone();
    
    for (std::uint_fast64_t profile1 = 0; profile1 < UpSets()->size(); ++profile1) {
        auto profile1_orig = elements_to_original->at(profile1);
        for (std::uint_fast64_t profile2 = 0; profile2 < UpSets()->size(); ++profile2) {
            auto profile2_orig = elements_to_original->at(profile2);
            for (std::uint_fast64_t k = 0; k < posets_linear_order.size(); ++k) {
                auto p = posets_linear_order.at(k);
                auto poset_at_p = posets.at(p);
                auto profile1_at_p = profile1_orig.at(p);
                auto profile2_at_p = profile2_orig.at(p);
                if (poset_at_p->UpSets()->at(profile1_at_p)->find(profile2_at_p) != poset_at_p->UpSets()->at(profile1_at_p)->end()) {
                    if (profile1_orig.at(profile2_at_p) < profile2_orig.at(profile2_at_p)) {
                        r->UpSets()->at(profile1)->insert(profile2);
                        POSet::TransitiveClosure(*r, profile1, profile2);
                    } else {
                        r->UpSets()->at(profile2)->insert(profile1);
                        POSet::TransitiveClosure(*r, profile2, profile1);
                    }
                    break;
                } else if (poset_at_p->UpSets()->at(profile2_at_p)->find(profile1_at_p) != poset_at_p->UpSets()->at(profile2_at_p)->end()) {
                    if (profile1_orig.at(profile1_at_p) < profile2_orig.at(profile1_at_p)) {
                        r->UpSets()->at(profile1)->insert(profile2);
                        POSet::TransitiveClosure(*r, profile1, profile2);
                    } else {
                        r->UpSets()->at(profile2)->insert(profile1);
                        POSet::TransitiveClosure(*r, profile2, profile1);
                    }
                    break;
                }
            }
        }
    }
    
    return r;
}

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

std::shared_ptr<POSet> ProductPOSet::FilterStrong(std::shared_ptr<POSet> poset) {
    auto copyLE = [&](std::shared_ptr<LinearExtension> leval, std::vector<std::uint_fast64_t>& result) {
        for (std::uint_fast64_t k = 0; k < result.size(); ++k) {
            result.at(k) = leval->getVal(k);
        }
    };
    
    auto poss = std::make_shared<std::vector<std::shared_ptr<POSet>>>();
    poss->push_back(poset);
    
    auto posets = std::shared_ptr<std::vector<std::shared_ptr<POSet>>>();
    auto leg = std::make_shared<LEGTreeOfIdeals>(poss);
    leg->start(0);
    
    auto leval = leg->get();
    std::vector<std::uint_fast64_t> posets_linear_order(leval->size());
    copyLE(leval, posets_linear_order);
    
    auto result = FilterStrong(posets_linear_order);
    
    while (leg->hasNext()) {
        leg->next();
        auto le = leg->get();
        copyLE(le, posets_linear_order);

        auto r = FilterStrong(posets_linear_order);
        //result = POSet::Intersection(result, r);
        result = POSet::Intersection(*result, *r);
    }
    return result;
}

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

std::shared_ptr<POSet> ProductPOSet::FilterLight(std::shared_ptr<POSet> poset) {
    auto r = this->clone();
    
    auto checkElement = [&](std::vector<std::uint_fast64_t>& v1, std::vector<std::uint_fast64_t>& v2) {
        std::uint_fast64_t conta = 0;
        std::vector<std::uint_fast64_t> risultato = {0, 0};
        for (std::uint_fast64_t k = 0; k < v1.size(); ++k) {
            if (v1.at(k) != v2.at(k)) {
                if (conta == 2) {
                    return std::vector<std::uint_fast64_t> {0, 0};
                }
                risultato[conta] = k;
                ++conta;
            }
        }
        if (conta == 2) {
            return risultato;
        }
        return std::vector<std::uint_fast64_t> {0, 0};

    };
    
    for (std::uint_fast64_t profile1 = 0; profile1 < UpSets()->size(); ++profile1) {
        auto profile1_orig = elements_to_original->at(profile1);
        //if (profile1_orig.at(0) == 1 && profile1_orig.at(1) == 2 && profile1_orig.at(2) == 2)
        //    profile1 = profile1;
        
        //if (profile1_orig.at(0) == 2 && profile1_orig.at(1) == 2 && profile1_orig.at(2) == 2)
        //    profile1 = profile1;
        for (std::uint_fast64_t profile2 = profile1 + 1; profile2 < UpSets()->size(); ++profile2) {
            auto profile2_orig = elements_to_original->at(profile2);
            //if (profile2_orig.at(0) == 2 && profile2_orig.at(1) == 2 && profile2_orig.at(2) == 1)
            //    profile1 = profile1;

            auto to_do = checkElement(profile1_orig, profile2_orig);
            if (to_do[0] != 0 || to_do[1] != 0) {
                if (profile1_orig.at(to_do[0]) == profile2_orig.at(to_do[1]) && profile1_orig.at(to_do[1]) == profile2_orig.at(to_do[0])) {
                    if (poset->UpSets()->at(to_do[0])->find(to_do[1]) != poset->UpSets()->at(to_do[0])->end()) {
                        if (profile1_orig.at(to_do[1]) < profile2_orig.at(to_do[1])) {
                            r->UpSets()->at(profile1)->insert(profile2);
                            POSet::TransitiveClosure(*r, profile1, profile2);
                        } else {
                            r->UpSets()->at(profile2)->insert(profile1);
                            POSet::TransitiveClosure(*r, profile2, profile1);
                        }
                    }
                    if (poset->UpSets()->at(to_do[1])->find(to_do[0]) != poset->UpSets()->at(to_do[1])->end()) {
                        if (profile1_orig.at(to_do[0]) < profile2_orig.at(to_do[0])) {
                            r->UpSets()->at(profile1)->insert(profile2);
                            POSet::TransitiveClosure(*r, profile1, profile2);
                        } else {
                            r->UpSets()->at(profile2)->insert(profile1);
                            POSet::TransitiveClosure(*r, profile2, profile1);
                        }
                        
                        //break;
                    }
                }
                
            }
        }
    }
    
    return r;
}

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