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

#pragma once

#include "glmCoreAPI.h"
#include "glmTimer.h"
#include "glmAssert.h"

namespace glm
{
	/**
	* \brief Count the time spent in code portions
	*
	* This class compute the time spent in parts of the code.
	* It allow to pause the counter to cumulate time from different code parts, and compute a mean time value.
	*
	* The correct usage of the coutner is the following one:
	*	-call startPerfCounter() to start the perfomance counter
	*	-call pausePerfCounter(true/false) to pause/unpause the performance counter
	*	-call stopPerfCounter at the end of one iteration to stop the performance counter and compute the mean value
	*
	* The number of iterations used to compute the mean value can be configured by the setNbSamples() method.
	*
	* \ingroup time
	* \author Golaem <dev@golaem.com>
	*/
	class PerfCounter : public Timer
	{
	public:
		//------------------------------------------------------------------
		///@name Constructors, destructors
		//------------------------------------------------------------------
		//@{
		/**
		* \brief constructor
		* \param nbSamplesToUse number of samples to use to compute the mean value
		* \param diviser the diviser for the unit of the counter (1 for seconds, 1000 for milliseconds, ...)
		*/
		PerfCounter(const unsigned int nbSamplesToUse=1000, const unsigned int diviser=1000) 
			:Timer(diviser)
			,_cumulativeCount(0)
			,_cumulativeCountMeanValue(0)
			,_nbSamplesToUse(nbSamplesToUse)
			,_nbSamplesInUse(0)
			,_perfMeanValue(0.)
			,_perfCountStarted(false)
			,_perfCountPaused(false)
		{
		}

		/**
		* \brief destructor
		*/
		virtual ~PerfCounter( )
		{ }
		//@}

		//------------------------------------------------------------------
		///@name Accessors
		//------------------------------------------------------------------
		//@{
		/**
		* \brief Get the max number of samples to use to compute the mean value
		* \return the max number of samples to use
		*/
		inline unsigned int	getNbMaxSamples() const;

		/**
		* \brief Get the number of samples used to compute the current mean value
		* \return the number of samples that were used to compute the mean value. Note that it is not necessary the number of samples that were configured (it may be less if the number of samples to use is not reached yet)
		*/
		inline unsigned int	getNbSamples() const;

		/**
		* \brief Get the current mean value (the unit is 1/diviser s)
		*/
		inline Time getMeanValue() const;

		/**
		* \brief Tells if the counter is started (may return true even if in pause mode)
		*/
		inline bool isStarted() const;

		/**
		* \brief Tells if the counter is in pause mode (also return false if the counter is nto started)
		*/
		inline bool isPaused() const;
		//@}

		//------------------------------------------------------------------
		///@name Configuration
		//------------------------------------------------------------------
		//@{
		/**
		* \brief Reset the current mean value
		* \param nbSamplesToUse number of samples to use to compute the mean value
		*/
		inline void	setNbMaxSamples(const unsigned int nbSamplesToUse);

		/**
		* \brief Reset the current mean value
		*/
		inline void resetMeanValue();
		//@}

		//------------------------------------------------------------------
		///@name Performance counters
		//------------------------------------------------------------------
		//@{
		/**
		* \brief Start a new sample for the performance counter
		*/
		inline Time start();

		/**
		* \brief Pause/unpause the performance counter in the current sample
		* \param pause true to pause, false to unpause
		*/
		inline void pause(bool pause);

		/**
		* \brief Stop the current sample for the performance counter and compute the new mean value
		* \return the new mean value considering the new sample
		*/
		inline Time stop();
		//@}

	private:

		//------------------------------------------------------------------
		///@name private datas
		//@{

		Time _cumulativeCount; //!< cumul of all (_stopValue-_startValue) for one sample
		Time _cumulativeCountMeanValue; //!< mean cumulative count for all samples

		unsigned int _nbSamplesToUse;	//!< number of samples to use to compute the mean value
		unsigned int _nbSamplesInUse;	//!< number of samples already used to compute the current mean value

		Time _perfMeanValue;
		//@}

		//------------------------------------------------------------------
		///@name datas used to check the correct usage of the counter
		//@{
		bool	_perfCountStarted;
		bool	_perfCountPaused;
		//@}

	} ;



	//************************************************************
	// Inline functions
	//************************************************************
	//------------------------------------------------------------------
	unsigned int PerfCounter::getNbSamples() const
	{
		return _nbSamplesInUse;
	}

	//------------------------------------------------------------------
	Time PerfCounter::getMeanValue() const
	{
		return _perfMeanValue;
	}

	//------------------------------------------------------------------
	unsigned int PerfCounter::getNbMaxSamples() const
	{
		return _nbSamplesToUse;
	}

	//------------------------------------------------------------------
	bool PerfCounter::isStarted() const
	{
		return _perfCountStarted;
	}

	//------------------------------------------------------------------
	bool PerfCounter::isPaused() const
	{
		return (_perfCountStarted&&_perfCountPaused);
	}

	//------------------------------------------------------------------
	void PerfCounter::setNbMaxSamples(const unsigned int nbSamplesToUse)
	{
		_nbSamplesToUse=nbSamplesToUse;
	}

	//------------------------------------------------------------------
	void PerfCounter::resetMeanValue()
	{
		_nbSamplesInUse=0;
		_perfMeanValue=0;
	}

	//------------------------------------------------------------------
	Time PerfCounter::start()
	{
		GLM_DEBUG_ASSERT(!_perfCountStarted);
		Timer::start();
		_perfCountPaused=false;
		_perfCountStarted=true;
		return 0;
	}

	//------------------------------------------------------------------
	void PerfCounter::pause(bool pause)
	{
		GLM_DEBUG_ASSERT(_perfCountStarted);

		if(_perfCountPaused==pause)	//mode already active
			return;
		_perfCountPaused=pause;

		if(pause) //add value to the counter and get current time
		{
			_cumulativeCount += Timer::stop();
		}
		else //start again to count time
		{
			Timer::start();
		}
	}

	//------------------------------------------------------------------
	Time PerfCounter::stop()
	{
		GLM_DEBUG_ASSERT(_perfCountStarted);

		pause(true);	//accumulate time

		//compute the new mean value:
		//1-make sure the number of samples to use is not already reached			
		if(_nbSamplesInUse >= _nbSamplesToUse)
			_nbSamplesInUse--;

		//2-compute the global cumulative time (mean*nbSambleInUse)
		_cumulativeCountMeanValue *= _nbSamplesInUse;
		//3-now add the current time to the glabal time
		_cumulativeCountMeanValue += _cumulativeCount;
		++_nbSamplesInUse;
		_cumulativeCount=0;
		//4-and compute the new mean value			
		_cumulativeCountMeanValue /= _nbSamplesInUse;
		_perfMeanValue = _cumulativeCountMeanValue;

		_perfCountPaused=false;
		_perfCountStarted=false;

		return _perfMeanValue;
	}
}

