#include "FaceMesh.h"

#include "Node.h"
#include "Edge.h"
#include "AbstractFace.h"

FaceMesh::~FaceMesh()
{

}

void FaceMesh::populate(VecNodes& nodes, VecEdges& edges, VecFaces& faces)
{
    mNodes = std::move(nodes);
    mEdges = std::move(edges);
    mFaces = std::move(faces);

    resetIds();
    resetConnectivity();
}

const VecFaces& FaceMesh::getFaces() const
{
    return mFaces;
}

std::vector<std::size_t> FaceMesh::getFaceNodeIds() const
{
    if (mFaces.empty())
    {
        return {};
    }

    auto nodes_per_face = mFaces[0]->getNumNodes();
    std::vector<std::size_t> ids(nodes_per_face*mFaces.size());

    for(std::size_t idx=0; idx<mFaces.size(); idx++)
    {
        const auto nodeIds = mFaces[idx]->getNodeIds();
        for(std::size_t jdx=0; jdx<nodes_per_face; jdx++)
        {
            ids[nodes_per_face*idx + jdx] = nodeIds[jdx];
        }
    }
    return ids;
}

std::vector<std::vector<double> > FaceMesh::getFaceVectorAttributes(const std::string& tag)
{
    std::vector<std::vector<double> > attribs(mFaces.size());
    for(std::size_t idx=0; idx<mFaces.size(); idx++)
    {
        attribs[idx] = {mFaces[idx]->getVectorAttribute(tag)};
    }
    return attribs;
}

void FaceMesh::addConstantFaceVectorAttribute(const std::string& tag, const std::vector<double>& values)
{
    for (const auto& face : mFaces)
    {
        face->addVectorAttribute(tag, values);
    }
}

void FaceMesh::resetIds()
{
    std::size_t count = 0;
    for (auto& node : mNodes)
    {
        node->setIndex(count);
        count++;
    }

    count = 0;
    for (auto& edge : mEdges)
    {
        edge->setIndex(count);
        count++;
    }

    count = 0;
    for (auto& face : mFaces)
    {
        face->setIndex(count);
        count++;
    }
}

void FaceMesh::resetConnectivity()
{
    for (auto& node : mNodes)
    {
        node->clearConnectivity();
    }

    for (auto& edge : mEdges)
    {
        edge->associateWithNodes();
        edge->clearConnectivity();
    }

    for (auto& face : mFaces)
    {
        face->associateWidthEdges();
    }
}

void FaceMesh::replaceIfOverlapping(FaceMesh* mesh, Node* target_node) const
{
    for (auto& node : mNodes)
    {
        if (node->getNumConnectedEdges() == 3 && node->isCoincident(target_node))
        {
            target_node->setState(Node::State::DIRTY);
            for (std::size_t idx=0; idx<3; idx++)
            {
                auto edge = mesh->mEdges[idx].get();
                edge->replaceNodes(target_node, node.get());
            }
            break;
        }
    }
}

void FaceMesh::replaceIfOverlapping(FaceMesh* mesh, Edge* target_edge) const
{
    for (auto& edge : mEdges)
    {
        if (edge->getNumConnectedFaces() == 2 && target_edge->hasSameNodes(edge.get()))
        {
            target_edge->setState(Edge::State::DIRTY);
            for (std::size_t idx=0; idx<2; idx++)
            {
                auto face = mesh->mFaces[idx].get();
                face->replaceEdge(target_edge, edge.get());
            }
            break;
        }
    }
}

std::vector<std::size_t> FaceMesh::getEdgeNodeIds() const
{
    std::vector<std::size_t> ids(2*mEdges.size());
    for(std::size_t idx=0; idx<mEdges.size(); idx++)
    {
        ids[2*idx] = mEdges[idx]->getNode0Id();
        ids[2*idx + 1] = mEdges[idx]->getNode1Id();
    }
    return ids;
}

void FaceMesh::merge(std::unique_ptr<FaceMesh> mesh)
{
    if (mesh->getType() != getType())
    {
        return;
    }

    for (auto& node : mesh->mNodes)
    {
        if (node->getNumConnectedEdges() == 3)
        {
            replaceIfOverlapping(mesh.get(), node.get());
        }
    }

    for (auto& node : mesh->mNodes)
    {
        if (node->getState() == Node::State::HEALTHY)
        {
            mNodes.push_back(std::move(node));
        }
    }

    for (auto& edge : mesh->mEdges)
    {
        if (edge->getNumConnectedFaces() == 2)
        {
            replaceIfOverlapping(mesh.get(), edge.get());
        }
    }

    for (auto& edge : mesh->mEdges)
    {
        if (edge->getState() == Edge::State::HEALTHY)
        {
            mEdges.push_back(std::move(edge));
        }
    }

    for (auto& face : mesh->mFaces)
    {
        mFaces.push_back(std::move(face));
    }

    mesh.reset();
    resetIds();
    resetConnectivity();
}
