#include "MeshLoader.h"


//!!! Эти методы должны быть для чтения и для записи.

std::vector<Node> MeshLoader::getNodes() { //!!! Копирование при возврате
    return nodes;
}

std::vector<FiniteElement> MeshLoader::getFiniteElements() {//!!! Копирование при возврате
    return finiteElements;
}

std::vector<BoundaryElement> MeshLoader::getBoundaryElements() {//!!! Копирование при возврате
    return boundaryElements;
}


//!!! Все методы должны возвращать только контейнеры ID, а не сами КЭ или узлы.


std::vector<FiniteElement> MeshLoader::getElementsByNodesID(unsigned int ID1, unsigned int ID2, unsigned int ID3) {
    std::vector<FiniteElement> elements;
    auto beg = finiteElements.begin(), end = finiteElements.end();
    while (beg != end) {
        auto e = std::find_if(beg, end,
                              [ID1, ID2, ID3](const FiniteElement &elem) -> bool {//!!! -> bool можно не писать
                                  auto iter_curr = elem.nodesID.begin(), iter_end = elem.nodesID.end();
                                  return std::find(iter_curr, iter_end, ID1) != iter_end &&
                                         std::find(iter_curr, iter_end, ID2) != iter_end &&
                                         std::find(iter_curr, iter_end, ID3) != iter_end;
                              });
        if (e != end) {
            elements.push_back(*e);
            ++e;
        }
        beg = e;
    }
    return elements;
}

std::vector<FiniteElement> MeshLoader::getElementsByEdge(unsigned int ID1, unsigned int ID2) {
    std::vector<FiniteElement> elements;
    auto beg = finiteElements.begin(), end = finiteElements.end();
    while (beg != end) {
        auto e = std::find_if(beg, end,
                              [ID1, ID2](const FiniteElement &elem) -> bool {//!!! -> bool можно не писать
                                  auto iter_curr = elem.nodesID.begin(), iter_end = elem.nodesID.end();
                                  return std::find(iter_curr, iter_end, ID1) != iter_end &&
                                         std::find(iter_curr, iter_end, ID2) != iter_end;
                              });
        if (e != end) {
            elements.push_back(*e);
            ++e;
        }
        beg = e;
    }
    return elements;
}

std::unordered_set<unsigned int> MeshLoader::getBoundaryNodesByBoundaryID(unsigned int ID) {
    std::unordered_set<unsigned int> elements;
    auto beg = boundaryElements.begin(), end = boundaryElements.end();
    while (beg != end) {
        auto e = std::find_if(beg, end,
                              [ID](const BoundaryElement &elem) -> bool {//!!! -> bool можно не писать
                                  return elem.boundaryID == ID;
                              });
        if (beg != end) {
            for (auto element: e->nodesID) {
                elements.insert(element);
            }
        }
        ++beg;
    }
    return elements;
}

std::vector<FiniteElement> MeshLoader::getFiniteElementsByAreaID(unsigned int ID) {
    std::vector<FiniteElement> elements;
    auto beg = finiteElements.begin(), end = finiteElements.end();
    while (beg != end) {
        auto e = std::find_if(beg, end,
                              [ID](const FiniteElement &elem) -> bool {//!!! -> bool можно не писать
                                  return elem.areaID == ID;
                              });
        if (beg != end) {
            elements.push_back(*e);
            ++e;
        }
        ++beg;
    }
    return elements;
}

std::vector<BoundaryElement> MeshLoader::getBoundaryElementsByBoundaryID(unsigned int ID) {
    std::vector<BoundaryElement> elements;
    auto beg = boundaryElements.begin(), end = boundaryElements.end();
    while (beg != end) {
        auto e = std::find_if(beg, end,
                              [ID](const BoundaryElement &elem) -> bool { //!!! -> bool можно не писать
                                  return elem.boundaryID == ID;
                              });
        if (beg != end) {
            elements.push_back(*e);
            ++e;
        }
        ++beg;
    }
    return elements;
}

void MeshLoader::insertNodesIntoMiddles() {
    
    //!!! Хеш-функция должна быть реализована на основе hashCombine (см. пример в лекции)
    //!!! Учесть, что ребра (a,b) и (b,a) будут иметь разные хеш-значения, хотя это одно ребро
    //!!! (т.е. вычислять хеш-значение нужно для отсортированной последовательности)
    
    auto hash = [](const Edge &element) {
        unsigned int hashcode = 23;
        hashcode = (hashcode * 37) + element.node1;
        hashcode = (hashcode * 37) + element.node2;
        return hashcode;
    };

    std::unordered_set<Edge, decltype(hash)> edges;

    for (auto &element: finiteElements) { //!!! const auto&
        size_t size = element.nodesID.size();
        for (int i = 0; i < size; ++i) {
            for (int j = i + 1; j < size; ++j) {
                Edge edge(element.nodesID[i], element.nodesID[j], nodes.size() + 1);
                auto[iter, res] = edges.insert(edge);
                if (res) {
                    Node node = makeMiddleNode(edge.node1, edge.node2);
                    edge.middleNode = node.ID;
                    nodes.push_back(node);
                    element.nodesID.push_back(node.ID);
                } else {
                    element.nodesID.push_back(iter->middleNode);
                }
            }
        }
    }

    for (auto &element: boundaryElements) { //!!! const auto&
        size_t size = element.nodesID.size();
        for (int i = 0; i < size; ++i) {
            for (int j = i + 1; j < size; ++j) {
                unsigned int ID1 = element.nodesID[i];
                unsigned int ID2 = element.nodesID[j];
                Edge edge(ID1, ID2, 0);
                auto iter = edges.find(edge);
                element.nodesID.push_back(iter->middleNode);
            }
        }
    }
}

Node MeshLoader::makeMiddleNode(unsigned int ID1, unsigned int ID2) {
    unsigned int ID = nodes.size() + 1;
    ID1--;
    ID2--;
    double x = (nodes[ID1].x + nodes[ID2].x) / 2;
    double y = (nodes[ID1].y + nodes[ID2].y) / 2;
    double z = (nodes[ID1].z + nodes[ID2].z) / 2;
    bool isVertex = nodes[ID1].isVertex && nodes[ID2].isVertex;
    Node node{ID, x, y, z, isVertex};
    return node;
}

std::vector<std::unordered_set<unsigned int>> MeshLoader::makeVectorOfNeighbours() {
    std::vector<std::unordered_set<unsigned int>> neighbours(nodes.size() + 1);
    for (auto &elem: boundaryElements) {
        for (auto &i: elem.nodesID) {
            for (auto &j: elem.nodesID) {
                if (i != j) {
                    neighbours[i].insert(j);
                }
            }
        }
    }
    return neighbours;
}
