/***************************************************************************
*                                                                          *
*  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 LooseQuadtreeSpawningZone : public AbstractSpawningZone
	{
	public:
        GLM_CLASS_STAT_ID(glm::MemoryStatIds::Simulation);

        LooseQuadtreeSpawningZone();
		LooseQuadtreeSpawningZone( const Vector3& center, unsigned int nbColumn, unsigned int nbRow, const Quaternion& rotation );
		LooseQuadtreeSpawningZone( const Vector3& center, const Vector2& quadSize, const Quaternion& rotation );
		LooseQuadtreeSpawningZone( const Vector3& center, const glm::Array< Vector2 >& polygon, const Quaternion& rotation );
		LooseQuadtreeSpawningZone( const Vector3& center, const Circle2& circle, const Quaternion& rotation );
        virtual ~LooseQuadtreeSpawningZone();

        const Vector2& getQuad() const ;
		void setQuad( const Vector2& quad ) ;

		const glm::Array< Vector2 >& getPolygon() const;
		void setPolygon( const glm::Array< Vector2 >& polygon ) ;

		const Circle2& getCircle() const;
		void setCircle( const Circle2& circle ) ;

		// center of grid 
		const Vector3& getCenter() const ;
		void setCenter(const Vector3& center) ;

        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 setForwards(const glm::Array<Vector3>& forwards);
        inline void setForwardsOverrides(const glm::Array<Vector3>& forwardsOverrides);

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

		virtual void selectSpawningZones() ;

		void refreshNoise() ;

		void computeQuadSize( 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) ;

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

        Vector3 _center;

        glm::Array<Vector3> _forwards;
        glm::Array<Vector3> _forwardsOverrides;

        Vector2 _quadSize;
        Circle2 _circle ;
		glm::Array< Vector2 > _polygon ;
		
		LooseQuadtreeRootNode* _looseQuadtree;

		glm::PODArray< LooseQuadtreeNode* > _occupiedSpawningZones ;
		glm::PODArray< LooseQuadtreeNode* > _selectedSpawningZones ;
		glm::GlmMap< LooseQuadtreeNode*, unsigned int > _spawningZonesCurrentlyNoised ;

		Vector2 _quadTreeSize ;

		LooseQuadtreeRootNode::Shape::Value _shape ;

		unsigned int _nbColumn ;
		unsigned int _nbRow ; 

		bool _sizeChanged ;
	};

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

	//-----------------------------------------------------------------------------
	// setQuad
	//-----------------------------------------------------------------------------
	inline void LooseQuadtreeSpawningZone::setQuad(const Vector2& size)
	{
		if( _quadSize != size )
		{
			_sizeChanged = true ;
			_quadSize = size;
		} 
		_shape = LooseQuadtreeRootNode::Shape::QUAD ;
	}
	//-----------------------------------------------------------------------------
	// getQuad
	//-----------------------------------------------------------------------------
	inline const Vector2& LooseQuadtreeSpawningZone::getQuad() const
	{
		return _quadSize;
	}

	//-----------------------------------------------------------------------------
	// setPolygon
	//-----------------------------------------------------------------------------
	inline void LooseQuadtreeSpawningZone::setPolygon(const glm::Array< Vector2 >& polygon)
	{
		GLM_ASSERT_MSG(polygon.size()>2, "The polygon should have at least three points.");
		if( _polygon != polygon )
		{
			_sizeChanged = true ;
		}
		_polygon = polygon ;
		_shape = LooseQuadtreeRootNode::Shape::POLYGON ;
	}
	//-----------------------------------------------------------------------------
	// getPolygon
	//-----------------------------------------------------------------------------
	inline const glm::Array< Vector2 >& LooseQuadtreeSpawningZone::getPolygon() const
	{
		return _polygon;
	}
	
	//-----------------------------------------------------------------------------
	// getCircle
	//-----------------------------------------------------------------------------
	inline const Circle2& LooseQuadtreeSpawningZone::getCircle() const
	{
		return _circle;
	}

	//-----------------------------------------------------------------------------
	// setCircle
	//-----------------------------------------------------------------------------
	inline void LooseQuadtreeSpawningZone::setCircle( const Circle2& circle )
	{
		if( _circle.getRadius2() != circle.getRadius2() )
		{
			_sizeChanged = true ;
		}
		_circle = circle ;
		_shape = LooseQuadtreeRootNode::Shape::CIRCLE ;
	}

	//-----------------------------------------------------------------------------
	// getCenter
	//-----------------------------------------------------------------------------
	inline const Vector3& LooseQuadtreeSpawningZone::getCenter() const
	{
		return _center;
	}

	//-----------------------------------------------------------------------------
	// setCenter
	//-----------------------------------------------------------------------------
	inline void LooseQuadtreeSpawningZone::setCenter(const Vector3& center)
	{
		_center = center;
	}

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

	//-----------------------------------------------------------------------------
	// setNbColumn
	//-----------------------------------------------------------------------------
	inline void LooseQuadtreeSpawningZone::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 ;
	}

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

	//-----------------------------------------------------------------------------
	// setNbRow
	//-----------------------------------------------------------------------------
	inline void LooseQuadtreeSpawningZone::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 ;
	}

    //-----------------------------------------------------------------------------
    inline void LooseQuadtreeSpawningZone::setForwards(const glm::Array<Vector3>& forwards)
    {
        _forwards = forwards;
    }

    //-----------------------------------------------------------------------------
    inline void LooseQuadtreeSpawningZone::setForwardsOverrides(const glm::Array<Vector3>& forwardsOverrides)
    {
        _forwardsOverrides = forwardsOverrides;
    }
}
