#ifndef treeOfIdeals_h
#define treeOfIdeals_h


#include <vector>
#include <map>
#include <set>
#include <memory>
#include <string>
#include <fstream>
#include <functional>
#include <numeric>
#include "linearExtension.h"
#include "bitSet.h"

class LatticeOfIdeals;
class POSet;

// Dato un poset $p = <X, \leq_P>$, un sottoinsieme $I\subeteq X$ è un ideale se
// per ogni $a, b\in X$ se $a \leq_P b$ e $b\in I$ allora anche $a\in I$

class TreeOfIdeals {
    
    // Attenzione!! la procedura per costruire l'albero degli ideali assume che
    // gli elementi del poset in ordine crescente: 0, 1, 2, 3, ... siano un estenzione lineare
private:
    // __genitore_di[k] associa ad ogni vertice k dell'albero il suo genitore.
    std::vector<std::uint64_t> __genitore_di;
    // __etichette_archi[k] associa ad ogni vertice k dell'albero un etichetta per l'arco tra k e __genitore_di[k].
    // L'etichetta di k è l'elemento dell'estensione lineare ottenuto come differenza
    // tra l'ideale associato a k e l'ideale di __genitore_di[k].
    std::vector<std::uint64_t> __etichette_archi;
    // __figli_di[k] associa ad ogni vertice k dell'albero la lista dei figli di k ordinati in maniera decrescente
    // secondo il valore di __etichette_archi.
    std::vector<std::vector<std::uint64_t>> __figli_di;
    // __ideals[k] associa ad ogni vertice k dell'albero l'ideale di k
    std::vector<std::shared_ptr<std::set<std::uint64_t>>> __ideals;

    // __imPred[k] associa ad ogni vertice k del POSet gli immediati predecessori di k
    std::shared_ptr<std::vector<std::set<std::uint64_t>>> __imPred;
    std::uint64_t __ROOT;
    std::shared_ptr<LinearExtension> __leForConversion;
public:
    /**
    * Tree build the tree of ideal.
    *
    * @param imPred Container of the immediate predecessors of each element of the poset.
    *           Keys and values are coded using the linear extention, i.e. each element E of the poset is coded by is position (1...n) in the linear extention.
    *
    */
    TreeOfIdeals(std::shared_ptr<std::vector<std::set<std::uint64_t>>> imPred,
                 std::shared_ptr<LinearExtension> le) : __imPred(imPred), __leForConversion(le) {
        std::uint64_t n = __imPred->size() - 1;
        std::vector<std::uint64_t> idxs (n);
        std::iota(idxs.begin(), idxs.end(), 1);
        std::shared_ptr<std::set<std::uint64_t>> ideal = std::make_shared<std::set<std::uint64_t>>(idxs.begin(), idxs.end());

        __ROOT = Left(n, ideal);
        return;
    }
    
    std::shared_ptr<LinearExtension>& leForConversion() {
        return __leForConversion;
    }
    
    std::vector<std::uint64_t>& labels() {
        return __etichette_archi;
    }
    
    std::vector<std::uint64_t>& parent() {
        return __genitore_di;
    }
    
    std::shared_ptr<std::vector<std::set<std::uint64_t>>>& imPred() {
        return __imPred;
    }
    
    std::vector<std::shared_ptr<std::set<std::uint64_t>>>& ideals() {
        return __ideals;
    }
    
    std::uint64_t root() {
        return __ROOT;
    }

    
    std::shared_ptr<std::vector<std::vector<std::uint64_t>>> ChildrenSortedLabel() {
        auto result = std::make_shared<std::vector<std::vector<std::uint64_t>>>(__figli_di.size());
        for (std::uint64_t parent = 0; parent < __figli_di.size(); ++parent) {
            auto& childrenOf = __figli_di.at(parent);
            auto& childrenOfResult = result->at(parent);
            childrenOfResult.assign(childrenOf.size(), 0);
            std::map<std::uint64_t, std::uint64_t, std::greater<std::uint64_t>> label_children;
            for (auto child : childrenOf) {
                auto label = __etichette_archi.at(child);
                label_children[label] = child;
            }
            std::uint64_t pos = 0;
            for (auto val : label_children) {
                childrenOfResult[pos++] = val.second;
            }
        }
        return result;
    }
    
private:
    /**
    * Left create the leftmost subtree containning all the ideals that not include n.
    *
    * @param n position of the elementi in the linear extention.
    * @return id of the tree.
    */
    std::uint64_t Left(std::uint64_t n, std::shared_ptr<std::set<std::uint64_t>>& ideal) {
        std::uint64_t root = __figli_di.size();
        __figli_di.push_back({});
        __genitore_di.push_back(0);
        __etichette_archi.push_back(0);
        __ideals.push_back(ideal);
        if (n == 0)
            return root;

        auto sub_ideal = std::make_shared<std::set<std::uint64_t>>(ideal->begin(), ideal->end());
        sub_ideal->erase(n);
        
        std::uint64_t r = Left(n - 1, sub_ideal);
        Right(n, r, root);
        __genitore_di.at(r) = root;
        __etichette_archi.at(r) = n;
        __figli_di.at(root).push_back(r);

        __ideals.at(r)->erase(n);

        return root;
    }
    
    /**
    * Right create all ideals containing the element whos position in linear extention is n.
    *
    * The ideals are created by traversing the tree rooted by r. The linear extention position strat from 1
    *
    * @param n position of the elementi in the linear extention.
    * @param r id of the subtree.
    * @param root id of the tree to be modified.
    */
    
    void Right(std::uint64_t n, std::uint64_t r, std::uint64_t root) {
        for(auto child : __figli_di.at(r)) {
           std::uint64_t label_s = __etichette_archi.at(child);
            if (__imPred->at(n).find(label_s) == __imPred->at(n).end()) {
            //if (!__imPred->at(n).check(label_s)) {
                auto ideal = __ideals.at(root);
                auto sub_ideal = std::make_shared<std::set<std::uint64_t>>(ideal->begin(), ideal->end());
                sub_ideal->erase(label_s);
                
                std::uint64_t t = __figli_di.size();
                __figli_di.push_back({});
                __genitore_di.push_back(0);
                __etichette_archi.push_back(0);

                __ideals.push_back(sub_ideal);
                
                __genitore_di.at(t) = root;
                __etichette_archi.at(t) = label_s;
                __figli_di.at(root).push_back(t);
                
                __ideals.at(t)->erase(label_s);
                Right(n, child, t);
            }
        }
    }
    
    std::uint64_t labelSize() {
        return __etichette_archi.size();
    }
        
    void toFile(std::ofstream& fp, std::uint64_t n) {
        auto& children = __figli_di.at(n);
        for (auto s : children) {
            std::uint64_t label = __etichette_archi.at(s);
            auto label_converted = __leForConversion->getVal(label - 1);
            fp << std::to_string(n) << "," << std::to_string(s) << "," << std::to_string(label_converted) << std::endl;
            toFile(fp, s);
        }
    }
    
};



#endif /* treeOfIdeals_h */
