#ifndef FASTTIMER_H
#define FASTTIMER_H
#ifdef _WIN32
#pragma once
#endif

#ifdef _WIN32
#include <intrin.h>
#endif

#include <assert.h>
#include "platform.h"

PLATFORM_INTERFACE uint64 g_ClockSpeed;
#if defined( _X360 ) && defined( _CERT )
PLATFORM_INTERFACE unsigned long g_dwFakeFastCounter;
#endif

PLATFORM_INTERFACE double g_ClockSpeedMicrosecondsMultiplier;
PLATFORM_INTERFACE double g_ClockSpeedMillisecondsMultiplier;
PLATFORM_INTERFACE double g_ClockSpeedSecondsMultiplier;

class CCycleCount
{
	friend class CFastTimer;

public:
	CCycleCount();
	CCycleCount(uint64 cycles);

	void			Sample();	                  

	void			Init();		   
	void			Init(float initTimeMsec);
	void			Init(double initTimeMsec) { Init((float)initTimeMsec); }
	void			Init(uint64 cycles);
	bool			IsLessThan(CCycleCount const& other) const;					   

	unsigned long	GetCycles()  const;
	uint64			GetLongCycles() const;

	unsigned long	GetMicroseconds() const;
	uint64			GetUlMicroseconds() const;
	double			GetMicrosecondsF() const;
	void			SetMicroseconds(unsigned long nMicroseconds);

	unsigned long	GetMilliseconds() const;
	double			GetMillisecondsF() const;

	double			GetSeconds() const;

	CCycleCount& operator+=(CCycleCount const& other);

	static void		Add(CCycleCount const& rSrc1, CCycleCount const& rSrc2, CCycleCount& dest);	    

	static void		Sub(CCycleCount const& rSrc1, CCycleCount const& rSrc2, CCycleCount& dest);	    

	static uint64	GetTimestamp();

	uint64			m_Int64;
};

class PLATFORM_CLASS CClockSpeedInit
{
public:
	CClockSpeedInit()
	{
		Init();
	}

	static void Init();
};

class CFastTimer
{
public:
	void				Start();
	void				End();

	const CCycleCount& GetDuration() const;	         
	CCycleCount 		GetDurationInProgress() const;       

	static inline int64	GetClockSpeed();

private:
	CCycleCount	m_Duration;
#ifdef DEBUG_FASTTIMER
	bool m_bRunning;		    
#endif
};


class CTimeScope
{
public:
	CTimeScope(CFastTimer* pTimer);
	~CTimeScope();

private:
	CFastTimer* m_pTimer;
};

inline CTimeScope::CTimeScope(CFastTimer* pTotal)
{
	m_pTimer = pTotal;
	m_pTimer->Start();
}

inline CTimeScope::~CTimeScope()
{
	m_pTimer->End();
}

class CTimeAdder
{
public:
	CTimeAdder(CCycleCount* pTotal);
	~CTimeAdder();

	void		End();

private:
	CCycleCount* m_pTotal;
	CFastTimer	m_Timer;
};

inline CTimeAdder::CTimeAdder(CCycleCount* pTotal)
{
	m_pTotal = pTotal;
	m_Timer.Start();
}

inline CTimeAdder::~CTimeAdder()
{
	End();
}

inline void CTimeAdder::End()
{
	if (m_pTotal)
	{
		m_Timer.End();
		*m_pTotal += m_Timer.GetDuration();
		m_pTotal = 0;
	}
}



#define PROFILE_SCOPE(name) \
	class C##name##ACC : public CAverageCycleCounter \
	{ \
	public: \
		~C##name##ACC() \
		{ \
			Msg("%-48s: %6.3f avg (%8.1f total, %7.3f peak, %5d iters)\n",  \
				#name, \
				GetAverageMilliseconds(), \
				GetTotalMilliseconds(), \
				GetPeakMilliseconds(), \
				GetIters() ); \
		} \
	}; \
	static C##name##ACC name##_ACC; \
	CAverageTimeMarker name##_ATM( &name##_ACC )

#define TIME_SCOPE(name) \
	class CTimeScopeMsg_##name \
	{ \
	public: \
		CTimeScopeMsg_##name() { m_Timer.Start(); } \
		~CTimeScopeMsg_##name() \
		{ \
			m_Timer.End(); \
			Msg( #name "time: %.4fms\n", m_Timer.GetDuration().GetMillisecondsF() ); \
		} \
	private:	\
		CFastTimer	m_Timer; \
	} name##_TSM;


class CAverageCycleCounter
{
public:
	CAverageCycleCounter();

	void Init();
	void MarkIter(const CCycleCount& duration);

	unsigned GetIters() const;

	double GetAverageMilliseconds() const;
	double GetTotalMilliseconds() const;
	double GetPeakMilliseconds() const;

private:
	unsigned	m_nIters;
	CCycleCount m_Total;
	CCycleCount	m_Peak;
};

class CAverageTimeMarker
{
public:
	CAverageTimeMarker(CAverageCycleCounter* pCounter);
	~CAverageTimeMarker();

private:
	CAverageCycleCounter* m_pCounter;
	CFastTimer	m_Timer;
};


inline CCycleCount::CCycleCount()
{
	Init((uint64)0);
}

inline CCycleCount::CCycleCount(uint64 cycles)
{
	Init(cycles);
}

inline void CCycleCount::Init()
{
	Init((uint64)0);
}

inline void CCycleCount::Init(float initTimeMsec)
{
	if (g_ClockSpeedMillisecondsMultiplier > 0)
		Init((uint64)(initTimeMsec / g_ClockSpeedMillisecondsMultiplier));
	else
		Init((uint64)0);
}

inline void CCycleCount::Init(uint64 cycles)
{
	m_Int64 = cycles;
}

inline void CCycleCount::Sample()
{
	m_Int64 = Plat_Rdtsc();
}

inline CCycleCount& CCycleCount::operator+=(CCycleCount const& other)
{
	m_Int64 += other.m_Int64;
	return *this;
}


inline void CCycleCount::Add(CCycleCount const& rSrc1, CCycleCount const& rSrc2, CCycleCount& dest)
{
	dest.m_Int64 = rSrc1.m_Int64 + rSrc2.m_Int64;
}

inline void CCycleCount::Sub(CCycleCount const& rSrc1, CCycleCount const& rSrc2, CCycleCount& dest)
{
	dest.m_Int64 = rSrc1.m_Int64 - rSrc2.m_Int64;
}

inline uint64 CCycleCount::GetTimestamp()
{
	CCycleCount c;
	c.Sample();
	return c.GetLongCycles();
}

inline bool CCycleCount::IsLessThan(CCycleCount const& other) const
{
	return m_Int64 < other.m_Int64;
}


inline unsigned long CCycleCount::GetCycles() const
{
	return (unsigned long)m_Int64;
}

inline uint64 CCycleCount::GetLongCycles() const
{
	return m_Int64;
}

inline unsigned long CCycleCount::GetMicroseconds() const
{
	return (unsigned long)((m_Int64 * 1000000) / g_ClockSpeed);
}

inline uint64 CCycleCount::GetUlMicroseconds() const
{
	return ((m_Int64 * 1000000) / g_ClockSpeed);
}


inline double CCycleCount::GetMicrosecondsF() const
{
	return (double)(m_Int64 * g_ClockSpeedMicrosecondsMultiplier);
}


inline void	CCycleCount::SetMicroseconds(unsigned long nMicroseconds)
{
	m_Int64 = ((uint64)nMicroseconds * g_ClockSpeed) / 1000000;
}


inline unsigned long CCycleCount::GetMilliseconds() const
{
	return (unsigned long)((m_Int64 * 1000) / g_ClockSpeed);
}


inline double CCycleCount::GetMillisecondsF() const
{
	return (double)(m_Int64 * g_ClockSpeedMillisecondsMultiplier);
}


inline double CCycleCount::GetSeconds() const
{
	return (double)(m_Int64 * g_ClockSpeedSecondsMultiplier);
}


inline void CFastTimer::Start()
{
	m_Duration.Sample();
#ifdef DEBUG_FASTTIMER
	m_bRunning = true;
#endif
}


inline void CFastTimer::End()
{
	CCycleCount cnt;
	cnt.Sample();
	if (IsX360())
	{
		if ((uint64)cnt.m_Int64 <= (uint64)m_Duration.m_Int64)
		{
			cnt.m_Int64 += 0x100000000LL;
		}
	}

	m_Duration.m_Int64 = cnt.m_Int64 - m_Duration.m_Int64;

#ifdef DEBUG_FASTTIMER
	m_bRunning = false;
#endif
}

inline CCycleCount CFastTimer::GetDurationInProgress() const
{
	CCycleCount cnt;
	cnt.Sample();
	if (IsX360())
	{
		if ((uint64)cnt.m_Int64 <= (uint64)m_Duration.m_Int64)
		{
			cnt.m_Int64 += 0x100000000LL;
		}
	}

	CCycleCount result;
	result.m_Int64 = cnt.m_Int64 - m_Duration.m_Int64;

	return result;
}


inline int64 CFastTimer::GetClockSpeed()
{
	return g_ClockSpeed;
}


inline CCycleCount const& CFastTimer::GetDuration() const
{
#ifdef DEBUG_FASTTIMER
	assert(!m_bRunning);
#endif
	return m_Duration;
}


inline CAverageCycleCounter::CAverageCycleCounter()
	: m_nIters(0)
{
}

inline void CAverageCycleCounter::Init()
{
	m_Total.Init();
	m_Peak.Init();
	m_nIters = 0;
}

inline void CAverageCycleCounter::MarkIter(const CCycleCount& duration)
{
	++m_nIters;
	m_Total += duration;
	if (m_Peak.IsLessThan(duration))
		m_Peak = duration;
}

inline unsigned CAverageCycleCounter::GetIters() const
{
	return m_nIters;
}

inline double CAverageCycleCounter::GetAverageMilliseconds() const
{
	if (m_nIters)
		return (m_Total.GetMillisecondsF() / (double)m_nIters);
	else
		return 0;
}

inline double CAverageCycleCounter::GetTotalMilliseconds() const
{
	return m_Total.GetMillisecondsF();
}

inline double CAverageCycleCounter::GetPeakMilliseconds() const
{
	return m_Peak.GetMillisecondsF();
}

inline CAverageTimeMarker::CAverageTimeMarker(CAverageCycleCounter* pCounter)
{
	m_pCounter = pCounter;
	m_Timer.Start();
}

inline CAverageTimeMarker::~CAverageTimeMarker()
{
	m_Timer.End();
	m_pCounter->MarkIter(m_Timer.GetDuration());
}


class CLimitTimer
{
public:
	CLimitTimer() {}
	CLimitTimer(uint64 cMicroSecDuration) { SetLimit(cMicroSecDuration); }
	void SetLimit(uint64 m_cMicroSecDuration);
	bool BLimitReached() const;

	int CMicroSecOverage() const;
	uint64 CMicroSecLeft() const;

private:
	uint64 m_lCycleLimit;
};


inline void CLimitTimer::SetLimit(uint64 cMicroSecDuration)
{
	uint64 dlCycles = ((uint64)cMicroSecDuration * g_ClockSpeed) / (uint64)1000000L;
	CCycleCount cycleCount;
	cycleCount.Sample();
	m_lCycleLimit = cycleCount.GetLongCycles() + dlCycles;
}


inline bool CLimitTimer::BLimitReached() const
{
	CCycleCount cycleCount;
	cycleCount.Sample();
	return (cycleCount.GetLongCycles() >= m_lCycleLimit);
}


inline int CLimitTimer::CMicroSecOverage() const
{
	CCycleCount cycleCount;
	cycleCount.Sample();
	uint64 lcCycles = cycleCount.GetLongCycles();

	if (lcCycles < m_lCycleLimit)
		return 0;

	return((int)((lcCycles - m_lCycleLimit) * (uint64)1000000L / g_ClockSpeed));
}


inline uint64 CLimitTimer::CMicroSecLeft() const
{
	CCycleCount cycleCount;
	cycleCount.Sample();
	uint64 lcCycles = cycleCount.GetLongCycles();

	if (lcCycles >= m_lCycleLimit)
		return 0;

	return((uint64)((m_lCycleLimit - lcCycles) * (uint64)1000000L / g_ClockSpeed));
}


#endif  