//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
//=============================================================================//

#include <windows.h>
#include "imysqlwrapper.h"
#include "mysql_async.h"
#include "utllinkedlist.h"


static char* CopyString( const char *pStr )
{
	char *pRet = new char[ strlen( pStr ) + 1 ];
	strcpy( pRet, pStr );
	return pRet;
}


class CMySQLAsync : public IMySQLAsync
{
public:
	
	CMySQLAsync()
	{
		m_hThread = NULL;
		m_pSQL = NULL;

		m_hExitEvent = CreateEvent( NULL, true, false, NULL );	// Use manual reset because we want it to cascade out without
																// resetting the event if it gets set.
		m_hPendingQueryEvent = CreateEvent( NULL, false, false, NULL );
		m_hQueryResultsEvent = CreateEvent( NULL, false, false, NULL );
		
		InitializeCriticalSection( &m_ExecuteQueryCS );
		InitializeCriticalSection( &m_PendingQueryCS );
	}

	~CMySQLAsync()
	{
		Term();
		
		CloseHandle( m_hExitEvent );
		CloseHandle( m_hPendingQueryEvent );
		CloseHandle( m_hQueryResultsEvent );

		DeleteCriticalSection( &m_ExecuteQueryCS );
		DeleteCriticalSection( &m_PendingQueryCS );
	}

	virtual void Release()
	{
		delete this;
	}

	virtual IMySQLRowSet* ExecuteBlocking( const char *pStr )
	{
		IMySQLRowSet *pRet;
		
		EnterCriticalSection( &m_ExecuteQueryCS );
			m_pSQL->Execute( pStr );
			pRet = m_pSQL->DuplicateRowSet();
		LeaveCriticalSection( &m_ExecuteQueryCS );

		return pRet;
	}

	virtual void Execute( const char *pStr, void *pUserData )
	{
		EnterCriticalSection( &m_PendingQueryCS );

			CPendingQuery query;
			query.m_pStr = CopyString( pStr );
			query.m_pUserData = pUserData;
			query.m_Timer.Start();

			m_PendingQueries.AddToTail( query );
			SetEvent( m_hPendingQueryEvent );

		LeaveCriticalSection( &m_PendingQueryCS );
	}
	
	virtual bool GetNextResults( CQueryResults &results )
	{
		results.m_pResults = NULL;

		if ( WaitForSingleObject( m_hQueryResultsEvent, 0 ) == WAIT_OBJECT_0 )
		{
			EnterCriticalSection( &m_PendingQueryCS );
			
				Assert( m_QueryResults.Count() > 0 );
				int iHead = m_QueryResults.Head();
				results = m_QueryResults[iHead];
				m_QueryResults.Remove( iHead );

				if ( m_QueryResults.Count() > 0 )
					SetEvent( m_hQueryResultsEvent );
			
			LeaveCriticalSection( &m_PendingQueryCS );
			return true;
		}
		else
		{
			return false;
		}
	}

	bool Init( IMySQL *pSQL )
	{
		Term();

		DWORD dwThreadID;
		m_hThread = CreateThread( NULL, 0, &CMySQLAsync::StaticThreadFn, this, 0, &dwThreadID );
		if ( m_hThread )
		{										
			m_pSQL = pSQL;
			return true;
		}
		else
		{
			return false;
		}
	}

	void Term()
	{
		// Stop the thread.
		if ( m_hThread )
		{
			// Delete all our queries.
			SetEvent( m_hExitEvent );
			WaitForSingleObject( m_hThread, INFINITE );
			CloseHandle( m_hThread );
			m_hThread = NULL;
		}

		// Delete leftover queries.
		FOR_EACH_LL( m_PendingQueries, iPendingQuery )
		{
			delete [] m_PendingQueries[iPendingQuery].m_pStr;
		}
		m_PendingQueries.Purge();
		
		FOR_EACH_LL( m_QueryResults, i )
		{
			m_QueryResults[i].m_pResults->Release();
		}
		m_QueryResults.Purge();

		if ( m_pSQL )
		{
			m_pSQL->Release();
			m_pSQL = NULL;
		}
	}


private:

	DWORD ThreadFn()
	{
		HANDLE hEvents[2] = { m_hExitEvent, m_hPendingQueryEvent };

		// 
		while ( 1 )
		{
			int ret = WaitForMultipleObjects( ARRAYSIZE( hEvents ), hEvents, false, INFINITE );
			if ( ret == WAIT_OBJECT_0 )
				break;

			if ( ret == WAIT_OBJECT_0+1 )
			{
				// A new string has been queued up for us to execute.
				EnterCriticalSection( &m_PendingQueryCS );
					
					Assert( m_PendingQueries.Count() > 0 );
					int iHead = m_PendingQueries.Head();
					
					CPendingQuery pending = m_PendingQueries[iHead];
					m_PendingQueries.Remove( iHead );

					// Set the pending query event if there are more queries waiting to run.
					if ( m_PendingQueries.Count() > 0 )
						SetEvent( m_hPendingQueryEvent );
					
				LeaveCriticalSection( &m_PendingQueryCS );

							
				// Run the query.
				EnterCriticalSection( &m_ExecuteQueryCS );

					CQueryResults results;
					results.m_pResults = NULL;
					results.m_pUserData = pending.m_pUserData;
					results.m_ExecuteTime.Init();
					pending.m_Timer.End();
					results.m_QueueTime = pending.m_Timer.GetDuration();
					
					CFastTimer executeTimer;
					executeTimer.Start();

					if ( m_pSQL->Execute( pending.m_pStr ) == 0 )
					{			
						executeTimer.End();
						results.m_ExecuteTime = executeTimer.GetDuration();
						results.m_pResults = m_pSQL->DuplicateRowSet();
					}

					delete pending.m_pStr;

				LeaveCriticalSection( &m_ExecuteQueryCS );


				// Store the results.
				EnterCriticalSection( &m_PendingQueryCS );

					m_QueryResults.AddToTail( results );
					SetEvent( m_hQueryResultsEvent );

				LeaveCriticalSection( &m_PendingQueryCS );
			}
		}
		
		return 0;
	}

	static DWORD WINAPI StaticThreadFn( LPVOID lpParameter )
	{
		return ((CMySQLAsync*)lpParameter)->ThreadFn();
	}

private:
	
	HANDLE m_hThread;
	HANDLE m_hExitEvent;
	HANDLE m_hPendingQueryEvent;	// Signaled when a new query is added.
	HANDLE m_hQueryResultsEvent;

	IMySQL *m_pSQL;

	CRITICAL_SECTION m_PendingQueryCS;
	CRITICAL_SECTION m_ExecuteQueryCS;

	
	// Outgoing query results. New ones are added to the tail.
	CUtlLinkedList<CQueryResults, int> m_QueryResults;

	
	// New ones added to the tail.
	class CPendingQuery
	{
	public:
		char *m_pStr;
		void *m_pUserData;
		CFastTimer m_Timer;	// Times how long this query is in the queue.
	};

	CUtlLinkedList<CPendingQuery,int> m_PendingQueries;
};


IMySQLAsync* CreateMySQLAsync( IMySQL *pSQL )
{
	CMySQLAsync *pRet = new CMySQLAsync;
	if ( pRet->Init( pSQL ) )
	{
		return pRet;
	}
	else
	{
		delete pRet;
		return NULL;
	}
}