//====== Copyright (c), Valve Corporation, All rights reserved. =======
//
// Purpose: Provides a scheduled function manager that will bucket events into
//		time chunks and execute them as time elapses
//
//=============================================================================

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

namespace GCSDK
{

//interface for events that can be scheduled to run on the GC base
class IGCScheduledFunction
{
public:
	IGCScheduledFunction() : m_nAbsScheduleBucket( knInvalidBucket )	{}
	virtual ~IGCScheduledFunction();
	//called in response to our event time elapsing
	virtual void OnEvent() = 0;

	bool BIsScheduled()	const											{ return m_nAbsScheduleBucket != IGCScheduledFunction::knInvalidBucket; }

private:
	//the absolute bucket that we were scheduled in (or invalid). Used to enable deregistering
	friend class CScheduledFunctionMgr;
	uint32		m_nAbsScheduleBucket;
	uint32		m_nLLIndex;
	static const uint32 knInvalidBucket = ( uint32 )-1;
};

//utility for scheduling a global function
class CGlobalScheduledFunction :
	public IGCScheduledFunction
{
public:

	CGlobalScheduledFunction();
	
	typedef void ( *func_t )();

	void ScheduleMS( func_t pfn, uint32 nDelayMS );
	void ScheduleSecond( func_t pfn, uint32 nDelaySecond );
	void ScheduleMinute( func_t pfn, uint32 nDelayMinute );
	void Cancel();

	virtual void OnEvent() OVERRIDE;

private:
	func_t	m_pfn;
};

//the same as the above, but automatically deletes the object once the event is fired
class CGlobalScheduledFunctionAutoDelete :
	public CGlobalScheduledFunction
{
public:
	CGlobalScheduledFunctionAutoDelete()									{}

	virtual void OnEvent() OVERRIDE
	{
		CGlobalScheduledFunction::OnEvent();
		delete this;
	}
};

//utility for scheduling a member function
template< class T >
class CScheduledFunction :
	public IGCScheduledFunction
{
public:
	CScheduledFunction() : m_pObj( NULL ), m_pfn( NULL ) {}

	typedef void ( T::*func_t )();

	void ScheduleMS( T* pObj, func_t pfn, uint32 nDelayMS )
	{
		m_pObj = pObj;
		m_pfn = pfn;
		GScheduledFunctionMgr().ScheduleMS( this, nDelayMS );
	}

	void ScheduleSecond( T* pObj, func_t pfn, uint32 nDelaySecond )
	{
		m_pObj = pObj;
		m_pfn = pfn;
		GScheduledFunctionMgr().ScheduleSecond( this, nDelaySecond );
	}

	void ScheduleMinute( T* pObj, func_t pfn, uint32 nDelayMinute )
	{
		m_pObj = pObj;
		m_pfn = pfn;
		GScheduledFunctionMgr().ScheduleMinute( this, nDelayMinute );
	}


	void Cancel()
	{
		GScheduledFunctionMgr().Cancel( this );
	}

	virtual void OnEvent() OVERRIDE
	{
		( m_pObj->*m_pfn )();
	}

private:
	T*		m_pObj;
	func_t	m_pfn;
};

//similar to the above, but auto deletes once the event is fired
template< class T >
class CScheduledFunctionAutoDelete :
	public CScheduledFunction< T >
{
public:
	typedef void ( T::*func_t )();

	CScheduledFunctionAutoDelete()											{}
	CScheduledFunctionAutoDelete( T* pObj, func_t pfn, uint32 nDelayMS )	{ Schedule( pObj, pfn, nDelayMS ); }
	virtual void OnEvent() OVERRIDE
	{
		CScheduledFunction< T >::OnEvent();
		delete this;
	}
};


class CScheduledFunctionMgr
{
public:

	CScheduledFunctionMgr();

	//called to initialize the starting time for the scheduled function manager. It doesn't need to be globally absolute, just relative to the time values provided
	//to the run function
	void InitStartingTime();

	//called to register a scheduled event for a certain period in the future, with the delay specified in milliseconds. This has resolution of an individual frame, and
	//will unregister from any previously registered time slot
	void ScheduleMS( IGCScheduledFunction* pEvent, uint32 nMSDelay );
	//similar to the above, but instead of having frame resolution, this has second level resolution, and should be used for low granularity tasks
	void ScheduleSecond( IGCScheduledFunction* pEvent, uint32 nSDelay );
	//similar to the above but has minute level resolution which should be used for very low resolution tasks
	void ScheduleMinute( IGCScheduledFunction* pEvent, uint32 nMinuteDelay );

	//deregisters a previously registered event
	void Cancel( IGCScheduledFunction* pEvent );

	//called to run registered functions
	void RunFunctions();
	
private:
	
	//called internally by the other schedule functions to schedule the event at the specified resolution in our resolution array
	void InternalSchedule( uint32 nResolution, IGCScheduledFunction* pEvent, uint32 nMSDelay );

	//the list type that we store all of the entries in. We use a single list to avoid the overhead of so many lists 
	typedef CUtlLinkedList< IGCScheduledFunction*, uint32 >	TScheduleList;

	//all information tied to a specific resolution, including the time hash buckets, number of buckets, and which buckets it has executed
	class CScheduleBucket
	{
	public:
		CScheduleBucket();
		~CScheduleBucket();
		void Init( TScheduleList& MasterList, uint32 nNumBuckets, uint32 nMicroSPerBucket );		

		//maps a micro second time to a bucket time
		uint32 GetAbsScheduleBucketIndex( uint64 nMicroSTime ) const			{ return ( uint32 )( nMicroSTime / m_nMicroSPerBucket ); }

		//called to run registered functions
		void RunFunctions( TScheduleList& MasterList, uint64 nMicroSTime );

		//for each bucket, we insert a node into the master linked list, then our list runs from this node to the next empty node (or end) in the list
		uint32*		m_pBuckets;
		//the number of buckets that we have
		uint32		m_nNumBuckets;
		//how many micro seconds each bucket represents
		uint32		m_nMicroSPerBucket;
		//the last bucket that we had executed
		uint32		m_nAbsLastScheduleBucket;
	};

	//the list of all of our entries. We store bucket starts within here, and then insert the events in between them
	TScheduleList	m_ScheduleList;

	//the list of resolutions that we have
	CScheduleBucket	m_Resolutions[ 3 ];
};

//global singleton access
CScheduledFunctionMgr& GScheduledFunctionMgr();


} //namespace GCSDK

#endif