//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================

#define WIN32_LEAN_AND_MEAN
#include <windows.h>

#include <assert.h>

#pragma optimize( "", off )

#pragma pack( push, thing )
#pragma pack( 4 )
static long g_cw, g_single_cw, g_highchop_cw, g_full_cw, g_ceil_cw, g_pushed_cw;
static struct
{
	long dummy[8];
} g_fpenv;
#pragma pack( pop, thing )

	
__declspec ( naked ) MaskExceptions()
{
	_asm
	{
		fnstenv ds:dword ptr[g_fpenv]
		or ds:dword ptr[g_fpenv],03Fh
		fldenv ds:dword ptr[g_fpenv]
		ret
	}
}

__declspec ( naked ) Sys_SetFPCW()
{
	_asm
	{
		fnstcw ds:word ptr[g_cw]
		mov eax,ds:dword ptr[g_cw]
		and ah,0F0h
		or ah,003h
		mov ds:dword ptr[g_full_cw],eax
		mov ds:dword ptr[g_highchop_cw],eax
		and ah,0F0h
		or ah,00Ch
		mov ds:dword ptr[g_single_cw],eax
		and ah,0F0h
		or ah,008h
		mov ds:dword ptr[g_ceil_cw],eax
		ret
	}
}

__declspec ( naked ) Sys_PushFPCW_SetHigh()
{
	_asm
	{
		fnstcw ds:word ptr[g_pushed_cw]
		fldcw ds:word ptr[g_full_cw]
		ret
	}
}

__declspec ( naked ) Sys_PopFPCW()
{
	_asm
	{
		fldcw ds:word ptr[g_pushed_cw]
		ret
	}
}

#pragma optimize( "", on )

//-----------------------------------------------------------------------------
// Purpose: Implements high precision clock
// TODO:  Make into an interface?
//-----------------------------------------------------------------------------
class CSysClock
{
public:
	// Construction
							CSysClock( void );

	// Initialization
	void					Init( void );
	void					SetStartTime( void );

	// Sample the clock
	double					GetTime( void );

private:
	// High performance clock frequency
	double					m_dClockFrequency;
	// Current accumulated time
	double					m_dCurrentTime;
	// How many bits to shift raw 64 bit sample count by
	int						m_nTimeSampleShift;
	// Previous 32 bit sample count
	unsigned int			m_uiPreviousTime;

	bool					m_bInitialized;
};

static CSysClock g_Clock;

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CSysClock::CSysClock( void )
{
	m_bInitialized = false;
}

//-----------------------------------------------------------------------------
// Purpose: Initialize the clock
//-----------------------------------------------------------------------------
void CSysClock::Init( void )
{
	BOOL success;
	LARGE_INTEGER	PerformanceFreq;
	unsigned int	lowpart, highpart;

	MaskExceptions ();
	Sys_SetFPCW ();

	// Start clock at zero
	m_dCurrentTime			= 0.0;

	success = QueryPerformanceFrequency( &PerformanceFreq );
	assert( success );

	// get 32 out of the 64 time bits such that we have around
	// 1 microsecond resolution
	lowpart		= (unsigned int)PerformanceFreq.LowPart;
	highpart	= (unsigned int)PerformanceFreq.HighPart;
	
	m_nTimeSampleShift	= 0;

	while ( highpart || ( lowpart > 2000000.0 ) )
	{
		m_nTimeSampleShift++;
		lowpart >>= 1;
		lowpart |= (highpart & 1) << 31;
		highpart >>= 1;
	}
	
	m_dClockFrequency = 1.0 / (double)lowpart;

	// Get initial sample
	unsigned int		temp;
	LARGE_INTEGER		PerformanceCount;
	QueryPerformanceCounter( &PerformanceCount );
	if ( !m_nTimeSampleShift )
	{
		temp = (unsigned int)PerformanceCount.LowPart;
	}
	else
	{
		// Rotate counter to right by m_nTimeSampleShift places
		temp = ((unsigned int)PerformanceCount.LowPart >> m_nTimeSampleShift) |
			   ((unsigned int)PerformanceCount.HighPart << (32 - m_nTimeSampleShift));
	}

	// Set first time stamp
	m_uiPreviousTime = temp;

	m_bInitialized = true;

	SetStartTime();
}

void CSysClock::SetStartTime( void )
{
	GetTime();

	m_dCurrentTime = 0.0;

	m_uiPreviousTime = ( unsigned int )m_dCurrentTime;
}

double CSysClock::GetTime( void )
{
	LARGE_INTEGER		PerformanceCount;
	unsigned int		temp, t2;
	double				time;
	
	if ( !m_bInitialized )
	{
		return 0.0;
	}

	Sys_PushFPCW_SetHigh();

	// Get sample counter
	QueryPerformanceCounter( &PerformanceCount );

	if ( !m_nTimeSampleShift )
	{
		temp = (unsigned int)PerformanceCount.LowPart;
	}
	else
	{
		// Rotate counter to right by m_nTimeSampleShift places
		temp = ((unsigned int)PerformanceCount.LowPart >> m_nTimeSampleShift) |
			   ((unsigned int)PerformanceCount.HighPart << (32 - m_nTimeSampleShift));
	}

	// check for turnover or backward time
	if ( ( temp <= m_uiPreviousTime ) && 
		( ( m_uiPreviousTime - temp ) < 0x10000000) )
	{
		m_uiPreviousTime = temp;	// so we can't get stuck
	}
	else
	{
		// gap in performance clocks
		t2 = temp - m_uiPreviousTime;

		// Convert to time using frequencey of clock
		time = (double)t2 * m_dClockFrequency;

		// Remember old time
		m_uiPreviousTime = temp;

		// Increment clock
		m_dCurrentTime += time;
	}

	Sys_PopFPCW();

	// Convert to float
    return m_dCurrentTime;

}

//-----------------------------------------------------------------------------
// Purpose: Sample the high-precision clock
// Output : double
//-----------------------------------------------------------------------------
double Sys_FloatTime( void )
{
	return g_Clock.GetTime();
}

//-----------------------------------------------------------------------------
// Purpose: Initialize high-precision clock
//-----------------------------------------------------------------------------
void Sys_InitFloatTime( void )
{
	g_Clock.Init();
}
