#include <memory>
#include <tuple>

#include "types.h"
#include "matrice.h"
#include "poset.h"

extern bool STAMPA;

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

BJRES BucketOrder::BestJoinAt(POSet& starting_poset, POSet& preserve_poset, LossFunctionMRP& lfmrp, std::uint64_t bucket_position) {
    std::uint64_t bucket_order_size = this->eSize();
    double best_lfmrp_value = 0.0;
    std::shared_ptr<Matrice<double>> best_mrp = nullptr;
    BUCKETPTR best_b = nullptr;

    auto bucket = this->at(bucket_position);
    if (bucket->size() == 1) {
        return std::make_tuple(nullptr, nullptr, 0.0);
    }

    auto b1 = std::make_shared<Bucket>(bucket_order_size);
    for (auto it_bucket = bucket->begin(); it_bucket != bucket->end() && b1->size() < bucket->size() - 1; ++it_bucket) {
        bool all_incomparable = true;
        auto bin = this->at(bucket_position);
        auto upset_it = starting_poset.UpSets()->at(*it_bucket);
        for (auto e : *bin) {
            if (e == *it_bucket) continue;
            if (upset_it->find(e) != upset_it->end() ) {
                all_incomparable = true;
                break;
            }
        }
        if (!all_incomparable)
            continue;

        b1->insert(*it_bucket);
        auto b2 = bucket->set_difference(*b1);
        if (!BucketOrder::BJoinPreserve(preserve_poset, *b2, *b1, *(this->at(bucket_position + 1)))) {
            continue;
        }


        auto bucket_order_copy = this->copy();

        bucket_order_copy->addAt(*b1, bucket_position + 1);
        bucket_order_copy->eraseAt(*b1, bucket_position);
        auto mrp = BucketPOSet::evaluationMRP(*bucket_order_copy);
        auto lfmrp_value = lfmrp(*mrp);
        if (lfmrp_value < best_lfmrp_value || best_b == nullptr) {
            best_lfmrp_value = lfmrp_value;
            best_mrp = mrp;
            best_b = std::make_shared<Bucket>(bucket_order_size, b1->begin(), b1->end());
        }
    }

    return std::make_tuple(best_b, best_mrp, best_lfmrp_value);
}

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

BSRES BucketOrder::BestSplitAt(POSet& starting_poset, POSet& preserve_poset, LossFunctionMRP& lfmrp, std::uint64_t bucket_position) {
    std::uint64_t bucket_order_size = this->eSize();
    double best_lfmrp_value = 0.0;
    std::shared_ptr<Matrice<double>> best_mrp = nullptr;
    BUCKETPTR best_b1 = nullptr;
    BUCKETPTR best_b2 = nullptr;

    auto bucket = this->at(bucket_position);
    auto bucket_size = bucket->size();
    if (bucket_size == 1) {
        return std::make_tuple(nullptr, nullptr, nullptr, 0.0);
    }

    auto b1 = std::make_shared<Bucket>(bucket_order_size);
    for (auto it_bucket = bucket->begin(); it_bucket != bucket->end() && b1->size() < bucket->size() - 1; ++it_bucket) {
        b1->insert(*it_bucket);
        auto b2 = bucket->set_difference(*b1);

        if (!BucketOrder::BSplitPreserve(preserve_poset, *b1, *b2)) {
            continue;
        }

        auto bucket_order_copy = this->copy();

        bucket_order_copy->replace(b2, bucket_position);
        bucket_order_copy->insert(b1, bucket_position);
        auto mrp = BucketPOSet::evaluationMRP(*bucket_order_copy);
        auto lfmrp_value = lfmrp(*mrp);
        if (lfmrp_value < best_lfmrp_value || best_b1 == nullptr) {
            best_lfmrp_value = lfmrp_value;
            best_mrp = mrp;
            best_b1 = std::make_shared<Bucket>(bucket_order_size, b1->begin(), b1->end());
            best_b2 = std::make_shared<Bucket>(bucket_order_size, b2->begin(), b2->end());
        }
    }

    return std::make_tuple(best_b1, best_b2, best_mrp, best_lfmrp_value);
}



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

bool BucketOrder::BSplitPreserve(POSet& p, Bucket& b_down, Bucket& b_up) {
    for (auto v1 : b_up) {
        auto upset_v1 = p.UpSets()->at(v1);
        for (auto v2 : b_down) {
            if (upset_v1->find(v2) != upset_v1->end()) {
                return false;
            }
        }
    }
    return true;
}

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

bool BucketOrder::BJoinPreserve(POSet& p, Bucket& b_down, Bucket& b_up, Bucket& b_top) {
    for (auto v1 : b_up) {
        auto upset_v1 = p.UpSets()->at(v1);
        for (auto v2 : b_down) {
            if (upset_v1->find(v2) != upset_v1->end()) {
                return false;
            }
        }
    }

    for (auto v1 : b_up) {
        auto upset_v1 = p.UpSets()->at(v1);
        for (auto v2 : b_top) {
            if (upset_v1->find(v2) != upset_v1->end()) {
                return false;
            }
        }
    }
    return true;
}
