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

#pragma once

#include "glmCoreAPI.h"
#include "glmProgressEvent.h"
#include "glmEventSource.h"
#include "glmIProgressListener.h"
#include "glmSmartPointer.h"
#include "glmLog.h"
#include "glmTimer.h"
#include "glmEvent.h"
#include "glmString.h"
#include <sstream>

namespace glm
{
	// Utility class handling the progress of a process.
	class GLM_CORE_API Progress : public EventSource<IProgressListener,ProgressEvent>, public ReferenceCounter
	{
	public:
		typedef SmartPointer<Progress> SP;

	public:

		GLM_CLASS_STAT_ID( glm::MemoryStatIds::GolaemShared)

		Progress(const char* processName);
		~Progress();

		void reset();

		const char* getProcessName() const;
		void setProcessName(const char* name);

		/**
		* \brief Add a child to the current progress.
		* This child will represent the progress of one of the monitored process' subprocess.
		* \param childProgress The child.
		* \param childTodo The amount of work represented by the child.
		* \pre The given \p child must be not NULL and can't have a parent.
		*/
		void addChild(Progress::SP child, unsigned int childTodo);

		/**
		* \name OwnTodo
		* \brief The amount of work to be done in the monitored process.
		* This amount of work excludes the amount of work to be done in the monitored process' subprocesses.
		*/
		//@{
		unsigned int getOwnTodo();
		void setOwnTodo(unsigned int todo);
		//@}

		/**
		* \name OwnDone
		* \brief The amount of work done in the monitored process.
		* This amount of work excludes the amount of work done in the monitored process' subprocesses.
		*/
		//@{
		unsigned int getOwnDone();
		void setOwnDone(unsigned int done);
		void operator++();
		void operator--();
		void operator++(int);
		void operator--(int);
		void operator+=(int ownDoneIncrement);
		void operator-=(int ownDoneDecrement);
		//@}

		/**
		* \brief Retrieve the amount of work to be done in the monitored process and its subprocesses.
		* This value is the sum of the monitored process \c ownTodo and the subprocesses given \c childTodo.
		*/
		unsigned int getTodo() const;

		/**
		* \brief Retrieve the amount of work done in the monitored process and its subprocesses.
		* This value is the sum of the monitored process \c ownDone and the amount of work done 
		* in the subprocesses on their \c childTodo.
		*/
		unsigned int getDone() const;

		/**
		* \brief Retrieve the current progress as a float value between 0 and 1.
		*/
		float getProgress() const;

		/**
		* \brief Retrieve the current progress as a percentage.
		*/
		unsigned int getProgressPercentage() const;

		/**
		* \brief Notify the start of the monitored process.
		* \pre The monitored process can't be \ref isRunning() "running".
		* \see \ref GLM_PROGRESS_START and \ref GLM_PROGRESS_START_MESSAGE
		*/
		void start();

		/**
		* \brief Notify the success of the monitored process.
		* \pre The monitored process must be \ref isRunning() "running".
		* \see \ref GLM_PROGRESS_SUCCESS and \ref GLM_PROGRESS_SUCCESS_MESSAGE
		*/
		void success();

		/**
		* \brief Notify the success of the monitored process.
		* \pre The monitored process must be \ref isRunning() "running".
		* \see \ref GLM_PROGRESS_FAILURE
		*/
		void failure(const char* reason);

		/**
		* \brief Notifiy a messagefrom the monitored process.
		* \pre The monitored process must be \ref isRunning() "running".
		* \see \ref GLM_PROGRESS_MESSAGE
		*/
		void message(const char* message);

		/**
		* \brief Tells if the monitored process is currently running.
		* This flag is true once the process is \ref start() "started", it 
		* returns to false when the process \ref success() "succeeds" or 
		* \ref failure(const glm::GlmString & reason) "fails".
		*/
		bool isRunning() const;

		/**
		* \brief Retrieved the elapsed running time
		* If the monitored process is running this method returns the elapsed time (is seconds) since the \ref success() "succeeds" notification. 
		*
		* If the monitored process is not running, this method returns the duration of
		* the last run.
		*/
		Time getElapsedRunningTime() const;

		/**
		* \brief Retrieve the last published event.
		*/
		const ProgressEvent& getLastEvent() const;

	private:
		Progress() {}
		void updateDone();
		void notifyProgressChange();

		Progress* _parent;
		glm::Array< std::pair<unsigned int,Progress::SP> > _children;
		glm::GlmString _processName;
		unsigned int _ownDone;
		unsigned int _ownTodo;
		unsigned int _childrenTodo;
		Timer _timer;
		unsigned int _done;
		ProgressEvent* _lastEvent;
	};

	// Progress macro notifying the start of a process.
#define GLM_PROGRESS_START(PROGRESS) \
	GLM_SDK_TRACE_DEBUG(PROGRESS->getProcessName() << " starts."); \
	PROGRESS->start(); \

	// Progress macro notifying the start of a process with a message.
#define GLM_PROGRESS_START_MESSAGE(PROGRESS, THEMESSAGE) \
	GLM_SDK_TRACE_INFO(PROGRESS->getProcessName() << " starts - " << THEMESSAGE << "."); \
	PROGRESS->start(); \
	{ \
	std::ostringstream oss; \
	oss << THEMESSAGE; \
	PROGRESS->message(oss.str().c_str()); \
	} \

	// Progress macro notifying the success of a process.
#define GLM_PROGRESS_SUCCESS(PROGRESS) \
	GLM_SDK_TRACE_INFO(PROGRESS->getProcessName() << " ends in success after " << PROGRESS->getElapsedRunningTime() << "s."); \
	PROGRESS->success(); \

	// Progress macro notifying the success of a process with a message.
#define GLM_PROGRESS_SUCCESS_MESSAGE(PROGRESS, THEMESSAGE) \
	GLM_SDK_TRACE_INFO(PROGRESS->getProcessName() << " ends in success after " << PROGRESS->getElapsedRunningTime() << "s - " << THEMESSAGE << "."); \
	{ \
	std::ostringstream oss; \
	oss << THEMESSAGE; \
	PROGRESS->message(oss.str().c_str()); \
	} \
	PROGRESS->success(); \

	// Progress macro notifying the failure of a process.
#define GLM_PROGRESS_FAILURE(PROGRESS, REASON)\
	GLM_SDK_TRACE_ERROR(PROGRESS->getProcessName() << " ends in failure after " << PROGRESS->getElapsedRunningTime() << "s (" << PROGRESS->getProgressPercentage() << "% done) - " << REASON); \
	{ \
	std::ostringstream oss; \
	oss << REASON; \
	PROGRESS->failure(oss.str().c_str()); \
	} \

	// Progress macro notifying a process' message.
#define GLM_PROGRESS_MESSAGE(PROGRESS, THEMESSAGE) \
	GLM_SDK_TRACE_INFO(PROGRESS->getProcessName() << " [" << PROGRESS->getElapsedRunningTime() << "s/" << PROGRESS->getProgressPercentage() << "%] - " << THEMESSAGE); \
	{ \
	std::ostringstream oss; \
	oss << THEMESSAGE; \
	PROGRESS->message(oss.str().c_str()); \
	} \

	//*****************************************************************************
	// Progress inline definitions
	//*****************************************************************************

	//-----------------------------------------------------------------------------
	inline const char* Progress::getProcessName() const
	{
		return _processName.c_str();
	}

	//-----------------------------------------------------------------------------
	inline void Progress::setProcessName(const char* name)
	{
		_processName = name;
	}

	//-----------------------------------------------------------------------------
	inline unsigned int Progress::getOwnTodo()
	{
		return _ownTodo;
	}

	//-----------------------------------------------------------------------------
	inline void Progress::setOwnTodo(unsigned int todo)
	{
		_ownTodo = todo;
		notifyProgressChange();
	}

	//-----------------------------------------------------------------------------
	inline unsigned int Progress::getOwnDone()
	{
		return _ownDone;
	}

	//-----------------------------------------------------------------------------
	inline void Progress::setOwnDone(unsigned int done)
	{
		_ownDone = done;
		notifyProgressChange();
	}

	//-----------------------------------------------------------------------------
	inline Time Progress::getElapsedRunningTime() const
	{
		return _timer.getTime(); 
	}

	//-----------------------------------------------------------------------------
	inline const ProgressEvent& Progress::getLastEvent() const
	{
		return *_lastEvent;
	}
}

