#include "MeshLoader.h"

std::vector<Node> &MeshLoader::getNodes() {
    return nodes;
}

std::vector<Node> MeshLoader::getNodes() const {
    return nodes;
}

std::vector<FiniteElement> &MeshLoader::getFiniteElements() {
    return finiteElements;
}

std::vector<FiniteElement> &MeshLoader::getBoundaryElements() {
    return boundaryElements;
}

std::vector<unsigned int> MeshLoader::getElementsByNodesID(unsigned int ID1, unsigned int ID2, unsigned int ID3) {
    std::vector<unsigned int> elements;
    auto beg = finiteElements.begin(), end = finiteElements.end();
    while (beg != end) {
        auto e = std::find_if(beg, end,
                              [ID1, ID2, ID3](const FiniteElement &elem) {
                                  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->ID);
            ++e;
        }
        beg = e;
    }
    return elements;
}

std::vector<unsigned int> MeshLoader::getElementsByEdge(unsigned int ID1, unsigned int ID2) {
    std::vector<unsigned int> elements;
    auto beg = finiteElements.begin(), end = finiteElements.end();
    while (beg != end) {
        auto e = std::find_if(beg, end,
                              [ID1, ID2](const FiniteElement &elem) {
                                  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->ID);
            ++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 FiniteElement &elem) {
                                  return elem.areaID == ID;
                              });
        if (beg != end) {
            for (auto element: e->nodesID) {
                elements.insert(element);
            }
        }
        ++beg;
    }
    return elements;
}

std::vector<unsigned int> MeshLoader::getFiniteElementsByAreaID(unsigned int ID) {
    std::vector<unsigned int> elements;
    auto beg = finiteElements.begin(), end = finiteElements.end();
    while (beg != end) {
        auto e = std::find_if(beg, end,
                              [ID](const FiniteElement &elem) {
                                  return elem.areaID == ID;
                              });
        if (beg != end) {
            elements.push_back(e->ID);
            ++e;
        }
        ++beg;
    }
    return elements;
}

std::vector<unsigned int> MeshLoader::getBoundaryElementsByBoundaryID(unsigned int ID) {
    std::vector<unsigned int> elements;
    auto beg = boundaryElements.begin(), end = boundaryElements.end();
    while (beg != end) {
        auto e = std::find_if(beg, end,
                              [ID](const FiniteElement &elem) {
                                  return elem.areaID == ID;
                              });
        if (beg != end) {
            elements.push_back(e->ID);
            ++e;
        }
        ++beg;
    }
    return elements;
}

inline void hashCombine(std::size_t &o_seed, unsigned int &p_val) {
    o_seed ^= std::hash<unsigned int>()(p_val) + 0x9e3779b9 + (o_seed << 6) + (o_seed >> 2);
}

inline std::size_t hashVal(const Edge &e) {
    std::size_t seed = 0;
    unsigned int a = std::min(e.node1, e.node2);
    unsigned int b = std::max(e.node1, e.node2);
    hashCombine(seed, a);
    hashCombine(seed, b);
    return seed;
}

class Hash {
public :
    std::size_t operator()(const Edge &p_c) const {
        return hashVal(p_c);
    }
};

void MeshLoader::insertNodesIntoMiddles() {
    std::unordered_set<Edge, Hash> edges;

    for (auto &element: finiteElements) {
        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;

                    element.nodesID.push_back(node.ID);
                    
                    nodes.push_back(std::move(node));
                    
                } 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 (const auto &elem: boundaryElements) {
        for (const auto &i: elem.nodesID) {
            for (const auto &j: elem.nodesID) {
                if (i != j) {
                    neighbours[i].insert(j);
                }
            }
        }
    }
    return neighbours;
}
