#include "utilita.h"
#include "poset.h"

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

std::shared_ptr<ProductLinearPOSet> ProductLinearPOSet::Build(std::shared_ptr<POSet> p1, std::shared_ptr<POSet> p2) {
    if (!instanceof<LinearPOSet>(&(*p1)) || instanceof<ProductLinearPOSet>(&(*p1))) {
        std::string err_str = "ProductLinearPOSet error: wrong poset";
        throw_line(err_str);
    }
    if (!instanceof<LinearPOSet>(&(*p2)) || instanceof<ProductLinearPOSet>(&(*p2))) {
        std::string err_str = "ProductLinearPOSet error: wrong poset";
        throw_line(err_str);
    }
    std::shared_ptr<ProductLinearPOSet> result(new ProductLinearPOSet());

    if (instanceof<FromPOSets>(&(*p1))) {
        auto p = dynamic_cast<const FromPOSets*>(&(*p1));
        result->posets.insert(result->posets.end(), p->posets.begin(), p->posets.end());
    } else {
        result->posets.push_back(p1);
    }
    if (instanceof<FromPOSets>(&(*p2))) {
        auto p = dynamic_cast<const FromPOSets*>(&(*p2));
        result->posets.insert(result->posets.end(), p->posets.begin(), p->posets.end());
    } else {
        result->posets.push_back(p1);
    }
    
    auto ec = ElementComparability(p1, p2);
    auto elements_to_original = std::get<0>(ec);
    auto elements_to_string = std::get<1>(ec);
    auto comparabilities = std::get<2>(ec);
    
    result->elements_to_original = elements_to_original;
    result->FillBaseAttribute(*elements_to_string, *comparabilities, nullptr, false);
    
    return result;
}

// ***********************************************
// ***********************************************
// ***********************************************
/*
ProductLinearPOSet::ProductLinearPOSet(std::shared_ptr<LinearPOSet> p1, std::shared_ptr<LinearPOSet> p2) : FromPOSets(p1, p2) {
    auto ec = ElementComparability(p1, p2);
    auto elements_to_original = std::get<0>(ec);
    auto elements_to_string = std::get<1>(ec);
    auto comparabilities = std::get<2>(ec);
    
    this->elements_to_original = elements_to_original;
    this->fillBaseAttribute(*elements_to_string, *comparabilities, nullptr, false);
}*/

// ***********************************************
// ***********************************************
// ***********************************************
/*
ProductLinearPOSet::ProductLinearPOSet(std::shared_ptr<LinearPOSet> p1, std::shared_ptr<ProductLinearPOSet> p2) : FromPOSets(p1, p2) {
    auto ec = ElementComparability(p1, p2);
    auto elements_to_original = std::get<0>(ec);
    auto elements_to_string = std::get<1>(ec);
    auto comparabilities = std::get<2>(ec);
    
    this->elements_to_original = elements_to_original;
    this->fillBaseAttribute(*elements_to_string, *comparabilities, nullptr, false);
}*/

// ***********************************************
// ***********************************************
// ***********************************************
/*
ProductLinearPOSet::ProductLinearPOSet(std::shared_ptr<ProductLinearPOSet> p1, std::shared_ptr<LinearPOSet> p2) : FromPOSets(p1, p2) {
    auto ec = ElementComparability(p1, p2);
    auto elements_to_original = std::get<0>(ec);
    auto elements_to_string = std::get<1>(ec);
    auto comparabilities = std::get<2>(ec);
    
    this->elements_to_original = elements_to_original;
    this->fillBaseAttribute(*elements_to_string, *comparabilities, nullptr, false);
}
*/
// ***********************************************
// ***********************************************
// ***********************************************
/*
ProductLinearPOSet::ProductLinearPOSet(std::shared_ptr<ProductLinearPOSet> p1, std::shared_ptr<ProductLinearPOSet> p2) : FromPOSets(p1, p2) {
    auto ec = ElementComparability(p1, p2);
    auto elements_to_original = std::get<0>(ec);
    auto elements_to_string = std::get<1>(ec);
    auto comparabilities = std::get<2>(ec);
    
    this->elements_to_original = elements_to_original;
    this->fillBaseAttribute(*elements_to_string, *comparabilities, nullptr, false);
}
*/
// ***********************************************
// ***********************************************
// ***********************************************

FromPOSets::BEC ProductLinearPOSet::ElementComparability(std::shared_ptr<POSet> p1, std::shared_ptr<POSet> p2) {
    std::uint_fast64_t elements_size = p1->size() * p2->size();
    
    std::vector<std::shared_ptr<POSet>> posets;
    if (instanceof<FromPOSets>(&(*p1))) {
        auto pp1 = dynamic_cast<const FromPOSets*>(&(*p1));
        posets.insert(posets.end(), pp1->posets.begin(), pp1->posets.end());
    } else {
        posets.push_back(p1);
    }
    if (instanceof<FromPOSets>(&(*p2))) {
        auto pp2 = dynamic_cast<const FromPOSets*>(&(*p2));
        posets.insert(posets.end(), pp2->posets.begin(), pp2->posets.end());
    } else {
        posets.push_back(p2);
    }
    
    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 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;
    };
    
    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 = posets.at(p)->getEID(estring_split.at(p));
            eids.at(p) = v1;
        }
    };
    {
        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>(posets.size()));

    std::vector<std::uint_fast64_t> eids(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));
            }
            
        }
    }
    auto result = std::make_tuple(elements_to_original, elements_to_string, comparabilities);
    return result;
}

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

std::shared_ptr<Matrice<double>> ProductLinearPOSet::ComputeMRPLex() {
    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;
    }
    auto k = posets.size();
    
    // std::vector<std::shared_ptr<std::set<std::uint_fast64_t>>>;
    for (std::uint_fast64_t v1 = 0; v1 < matrice->Rows(); ++v1) {
        for (std::uint_fast64_t v2 = v1 + 1; v2 < matrice->Rows(); ++v2) {
            std::uint_fast64_t k1_1 = 0;
            std::uint_fast64_t k1_2 = 0;
            std::uint_fast64_t k2 = 0;
            for (std::uint_fast64_t p = 0; p < posets.size(); ++p) {
                auto v1_p = elements_to_original->at(v1).at(p);
                auto v2_p = elements_to_original->at(v2).at(p);
                auto upsets = posets.at(p)->UpSets();
                if (v1_p == v2_p) {
                    ++k2;
                } else if (upsets->at(v1_p)->find(v2_p) != upsets->at(v1_p)->end()) {
                    ++k1_1;
                } else if (upsets->at(v2_p)->find(v1_p) != upsets->at(v2_p)->end()) {
                    ++k1_2;
                }
            }
            std::uint_fast64_t k2_fatt = k2;
            std::uint_fast64_t k2_termine = k2 - 1;
            std::uint_fast64_t k_fatt = k - 1;
            std::uint_fast64_t k_termine = k - 1 - 1;
            std::uint_fast64_t somma = 1;
            for (std::uint_fast64_t s = 1; s <= k2; ++s) {
                somma += k2_fatt * k_fatt;
                k2_fatt *= k2_termine;
                k_fatt *= k_termine;
                --k2_termine;
                --k_termine;
            }
            (*matrice)(v1, v2) = somma * (k1_1 * 1.0) / k;
            (*matrice)(v2, v1) = somma * (k1_2 * 1.0) / k;
        }
    }
    
    return matrice;
}
