
#ifndef coveringGraph_hpp
#define coveringGraph_hpp

#include <map>
#include <functional>
#include <algorithm>
#include <vector>
#include <list>
#include <tuple>
#include <stack>
#include <iterator>

#include "treeOfIdeals.h"

class POSet;

class LatticeOfIdeals {
private:
    std::uint64_t __top;
    std::uint64_t __bottom;
    std::vector<std::vector<std::uint64_t>> __immediate_predecessors;
    std::vector<std::set<std::uint64_t>> __immediate_successors;
    std::map<std::uint64_t, std::uint64_t> __numberLEFilter;
    std::map<std::uint64_t, std::uint64_t> __numberLEIdeal;
    std::vector<std::set<std::uint64_t>> __with_label;
    std::shared_ptr<TreeOfIdeals> __tree_of_ideals;
public:
    LatticeOfIdeals(std::shared_ptr<TreeOfIdeals> treeOfIdeals, std::uint64_t root) : __tree_of_ideals(treeOfIdeals) {
        auto childrenSortedLabel = __tree_of_ideals->ChildrenSortedLabel();

        __top = root;
        __immediate_predecessors.assign(__tree_of_ideals->labels().size(), {});
        __immediate_successors.assign(__tree_of_ideals->labels().size(), {});
        for(auto it = childrenSortedLabel->at(root).begin(); it != childrenSortedLabel->at(root).end(); ++it) {
            auto pred = *it;
            __immediate_predecessors[root].push_back(pred);
            __immediate_successors[pred].insert(root);
        }
        
        ComputeSort();
        for (std::uint64_t p_with_label = __with_label.size() - 1; p_with_label > 0; --p_with_label) {
            auto& with_label_k = __with_label.at(p_with_label);
            for (auto i : with_label_k) {
                __immediate_predecessors[i].clear();
                std::uint64_t pi = __tree_of_ideals->parent().at(i);
                
                std::uint64_t h = 0;
                std::uint64_t j_primo = __immediate_predecessors[pi].at(h);
                while (j_primo != i) {
                    std::uint64_t j = *(childrenSortedLabel->at(j_primo).begin());
                    __immediate_predecessors[i].push_back(j);
                    __immediate_successors[j].insert(i);
                    j_primo = __immediate_predecessors[pi].at(++h);
                }
                for(auto it = childrenSortedLabel->at(i).begin(); it != childrenSortedLabel->at(i).end(); ++it) {
                    __immediate_predecessors[i].push_back(*it);
                    __immediate_successors[*it].insert(i);
                }
                if (__immediate_predecessors[i].empty()) {
                    __bottom = i;
                }
            }
            for (auto i : with_label_k) {
                std::uint64_t pi = __tree_of_ideals->parent().at(i);
                auto& at_pi = childrenSortedLabel->at(pi);
                for (auto it = at_pi.begin(); it != at_pi.end(); ++it) {
                    if (*it == i) {
                        at_pi.erase(it);
                        break;
                    }
                }
            }
        }
    }
    
    // ***********************************************
    // ***********************************************
    // ***********************************************

    std::shared_ptr<std::vector<std::uint64_t>> getFromPath(std::shared_ptr<std::vector<std::uint64_t>> path, std::shared_ptr<std::vector<bool>> more) {
        std::shared_ptr<std::vector<std::uint64_t>> result = std::make_shared<std::vector<std::uint64_t>>(path->size());
        
        std::uint64_t val_start = 0;
        for (std::uint64_t k = 0; k < path->size(); ++k) {
            auto set_start = __immediate_predecessors.at(val_start);
            std::uint64_t val_end = set_start.at(path->at(k));
            if (path->at(k) + 1 < set_start.size()) {
                more->at(k) = true;
            } else {
                more->at(k) = false;
            }
            
            std::vector<std::uint64_t> diff;
            std::shared_ptr<std::set<std::uint64_t>> ideal1 = __tree_of_ideals->ideals().at(val_start);
            std::shared_ptr<std::set<std::uint64_t>> ideal2 = __tree_of_ideals->ideals().at(val_end);
            std::set_difference(ideal1->begin(), ideal1->end(), ideal2->begin(), ideal2->end(), std::inserter(diff, diff.begin()));
            std::uint64_t label = diff.front();
            auto label_converted = __tree_of_ideals->leForConversion()->getVal(label - 1);
            result->at(path->size() - k - 1) = label_converted;
            val_start = val_end;
        }
        return result;
    }
    
    // ***********************************************
    // ***********************************************
    // ***********************************************

    void toFile(std::ofstream& fp) {
        for (std::uint64_t parent = 0; parent < __immediate_predecessors.size(); ++parent) {
            for (auto b : __immediate_predecessors.at(parent)) {
                std::vector<std::uint64_t> diff;
                auto ideal1 = __tree_of_ideals->ideals().at(parent);
                auto ideal2 = __tree_of_ideals->ideals().at(b);
                std::set_difference(ideal1->begin(), ideal1->end(), ideal2->begin(), ideal2->end(), std::inserter(diff, diff.begin()));
                
                std::uint64_t label = diff.front();
                auto label_converted = __tree_of_ideals->leForConversion()->getVal(label - 1);
                fp << std::to_string(b) << "," << std::to_string(parent) << "," << std::to_string(label_converted) << std::endl;
            }
        }
    }
    
    // ***********************************************
    // ***********************************************
    // ***********************************************

    std::shared_ptr<std::list<std::tuple<std::uint64_t, std::uint64_t, std::uint64_t>>> toList() {
        std::shared_ptr<std::list<std::tuple<std::uint64_t, std::uint64_t, std::uint64_t>>> result;
        result = std::make_shared<std::list<std::tuple<std::uint64_t, std::uint64_t, std::uint64_t>>>();
        for (std::uint64_t parent = 0; parent < __immediate_predecessors.size(); ++parent) {
            for (auto b : __immediate_predecessors.at(parent)) {
                std::vector<std::uint64_t> diff;
                auto ideal1 = __tree_of_ideals->ideals().at(parent);
                auto ideal2 = __tree_of_ideals->ideals().at(b);
                std::set_difference(ideal1->begin(), ideal1->end(), ideal2->begin(), ideal2->end(), std::inserter(diff, diff.begin()));
                
                std::uint64_t label = diff.front();
                auto label_converted = __tree_of_ideals->leForConversion()->getVal(label - 1);
                result->push_back(std::make_tuple(b, parent, label_converted));
            }
        }
        return result;
    }
    void ComputeSort() {
        auto& labes = __tree_of_ideals->labels();
        __with_label.assign(__tree_of_ideals->imPred()->size(), {});
        for (std::uint64_t lp = 0; lp < labes.size(); ++lp) {
            auto label = labes.at(lp);
            __with_label.at(label).insert(lp);
        }
    }
    
    // ***********************************************
    // ***********************************************
    // ***********************************************

    std::uint64_t ComputeFiltersCount() {
        std::map<std::uint64_t, bool> visited;
        for (std::uint64_t parent = 0; parent < __immediate_predecessors.size(); ++parent) {
            visited[parent] = false;
        }
        auto  number_LE_filter_bottom = AssignTopDown();
        return number_LE_filter_bottom;
    }
    
    // ***********************************************
    // ***********************************************
    // ***********************************************
    
    void ComputeIdealsCount() {
        AssignBottomUp();
    }
    
    // ***********************************************
    // ***********************************************
    // ***********************************************
    
    std::vector<std::vector<std::uint64_t>>& ImPred() {
        return __immediate_predecessors;
    }
    
    std::vector<std::set<std::uint64_t>>& ImSucc() {
        return __immediate_successors;
    }
    
    std::uint64_t Bottom() {
        return __bottom;
    }
    
    std::uint64_t Top() {
        return __top;
    }
    
    TreeOfIdeals& getTreeOfIdeals() {
        return *__tree_of_ideals;
    }
    
    std::map<std::uint64_t, std::uint64_t>& FiltersCount() {
        return __numberLEFilter;
    }
    
    std::map<std::uint64_t, std::uint64_t>& IdealsCount() {
        return __numberLEIdeal;
    }
     
private:
    void AssignBottomUp() {
        std::map<std::uint64_t, bool> visited;
        for (std::uint64_t parent = 0; parent < __immediate_predecessors.size(); ++parent) {
            visited[parent] = false;
        }
        
        __numberLEIdeal[__bottom] = 1;
        
        std::list<std::uint64_t> L;
        L.push_back(__bottom);
        while (!L.empty()) {
            auto v = *(L.begin());
            L.pop_front();
            if (!visited[v]) {
                visited[v] = true;
                for (auto v_primo : __immediate_successors[v]) {
                    __numberLEIdeal[v_primo] = __numberLEIdeal[v_primo] + __numberLEIdeal[v];
                    if (!visited[v_primo]) {
                        L.push_back(v_primo);
                    }
                }
            }
        }
    }

    // ***********************************************
    // ***********************************************
    // ***********************************************
    
    std::uint64_t AssignTopDown() {
        __numberLEFilter[__top] = 1;
        std::stack<std::uint64_t> S;
        S.push(__bottom);
        while (!S.empty()) {
            auto v = S.top();
            std::uint64_t e = 0;
            bool number_LE_filter_for_all_imsuc = true;
            for (auto v_primo : __immediate_successors[v]) {
                if (__numberLEFilter.find(v_primo) != __numberLEFilter.end()) {
                    e += __numberLEFilter.at(v_primo);
                } else {
                    number_LE_filter_for_all_imsuc = false;
                    S.push(v_primo);
                }
            }
            if (number_LE_filter_for_all_imsuc) {
                __numberLEFilter[v] = e;
                S.pop();
            }
        }
        
        return __numberLEFilter[__bottom];
    }
};






#endif 
