/****************************************************************************
** Copyright (c) 2016, Fougue SAS <https://www.fougue.pro>
** SPDX-License-Identifier: BSD-2-Clause
****************************************************************************/

#include "graphics_mesh_data_source.h"

#include "../base/geom_utils.h"
#include "../base/mesh_utils.h"

#include <Precision.hxx>
#include <Standard_Type.hxx>
#include <TColgp_SequenceOfXYZ.hxx>
#include <TColStd_DataMapOfIntegerInteger.hxx>
#include <TColStd_DataMapOfIntegerReal.hxx>

namespace Mayo {

GraphicsMeshDataSource::GraphicsMeshDataSource(const OccHandle<Poly_Triangulation>& mesh)
    : m_mesh(mesh)
{
    if (!m_mesh.IsNull()) {
        const int lenCoords = m_mesh->NbNodes();
        m_nodeCoords = makeOccHandle<TColStd_HArray2OfReal>(1, lenCoords, 1, 3);

        for(int i = 1; i <= lenCoords; ++i) {
            m_nodes.Add(i);
            const gp_XYZ xyz = m_mesh->Node(i).XYZ();
            m_nodeCoords->SetValue(i, 1, xyz.X());
            m_nodeCoords->SetValue(i, 2, xyz.Y());
            m_nodeCoords->SetValue(i, 3, xyz.Z());
        }

        const Poly_Array1OfTriangle& aSeq = MeshUtils::triangles(m_mesh);
        const int lenTriangles = aSeq.Length();
        m_elemNormals = makeOccHandle<TColStd_HArray2OfReal>(1, lenTriangles, 1, 3);
        m_elemNodes = makeOccHandle<TColStd_HArray2OfInteger>(1, lenTriangles, 1, 3);

        for(int i = 1; i <= lenTriangles; ++i ) {
            m_elements.Add(i);
            const Poly_Triangle& aTri = aSeq(i);

            int V[3];
            aTri.Get(V[0], V[1], V[2]);

            const gp_Vec aV1(m_mesh->Node(V[0]), m_mesh->Node(V[1]));
            const gp_Vec aV2(m_mesh->Node(V[1]), m_mesh->Node(V[2]));

            gp_Vec aN = aV1.Crossed(aV2);
            if (!GeomUtils::isNull(aN))
                aN.Normalize();
            else
                aN.SetCoord(0.0, 0.0, 0.0);

            for (int j = 0; j < 3; ++j)
                m_elemNodes->SetValue(i, j+1, V[j]);

            m_elemNormals->SetValue (i, 1, aN.X());
            m_elemNormals->SetValue (i, 2, aN.Y());
            m_elemNormals->SetValue (i, 3, aN.Z());
        }
    }
}

bool GraphicsMeshDataSource::GetGeom(
        const int ID,
        const bool IsElement,
        TColStd_Array1OfReal& Coords,
        int& NbNodes,
        MeshVS_EntityType& Type
    ) const
{
    if (m_mesh.IsNull())
        return false;

    if (IsElement) {
        if (ID >= 1 && ID <= m_elements.Extent()) {
            Type = MeshVS_ET_Face;
            NbNodes = 3;
            for (int i = 1, k = 1; i <= 3; ++i) {
                const int IdxNode = m_elemNodes->Value(ID, i);
                for (int j = 1; j <= 3; ++j, ++k)
                    Coords(k) = m_nodeCoords->Value(IdxNode, j);
            }

            return true;
        }

        return false;
    }
    else {
        if (ID >= 1 && ID <= m_nodes.Extent()) {
            Type = MeshVS_ET_Node;
            NbNodes = 1;

            Coords(1) = m_nodeCoords->Value(ID, 1);
            Coords(2) = m_nodeCoords->Value(ID, 2);
            Coords(3) = m_nodeCoords->Value(ID, 3);
            return true;
        }

        return false;
    }
}

bool GraphicsMeshDataSource::GetGeomType(const int, const bool IsElement, MeshVS_EntityType& Type) const
{
    Type = IsElement ? MeshVS_ET_Face : MeshVS_ET_Node;
    return true;
}

bool GraphicsMeshDataSource::GetNodesByElement(const int ID, TColStd_Array1OfInteger& theNodeIDs, int& /*theNbNodes*/) const
{
    if (m_mesh.IsNull())
        return false;

    if (ID >= 1 && ID <= m_elements.Extent() && theNodeIDs.Length() >= 3) {
        const int aLow = theNodeIDs.Lower();
        theNodeIDs(aLow)     = m_elemNodes->Value(ID, 1);
        theNodeIDs(aLow + 1) = m_elemNodes->Value(ID, 2);
        theNodeIDs(aLow + 2) = m_elemNodes->Value(ID, 3);
        return true;
    }

    return false;
}

bool GraphicsMeshDataSource::GetNormal(const int Id, const int Max, double& nx, double& ny, double& nz) const
{
    if (m_mesh.IsNull())
        return false;

    if (Id >= 1 && Id <= m_elements.Extent() && Max >= 3) {
        nx = m_elemNormals->Value(Id, 1);
        ny = m_elemNormals->Value(Id, 2);
        nz = m_elemNormals->Value(Id, 3);
        return true;
    }

    return false;
}

} // namespace Mayo
