#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"


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

std::shared_ptr<BucketPOSet> BucketPOSet::Build(std::shared_ptr<std::vector<std::string>> elements, std::list<std::pair<std::string, std::string>>& comparabilities) {
    std::shared_ptr<BucketPOSet> r(new BucketPOSet(elements->size()));
    r->FillBaseAttribute(elements, comparabilities, true);
    r->BuildBucketOrder();
    return r;
}

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

void BucketPOSet::BuildBucketOrder() {
    for (std::uint64_t e_value = 0;  e_value < elementi->size(); ++e_value) {
        auto upset_e = elementi->at(e_value);
        std::uint64_t bucket_position = 0;
        bool bucket_trovato = false;
        for (auto it_bucket = this->buckets.begin(); bucket_position < this->buckets.size(); ++bucket_position, ++it_bucket) {
            auto bucket = *it_bucket;
            auto bucket_first = *(bucket->begin());
            auto bucket_first_set = this->elementi->at(bucket_first);
            if (upset_e->find(bucket_first) == upset_e->end()) {
                if (bucket_first_set->find(e_value) == bucket_first_set->end()) {
                    if (*upset_e != *bucket_first_set) {// molto costoso cercare di eliminare il controllo
                        std::string err_str = "BucketPOSet not valid: (" + std::to_string(e_value) + ", " + std::to_string(bucket_first) + ")";
                        throw_line(err_str);
                    }
                    bucket->insert(e_value);
                    bucket_trovato = true;
                    break;
                }
            } else {
                break;
            }
        }
        if (!bucket_trovato) {
            auto new_bucket = std::make_shared<Bucket>(this->elementi->size());
            new_bucket->insert(e_value);
            if (bucket_position == this->buckets.size()) {
                this->buckets.push_back(new_bucket);
            } else {
                this->buckets.insert(new_bucket, bucket_position);
            }
        }
        bucket_trovato = false;
    }
   
}

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

BucketPOSet::~BucketPOSet() {
    
}

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

void BucketPOSet::ReplaceBuckets(BucketOrder& new_buckets) {
    for (std::uint64_t e_value = 0;  e_value < elementi->size(); ++e_value) {
        elementi->at(e_value)->clear();
    }
    this->buckets.clear();
    
    std::set<std::uint64_t> used;
    for (auto b : new_buckets) {
        this->buckets.push_back(b);
        for (auto e : used) {
            this->elementi->at(e)->insert(b->begin(), b->end());
        }
        used.insert(b->begin(), b->end());
    }
}

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

std::shared_ptr<Matrice<double>> BucketPOSet::evaluationMRP() {
    return BucketPOSet::evaluationMRP(this->buckets);
}

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

std::shared_ptr<Matrice<double>> BucketPOSet::evaluationMRP(BucketOrder& bucket_order) {
    std::uint64_t totale_elementi = 0;
    for (auto v : bucket_order) {
        totale_elementi += v->size();
    }
    
    auto mrp = std::make_shared<Matrice<double>>(totale_elementi);
    
    std::set<std::uint64_t> used;
    for (auto bucket : bucket_order) {
        for (auto riga : *bucket) {
            (*mrp)(riga, riga) = 1.0;
            for (std::uint64_t colonna = 0; colonna < totale_elementi; ++colonna) {
                if (riga == colonna) continue;
                if (used.find(colonna) == used.end()) {
                    if (!bucket->find(colonna)) {
                        (*mrp)(riga, colonna) = 1.0;
                        (*mrp)(colonna, riga) = 0.0;
                    } else {
                        (*mrp)(riga, colonna) = 0.5;
                        (*mrp)(colonna, riga) = 0.5;
                    }
                }
            }
            used.insert(riga);
        }
    }
    
    return mrp;
}

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


bool BucketPOSet::IsTotalOrder() const {
    return this->buckets.size() == this->elementi->size();
}


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


std::uint64_t BucketPOSet::LSize(std::uint64_t layer) const {
    if (this->buckets.size() <= layer) {
        std::string err_str = "BucketPOSet layer not valid";
        throw_line(err_str);
    }
    return buckets.size(layer);
}

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


std::uint64_t BucketPOSet::LSize() const {
    return this->buckets.size();
}

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


std::shared_ptr<Bucket> BucketPOSet::BucketAt(std::uint64_t layer) {
    if (this->buckets.size() <= layer) {
        std::string err_str = "BucketPOSet layer not valid";
        throw_line(err_str);
    }
    auto it = this->buckets.begin();
    advance(it, layer);
    return (*it);
}

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

std::shared_ptr<POSet> BucketPOSet::clone() const {
    auto elements = std::make_shared<std::vector<std::string>>();
    for (auto v:*this->eid_vs_ename)
        elements->push_back(v);
    
    std::list<std::pair<std::string, std::string>> comparabilities;
    for (std::uint64_t d1 = 0;  d1 < elementi->size(); ++d1) {
        std::string v1 = this->eid_vs_ename->at(d1);
        auto d1_set = elementi->at(d1);
        for (auto d2 : *(d1_set)) {
            std::string v2 = this->eid_vs_ename->at(d2);
            comparabilities.push_back(std::pair<std::string, std::string>(v1, v2));
        }
    }
    
    std::shared_ptr<BucketPOSet> result = BucketPOSet::Build(elements, comparabilities);
    
    return result;
}
// ***********************************************
// ***********************************************
// ***********************************************



