#ifndef functionLinearExtension_hpp
#define functionLinearExtension_hpp


#include <cmath>
#include <vector>
#include <string>
#include <map>
#include <stdexcept>      // std::out_of_range
#include <limits>
#include <memory>
#include <algorithm>
#include <tuple>

#include "linearExtension.h"
#include "utilita.h"
#include "paramType.h"
#include "poset.h"

class FunctionLinearExtension {
protected:
    std::uint64_t __calls;
    std::vector<std::tuple<std::uint64_t, std::uint64_t, double>> __data;
    size_t __data_used;
    std::shared_ptr<POSet> __poset;
    std::vector<std::uint64_t> __shape;
    std::vector<std::string> __rows_name;
    std::vector<std::string> __cols_name;
public:
    static double ERRORE;
    FunctionLinearExtension(std::shared_ptr<POSet> poset) : __poset(poset) {
        __calls = 0;
        __data_used = 0;
    }
    virtual ~FunctionLinearExtension() {};
    virtual std::string to_string() const {
        std::string result = "";
        result += "Calls: " + std::to_string(__calls);
        return result;
    }
    
    virtual std::uint64_t at0(std::uint64_t k) {
        return std::get<0>(__data.at(k));
    }
    
    virtual std::uint64_t at1(std::uint64_t k) {
        return std::get<1>(__data.at(k));
    }
    
    virtual double at2(std::uint64_t k) {
        return std::get<2>(__data.at(k));
    }
    
    std::uint64_t resSize() const {
        return __data.size();
    }
    
    virtual void operator()(std::shared_ptr<LinearExtension>) = 0;
    
    virtual std::vector<std::uint64_t>& shape() {
        return __shape;
    }
    
    virtual std::vector<std::string>& rowsName() {
        return __rows_name;
    }
    
    virtual std::vector<std::string>& colsName() {
        return __cols_name;
    }
};


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

class FLETest : public FunctionLinearExtension {
public:
    FLETest(std::shared_ptr<POSet> poset) : FunctionLinearExtension(poset) {
        __data.clear();
        __shape.push_back(1);
        __shape.push_back(1);
        __rows_name.push_back("");
        __cols_name.push_back("");
    }
    
    std::string to_string() const {
        std::string base_string = FunctionLinearExtension::to_string();
        std::string result = "FLETest:";
        result += "\n\t" + FindAndReplaceAll(base_string, "\n", "\n\t");
        return result;
    }
    
    // ***********************************************
    // ***********************************************
    // ***********************************************
    
    void operator()(std::shared_ptr<LinearExtension> x) {
        ++__calls;
        std::string val = "";
        for (size_t k = 0; k < x->size(); ++k) {
            std::string el = std::to_string(x->getVal(k));
            val += el + ";";
        }
        return;
    }
    
};

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

class FLEMutualRankingProbability : public FunctionLinearExtension
{
    //************************************
    //************************************
    //************************************
    //** \{(i, j, 1) \mid i <_{LE} j\}_{(i,j)\in ICP} \bigcup \{(i, j, 0) \mid i \not<_{LE} j\}_{(i,j)\in ICP}
    //** LE = linear extension
    //** ICP = Incoparability set in Poset
    //************************************
    //************************************
    //************************************
public:
    FLEMutualRankingProbability(std::shared_ptr<POSet> poset) : FunctionLinearExtension(poset) {
        __data.clear();
        for (std::uint64_t first = 0; first < poset->size(); ++first) {
            for (std::uint64_t second = 0; second < poset->size(); ++second) {
                __data.push_back(std::tuple<std::uint64_t, std::uint64_t, double>(first, second, 0));
            }
        }
        auto poset_elements = __poset->Elements();
        __rows_name .insert(__rows_name.end(), poset_elements->begin(), poset_elements->end());
        __cols_name.insert(__cols_name.end(), poset_elements->begin(), poset_elements->end());
        
        __shape.push_back(poset->size());
        __shape.push_back(poset->size());
    }
    
    std::string to_string() const {
        std::string base_string = FunctionLinearExtension::to_string();
        std::string result = "FLEMutualRankingProbability:";
        result += "\n\t" + FindAndReplaceAll(base_string, "\n", "\n\t");
        return result;
    }
    virtual void operator()(std::shared_ptr<LinearExtension> x) {
        ++__calls;
        for (size_t k = 0; k < __data.size(); ++k)
        {
            std::uint64_t first_idx = std::get<0>(__data.at(k));
            std::uint64_t second_idx = std::get<1>(__data.at(k));
            size_t first_pos = x->getPos(first_idx);
            size_t second_pos = x->getPos(second_idx);
            __data.at(k) = std::make_tuple(first_idx, second_idx, first_pos <= second_pos);
        }
        return;
    }
};

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

class FLEAverageHeight : public FunctionLinearExtension
{
    //************************************
    //************************************
    //************************************
    //** \{(i, j, 1) \mid i <_{LE} j\}_{(i,j)\in ICP} \bigcup \{(i, j, 0) \mid i \not<_{LE} j\}_{(i,j)\in ICP}
    //** LE = linear extension
    //** ICP = Incoparability set in Poset
    //************************************
    //************************************
    //************************************
public:
    FLEAverageHeight(std::shared_ptr<POSet> poset) : FunctionLinearExtension(poset) {
        __data.clear();
        for(std::uint64_t i = 0; i < poset->size(); ++i ) {
            __data.push_back(std::tuple<std::uint64_t, std::uint64_t, double>(i, 0, 0.0));
        }
        __shape.push_back(poset->size());
        __shape.push_back(1);
        
        auto poset_elements = __poset->Elements();
        __rows_name.insert(__rows_name.end(), poset_elements->begin(), poset_elements->end());
        __cols_name.push_back("AverageHeight");
    }
    std::string to_string() const
    {
        std::string base_string = FunctionLinearExtension::to_string();
        std::string result = "FLEAverageHeight:";
        result += "\n\t" + FindAndReplaceAll(base_string, "\n", "\n\t");
        return result;
    }
    
    virtual void operator()(std::shared_ptr<LinearExtension> x) {
        ++__calls;
        for (size_t k = 0; k < __data.size(); ++k) {
            size_t value = std::get<0>(__data.at(k));
            size_t pos = x->getPos(value);
            __data.at(k) = std::make_tuple(value, 0, pos + 1);
        }
        
        return;
    }
    
    std::pair<std::shared_ptr<std::vector<std::string>>, std::shared_ptr<std::vector<std::string>>> size()  const;
};

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

class FLESeparationAsymmetricLower : public FunctionLinearExtension
{
    //************************************
    //************************************
    //************************************
    //** \{(i, j, k) \mid i <_{LE} j and k = distance(j, i) \}_{(i,j)\in ICP} \bigcup \{(i, j, 0) \mid i \not<_{LE} j\}_{(i,j)\in ICP}
    //** LE = linear extension
    //** ICP = Incoparability set in Poset
    //************************************
    //************************************
    //************************************
private:
    std::shared_ptr<std::vector<std::pair<std::string, std::string>>> incomparabilities;
public:
    std::string to_string() const {
        std::string base_string = FunctionLinearExtension::to_string();
        std::string result = "FLESeparation:";
        result += "\n\t" + FindAndReplaceAll(base_string, "\n", "\n\t");
        return result;
    }
    FLESeparationAsymmetricLower(std::shared_ptr<POSet> poset) : FunctionLinearExtension(poset) {
        this->incomparabilities = nullptr;
        
        __data.clear();
        for (std::uint64_t first = 0; first < poset->size(); ++first) {
            for (std::uint64_t second = 0; second < poset->size(); ++second) {
                __data.push_back(std::tuple<std::uint64_t, std::uint64_t, double>(first, second, 0));
            }
        }
        __shape.push_back(poset->size());
        __shape.push_back(poset->size());
        
        auto poset_elements = __poset->Elements();
        __rows_name.insert(__rows_name.end(), poset_elements->begin(), poset_elements->end());
        __cols_name.insert(__cols_name.end(), poset_elements->begin(), poset_elements->end());
    }
    
    virtual void operator()(std::shared_ptr<LinearExtension> x) {
        ++__calls;
        for (size_t k = 0; k < __data.size(); ++k)
        {
            std::uint64_t first_idx = std::get<0>(__data.at(k));
            std::uint64_t second_idx = std::get<1>(__data.at(k));
            size_t first_pos = x->getPos(first_idx);
            size_t second_pos = x->getPos(second_idx);
            __data.at(k) = std::make_tuple(first_idx, second_idx, (first_pos < second_pos) * (second_pos - first_pos));
        }
        
        return;
    }
};

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

class FLESeparationAsymmetricUpper : public FunctionLinearExtension
{
    //************************************
    //************************************
    //************************************
    //** \{(i, j, k) \mid j <_{LE} i and k = distance(j, i) \}_{(i,j)\in ICP} \bigcup \{(i, j, 0) \mid j \not<_{LE} i\}_{(i,j)\in ICP}
    //** LE = linear extension
    //** ICP = Incoparability set in Poset
    //************************************
    //************************************
    //************************************
private:
    std::shared_ptr<std::vector<std::pair<std::string, std::string>>> incomparabilities;
public:
    std::string to_string() const {
        std::string base_string = FunctionLinearExtension::to_string();
        std::string result = "FLESeparation:";
        result += "\n\t" + FindAndReplaceAll(base_string, "\n", "\n\t");
        return result;
    }
    FLESeparationAsymmetricUpper(std::shared_ptr<POSet> poset) : FunctionLinearExtension(poset) {
        this->incomparabilities = nullptr;
        
        __data.clear();
        for (std::uint64_t first = 0; first < poset->size(); ++first) {
            for (std::uint64_t second = 0; second < poset->size(); ++second) {
                __data.push_back(std::tuple<std::uint64_t, std::uint64_t, double>(first, second, 0));
            }
        }
        __shape.push_back(poset->size());
        __shape.push_back(poset->size());
        
        auto poset_elements = __poset->Elements();
        __rows_name.insert(__rows_name.end(), poset_elements->begin(), poset_elements->end());
        __cols_name.insert(__cols_name.end(), poset_elements->begin(), poset_elements->end());
    }
    
    virtual void operator()(std::shared_ptr<LinearExtension> x) {
        ++__calls;
        for (size_t k = 0; k < __data.size(); ++k)
        {
            std::uint64_t first_idx = std::get<0>(__data.at(k));
            std::uint64_t second_idx = std::get<1>(__data.at(k));
            size_t first_pos = x->getPos(first_idx);
            size_t second_pos = x->getPos(second_idx);
            __data.at(k) = std::make_tuple(first_idx, second_idx, (second_pos < first_pos) * (first_pos - second_pos));
        }
        
        return;
    }
};

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

class FLESeparationSymmetric : public FunctionLinearExtension
{
    //************************************
    //************************************
    //************************************
    //** \{(i, j, k) \mid k = distance(j, i) \}
    //** LE = linear extension
    //************************************
    //************************************
    //************************************
private:
    std::shared_ptr<std::vector<std::pair<std::string, std::string>>> incomparabilities;
public:
    std::string to_string() const {
        std::string base_string = FunctionLinearExtension::to_string();
        std::string result = "FLESeparation:";
        result += "\n\t" + FindAndReplaceAll(base_string, "\n", "\n\t");
        return result;
    }
    FLESeparationSymmetric(std::shared_ptr<POSet> poset) : FunctionLinearExtension(poset) {
        this->incomparabilities = nullptr;
        
        __data.clear();
        for (std::uint64_t first = 0; first < poset->size(); ++first) {
            for (std::uint64_t second = 0; second < poset->size(); ++second) {
                __data.push_back(std::tuple<std::uint64_t, std::uint64_t, double>(first, second, 0));
            }
        }
        __shape.push_back(poset->size());
        __shape.push_back(poset->size());
        
        auto poset_elements = __poset->Elements();
        __rows_name.insert(__rows_name.end(), poset_elements->begin(), poset_elements->end());
        __cols_name.insert(__cols_name.end(), poset_elements->begin(), poset_elements->end());
    }
    
    virtual void operator()(std::shared_ptr<LinearExtension> x) {
        ++__calls;
        for (size_t k = 0; k < __data.size(); ++k)
        {
            std::uint64_t first_idx = std::get<0>(__data.at(k));
            std::uint64_t second_idx = std::get<1>(__data.at(k));
            size_t first_pos = x->getPos(first_idx);
            size_t second_pos = x->getPos(second_idx);
            __data.at(k) = std::make_tuple(first_idx, second_idx, (first_pos > second_pos ? (first_pos - second_pos) : (second_pos - first_pos)));
        }
        
        return;
    }
};

#endif /* functionLinearExtension_hpp */
