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

#pragma once

#include "glmSDKAPI.h"
#include "glmMemory.h"
#include "glmArray.h"
#include "glmPODArray.h"
#include "glmSet.h"
#include "glmQueue.h"
#include "glmVector3.h"
#include "glmTriangle3.h"
#include "glmFastBoundingBox.h"
#include "glmScatterOctree.h"
#include "glmCachePerThread.h"

namespace glm
{
    class Segment3;
    struct PointDistancePredicate;

    struct GLM_SDK_API Voxel
    {
        int x;
        int y;
        int z;

        inline bool operator==(const Voxel& other) const;
        inline bool operator<(const Voxel& other) const;
    };

    class GLM_SDK_API VoxelField
    {
    public:
        GLM_CLASS_STAT_ID( glm::MemoryStatIds::Simulation);

        float _volume = 0;
        FastBoundingBox _boundingBox;
        ScatterOctree _octree;
        glm::Array<Vector3> _voxelPositions;
        PODArray<Voxel> _geoVoxels;
        GlmSet<Voxel> _geoVoxelsSet;

        glm::GlmMap<Voxel, size_t> _voxelMap;

        struct PerThreadCache
        {
            GlmQueue<Voxel> _voxelsToProcess;
            PODArray<int> _processedVoxels;
        };

        VoxelField();

        void clear();

        void getVoxelOrigin(Vector3& result, const glm::Voxel& voxel) const;
        void getVoxelCenter(Vector3& result, const glm::Voxel& voxel) const;

        void findNeighbors(glm::PODArray<Voxel>& neighbors, const Voxel& voxel, int size = 1);

        void initFromGeometry(const glm::Array<glm::Array<glm::Triangle3>>& geometryMeshes, unsigned int maxVoxelCount);
        static bool checkMesh(const glm::Array<glm::Triangle3>& meshGeometry);

        void traverseVoxels(
            glm::PODArray<Voxel>& traversedVoxels,
            const Segment3& segment,
            float radius,
            size_t maxCount = 0);

        void getVoxelsInsideRadius(
            glm::PODArray<Voxel>& foundVoxels,
            const Vector3& center,
            float radius,
            size_t maxCount = 0);

        void getClosestVoxel(Voxel& foundVoxel, const Vector3& pos, bool optimized = true);

        bool insideBounds(const Voxel& voxel);

        inline float getVoxelSize() const;

        int getVoxelIndex(const Voxel& voxel) const;

    protected:
        static bool pointInsideMesh(const glm::Array<glm::Triangle3>& meshGeometry, const Vector3& point);

        void voxelToOctreePos(const Voxel& voxel, unsigned int (&octreePos)[3]);
        void octreePosToVoxel(const unsigned int (&octreePos)[3], Voxel& voxel);

        void voxelQuery(glm::PODArray<Voxel>& foundVoxels, const PointDistancePredicate& predicate, PODArray<int>& processedVoxels, GlmQueue<Voxel>& voxelsToProcess, size_t maxCount);

    private:
        CachePerThread<PerThreadCache> _perThreadCache;
    };

    //-------------------------------------------------------------------------
    inline bool Voxel::operator==(const Voxel& other) const
    {
        return x == other.x && y == other.y && z == other.z;
    }

    //-------------------------------------------------------------------------
    inline bool Voxel::operator<(const Voxel& other) const
    {
        if (x < other.x)
        {
            return true;
        }
        if (x > other.x)
        {
            return false;
        }
        if (y < other.y)
        {
            return true;
        }
        if (y > other.y)
        {
            return false;
        }
        if (z < other.z)
        {
            return true;
        }
        if (z > other.z)
        {
            return false;
        }

        return false;
    }

    //-------------------------------------------------------------------------
    inline float VoxelField::getVoxelSize() const
    {
        return _octree._cellSize;
    }

} // namespace glm
