/********************************************************************************
*                                                                               *
*   Copyright (C) Golaem S.A.  All Rights Reserved.                             *
*                                                                               *
*   Original code & license:                                                    *
*                                                                               *
*   OpenSteer -- Steering Behaviors for Autonomous Characters                   *
*                                                                               *
*   Copyright (c) 2002-2003, Sony Computer Entertainment America                *
*   Original author: Craig Reynolds <craig_reynolds@playstation.sony.com>       *
*                                                                               *
*   Permission is hereby granted, free of charge, to any person obtaining a     *
*   copy of this software and associated documentation files (the "Software"),  *
*   to deal in the Software without restriction, including without limitation   *
*   the rights to use, copy, modify, merge, publish, distribute, sublicense,    *
*   and/or sell copies of the Software, and to permit persons to whom the       *
*   Software is furnished to do so, subject to the following conditions:        *
*                                                                               *
*   The above copyright notice and this permission notice shall be included in  *
*   all copies or substantial portions of the Software.                         *
*                                                                               *
*   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  *
*   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,    *
*   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL    *
*   THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER  *
*   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING     *
*   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER         *
*   DEALINGS IN THE SOFTWARE.                                                   *
*                                                                               *
********************************************************************************/

#pragma once

#include "glmSDKAPI.h"
#include "glmBoidEntityComponent.h"
#include "glmAbstractFlockPerchingSupport.h"
#include "glmAbstractFlockObstacle.h"
#include <glmIContext.h>
#include <glmRandomNumberGenerator.h>
#include <glmSimulation.h>
#include "glmAbstractProximityDatabase.h"
#include "glmBoidSharedNeighborhood.h"

namespace glm
{
	class AbstractFlockObstacle;
	class AbstractFlockPerchingSupport;
	class GLBoidContextVisualizationInterface;

	class GLM_SDK_API BoidContext : public IContext
	{
		friend class GLBoidContextVisualizationInterface;

	public:

		GLM_CLASS_STAT_ID( glm::MemoryStatIds::Simulation)

		struct ProximityDatabaseType
		{
			enum Value
			{
				BRUTE_FORCE,
				LOCALITY_QUERY,
				END
			};
		};

		BoidContext(float flockRadius = 100.f,
			const Vector3& flockCenter = Vector3::getNullVector(),
			ProximityDatabaseType::Value databaseType = ProximityDatabaseType::LOCALITY_QUERY,
			bool is2D = false);

		virtual ~BoidContext();

		inline float getFlockRadius() const;
		inline const Vector3& getFlockCenter() const;
		void setFlockRadiusAndCenter(float radius, const Vector3& center);
		inline float getMaxRadiusFactor() const;
		inline ProximityDatabaseType::Value getProximityDatabaseType() const;

		BoidEntityComponent& getEntityComponent(int typeIndex, int64_t entityIndex);
		BoidEntityComponent& getEntityComponent(SimuEntityHandle handle);

		/// Returns a position randomly distributed inside a sphere of unit radius centered at the origin.
		Vector3 getRandomVectorInFlockRadiusSphere() const;

		BoidSharedNeighborhood _internalNeighborhood;
		BoidSharedNeighborhood* _neighborhood;
		AbstractProximityDatabase* _proximityDatabase;

		Time _obstacleDecisionTick;

		float _innerRadiusFactor;

		float _separationRadiusFactor;
		float _separationCosAngle;

		float _alignmentRadiusFactor;
		float _alignmentCosAngle;

		float _cohesionRadiusFactor;
		float _cohesionCosAngle;

		void addObstacle(AbstractFlockObstacle* obstacle);
		void removeObstacle(AbstractFlockObstacle* obstacle); // UNSAFE in multithread
		bool handleObstacles(Entity& entity, BoidEntityComponent& component, Time dt);

		inline bool is2D() const { return _is2D; }

		static ContextType::Value getStaticContextType() { return IContext::ContextType::BOID; }

	private:

		virtual void doInit() override;
		virtual void doUpdate(Time) override;
		virtual void doUpdateSubset(Time, int updateOrder) override;
		virtual void doFinish() override;
		virtual void setContextType() override;

		virtual bool registeredOnCreateEntityType() override { return true; }
		virtual void onCreateEntityType(int64_t maxCount) override;

		virtual bool registeredOnCreateEntityComponent() override { return true; }
		virtual void onCreateEntityComponent(int entityTypeIndex, int64_t entityIndex) override;

		virtual bool registeredOnInitEntityComponent() override { return true; }
		virtual void onInitEntityComponent(int entityTypeIndex, int64_t entityIndex) override;

		virtual bool registeredOnUpdateEntityComponent() override { return true; }
		virtual void onUpdateEntityComponent(Time dt, int entityTypeIndex, int64_t entityIndex) override;

		virtual bool registeredOnDestroyEntityComponent() override { return true; }
		virtual void onDestroyEntityComponent(int entityTypeIndex, int64_t entityIndex) override;

		void rebuildProximityDataBase(bool);

		GLM_DISALLOW_COPY_AND_ASSIGN(BoidContext);

		glm::ComponentsArray<BoidEntityComponent> _components;
		float _flockRadius;
		Vector3 _flockCenter;
		float _maxRadiusFactor;
		ProximityDatabaseType::Value _databaseType;
		bool _is2D;

		glm::Array<AbstractFlockObstacle*>						_obstacles;			// obstacles must be added in single threaded context (obstacles modification is not threadsafe if updating components)
	};

	//-------------------------------------------------------------------------
	inline float BoidContext::getFlockRadius() const
	{
		return _flockRadius;
	}

	//-------------------------------------------------------------------------
	inline const Vector3& BoidContext::getFlockCenter() const
	{
		return _flockCenter;
	}

	//-------------------------------------------------------------------------
	inline float BoidContext::getMaxRadiusFactor() const
	{
		return _maxRadiusFactor;
	}

	//-------------------------------------------------------------------------
	inline BoidContext::ProximityDatabaseType::Value BoidContext::getProximityDatabaseType() const
	{
		return _databaseType;
	}

	//-------------------------------------------------------------------------
	inline BoidEntityComponent& BoidContext::getEntityComponent(int typeIndex, int64_t entityIndex)
	{
		return _components.getComponent(typeIndex, entityIndex);
	}

	//-------------------------------------------------------------------------
	inline BoidEntityComponent& BoidContext::getEntityComponent(SimuEntityHandle handle)
	{
		return _components.getComponent(handle._typeIndex, handle._index);
	}
}
