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

#pragma once

#include "glmCoreAPI.h"
#include <glmAtomic.h>
#include <glmThreading.h>
#include <glmString.h>
#include <glmTypes.h>
#include <glmMutex.h>
#include <glmArray.h>
#include <stdlib.h>

namespace glm
{
	class PerfAnalysisManager;
	class PerfAnalysisSocketConnectionThread;

	//************************************************************
	// \brief Singleton to use to access the perfomance analysis manager
	// \author Golaem <dev@golaem.com>
	//************************************************************
	GLM_CORE_API PerfAnalysisManager& getPerfAnalysisManagerSingleton();

	//************************************************************
	// \class PerfAnalysisManager
	// \brief Receives events and dispatch them to UDB sockets on a different thread
	// \author Golaem <dev@golaem.com>
	//************************************************************
	class GLM_CORE_API PerfAnalysisManager
	{
		friend class PerfAnalysisSocketConnectionThread;

	public:	//structures
		struct EventType
		{
			enum Value
			{
				PERF,
				MEMORY_USAGE,
				FRAMEDATA_HANDLING,
				END
			};
		};

		struct EventUnit
		{
			enum Value
			{
				SECOND,
				MILLISECOND,
				MICROSECOND,
				NANOSECOND,
				BYTE,
				KILOBYTE,
				MEGABYTE,
				COUNTER,
				END				//will be used when the even type does not use the value
			};
		};

		struct Event
		{
			Event(EventType::Value type, const char* name, Time timeID, uint32_t integerID, double value, EventUnit::Value unit, short flag)
				: _type(type)
				, _threadID(getCurrentThreadId()._id)
				, _name(name)
				, _timeID(timeID)
				, _integerID(integerID)
				, _value(value)
				, _unit(unit)
				, _flag(flag)
			{
				//get current time
#if GLM_IS_PLATFORM_WINDOWS
				unsigned __int64 performanceFrequency;
				QueryPerformanceFrequency((LARGE_INTEGER*)&performanceFrequency);
				unsigned __int64 performanceCounter;
				QueryPerformanceCounter((LARGE_INTEGER*)&performanceCounter);
				long nanosecondsDivider = 1000000000;
				if (performanceFrequency > nanosecondsDivider)
					_date = performanceCounter / (performanceFrequency / nanosecondsDivider);
				else
					_date = performanceCounter * (nanosecondsDivider / performanceFrequency);
#else
				struct timespec clockTime;
				clock_gettime(CLOCK_REALTIME, &clockTime);

				// tv_sec and tv_nsec are long int, and unsigned long long precision is HUGE enough : 19 digits
				// even with a 10^9 shift (nano sec precision), this gives 10^10 seconds wide timer, 316 years
				_date = clockTime.tv_sec * 1000000000L + clockTime.tv_nsec;
#endif
			}
			void toJsonString(std::stringstream& outJsonString) const;

			//base
			EventType::Value _type;
			uint64_t _threadID;
			unsigned long long _date;

			//identification
			GlmString _name;			//If perf issues are detected with the string allocation, this could be changed in a char array with cosnt length. No problem detected on first tests
			Time _timeID;
			uint32_t _integerID;

			//data
			double _value;
			EventUnit::Value _unit;
			short _flag;
		};

	public:	//methods
		PerfAnalysisManager();
		virtual ~PerfAnalysisManager();

		void init();
		void finish();

		void setServer(const char* serverIpAdress, unsigned short serverPort);
		inline void setWakeIntervals(uint32_t wakeIntervals) { _wakeIntervals = wakeIntervals; }

		void addEvent(const Event& newEvent);
		void clearEvents();

		GLM_CLASS_STAT_ID( glm::MemoryStatIds::Editors)

	protected:	//methods

		void dispatchEvents();

		bool isLoadingThreadActive() const;
		void setLoadingThreadActive(bool loadingThreadActive);
		void socketCommunicationThreadMain();

	protected:	//data
		GlmString _serverIpAdress;
		unsigned short _serverPort;
		uint32_t _wakeIntervals;

		threaddef_t _loadingThread;
		atomic32_t _loadingThreadActive;

		glm::SpinLock _eventsBufferLock;
		Array<Event> _eventsBufferA;
		Array<Event> _eventsBufferB;
		Array<Event>* _eventsBufferWrite;	//events will be pushed on this buffer
		Array<Event>* _eventsBufferRead;		//events will be dispatched from this buffer

		int _serverConnection;

		bool _inited;
		glm::SpinLock _initedLock;
	};

	//************************************************************
	// \class PerfAnalysisSocketConnectionThread
	// \brief Just handle the start routine for the thread that handle the UDP connection
	// \author Golaem <dev@golaem.com>
	//************************************************************
	class PerfAnalysisSocketConnectionThread
	{
	public:
		static void startRoutine(void* threadArgument);
	};

	//************************************************************
	// \class ScopedPerfAnalysis
	// \brief Create perf events on creation and destruction, automatically compute the time stamp and thread ID for both events
	// \author Golaem <dev@golaem.com>
	//************************************************************
	class GLM_CORE_API ScopedPerfAnalysis
	{
	public:
		ScopedPerfAnalysis(const char* name, Time timeID = UINT32_MAX, uint32_t integerID = UINT32_MAX);
		~ScopedPerfAnalysis();

		void start();
		void end();

	private:
		GLM_DISALLOW_COPY_AND_ASSIGN(ScopedPerfAnalysis);

		void createAndAddPerfEvent();

		GlmString _name;
		Time _timeID;
		uint32_t _integerID;

		short _state;	//0: not started, 1: started, 2: ended
	};


}
