/***************************************************************************
*                                                                          *
*  Copyright (C) Golaem S.A. & Mikros Image - All Rights Reserved.         *
*                                                                          *
***************************************************************************/

#pragma once

#include <glmCoreDefinitions.h>
#include <glmCrowdIOAPI.h>

#include "glmGeometryFile.h"
#include "glmMatrix4.h"
#include "glmVector3.h"
#include "glmAABB.h"
#include <math.h>
#include <float.h>
#include "stdint.h"
#include <vector>
#include <stdlib.h>
#include <string.h>

namespace glm
{
    namespace crowdio
    {
        namespace crowdTerrain
        {
            struct TerrainMesh; //pre-declaration, see at the end of this file

            // write/read a GolaemTerrain file
            extern GLM_CROWDIO_API GlmGeometryGenerationStatus glmWriteTerrainFile(const char* filename, const uint16_t meshCount, const GlmFileMesh* fileMeshes, const uint16_t transformCount, const GlmFileMeshTransform* fileMeshTransforms);
            extern GLM_CROWDIO_API GlmGeometryGenerationStatus glmReadTerrainFile(const char* filename, uint16_t& meshCount, GlmFileMesh*& fileMeshes, uint16_t& transformCount, GlmFileMeshTransform*& fileMeshTransforms);

            extern GLM_CROWDIO_API GlmGeometryGenerationStatus loadTerrainGTG(const char* gtgCompletePath, TerrainMesh* mainMesh);

            extern GLM_CROWDIO_API TerrainMesh* loadTerrainAsset(const char* terrainCompletePath);
            extern GLM_CROWDIO_API void closeTerrainAsset(const TerrainMesh* terrainMesh);

            struct GLM_CROWDIO_API TerrainSubMesh
            {
                TerrainSubMesh();

                ~TerrainSubMesh();

                void clear();
                void allocate(unsigned int vertexCount, unsigned int indexCount);
                void updateAABB();

                void init(Vector3* lVertices, Vector3* lNormals, unsigned int lVertexCount, unsigned int* lIndices, unsigned int lIndiceCount);
                void merge(Vector3* lVertices, Vector3* lNormals, unsigned int lVertexCount, unsigned int* lIndices, unsigned int lIndiceCount);
                void setFrame(int frame);
                bool raycastClosest(const Vector3& rayOrigin, const Vector3& rayEnd, Vector3* collisionPoint, Vector3* collisionNormal, const Matrix4* proxyMatrix, const Matrix4* proxyMatrixInverse) const;

                ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

                Vector3* _vertices;
                Vector3* _normals;

                unsigned int* _indices;
                unsigned int _indiceCount;
                unsigned int _vertexCount;

                std::vector<AABB<Vector3> > _AABB;

                Matrix4 _worldToLocal;
                Matrix4 _localToWorld;

                std::vector<Matrix4> _animation; // one matrix per frame. world to local
                std::vector<Matrix4> _animationInverse;

                void* _userNode;
            };

            struct GLM_CROWDIO_API TerrainMesh
            {
				GLM_CLASS_STAT_ID(glm::MemoryStatIds::Cache)

                TerrainMesh();

                ~TerrainMesh();

                bool raycastClosest(const Vector3& rayOrigin, const Vector3& rayEnd, Vector3* collisionPoint, Vector3* collisionNormal, const Matrix4* simulationTransformMatrix, const Matrix4* simulationTransformMatrixInverse) const;
                void setFrame(int frame);

                void clear();
                void allocateSubmeshes(size_t count);

                std::vector<TerrainSubMesh*> _subMeshes;
                int _startFrame;
            };

            template <typename pointT>
            pointT computeBarycentric(const pointT& normal, const pointT& a, const pointT& b, const pointT& c, const pointT& P)
            {
                pointT bary;

                // The area of a triangle is
                float areaABC = normal * ((b - a) ^ (c - a));
                float areaPBC = normal * ((b - P) ^ (c - P));
                float areaPCA = normal * ((c - P) ^ (a - P));

                bary.x = areaPBC / areaABC;      // alpha
                bary.y = areaPCA / areaABC;      // beta
                bary.z = 1.0f - bary.x - bary.y; // gamma

                return bary;
            }

            inline Vector3 barycentricLerp(Vector3 a, Vector3 b, Vector3 c, float r, float s, float t)
            {
                return a * r + b * s + c * t;
            }

// branchless intersectRayAABox try
#if GLM_IS_COMPILER_MSVC && (defined(_M_X64) || defined(_M_IA64))
            // https://tavianator.com/fast-branchless-raybounding-box-intersections-part-2-nans/
            template <typename pointT>
            bool intersectRayAABoxBranchlessT(const pointT& rO, const pointT& rV_Inverse, const pointT& bboxMin, const pointT& bboxMax)
            {
                float t1 = (bboxMin.x - rO.x) * rV_Inverse.x;
                float t2 = (bboxMax.x - rO.x) * rV_Inverse.x;

                float tmin = glm::min(t1, t2);
                float tmax = glm::max(t1, t2);

                t1 = (bboxMin.y - rO.y) * rV_Inverse.y;
                t2 = (bboxMax.y - rO.y) * rV_Inverse.y;

                tmin = glm::max(tmin, glm::min(glm::min(t1, t2), tmax));
                tmax = glm::min(tmax, glm::max(glm::max(t1, t2), tmin));

                t1 = (bboxMin.z - rO.z) * rV_Inverse.z;
                t2 = (bboxMax.z - rO.z) * rV_Inverse.z;

                tmin = glm::max(tmin, glm::min(glm::min(t1, t2), tmax));
                tmax = glm::min(tmax, glm::max(glm::max(t1, t2), tmin));

                return tmax >= glm::max(tmin, 0.f); // changed > for >=, in case BBox coordinate in one dimension is very small compared to bbox - r0, it could lead to equalities
            }

#endif

            ////-----------------------------------------------------------------------------
            //// find closest Vertex in an AABox
            ////-----------------------------------------------------------------------------
            //template <typename pointT>
            //bool containsPosAABoxT(const pointT& rO, const pointT& min, const pointT& max)
            //{
            //	return (rO[0] >= min[0]) && (rO[0] <= max[0])
            //		&& (rO[1] >= min[1]) && (rO[1] <= max[1])
            //		&& (rO[2] >= min[2]) && (rO[2] <= max[2])
            //}
            //
            //template <typename vertexT, typename pointT>
            //bool hierarchicalContainsPos(const pointT& sourcePt, int& triIndex, float& tt, int aabbIndex, const AABB<pointT>* aabbArray, const vertexT* vertices, const int* indices)
            //{
            //	const AABB<pointT>& aabb(aabbArray[aabbIndex]);
            //
            //	if (containsPosAABoxT<pointT>(sourcePt, aabb._AABBmin, aabb._AABBmax))
            //	{
            //		bool hasHit(false);
            //
            //		if (aabb._left != -1)
            //			hasHit |= containsPosAABoxT<vertexT, pointT>(sourcePt, direction, triIndex, tt, aabb._left, aabbArray, vertices, indices);
            //
            //		if (aabb._right != -1)
            //			hasHit |= containsPosAABoxT<vertexT, pointT>(sourcePt, direction, triIndex, tt, aabb._right, aabbArray, vertices, indices);
            //
            //		// for leaves
            //		for (int i = aabb._firstIndex; i < (aabb._firstIndex + aabb._indexCount); i += 3)
            //		{
            //			float tu, tv, t;
            //			float* v1 = (float*)&vertices[indices[i]];
            //			float* v2 = (float*)&vertices[indices[i + 1]];
            //			float* v3 = (float*)&vertices[indices[i + 2]];
            //			if (rayTriangle((float*)&sourcePt.x, (float*)&direction.x,
            //				v1, v2, v3,
            //				&t, &tu, &tv))
            //			{
            //				if ((t < tt) && (t >= 0.f))
            //				{
            //					triIndex = i;
            //					tt = t;
            //					hasHit = true;
            //				}
            //			}
            //		}
            //
            //		// if no hit, and want to find closest point, should look at nb cells until finding a position, and insure it is closer than any non empty cells or hits.
            //		return hasHit;
            //	}
            //}



            //template <typename vertexT, typename pointT>
            //bool hierarchicalRaycast(const pointT& sourcePt, const pointT& direction, int& triIndex, float& tt, int aabbIndex, const AABB<pointT>* aabbArray, const vertexT* vertices, const int* indices)
            //{
            //	return hierarchicalRaycast(sourcePt, direction, pointT(1.f/direction.x, 1.f/direction.y, 1.f/direction.z), triIndex, tt, aabbIndex, aabbArray, vertices, indices);
            //}

        } // namespace crowdTerrain
    }     // namespace crowdio
} // namespace glm
