///////////////////////////////////////////////////////////////////////////////
/// @file deProfile.hpp
///
/// @brief Real-time profiling class and manager
///
/// @author Assassin
///
/// This file is the intellectual property of Novus Delta, LLC.. Usage of the
/// contents of this file is subject to the Destiny3D Member License which
/// can be found at http://www.destiny3d.com.  Any other usage is prohibited.
///
/// This file is distributed "AS IS" without warranty of any kind.  Novus
/// Delta, LLC. does not guarantee the fitness of the contents of this file
/// for any particular purpose.
///
/// Copyright (C) 2001-2003 Novus Delta, LLC. All Rights Reserved.
///
/// <hr>
///                                 Change History
/// <hr>
///
/// @date Sept 2002
/// @author Assassin
/// @remarks Creation
///
/// @date May 2003
/// @author Assassin
/// @remarks Updates for enhanced feedback
///
///////////////////////////////////////////////////////////////////////////////

#ifndef DEPROFILE_HPP
#define DEPROFILE_HPP

#include "deGlobalTypes.hpp"

#if defined(DEUTIL_DLL_EXPORTS) || defined(DESTINY3D_EXPORT_ALL)
#	define DEPROFILE_API extern "C" DEDLL_EXPORT
#elif defined(DESTINY3D_STATIC_LINK)
#	define DEPROFILE_API extern "C"
#else
#	define DEPROFILE_API extern "C" DEDLL_IMPORT
#endif

#ifdef USING_DESTINY3D
#ifdef _DEBUG
#	ifdef DESTINY3D_STATIC_LINK
#		pragma comment(lib, "deUtil_sd")
#	else
#		pragma comment(lib, "deUtild")
#	endif //DESTINY3D_STATIC_LINK
#else
#	ifdef DESTINY3D_STATIC_LINK
#		pragma comment(lib, "deUtil_s")
#	else
#		pragma comment(lib, "deUtil")
#	endif //DESTINY3D_STATIC_LINK
#endif //_DEBUG
#endif //USING_DESTINY3D

static inline void GrabTSC(u64 * Clock)
{
#ifdef __GNUC__
	__asm__ __volatile__ ( \
                "rdtsc\n\t"    \
                : "=A" (Clock));    
#else
	_asm
	{
		RDTSC
		mov  ecx,   Clock
		mov [ecx],  eax
		mov [ecx+4],edx
	}
#endif
}

class deProfile;
class IdeProfileManager;

// factory functions
	DEPROFILE_API IdeProfileManager*	IdeProfile_GetTheManager();
	DEPROFILE_API void					IdeProfile_ShutDown();

//class IdeProfileManager
DE3D_INTERFACE_(IdeProfileManager)
{
public:
	virtual void		StartProfile(deProfile * profile) = 0;
	virtual deBoolean	RecordInfo(deProfile * profile) = 0;
	virtual long		GrabIDNum() = 0;
	virtual long		EstimateFrequency() = 0;
	virtual s64			TimeSpentIn(char * ProfileName, long IDNum = 0) = 0;
	virtual s64			TimeSpentIn_IDByName(char * Name) = 0;
	virtual const char*	EndFrame(deBoolean OutputString) = 0;
	virtual void		ResetStats() = 0;
};

/// Utility class for automatically profiling the time spent in a given variable scope.
/// Will count the number of cycles executed by the CPU between time of creation and destruction.
class deProfile
{
public:
	friend class deProfileManager;
	
	inline deProfile::deProfile(char * ProfileName)
	{
		if (!ProfileName)
			throw "You must specify a name";
		TheManager = IdeProfile_GetTheManager();
		strncpy(m_ProfileInfo.IDName, ProfileName, 127);
		m_ProfileInfo.IDName[127] = '\0';
		m_ProfileInfo.IDNum = 0;
		TheManager->StartProfile(this);
	}
	
	inline deProfile::deProfile(long * ProfileIDNum)
	{
		// first time, set it to 0 and it will grab an ID
		// then it will look up the right entry each time;
		TheManager = IdeProfile_GetTheManager();
		if (*ProfileIDNum)
		{
			m_ProfileInfo.IDNum = *ProfileIDNum;
		}
		else
		{
			m_ProfileInfo.IDNum = TheManager->GrabIDNum();
			*ProfileIDNum = m_ProfileInfo.IDNum;
		}
		m_ProfileInfo.IDName[0] = '\0';
		TheManager->StartProfile(this);
	}
	
	inline deProfile::deProfile(long * ProfileIDNum, char * ProfileName)
	{
		TheManager = IdeProfile_GetTheManager();
		if (*ProfileIDNum)
		{
			m_ProfileInfo.IDNum = *ProfileIDNum;
		}
		else
		{
			m_ProfileInfo.IDNum = TheManager->GrabIDNum();
			*ProfileIDNum = m_ProfileInfo.IDNum;
		}
		
		if (!ProfileName)
			throw;
		strncpy(m_ProfileInfo.IDName, ProfileName, 127);
		m_ProfileInfo.IDName[127] = '\0';
		TheManager->StartProfile(this);
	}
	
	inline deProfile::~deProfile()
	{
		TheManager->RecordInfo(this);
	}

private:
	struct ProfileInfo_t
	{
		u32 IDNum;
		char IDName[128];
	} m_ProfileInfo;
	IdeProfileManager * TheManager;
};


#ifdef DESTINY3D_PLATFORM_WIN32
#	define WIN32_LEAN_AND_MEAN
#	include <windows.h>
#	include <mmsystem.h>
#	pragma comment(lib, "winmm")
// simple class for reading QueryPerformanceCounter times
// problems:
// - multiprocessor systems: may be inconsistent across CPUs;
// setting thread affinity is too much work.
// - if implemented with TSC: same problems as above.
// (we check if TSC/QPC freqs differ by more than 10% -
// can't assume PIT / PMT freq values, because new the new HPET
// timer's frequency is unspecified)
// - Q274323: jumps several seconds under heavy PCI bus load.
// readings are checked against system time and discarded if invalid.
// - "System clock problem can inflate benchmark scores":
// invalid value if not polled every 4.5 seconds? solved
// by calibration thread, which reads timer every second anyway.
class deTimer_QPC
{
	s64 m_StartTime;
	s64 m_StopTime;
	s64 m_Frequency;
public:
	inline deTimer_QPC() : m_StartTime(0), m_StopTime(0), m_Frequency(0) {}
	inline ~deTimer_QPC() {}
	inline void GrabStartTime()
	{
		if (!QueryPerformanceCounter((LARGE_INTEGER*)&m_StartTime))
			throw "QPC Unsupported!";
	}
	inline void GrabStopTime()
	{ 
		if (!QueryPerformanceCounter((LARGE_INTEGER*)&m_StopTime)) 
			throw "QPC Unsupported!"; 
	}
	inline double DiffTime()
	{
		if (m_Frequency == 0)
			QueryPerformanceFrequency((LARGE_INTEGER*)&m_Frequency);
		double retval = (m_StopTime-m_StartTime)/((double)m_Frequency);
		return retval;
	}
	inline double DiffTime(double maxStep)
	{
		double retval = DiffTime();
		if (maxStep > 0 && retval > maxStep)
			retval = maxStep;
		return retval;
	}
};

class deTimer_TGT
{
	u32 m_StartTime;
	u32 m_StopTime;
public:
	inline deTimer_TGT() : m_StartTime(0), m_StopTime(0) {}
	inline ~deTimer_TGT() {}
	inline void GrabStartTime()
	{
		timeBeginPeriod(1);
		m_StartTime = timeGetTime();
		timeEndPeriod(1);
	}
	inline void GrabStopTime()
	{ 
		timeBeginPeriod(1);
		m_StopTime = timeGetTime();
		timeEndPeriod(1);
	}
	inline double DiffTime()
	{
		double retval = (m_StopTime-m_StartTime)/1000.0;
		return retval;
	}
	inline double DiffTime(double maxStep)
	{
		double retval = DiffTime();
		if (maxStep > 0 && retval > maxStep)
			retval = maxStep;
		return retval;
	}
};
#else // DESTINY3D_PLATFORM_WIN32
#	pragma note(Unsupported platform for QPC and TGT timer - deferring to TSC)
#	define DE3D_TIMER_TSC
#endif // DESTINY3D_PLATFORM_WIN32

// very simple class for reading TSC times
// problems:
// - multiprocessor systems: may be inconsistent across CPUs;
// setting thread affinity is too much work.
// - deep sleep modes: TSC may not be advanced.
// - SpeedStep/'gearshift' CPUs: frequency may change.
// this happens on notebooks now, but eventually desktop systems
// will do this as well (if not to save power, for heat reasons).
class deTimer_TSC
{
	u64 m_StartTime;
	u64 m_StopTime;
	double m_CPU_Hz;
public:
	inline deTimer_TSC() : m_StartTime(0), m_StopTime(0), m_CPU_Hz(0.0) {}
	inline ~deTimer_TSC() {}
	inline void GrabStartTime()
	{ GrabTSC(&m_StartTime); }
	inline void GrabStopTime()
	{ GrabTSC(&m_StopTime); }
	inline double DiffTime()
	{
		if (m_CPU_Hz == 0.0)
			m_CPU_Hz = 1000000.0 * IdeProfile_GetTheManager()->EstimateFrequency();
		double retval = (m_StopTime-m_StartTime)/m_CPU_Hz;
		return retval;
	}
	inline double DiffTime(double maxStep)
	{
		double retval = DiffTime();
		if (maxStep > 0 && retval > maxStep)
			retval = maxStep;
		return retval;
	}
};


//#define DE3D_TIMER_TSC
#define DE3D_TIMER_QPC
//#define DE3D_TIMER_TGT

#if defined(DE3D_TIMER_TSC)
	typedef deTimer_TSC deTimer;
#elif defined(DE3D_TIMER_QPC)
	typedef deTimer_QPC deTimer;
#elif defined(DE3D_TIMER_TGT)
	typedef deTimer_TGT deTimer;
#endif // DE3D_TIMER_QPC

#undef DE3D_TIMER_TSC
#undef DE3D_TIMER_QPC
#undef DE3D_TIMER_TGT

#define PROFILE_RELEASE_ALSO

#if defined(_DEBUG) || defined(PROFILE_RELEASE_ALSO)
	#define PROFILE(ProfileID) deProfile Profile_Identifier___(ProfileID)
	#define PROFILE_ID_NAME(ProfileID,Name) deProfile Profile_Identifier___(ProfileID,Name)
#else
	#define PROFILE(ProfileID)
	#define PROFILE_ID_NAME(ProfileID,Name)
#endif


#endif
