//====== Copyright ©, Valve Corporation, All rights reserved. =======
// GCSqlWriteQueue.h
//
// A utility class that allows for templating based upon a SQL schema type, and then
// queuing up a number of those records to be written to SQL. This will buffer them until
// either a certain number have been queued or a period of time has elapsed, at which point
// it will flush them to SQL in a single transaction.
//
//===================================================================
#ifndef GCSQLWRITEQUEUE_H
#define GCSQLWRITEQUEUE_H

#include "scheduledfunction.h"

namespace GCSDK
{

class CGCBase;

template < typename TSqlClass >
class CGCSQLWriteQueue
{
public:

	CGCSQLWriteQueue( uint32 nMaxToCache, uint32 nMaxMSToWrite ) :
		m_nMaxToCache( nMaxToCache ),
		m_nMaxMSToWrite( nMaxMSToWrite )
	{
		m_QueuedRecords.EnsureCapacity( nMaxToCache );
	}
	
	//called to queue the record for writing, which will occur either when the maximum time between writebacks has occurred, or
	//when the cache has filled up
	void QueueRecord( const TSqlClass& sch )
	{
		m_QueuedRecords.AddToTail( sch );

		//now handle either dispatching, or scheduling a timeout dispatch
		if( ( uint32 )m_QueuedRecords.Count() >= m_nMaxToCache )
		{
			//unschedule first. This way if while we are yielded, we add another entry, it can schedule a new timeout
			m_TimeCommit.Cancel();
			CreateJobToCommitSQL();
		}
		else if( !m_TimeCommit.BIsScheduled() )
		{
			m_TimeCommit.Schedule( this, &CGCSQLWriteQueue< TSqlClass >::CreateJobToCommitSQL, m_nMaxMSToWrite );			
		}
	}

	//a yielding version of the above function
	void YieldingQueueRecord( const TSqlClass& sch )
	{
		m_QueuedRecords.AddToTail( sch );

		//now handle either dispatching, or scheduling a timeout dispatch
		if( ( uint32 )m_QueuedRecords.Count() >= m_nMaxToCache )
		{
			//unschedule first. This way if while we are yielded, we add another entry, it can schedule a new timeout
			m_TimeCommit.Cancel();
			YieldingFlushQueuedViewsToSQL();
		}
		else if( !m_TimeCommit.BIsScheduled() )
		{
			m_TimeCommit.Schedule( this, &CGCSQLWriteQueue< TSqlClass >::CreateJobToCommitSQL, m_nMaxMSToWrite );			
		}
	}
	
private:

	//called internally when we kick off a job after N amount of time has expired
	template < typename TSqlClass >
	class CTimeExpiredCommitJob : public CGCJob
	{
	public:
		CTimeExpiredCommitJob( CGCBase* pGC, CGCSQLWriteQueue< TSqlClass >* pClass ) : CGCJob( pGC ), m_pClass( pClass )	{}
		virtual bool BYieldingRunJob( void* pvStartParm )
		{
			m_pClass->YieldingFlushQueuedViewsToSQL();
			return true;
		}
	private:
		CGCSQLWriteQueue< TSqlClass >*	m_pClass;
	};

	//the function called when time expires to start a job and commit the requests to SQL
	void CreateJobToCommitSQL()
	{
		//kick off our job, which just calls the flush
		CGCJob* pJob = new CTimeExpiredCommitJob< TSqlClass >( GGCBase(), this );
		pJob->StartJobDelayed( NULL );
	}

	//handles committing the list of queued views to SQL
	void YieldingFlushQueuedViewsToSQL()
	{
		if( m_QueuedRecords.Count() == 0 )
			return;

		//move the contents into a local vector so we don't have any conflicts of global state
		CUtlVector< TSqlClass > localQueue;
		localQueue.Swap( m_QueuedRecords );
		//prepare the queue for the next batch (so we don't have intermediate resizes)
		m_QueuedRecords.EnsureCapacity( m_nMaxToCache );

		// start a transaction for all this work
		CSQLAccess sqlAccess;
		sqlAccess.BBeginTransaction( "CGCWatchDownloadedReplayJob::FlushQueuedViewsToSQL" );

		FOR_EACH_VEC( localQueue, nCurrView )
		{
			sqlAccess.BYieldingInsertRecord( &localQueue[ nCurrView ] );
		}

		sqlAccess.BCommitTransaction();
	}

	//the records that we have queued
	CUtlVector< TSqlClass >									m_QueuedRecords;
	//schedules a write back to ensure we commit at least every N seconds
	CScheduledFunction< CGCSQLWriteQueue< TSqlClass > >		m_TimeCommit;
	//maximum number of seconds between commits
	uint32													m_nMaxMSToWrite;
	//maximum number of records to buffer before writing back
	uint32													m_nMaxToCache;
};

} //namespace GCSDK

#endif