/***************************************************************************
*                                                                          *
*  Copyright (C) Golaem S.A.  All Rights Reserved.                         *
*                                                                          *
***************************************************************************/

#pragma once

#include "glmCoreAPI.h"
#include "glmTypes.h"
#include "glmMap.h"
#include "glmVector3.h"
#include "glmQuaternion.h"
#include "glmMatrix4.h"
#include "glmAABB.h"
#include "glmSmartPointer.h"

namespace glm
{
    //*************************************************************************
    // Pin Mesh
    // A utility Mesh to be able to compute shortest paths on meshes in a given direction
    //*************************************************************************
    //-------------------------------------------------------------------------
    class GLM_CORE_API PinMesh : public ReferenceCounter
    {
    public: //structures
        typedef SmartPointer<PinMesh> SP;

        class RayCastVertex
        {
        public:
            Vector3 _position;
            Vector3 _normal;
            //Vector2 _uv;
            int _faceIndex; // vertices indices are reorganized in the hierarchical raycast structure, so we need to store original vertex or face index to be able to get it back.

            RayCastVertex()
                : _faceIndex(-1)
            {
            }

            RayCastVertex(Vector3 position, int faceIndex)
                : _position(position)
                , _faceIndex(faceIndex)
            {
            }
        };

        class PairValue
        {
        public:
            PairValue()
                : _value0(-1)
                , _value1(-1)
            {
            }

            PairValue(int value0, int value1)
                : _value0(value0)
                , _value1(value1)
            {
            }

            int _value0;
            int _value1;

            // for map sorting :
            inline bool operator<(const PairValue& other) const;
        };

    public: //methods
        PinMesh();
        virtual ~PinMesh();

        virtual void clear();

        void setWorldMatrix(const Matrix4& worldMatrix);
        inline const Matrix4& getWorldMatrix() const;
        inline const Matrix4& getWorldMatrixInverse() const;

        inline Matrix4& touchWorldMatrix();
        inline Matrix4& touchWorldMatrixInverse();

        inline RayCastVertex* touchMeshVertices();
        inline const RayCastVertex* getMeshVertices() const;
        inline int getVertexCount() const;
        inline int getTotalTriangleCount() const;
        inline void setTotalTriangleCount(int triangleCount);
        inline int* getRayCastToMeshIndices();
        inline int* getFaceNeighbors(int triangleIndex);

        bool allIntersections(const Vector3& fromPosition, const Vector3& direction, glm::Array<Vector3>& foundPosition, glm::PODArray<int>& foundTriangleId, float distance, bool bothDirections, const Matrix4* additionalGroundTransform = &Matrix4::identity, const Matrix4* additionalGroundTransformInverse = &Matrix4::identity) const;

        bool raycast(const Vector3& fromPosition, const Vector3& direction, Vector3& foundPosition, int& foundTriangleId, float distance, bool bothDirections, const Matrix4* additionalGroundTransform = &Matrix4::identity, const Matrix4* additionalGroundTransformInverse = &Matrix4::identity) const;

        bool pinPosition(Vector3& inOutPostion, Vector3& inOutOrthoNormal, int& outGroundCellId, const Matrix4* additionalGroundTransform = &Matrix4::identity, const Matrix4* additionalGroundTransformInverse = &Matrix4::identity) const;
        bool pinFromPreviousPosition(const Vector3& previousPosition, const int previousTriangleId, const Vector3& displacementUpVector,
                                     Vector3& nextPosition, Vector3& nextNormal, int& nextTriangleId, const Matrix4* additionalGroundTransform = &Matrix4::identity, const Matrix4* additionalGroundTransformInverse = &Matrix4::identity) const;

        Vector3 getOrthoTriangleNormal(int triangleId, const Matrix4* additionalGroundTransform = &Matrix4::identity, const Matrix4* additionalGroundTransformInverse = &Matrix4::identity) const;
        Vector3 getSmoothedTriangleNormal(int triangleId, const Vector3& barycentricCoordinates, const Matrix4* additionalGroundTransform = &Matrix4::identity, const Matrix4* additionalGroundTransformInverse = &Matrix4::identity) const;

        void getBarycentricCoordinates(int triangleId, const Vector3& trianglePosition, Vector3& barycentricCoordinates, const Matrix4* additionalGroundTransform = &Matrix4::identity, const Matrix4* additionalGroundTransformInverse = &Matrix4::identity) const;
        Vector3 getBarycentricPosition(int triangleId, const Vector3& barycentricCoordinates, const Matrix4* additionalGroundTransform = &Matrix4::identity, const Matrix4* additionalGroundTransformInverse = &Matrix4::identity) const;

        inline const Matrix4& getWorldMatrixFrameDelta() const;

        // get deformation of a specific face with the barycenter on the face
        void getWorldMatrixFrameDelta(Matrix4& transformDelta, int triangleId, const Vector3& barycenter, const Matrix4* additionalGroundTransform = &Matrix4::identity, const Matrix4* additionalGroundTransformInverse = &Matrix4::identity) const;

        void setDeformed(bool deformed);

        void computeSurfaces();
        float getTotalSurface() const;
        bool getRandomSurfacePoint(RandomNumberGenerator& rng, Vector3& position, Vector3& normal, int& faceId, const Matrix4* additionalGroundTransform = &Matrix4::identity, const Matrix4* additionalGroundTransformInverse = &Matrix4::identity) const;

        void getAABBox(Vector3& aabboxMin, Vector3& aabboxMax) const;

        virtual void initialize(int triangleCount);
        virtual void finalize();

        inline const Vector3& getFaceNormal(int triangleIndex) const;
        inline const Vector3& getEdgeNormal(int edgeIndex) const;

        void computeFaceNormals(int triangleIndex);
        void computeEdgeNormals(int triangleIndex); // requires valid face normals
        void fillEdgeInfo(GlmMap<PairValue, PairValue>& edges, int currentTriangle, int currentEdge, PODArray<int>& meshIndices);

    protected: //structures
        class positionInTriangle
        {
        public:
            int _currentTriangle;
            float _remainingDistance;
            bool _reachedABorder;
            bool _normalChanged;
            int _previousTriangle;

            Vector3 _currentPosition;
            Vector3 _currentDirection;
        };

    protected: //methods
               // to get position at any time, raycast the -up vector of entity _lastPosition on mesh and find nearest point, then apply triangle/edge traversal
        bool rayCast(const Vector3& sourcePt, const Vector3& end, Vector3& res, int& faceHit) const;
        bool rayCastAll(const Vector3& sourcePt, const Vector3& direction, glm::Array<Vector3>& res, glm::PODArray<int>& rayCastFirstVertexIndex) const;
        void computeRaycastAABBHierarchy();

        static void applyTransformationToOrientationQuat(Quaternion& orientation, Matrix4& deltaTransform);

        // start with currentPosition & bary definition. Cast segments from it to next edge, see if < remaining distance compute new pos / normal, else compute next triangle position (with crossed segment as ref edge, angle referenced about this one) and continue
        void recursiveComputeTraversalMeshLocal(const positionInTriangle& currentPosition, positionInTriangle& nextPosition) const;

        bool hierarchicalRaycastAll(const Vector3& sourcePt, const Vector3& direction, glm::PODArray<int>& triIndex, glm::PODArray<float>& tt, int aabbIndex, const AABB<Vector3>* aabbArray, const RayCastVertex* vertices, const int* indices, SpinLock& spinLock) const;

        bool isInsideBounds(const Vector3& position, const Vector3& segmentP0, const Vector3& segmentP1) const;

    protected: //data
        Array<AABB<Vector3>> _AABB;

        SpinLock _setWorldMatrixLock;
        Matrix4 _meshWorldMatrixDeltaFromLastFrame;
        Matrix4 _meshWorldMatrixDeltaFromLastFrameInverse;
        Matrix4 _meshWorldMatrixInverse;
        Matrix4 _meshWorldMatrix;
        Matrix4 _previousMeshWorldMatrix;
        Matrix4 _meshWorldMatrixForNormals; // transpose inverse of the world matrix

        PODArray<float> _cumulativeFaceSurface;
        float _totalMeshSurface;

        // raycast structures
        int _totalTriangleCount;
        Vector3* _orthoFaceNormals;
        Vector3* _edgePlaneNormals; // _totalTriangleCount*3, first index is v1 - v0 plane, second is v2 - v1 plane, third is v0 - v2 plane
        //float*			_edgeNorms; // _totalTriangleCount*3, first index is v1 - v0 plane, second is v2 - v1 plane, third is v0 - v2 plane
        RayCastVertex* _meshVertices; // triangleId allows direct acces to vertex[iTriangle*3], vertex[iTriangle*3+1], vertex[iTriangle*3+2]
        RayCastVertex* _previousMeshVertices;
        int* _rayCastToMeshIndices; // only used to convert raycast indices to mesh indices (this keeps track of AABB moving indices when splitting them)
        int (*_faceNeighbors)[3];   // in edge order : v0v1, v1v2, v2v0. allow fast traversal through faces via neighbor to neihgbor hops, conserving the direction when bending edges

        bool _deformed;
        bool _initialized;
        bool _matrixChangedLastFrame;

    private:
        GLM_DISALLOW_COPY_AND_ASSIGN(PinMesh);
    };

    //-------------------------------------------------------------------------
    inline const Vector3& PinMesh::getFaceNormal(int triangleIndex) const
    {
        return _orthoFaceNormals[triangleIndex];
    }

    //-------------------------------------------------------------------------
    inline const Vector3& PinMesh::getEdgeNormal(int edgeIndex) const
    {
        return _edgePlaneNormals[edgeIndex];
    }

    //-------------------------------------------------------------------------
    inline const Matrix4& PinMesh::getWorldMatrix() const
    {
        return _meshWorldMatrix;
    }

    //-------------------------------------------------------------------------
    inline const Matrix4& PinMesh::getWorldMatrixInverse() const
    {
        return _meshWorldMatrixInverse;
    }

    //-------------------------------------------------------------------------
    inline Matrix4& PinMesh::touchWorldMatrix()
    {
        return _meshWorldMatrix;
    }

    //-------------------------------------------------------------------------
    inline Matrix4& PinMesh::touchWorldMatrixInverse()
    {
        return _meshWorldMatrixInverse;
    }

    //-------------------------------------------------------------------------
    inline PinMesh::RayCastVertex* PinMesh::touchMeshVertices()
    {
        return _meshVertices;
    }

    //-------------------------------------------------------------------------
    inline const PinMesh::RayCastVertex* PinMesh::getMeshVertices() const
    {
        return _meshVertices;
    }

    //-------------------------------------------------------------------------
    inline int PinMesh::getVertexCount() const
    {
        return _totalTriangleCount * 3;
    }

    //-------------------------------------------------------------------------
    inline int PinMesh::getTotalTriangleCount() const
    {
        return _totalTriangleCount;
    }

    //-------------------------------------------------------------------------
    inline void PinMesh::setTotalTriangleCount(int triangleCount)
    {
        _totalTriangleCount = triangleCount;
    }

    //-------------------------------------------------------------------------
    inline int* PinMesh::getRayCastToMeshIndices()
    {
        return _rayCastToMeshIndices;
    }

    //-------------------------------------------------------------------------
    inline int* PinMesh::getFaceNeighbors(int triangleIndex)
    {
        return _faceNeighbors[triangleIndex];
    }

    //-------------------------------------------------------------------------
    inline const Matrix4& PinMesh::getWorldMatrixFrameDelta() const
    {
        return _meshWorldMatrixDeltaFromLastFrame;
    }

    //-------------------------------------------------------------------------
    inline bool PinMesh::PairValue::operator<(const PairValue& other) const
    {
        return _value0 < other._value0 || (_value0 == other._value0 && _value1 < other._value1);
    }
} // namespace glm
