// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
// Copyright (C) 2010  Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.



#ifndef CL_TIMED_FX_MANAGER_H
#define CL_TIMED_FX_MANAGER_H

#include "nel/misc/vector.h"
#include "nel/misc/noise_value.h"
#include "game_share/season.h"
#include "time_client.h"
#include "fx_manager.h"

class CSeasonFXSheet;

namespace NL3D
{
	class UParticleSystemInstance;
	class UScene;
}

/** A fx that must be spawn at a given season and hour
  * \author Nicolas Vizerie
  * \author Nevrax France
  * \date 2003
  */
class CTimedFX
{
public:
	NLMISC::CVector			SpawnPosition;
	NLMISC::CQuat			Rot;
	NLMISC::CVector			Scale;
	const CSeasonFXSheet   *FXSheet;
#if !FINAL_VERSION
		bool				FromIG; // true if the fx comes from an ig, or false if it was generated dynamically
	#endif
public:
	CTimedFX() : SpawnPosition(0.f, 0.f, 0.f), FXSheet(NULL)
	{
#if !FINAL_VERSION
			FromIG = true;
		#endif
	}
	NLMISC::CMatrix			getInstanceMatrix() const;
};

typedef std::vector<CTimedFX> TTimedFXGroup;

/** Manager to spawn fxs at a given season and hour.
  * \author Nicolas Vizerie
  * \author Nevrax France
  * \date 2003
  */
class CTimedFXManager
{
private:
	class  CManagedFX;
	struct CTimeStampedFX
	{
		CClientDate    Date; // the date at which the FX must be added / removed
		//sint32         DebugDay;
		CManagedFX	   *FX;  // managed FX
		bool operator < (const CTimeStampedFX &rhs) const
		{
			if (Date == rhs.Date) return FX < rhs.FX;
			else return Date < rhs.Date;
		} // we want to deal with early dates sooner
		bool operator == (const CTimeStampedFX &rhs) const { return Date == rhs.Date && FX == rhs.FX; }
		CTimeStampedFX() : FX(NULL) {}
	};
	typedef std::set<CTimeStampedFX> TTimeStampedFXPtrSet;
	struct CManagedFXGroup;
	struct CCandidateFXListHead;
	/** A managed FX. Unless it is flagged as 'always intanciated', or is in a group that is shutting down,
	  * such a fx can only be in one set : _FXToAdd if it is not currently instanciated (or is shutting down), and _FXToRemove if it is instanciated and is not being shut down.
	  * and will be removed in the future.
	  */
	class CManagedFX : public CTimedFX
	{
		public:
			enum TState { Unknown = 0, Permanent, InAddList, InRemoveList };
			#ifdef NL_DEBUG
				CManagedFXGroup				  *OwnerGroup;
			#endif
			NL3D::UParticleSystemInstance	   Instance;  // Pointer to the actual FX. NULL if failed to instanciate or if not instanciated
			TState							   State;
			TTimeStampedFXPtrSet::iterator	   SetHandle;              // if the fx is in a list (see its state), then it is an iterator into that list (for removal)
			//
			CManagedFX						   **_PrevCandidateFX;     // if the fx has asked to be instanciated, it is inserted in a list of candidate fxs. points the previous "Next" pointer
			                                                           // even if the fx is currently instanciated, it remains in that list (those are the potnetially instanciated fxs)
			CManagedFX						   *_NextCandidateFX;      // next candidate FX
			//
			CManagedFX					       **_PrevInstanciatedFX;        // link into instanciated fx list, prev (is also a candidate)
			CManagedFX					       *_NextInstanciatedFX;         // link into instanciated fx list, next
			// Tmp for debug
			#ifdef NL_DEBUG
				uint32 Magic;
			#endif
		public:
			// ctor
			CManagedFX()
			{
				Instance = NULL;
				State = Unknown;
				#ifdef NL_DEBUG
					OwnerGroup = NULL;
					Magic = 0xbaadcafe;
				#endif
				_PrevCandidateFX = NULL;
				_NextCandidateFX = NULL;
				//
				_PrevInstanciatedFX = NULL;
				_NextInstanciatedFX = NULL;
			}
			// compute start hour of that fx for the given day
			void computeStartHour(sint32 cycle, CClientDate &resultDate, float cycleLength, float dayLength, const NLMISC::CNoiseValue &nv) const;
			// compute end hour of that fx for the given day
			void computeEndHour(sint32 cycle, CClientDate &resultDate, float cycleLength, float dayLength, const NLMISC::CNoiseValue &nv) const;
			// unlink from list of candidate fxs
			void unlinkFromCandidateFXList();
			// unlink from list of instanciated fx. NB : this doesn't remove the model!
			void unlinkFromInstanciatedFXList();
			/** Shutdown the fx
			  */
			void shutDown(NL3D::UScene *scene, CFXManager &fxManager);
		private:
			void computeHour(sint32 cycle, float bias, CClientDate &resultDate, float cycleLength, float dayLength, const NLMISC::CNoiseValue &nv, float minHour, float maxHour) const;
	};
	// A group of managed fx. We never resize these vectors after creation so we can keep pointers in them.
	typedef std::vector<CManagedFX> TManagedFXGroup;
	struct CManagedFXGroup
	{
		TManagedFXGroup  Group;
		#ifdef NL_DEBUG
			EGSPD::CSeason::TSeason Season;
		#endif
	};
	// a list of group of managed fxs.
	typedef std::list<CManagedFXGroup> TManagedFXGroupList;


public:
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//\name USER INTERFACE
//@{
	// mode of display for debug
	enum TDebugDisplayMode { NoText = 0, PSName, SpawnDate, DebugModeCount };
	// handle to add/remove a group of managed fxs (see add() / remove())
	typedef TManagedFXGroupList::iterator TFXGroupHandle;
	// dtor
	~CTimedFXManager();
	/** Init the manager. Must be called before first use
	  * \param scene                 The scene from which instances are created
	  * \param startDate             Date of init
	  * \param dayLength             Length of a day, in hours
	  * \param noiseFrequency        Frequency of noise used to compute pseudo random spwaning dates of fxs (depends on their spawn position and on the date)
	  * \param maxNumberOfFXInstance Max number of fx instances that are allowed to be instanciated at a given time. Nearest fx are instanciated first
	  * \param sortDistanceInterval  precision with which fx are sorted in distance. For example, with 1meter precision, fx at 0.2 & 0.5 from the user are considered
	  *                              to be at the same distance
	  * \param maxDist               Max dist at which fxs are sorted. For efficiency, it is important to keep maxDist / sortDistanceInterval
	  */
	void					init(NL3D::UScene *scene,
		                         const CClientDate &startDate,
								 float dayLength,
								 float noiseFrequency,
								 uint  maxNumberOfFXInstances,
								 float sortDistanceInterval,
								 float maxDist
								);
	// Reset all manager content
	void					reset();
	/** Register a set of fxs to be managed.
	  * A handle is returned to remove it later.
	  */
	TFXGroupHandle			add(const std::vector<CTimedFX> &fxs, EGSPD::CSeason::TSeason season);
	/** Remove a FX group that has previously been added
	  * All FX instances are deleted.
	  */
	void					remove(TFXGroupHandle handle);
	/** Delayed removal of a fx group that has previously been added.
	  * All FXs are shutdown by removing their emitters.
	  * This is useful to switch from one season to another.
	  */
	void					shutDown(TFXGroupHandle handle);
	// Update the manager state to match the new date. This add / removes FX as necessary
	void					update(const CClientDate &date, EGSPD::CSeason::TSeason currSeason, const NLMISC::CVector &camPos);
	// Set a new date, but do not update current fx. The new hour will be taken in account during the next call to 'add'
	void					setDate(const CClientDate &date);
	// get the current date
	const CClientDate	   &getDate() const { return _CurrDate; }
	// Access to the unique instance of this class
	static CTimedFXManager &getInstance();
	// for debug only
	void					dumpFXToAdd() const;
	void					dumpFXToRemove() const;
	void					dumpFXInfo() const;
	// debug bbox and dates of FXs to remove and to add.
	void					displayFXBoxes(TDebugDisplayMode displayMode) const;
	// set max number of fx to be instanciated at a time
	void					setMaxNumFXInstances(uint maxNumInstaces);
//@}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private:
	// Set of fx to be instanciated in the future. It is managed as a priority queue (sorted by date of instanciation).
	TTimeStampedFXPtrSet		_FXToAdd;
	// Set of fx to remove in the future, and that are currently instanciated (and are not shutting done). It is managed as a priority queue (sorted by date of removal).
	TTimeStampedFXPtrSet		_FXToRemove;
	// List of the groups of FXs that are currenlty managed.
	TManagedFXGroupList			_FXGroups;
	// The scene from which to add / remove instances
	NL3D::UScene				*_Scene;
	// Current date of the manager
	CClientDate					_CurrDate;
	// List of group that are shutting down (that is, shutDown(TFXGroupHandle handle) was called on that group)
	std::list<TFXGroupHandle>	_ShuttingDownGroups;
	// Length of a day, in hours
	float						_DayLength;
	// Noise function used to compute start/end date of fxs
	NLMISC::CNoiseValue			_Noise;
	// Init
	bool						_InitDone;

	//\name DISTANCE SORTING
	//@{
		// FX to be displayed can be limited in number -> the nearest fx are instanciated in priority
		// To achieve this, we keep a list of candidate fx for each interval of distance (thus achieving linear approximate sorting)

		typedef std::vector<CManagedFX *>  TCandidateFXListSortedByDist;
		TCandidateFXListSortedByDist	   _CandidateFXListSortedByDist;      // roughly sorted list of candidate fx. Should be rebuilt completely when players moves (done in linear time)
		TCandidateFXListSortedByDist	   _CandidateFXListSortedByDistTmp;   // already allocated vect for fast resorting of list when player moves

		                                                                 // NB : this vector nevers grows
		bool							   _CandidateFXListTouched;      // the list of candidate has been modified, so the manager should see which fxs should be instanciated, and which fxs should be removed
		float							   _SortDistance;                // length of each distance interval, in meters
		uint							   _MaxNumberOfFXInstances;       // max number of instances that can be 'alive' at a time
	//@}

	// Linked list of currently instanciated FXs (nearest candidates)
	CManagedFX *_InstanciatedFXs;
	NLMISC::CVector	_LastCamPos;

	// fx manager to deals with shutting down fxs
	CFXManager _FXManager;

private:
	// ctor
	CTimedFXManager();
	// for debug
	void checkIntegrity();
	// setup user params for a fx
	void setupUserParams(CManagedFX &fi, uint cycle);
	// insert a fx in list of candidate for instanciation (also unlink from a previous list)
	void linkCandidateFX(TCandidateFXListSortedByDist &targetList, float dist, CManagedFX *fx);
	// link a fx into a list of instances (also unlink from a previous list)
	void linkInstanciatedFX(CManagedFX *&listHead, CManagedFX *fx);
	// update list of instanciated fxs (create / removes fxs as necessary)
	void updateInstanciatedFXList();
	// re-sort list of candidate fx by distance (this is necessary when player moves)
	void updateCandidateFXListSorting();
public:
	// convert a cycle and an hour to a date
	static void   cycleToDate(sint32 cycle, float hour, float cycleLength, float dayLength, CClientDate &result);
	// give the matching cycle for a date
	static sint32 dateToCycle(const CClientDate &date, float cycleLength, float dayLength);
};



#endif