//////////////////////////////////////////////////////////////////////////////////
// SPARK particle engine														//
// Copyright (C) 2008-2009 - Julien Fryer - julienfryer@gmail.com				//
//																				//
// This software is provided 'as-is', without any express or implied			//
// warranty.  In no event will the authors be held liable for any damages		//
// arising from the use of this software.										//
//																				//
// Permission is granted to anyone to use this software for any purpose,		//
// including commercial applications, and to alter it and redistribute it		//
// freely, subject to the following restrictions:								//
//																				//
// 1. The origin of this software must not be misrepresented; you must not		//
//    claim that you wrote the original software. If you use this software		//
//    in a product, an acknowledgment in the product documentation would be		//
//    appreciated but is not required.											//
// 2. Altered source versions must be plainly marked as such, and must not be	//
//    misrepresented as being the original software.							//
// 3. This notice may not be removed or altered from any source distribution.	//
//////////////////////////////////////////////////////////////////////////////////


#ifndef H_SPK_PARTICLE
#define H_SPK_PARTICLE

#include "Core/SPK_DEF.h"
#include "Core/SPK_Vector3D.h"
#include "Core/SPK_Pool.h"
#include "Core/SPK_Model.h"


namespace SPK
{
	/**
	* @class Particle
	* @brief A single particle (a point in space with a velocity and different parameters)
	*
	* A Particle is the primitive on which all the SPARK engine relies.<br>
	* Particles are designed to be handled in large amounts (called Group).
	* This is why the user cannot update or render directly a single Particle.<br>
	* <br>
	* Note that the Particle class is only a class that presents an interface to the user (since 1.02.00), particles data are stored in the groups.
	* This is why copying a Particle will not copy its data.<br>
	*/
	class SPK_PREFIX Particle
	{
	friend inline bool isFurtherToCamera(const Particle&,const Particle&);
	friend void swapParticles(Particle& a,Particle& b);
	friend class Group;
	friend class Pool<Particle>;


	public :

		/**
		* @brief Sets the current value for the given parameter
		*
		* Note that the method will have no effect if the parameter is not enabled in the Particle's Model.
		* In this case, the method returns false.
		*
		* @param type : the parameter to set
		* @param value : the value of the parameter
		* @return true if the parameter can be set, false otherwise (if the parameter is not enabled)
		*/
		bool setParamCurrentValue(ModelParam type,float value);

		/**
		* @brief Sets the final value for the given parameter
		*
		* The final value is the value the parameter will have at the end of the Particle's life.<br>
		* Note that the method will have no effect if the parameter is not mutable in the Particle's Model.
		* In this case, the method returns false and setParamCurrentValue(ModelParam,float) should be used.
		*
		* @param type : the parameter to set
		* @param value : the value of the parameter
		* @return true if the parameter can be set, false otherwise (if the parameter is not mutable)
		*/
		bool setParamFinalValue(ModelParam type,float value);

		/**
		* @brief Changes the current value for the given parameter
		*
		* The delta is added to the current value of the parameter.<br>
		* For more information see setParamCurrentValue(ModelParam,float).
		*
		* @param type : the parameter to set
		* @param delta : the delta
		* @return true if the parameter can be changed, false otherwise (if the parameter is not enabled)
		* @since 1.02.00
		*/
		bool changeParamCurrentValue(ModelParam type,float delta);

		/**
		* @brief Changes the final value for the given parameter
		*
		* The delta is added to the final value of the parameter.<br>
		* For more information see setParamFinalValue(ModelParam,float).
		*
		* @param type : the parameter to set
		* @param delta : the delta
		* @return true if the parameter can be changed, false otherwise (if the parameter is not mutable)
		* @since 1.02.00
		*/
		bool changeParamFinalValue(ModelParam type,float delta);

		/**
		* @brief Sets the life left of the Particle.
		*
		* When the Particle's life reaches 0, the Particle is inactivated.
		*
		* @param life : the amount of life left of the Particle
		*/
		inline void setLifeLeft(float life);


		/////////////
		// Getters //
		/////////////

		/**
		* @brief Gets the position of the Particle
		* @return the position of this Particle
		* @since 1.02.00
		*/
		inline Vector3D& position();

		/**
		* @brief Gets the velocity of the Particle
		* @return the velocity of this Particle
		* @since 1.02.00
		*/
		inline Vector3D& velocity();

		/**
		* @brief Gets the old position of the Particle
		* @return the old position of this Particle
		* @since 1.02.00
		*/
		inline Vector3D& oldPosition();

		/**
		* @brief Gets the position of the Particle
		*
		* This is the constant version of position()
		*
		* @return the position of this Particle
		* @since 1.02.00
		*/
		inline const Vector3D& position() const;

		/**
		* @brief Gets the velocity of the Particle
		*
		* This is the constant version of velocity()
		*
		* @return the velocity of this Particle
		* @since 1.02.00
		*/
		inline const Vector3D& velocity() const;

		/**
		* @brief Gets the old position of the Particle
		*
		* This is the constant version of oldPosition()
		*
		* @return the old position of this Particle
		* @since 1.02.00
		*/
		inline const Vector3D& oldPosition() const;

		/**
		* @brief Gets the current value for the given parameter
		*
		* Note that if the the parameter is not enabled in the Particle's Model, the default value for the parameter is returned.
		*
		* @param type : the parameter to get the value
		* @return the current value of the parameter
		*/
		float getParamCurrentValue(ModelParam type) const;

		/**
		* @brief Gets the final value for the given parameter
		*
		* Note that if the the parameter is not enabled in the Particle's Model, the default value for the parameter is returned.<br>
		* If the parameter is enabled but not mutable, the current value is returned.
		*
		* @param type : the parameter to get the value
		* @return the current value of the parameter
		*/
		float getParamFinalValue(ModelParam type) const;

		/**
		* @brief Gets the Model of this Particle
		* @return A pointer on the Model of this Particle
		*/
		Model* getModel() const;

		/**
		* @brief Gets the group of this Particle
		* @return A pointer on the Group of this Particle
		* @since 1.02.00
		*/
		inline Group* getGroup() const;

		/**
		* @brief Gets the index of this Particle in its Group
		* @return the index of thi Particle in its Group
		* @since 1.03.00
		*/
		inline size_t getIndex() const;

		/**
		* @brief Gets the amount of life left of the Particle
		*
		* The life left of the Particle is the time left before the Particle dies.<br>
		* Note that in case of immortal particles, this value does not evolve.
		*
		* @return the amount of life left
		*/
		inline float getLifeLeft() const;

		/**
		* @brief Gets the age of the Particle
		*
		* The age of a Particle starts at zero when it is initialized and evolve at each update.<br>
		* Note that even immortal particles gets older.
		*
		* @return the age of the particle
		* @since 1.03.00
		*/
		inline float getAge() const;

		/**
		* @brief Gets the distance of this Particle from the camera.
		*
		* Note that the correct distance is only returned if the Group of this Particles has its distance computation enabled.
		*
		* @return the distance of this Particle from the camera
		* @since 1.01.00
		*/
		inline float getDistanceFromCamera() const;

		/**
		* @brief Gets the square distance of this Particle from the camera.
		*
		* Note that the correct distance is only returned if the Group of this Particles has its distance computation enabled.<br>
		* This method is faster than getDistanceFromCamera() and should be used instead when possible.
		*
		* @return the square distance of this Particle from the camera
		* @since 1.01.00
		*/
		inline float getSqrDistanceFromCamera() const;

		/**
		* @brief Tells whether this Particle was initialized at its latest update or not
		*
		* A call to this method is equivalent to <i>getAge() == 0.0f</i>
		*
		* @return true if this Particle was initialized at its latest update, false if not
		* @since 1.03.00
		*/
		inline bool isNewBorn() const;

		/**
		* @brief Tells whether this Particle is alive or not
		*
		* A call to this method is equivalent to <i>getLifeLeft > 0.0f</i>
		*
		* @return true if this Particle is alive, false if it is dead
		* @since 1.04.00
		*/
		inline bool isAlive() const;

		///////////////
		// Interface //
		///////////////

		/**
		* @brief Initializes the Particle
		*
		* When a Particle is initialized, all its parameters are reinitialized as well as its life.
		*/
		void init();

		/**
		* @brief Kills this Particle
		*
		* This method is equivalent to a call to setLifeLeft(float) with life being 0.<br>
		*
		* @since 1.01.00
		*/
		inline void kill();

		// As we know the color component are always enabled, we optimizes it a bit for access
		inline float getR() const { return currentParams[PARAM_RED]; }
		inline float getG() const { return currentParams[PARAM_GREEN]; }
		inline float getB() const { return currentParams[PARAM_BLUE]; }

	private :

		struct ParticleData
		{
			Vector3D oldPosition;
			Vector3D position;
			Vector3D velocity;
			float age;
			float life;
			float sqrDist;
		};

		Group* group;
		size_t index;

		ParticleData* data;
		float* currentParams;
		float* extendedParams;

		Particle(Group* group,size_t index);

		bool update(float timeDelta);
		void computeSqrDist();

		void interpolateParameters();
	};


	inline Group* Particle::getGroup() const
	{
		return group;
	}

	inline size_t Particle::getIndex() const
	{
		return index;
	}

	inline void Particle::setLifeLeft(float life)
	{
		data->life = life;
	}

	inline Vector3D& Particle::position()
	{
		return data->position;
	}

	inline Vector3D& Particle::velocity()
	{
		return data->velocity;
	}

	inline Vector3D& Particle::oldPosition()
	{
		return data->oldPosition;
	}

	inline const Vector3D& Particle::position() const
	{
		return data->position;
	}

	inline const Vector3D& Particle::velocity() const
	{
		return data->velocity;
	}

	inline const Vector3D& Particle::oldPosition() const
	{
		return data->oldPosition;
	}

	inline float Particle::getLifeLeft() const
	{
		return data->life;
	}

	inline float Particle::getAge() const
	{
		return data->age;
	}

	inline float Particle::getDistanceFromCamera() const
	{
		return std::sqrt(data->sqrDist);
	}

	inline float Particle::getSqrDistanceFromCamera() const
	{
		return data->sqrDist;
	}

	inline bool Particle::isNewBorn() const
	{
		return data->age == 0.0f;
	}

	inline bool Particle::isAlive() const
	{
		return data->life > 0.0f;
	}

	inline void Particle::kill()
	{
		data->life = 0.0f;
	}

	// specialization of the swap for particle
	template<>
	inline void Pool<Particle>::swapElements(Particle& a,Particle& b)
	{
		swapParticles(a,b);
	}


	//////////////////////////////////
	// Global functions definitions //
	//////////////////////////////////

	inline bool isFurtherToCamera(const Particle& a, const Particle& b)
	{
		return a.getSqrDistanceFromCamera() > b.getSqrDistanceFromCamera();
	}

	// Swaps particle data. Used internally. Do not use with particles that are not from the same group !
	extern void swapParticles(Particle& a,Particle& b);
}

#endif