#include <memory>
#include <string>
#include <list>
#include <set>
#include <map>

#include "genericFunctions.h"

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

bool Generic::isReflexive(std::vector<std::string>& elements,
                          std::map<std::string, std::set<std::string>>& comparabilities) {
    std::set<std::string> elements_ok;
    std::set<std::string> elements_not_ok;
    for (auto p : elements) {
        auto p_comp = comparabilities.find(p);
        if (p_comp == comparabilities.end()) {
            return false;
        }
        if (p_comp->second.find(p) == p_comp->second.end()) {
            return false;
        }
    }
    return true;
}

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

bool Generic::isSymmetric(std::list<std::pair<std::string, std::string>>& comparabilities) {
    std::set<std::pair<std::string, std::string>> elements_ok;
    std::set<std::pair<std::string, std::string>> elements_not_ok;
    for (auto p : comparabilities) {
        if (elements_ok.find(p) == elements_ok.end()) {
            auto p_inv = std::make_pair(p.second, p.first);
            if (elements_not_ok.find(p_inv) != elements_not_ok.end()) {
                elements_not_ok.erase(p_inv);
                elements_ok.insert(p);
                elements_ok.insert(p_inv);
            } else {
                elements_not_ok.insert(p);
            }
        }
    }
    return elements_not_ok.empty();
}

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

bool Generic::isAntisymmetric(std::list<std::pair<std::string, std::string>>& comparabilities) {
    std::map<std::string, std::set<std::string>> elements;
    for (auto& p : comparabilities) {
        if (elements.find(p.first) == elements.end()) {
            elements[p.first] = std::set<std::string>();
        }
        if (elements.find(p.second) == elements.end()) {
            elements[p.second] = std::set<std::string>();
        }
        elements.at(p.first).insert(p.second);
    }
    
    for (auto p1 : elements) {
        auto e1 = p1.first;
        auto& e1_set = p1.second;
        for (auto& e2 : e1_set) {
            auto& e2_set = elements.at(e2);
            if (e2_set.find(e1) != e2_set.end()) {
                return false;
            }
        }
    }
    return true;
}

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

bool Generic::isTransitive(std::list<std::pair<std::string, std::string>>& comparabilities) {
    std::map<std::string, std::set<std::string>> elements;
    for (auto& p : comparabilities) {
        if (elements.find(p.first) == elements.end()) {
            elements[p.first] = std::set<std::string>();
        }
        if (elements.find(p.second) == elements.end()) {
            elements[p.second] = std::set<std::string>();
        }
        elements.at(p.first).insert(p.second);
    }
    
    for (auto p1 : elements) {
        auto& e1_set = p1.second;
        for (auto& e2 : e1_set) {
            auto& e2_set = elements.at(e2);
            for (auto e3 : e2_set) {
                if (e1_set.find(e3) == e1_set.end()) {
                    return false;
                }
                
            }
        }
    }
    return true;
}

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

bool Generic::isPreorder(std::vector<std::string>& elements,
                         std::map<std::string, std::set<std::string>>& comparabilities) {
    
    if (comparabilities.size() != elements.size()) {
        return false;
    }
    
    for (auto p1 : comparabilities) {
        auto e1 = p1.first;
        auto& e1_set = p1.second;
        if (e1_set.find(e1) == e1_set.end()) {
            return false;
        }
        for (auto& e2 : e1_set) {
            auto& e2_set = comparabilities.at(e2);
            for (auto e3 : e2_set) {
                if (e1_set.find(e3) == e1_set.end()) {
                    return false;
                }
            }
        }
    }
    return true;
}

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

bool Generic::isPartialOrder(std::vector<std::string>& elements,
                             std::map<std::string, std::set<std::string>>& comparabilities) {
    if (comparabilities.size() != elements.size()) {
        return false;
    }
    
    for (auto p1 : comparabilities) {
        auto e1 = p1.first;
        auto& e1_set = p1.second;
        if (e1_set.find(e1) == e1_set.end()) {
            return false;
        }
        for (auto& e2 : e1_set) {
            auto& e2_set = comparabilities.at(e2);
            if (e1 != e2 && e2_set.find(e1) != e2_set.end()) {
                return false;
            }
            for (auto e3 : e2_set) {
                if (e1_set.find(e3) == e1_set.end()) {
                    return false;
                }
            }
        }
    }
    return true;
}

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

std::shared_ptr<std::list<std::pair<std::string, std::string>>> Generic::TransitiveClosure(std::list<std::pair<std::string, std::string>>& comparabilities) {
    std::map<std::string, std::set<std::string>> elements;
    for (auto& p : comparabilities) {
        if (elements.find(p.first) == elements.end()) {
            elements[p.first] = std::set<std::string>();
        }
        if (elements.find(p.second) == elements.end()) {
            elements[p.second] = std::set<std::string>();
        }
        elements.at(p.first).insert(p.second);
    }
    
    for (auto& p1 : elements) {
        auto e1 = p1.first;
        auto& e1_set = p1.second;
        std::list<std::string> to_do(e1_set.begin(), e1_set.end());
        while (!to_do.empty()) {
            auto e2 = to_do.front();
            to_do.pop_front();
            auto& e2_set = elements.at(e2);
            for (auto& e3 : e2_set) {
                auto r = e1_set.insert(e3);
                if (r.second) {
                    to_do.push_back(e3);
                }
            }
        }
    }
    
    auto result = std::make_shared<std::list<std::pair<std::string, std::string>>>();
    for (auto p1 : elements) {
        auto e1 = p1.first;
        auto& e1_set = p1.second;
        for (auto& e2 : e1_set) {
            result->push_back(std::make_pair(e1, e2));
        }
    }
    return result;
}


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

std::shared_ptr<std::list<std::pair<std::string, std::string>>>
Generic::ReflexiveClosure(std::vector<std::string>& elements,
                          std::map<std::string, std::set<std::string>>& comparabilities) {

    for (auto& p : elements) {
        auto p_upset = comparabilities.insert(std::make_pair(p, std::set<std::string>()));
        p_upset.first->second.insert(p);
    }
    auto result = std::make_shared<std::list<std::pair<std::string, std::string>>>();
    for (auto& p1 : comparabilities) {
        auto e1 = p1.first;
        auto& e1_set = p1.second;
        for (auto& e2 : e1_set) {
            result->push_back(std::make_pair(e1, e2));
        }
    }
    return result;
}
