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

#pragma once

#include "glmAbstractSpawningZone.h"
#include "glmSpawningCellData.h"

//golaem core includes
#include <glmInterval.h>
#include <glmLooseQuadtreeNode.h>
#include <glmLooseQuadtreeRootNode.h>

namespace glm
{
	/**
	* \brief Spawning zone generating position/orientation in a planar box using a grid.
	*
	* \ingroup placement
	*
	* \author Golaem <dev@golaem.com>
	*/
	class GLM_SDK_API MultipleLooseQuadtreeSpawningZone : public AbstractSpawningZone
	{
    public:
        GLM_CLASS_STAT_ID(glm::MemoryStatIds::Simulation);

        MultipleLooseQuadtreeSpawningZone();
        virtual ~MultipleLooseQuadtreeSpawningZone();

        void setCenters(const glm::Array<Vector3>& centers, const glm::Array<Vector3>& centersOverrides, const glm::Array<Vector3>& normals, const glm::Array<Vector3>& forwards, const glm::Array<Vector3>& forwardsOverrides);

        const Vector3& getTranslation() const;
        void setTranslation(const Vector3& translation) ;

        unsigned int getNbColumn() const;
        void setNbColumn(unsigned int nbColumn);
        unsigned int getNbRow() const;
        void setNbRow(unsigned int nbRow);

        /**
         * \brief Refresh the placement of the slots;
         */
        void refreshPlacement(bool random = true);

        /**
		* \brief Refresh the positions of the slots;
		*/
		void refreshPositionsAndOrientations() ;

		/**
		* \brief returns true if the given slot does not collide with any slot this Spawning Zone
		*/
		virtual bool isColliding(const PopulationSlot* slot) const;

        inline void setNoiseOnForward(bool value) { _noiseOnForward = value; }

		inline const Array<int>& getPlacedSequence() const { return _looseQuadtreesSpawningZonesSequence; }

    protected:
        virtual void setupGrid();
        virtual void setupGridSize();

        virtual void selectSpawningZones(bool random=true) ;

		void refreshNoise() ;

		void computeQuadSize( const Vector2& resolution ) ;
		
		bool placeSlot(LooseQuadtreeNode* node, PopulationSlot* slot);
		void orientSlot(LooseQuadtreeNode* node, PopulationSlot* slot);

		LooseQuadtreeNode* applySlotNoise(LooseQuadtreeNode* node, PopulationSlot* slot, const Vector2& positionNoise );

		void reduceNoise(LooseQuadtreeNode* node, PopulationSlot* slot, const Vector2& positionNoise, const Quaternion& upRotation) ;

        bool respectDistance(LooseQuadtreeNode* node, Vector3& position, float lateralClearance);

        int /*LooseQuadtreeNode**/ getRandomLQT(bool random);

        void cleanRegionsAndGrids() ;

		struct ZoneLocation
		{
			Vector3 _center;
            Vector3 _centerOverride;
            Vector3 _normal;
            Vector3 _forward;
            Vector3 _forwardOverride;
        };

        glm::Array<ZoneLocation> _centers;
        Vector3 _translation;

        glm::PODArray<LooseQuadtreeRootNode*> _looseQuadtrees;
        glm::PODArray<LooseQuadtreeNode*> _occupiedSpawningZones;
        glm::PODArray<LooseQuadtreeNode*> _selectedSpawningZones;
        glm::GlmMap<LooseQuadtreeNode*, unsigned int> _spawningZonesCurrentlyNoised;
        glm::GlmMap<LooseQuadtreeRootNode*, ZoneLocation> _spawningZonesCenters;
        glm::Array<int> _looseQuadtreesSpawningZonesSequence;

		glm::PODArray<int> _highestWeightNodesindices;

        Vector2 _quadTreeSize;

        LooseQuadtreeRootNode::Shape::Value _shape ;

		unsigned int _nbColumn ;
		unsigned int _nbRow ; 

		bool _sizeChanged ;

        bool _noiseOnForward;
    };

	//*****************************************************************************
	// LooseQuadtreeSpawningZone inline definitions
	//*****************************************************************************

	//-----------------------------------------------------------------------------
    inline void MultipleLooseQuadtreeSpawningZone::setCenters(const glm::Array<Vector3>& centers, const glm::Array<Vector3>& centersOverrides, const glm::Array<Vector3>& normals, const glm::Array<Vector3>& forwards, const glm::Array<Vector3>& forwardsOverrides)
    {
        _centers.resize(centers.size());
        for (size_t i = 0; i < _centers.size(); i++)
        {
            ZoneLocation& location = _centers[i];
            location._center = centers[i];
            location._centerOverride.setValues(FLT_MAX, 0, 0); // default values in case we have no override
            location._normal.setValues(0, 0, 1);               // default values in case we have no normals
            location._forward.setValues(0, 0, 0);              // default values in case we have no forwards
            location._forwardOverride.setValues(0, 0, 0);      // default values in case we have no override
        }

        for (size_t i = 0; i < centersOverrides.size() && i < _centers.size(); i++)
        {
            ZoneLocation& location = _centers[i];
            location._centerOverride = centersOverrides[i];
        }

        for (size_t i = 0; i < normals.size() && i < _centers.size(); i++)
        {
            const Vector3& normal = normals[i];
            if (normal.isNormalized())
            {
                _centers[i]._normal = normal;
            }
        }

        for (size_t i = 0; i < forwards.size() && i < _centers.size(); i++)
        {
            const Vector3& forward = forwards[i];
            if (forward.isNormalized())
            {
                _centers[i]._forward = forward;
            }
        }

        for (size_t i = 0; i < forwardsOverrides.size() && i < _centers.size(); i++)
        {
            const Vector3& forwardOverride = forwardsOverrides[i];
            if (forwardOverride.isNormalized())
            {
                _centers[i]._forwardOverride = forwardOverride;
            }
        }
    }

    //-----------------------------------------------------------------------------
    inline const Vector3& MultipleLooseQuadtreeSpawningZone::getTranslation() const
    {
        return _translation;
    }

    //-----------------------------------------------------------------------------
	inline void MultipleLooseQuadtreeSpawningZone::setTranslation(const Vector3& translation)
	{
		_translation = translation;
	}

    //-----------------------------------------------------------------------------
    inline unsigned int MultipleLooseQuadtreeSpawningZone::getNbColumn() const
    {
        return _nbColumn;
    }

    //-----------------------------------------------------------------------------
    inline void MultipleLooseQuadtreeSpawningZone::setNbColumn(unsigned int nbColumn)
    {
        GLM_ASSERT_MSG(nbColumn > 0, "The grid should have at least 1 column.");
		if( _nbColumn != nbColumn )
		{
			_sizeChanged = true ;
		}
		_nbColumn = nbColumn ;
		_shape = LooseQuadtreeRootNode::Shape::POINT ;
    }

    //-----------------------------------------------------------------------------
	inline unsigned int MultipleLooseQuadtreeSpawningZone::getNbRow() const
	{
		return _nbRow;
	}

	//-----------------------------------------------------------------------------
	inline void MultipleLooseQuadtreeSpawningZone::setNbRow( unsigned int nbRow )
	{
		GLM_ASSERT_MSG(nbRow > 0, "The grid should have at least 1 row.");
		if( _nbRow != nbRow )
		{
			_sizeChanged = true ;
		}
		_nbRow = nbRow ;
		_shape = LooseQuadtreeRootNode::Shape::POINT ;
    }
}
