#include "JSAPI2_CallbackManager.h"
#include "JSAPI2_TransportAPI.h"
#include "JSAPI2_AsyncDownloader.h"
#include "JSAPI2_MediaCore.h"
#include "api.h"

JSAPI2::CallbackManager JSAPI2::callbackManager;

JSAPI2::CallbackManager::CallbackManager() : callbackGuard("JSAPI2::CallbackManager::callbackGuard")
{}

void JSAPI2::CallbackManager::Register( JSAPI2::TransportAPI *me )
{
	/* benski> important note:
	even thought JSAPI2::Transport inherits from IUnknown,
	we don't call AddRef here!
	because this would introduce a circular reference.
	JSAPI2::TransportAPI will call Deregister during it's
	destructor.
	*/
	Nullsoft::Utility::AutoLock lock( callbackGuard );
	transports.push_back( new TransportCallback( me ) );
}

void JSAPI2::CallbackManager::Deregister( JSAPI2::TransportAPI *me )
{
	/* benski> important note:
	even thought JSAPI2::Transport inherits from IUnknown,
	we don't call Release here!
	because this would introduce a circular reference.
	JSAPI2::TransportAPI will call Deregister during it's
	destructor.
	*/
	Nullsoft::Utility::AutoLock lock( callbackGuard );
	for ( size_t i = 0; i != transports.size(); i++ )
	{
		TransportCallback *callback = transports[ i ];
		if ( callback->api == me )
		{
			delete callback;
			transports.erase( transports.begin() + i );
			i--;
		}
	}
}


void JSAPI2::CallbackManager::Register( JSAPI2::MediaCoreAPI *me )
{
	Nullsoft::Utility::AutoLock lock( callbackGuard );

	//if (!mediaCores.contains(me))
	if ( mediaCores.end() == std::find( mediaCores.begin(), mediaCores.end(), me ) )
	{
		mediaCores.push_back( me );
	}
}

void JSAPI2::CallbackManager::Deregister( JSAPI2::MediaCoreAPI *me )
{
	Nullsoft::Utility::AutoLock lock( callbackGuard );

	auto it = mediaCores.begin();
	while ( it != mediaCores.end() )
	{
		if ( *it != me )
		{
			it++;
			continue;
		}

		it = mediaCores.erase( it );
	}
}


void JSAPI2::CallbackManager::Register( JSAPI2::AsyncDownloaderAPI *me )
{
	Nullsoft::Utility::AutoLock lock( callbackGuard );
	asyncDownloaders.push_back( new AsyncDownloaderCallback( me ) );
}

void JSAPI2::CallbackManager::Deregister( JSAPI2::AsyncDownloaderAPI *me )
{
	Nullsoft::Utility::AutoLock lock( callbackGuard );
	for ( size_t i = 0; i != asyncDownloaders.size(); i++ )
	{
		AsyncDownloaderCallback *callback = asyncDownloaders[ i ];
		if ( callback->api == me )
		{
			delete callback;
			asyncDownloaders.erase( asyncDownloaders.begin() + i );
			i--;
		}
	}
}

/* --- OnStop --- */
struct OnStopAPCData
{
	JSAPI2::TransportAPI *transport;
	int position;
	int is_full_stop;
};

static void CALLBACK CMGR_OnStopAPC(ULONG_PTR param)
{
	OnStopAPCData *data = (OnStopAPCData *)param;
	data->transport->OnStop(data->position, data->is_full_stop);
	data->transport->Release();
	delete data;
}

void JSAPI2::CallbackManager::OnStop(int position, int is_full_stop)
{
	DWORD threadId = GetCurrentThreadId();
	Nullsoft::Utility::AutoLock lock(callbackGuard);

	for ( TransportCallback *l_transport : transports )
	{
		OnStopAPCData *data = new OnStopAPCData;
		data->transport    = l_transport->api;
		data->position     = position;
		data->is_full_stop = is_full_stop;

		data->transport->AddRef(); // so it doesn't disappear while we're switching threads

		if ( threadId == l_transport->threadId )
		{
			// same thread! huzzah but I wonder how that happened :)
			CMGR_OnStopAPC( (ULONG_PTR)data );
		}
		else
		{
			// different thread, do an APC
			if ( QueueUserAPC( CMGR_OnStopAPC, l_transport->threadHandle, (ULONG_PTR)data ) == 0 )
			{
				data->transport->Release();
				delete data;
			}
		}
	}
}
/* --- --- */

/* --- OnPlay --- */
struct OnPlayAPC
{
	JSAPI2::TransportAPI *transport;
	wchar_t *filename;
};

static void CALLBACK CMGR_OnPlayAPC(ULONG_PTR param)
{
	OnPlayAPC *data = (OnPlayAPC *)param;
	data->transport->OnPlay(data->filename);

	free(data->filename);

	data->transport->Release();
	delete data;
}

void JSAPI2::CallbackManager::OnPlay(const wchar_t *filename)
{
	DWORD threadId = GetCurrentThreadId();
	Nullsoft::Utility::AutoLock lock(callbackGuard);

	for ( TransportCallback *l_transport : transports )
	{
		OnPlayAPC *data = new OnPlayAPC;
		data->transport = l_transport->api;
		data->filename  = _wcsdup(filename);

		data->transport->AddRef(); // so it doesn't disappear while we're switching threads

		if ( threadId == l_transport->threadId )
		{
			// same thread! huzzah but I wonder how that happened :)
			CMGR_OnPlayAPC( (ULONG_PTR)data );
		}
		else
		{
			// different thread, do an APC
			if ( QueueUserAPC( CMGR_OnPlayAPC, l_transport->threadHandle, (ULONG_PTR)data ) == 0 )
			{
				data->transport->Release();
				free( data->filename );
				delete data;
			}
		}
	}
}

/* --- --- */


struct OnPauseAPC
{
	JSAPI2::TransportAPI *transport;
	bool pause_state;
};

static void CALLBACK CMGR_OnPauseAPC(ULONG_PTR param)
{
	OnPauseAPC *data = (OnPauseAPC *)param;
	data->transport->OnPause(data->pause_state);
	data->transport->Release();
	delete data;
}

void JSAPI2::CallbackManager::OnPause(bool pause_state)
{
	DWORD threadId = GetCurrentThreadId();
	Nullsoft::Utility::AutoLock lock( callbackGuard );

	for ( TransportCallback *l_transport : transports )
	{
		OnPauseAPC *data = new OnPauseAPC;
		data->transport   = l_transport->api;
		data->pause_state = pause_state;

		data->transport->AddRef(); // so it doesn't disappear while we're switching threads

		if (threadId == l_transport->threadId)
		{
			// same thread! huzzah but I wonder how that happened :)
			CMGR_OnPauseAPC((ULONG_PTR)data);
		}
		else
		{
			// different thread, do an APC
			if (QueueUserAPC(CMGR_OnPauseAPC, l_transport->threadHandle, (ULONG_PTR)data) == 0)
			{
				data->transport->Release();
				delete data;
			}
		}
	}
}

/* --- --- */
bool JSAPI2::CallbackManager::OverrideMetadata( const wchar_t *filename, const wchar_t *tag, wchar_t *out, size_t outCch )
{
	if ( NULL != filename && NULL != tag && NULL != out )
	{
		Nullsoft::Utility::AutoLock lock( callbackGuard );
		for ( MediaCoreAPI *l_mediaCore : mediaCores )
		{
			if ( l_mediaCore->OverrideMetadata( filename, tag, out, outCch ) )
				return true;
		}
	}

	return false;
}


/* --- OnInit --- */
struct OnInitAPC
{
	JSAPI2::AsyncDownloaderAPI *asyncDownloader;
	wchar_t *url;
};

static void CALLBACK CMGR_OnInitAPC(ULONG_PTR param)
{
	OnInitAPC *data = (OnInitAPC *)param;
	data->asyncDownloader->OnInit(data->url);
	free(data->url);
	data->asyncDownloader->Release();
	delete data;
}

void JSAPI2::CallbackManager::OnInit( const wchar_t *url, const wchar_t *onlinesvcId )
{
	DWORD threadId = GetCurrentThreadId();
	Nullsoft::Utility::AutoLock lock( callbackGuard );

	for ( AsyncDownloaderCallback *l_downloader : asyncDownloaders )
	{
		if ( wcscmp( onlinesvcId, l_downloader->api->GetKey() ) )
			continue;	//only call back to the same online service that issued the download reqeust

		OnInitAPC *data = new OnInitAPC;
		data->asyncDownloader = l_downloader->api;
		data->url             = _wcsdup( url );

		data->asyncDownloader->AddRef(); // so it doesn't disappear while we're switching threads

		if ( threadId == l_downloader->threadId )
		{
			// same thread! huzzah but I wonder how that happened :)
			CMGR_OnInitAPC( (ULONG_PTR)data );
		}
		else
		{
			// different thread, do an APC
			if ( QueueUserAPC( CMGR_OnInitAPC, l_downloader->threadHandle, (ULONG_PTR)data ) == 0 )
			{
				data->asyncDownloader->Release();
				free( data->url );
				delete data;
			}
		}

	}
}


/* --- OnConnect --- */
struct OnConnectAPC
{
	JSAPI2::AsyncDownloaderAPI *asyncDownloader;
	wchar_t *url;
};

static void CALLBACK CMGR_OnConnectAPC(ULONG_PTR param)
{
	OnConnectAPC *data = (OnConnectAPC *)param;
	data->asyncDownloader->OnConnect(data->url);
	free(data->url);
	data->asyncDownloader->Release();
	delete data;
}

void JSAPI2::CallbackManager::OnConnect( const wchar_t *url, const wchar_t *onlinesvcId )
{
	DWORD threadId = GetCurrentThreadId();
	Nullsoft::Utility::AutoLock lock( callbackGuard );

	for ( AsyncDownloaderCallback *l_downloader : asyncDownloaders )
	{
		if ( wcscmp( onlinesvcId, l_downloader->api->GetKey() ) )
			continue;	//only call back to the same online service that issued the download reqeust

		OnConnectAPC *data = new OnConnectAPC;
		data->asyncDownloader = l_downloader->api;
		data->url             = _wcsdup( url );

		data->asyncDownloader->AddRef(); // so it doesn't disappear while we're switching threads

		if ( threadId == l_downloader->threadId )
		{
			// same thread! huzzah but I wonder how that happened :)
			CMGR_OnConnectAPC( (ULONG_PTR)data );
		}
		else
		{
			// different thread, do an APC
			if ( QueueUserAPC( CMGR_OnConnectAPC, l_downloader->threadHandle, (ULONG_PTR)data ) == 0 )
			{
				data->asyncDownloader->Release();
				free( data->url );
				delete data;
			}
		}
	}
}


/* --- OnCancel --- */
struct OnCancelAPC
{
	JSAPI2::AsyncDownloaderAPI *asyncDownloader;
	wchar_t *url;
};

static void CALLBACK CMGR_OnCancelAPC(ULONG_PTR param)
{
	OnCancelAPC *data = (OnCancelAPC *)param;
	data->asyncDownloader->OnCancel(data->url);
	free(data->url);
	data->asyncDownloader->Release();
	delete data;
}

void JSAPI2::CallbackManager::OnCancel( const wchar_t *url, const wchar_t *onlinesvcId )
{
	DWORD threadId = GetCurrentThreadId();
	Nullsoft::Utility::AutoLock lock( callbackGuard );

	for ( AsyncDownloaderCallback *l_downloader : asyncDownloaders )
	{
		if ( wcscmp( onlinesvcId, l_downloader->api->GetKey() ) )
			continue;	//only call back to the same online service that issued the download reqeust

		OnCancelAPC *data = new OnCancelAPC;
		data->asyncDownloader = l_downloader->api;
		data->url             = _wcsdup( url );

		data->asyncDownloader->AddRef(); // so it doesn't disappear while we're switching threads

		if ( threadId == l_downloader->threadId )
		{
			// same thread! huzzah but I wonder how that happened :)
			CMGR_OnCancelAPC( (ULONG_PTR)data );
		}
		else
		{
			// different thread, do an APC
			if ( QueueUserAPC( CMGR_OnCancelAPC, l_downloader->threadHandle, (ULONG_PTR)data ) == 0 )
			{
				data->asyncDownloader->Release();
				free( data->url );
				delete data;
			}
		}
	}
}


/* --- OnData --- */
struct OnDataAPC
{
	JSAPI2::AsyncDownloaderAPI *asyncDownloader;
	wchar_t *url;
	size_t downloadedlen;
	size_t totallen;
};

static void CALLBACK CMGR_OnDataAPC(ULONG_PTR param)
{
	OnDataAPC *data = (OnDataAPC *)param;
	data->asyncDownloader->OnData(data->url, data->downloadedlen, data->totallen);
	free(data->url);
	data->asyncDownloader->Release();
	delete data;
}

void JSAPI2::CallbackManager::OnData(const wchar_t *url, size_t downloadedlen, size_t totallen, const wchar_t *onlinesvcId)
{
	DWORD threadId = GetCurrentThreadId();
	Nullsoft::Utility::AutoLock lock(callbackGuard);

	for ( AsyncDownloaderCallback *downloader : asyncDownloaders )
	{
		if ( wcscmp(onlinesvcId, downloader->api->GetKey()) )
			continue;	//only call back to the same online service that issued the download reqeust

		OnDataAPC *data = new OnDataAPC;
		data->asyncDownloader = downloader->api;
		data->url = _wcsdup(url);
		data->downloadedlen = downloadedlen;
		data->totallen = totallen;
		data->asyncDownloader->AddRef(); // so it doesn't disappear while we're switching threads
		if (threadId == downloader->threadId)
		{
			// same thread! huzzah but I wonder how that happened :)
			CMGR_OnDataAPC((ULONG_PTR)data);
		}
		else
		{
			// different thread, do an APC
			if (QueueUserAPC(CMGR_OnDataAPC, downloader->threadHandle, (ULONG_PTR)data) == 0)
			{
				data->asyncDownloader->Release();
				free(data->url);
				delete data;
			}
		}
	}
}


/* --- OnError --- */
struct OnErrorAPC
{
	JSAPI2::AsyncDownloaderAPI *asyncDownloader;
	wchar_t *url;
	int error;
};

static void CALLBACK CMGR_OnErrorAPC(ULONG_PTR param)
{
	OnErrorAPC *data = (OnErrorAPC *)param;
	data->asyncDownloader->OnError(data->url, data->error);
	free(data->url);
	data->asyncDownloader->Release();
	delete data;
}

void JSAPI2::CallbackManager::OnError(const wchar_t *url, int error, const wchar_t *onlinesvcId)
{
	DWORD threadId = GetCurrentThreadId();
	Nullsoft::Utility::AutoLock lock(callbackGuard);

	for ( AsyncDownloaderCallback *downloader : asyncDownloaders )
	{
		if ( wcscmp(onlinesvcId, downloader->api->GetKey()) )
			continue;	//only call back to the same online service that issued the download reqeust

		OnErrorAPC *data = new OnErrorAPC;
		data->asyncDownloader = downloader->api;
		data->url = _wcsdup(url);
		data->error = error;
		data->asyncDownloader->AddRef(); // so it doesn't disappear while we're switching threads
		if (threadId == downloader->threadId)
		{
			// same thread! huzzah but I wonder how that happened :)
			CMGR_OnErrorAPC((ULONG_PTR)data);
		}
		else
		{
			// different thread, do an APC
			if (QueueUserAPC(CMGR_OnErrorAPC, downloader->threadHandle, (ULONG_PTR)data) == 0)
			{
				data->asyncDownloader->Release();
				free(data->url);
				delete data;
			}
		}
	}
}


/* --- OnFinish --- */
struct OnFinishAPC
{
	JSAPI2::AsyncDownloaderAPI *asyncDownloader;
	wchar_t *url;
	wchar_t *destfilename;
};

static void CALLBACK CMGR_OnFinishAPC(ULONG_PTR param)
{
	OnFinishAPC *data = (OnFinishAPC *)param;
	data->asyncDownloader->OnFinish(data->url, data->destfilename);
	free(data->url);
	free(data->destfilename);
	data->asyncDownloader->Release();
	delete data;
}

void JSAPI2::CallbackManager::OnFinish(const wchar_t *url, const wchar_t *destfilename, const wchar_t *onlinesvcId)
{
	DWORD threadId = GetCurrentThreadId();
	Nullsoft::Utility::AutoLock lock(callbackGuard);

	for ( AsyncDownloaderCallback *downloader : asyncDownloaders )
	{
		if ( wcscmp(onlinesvcId, downloader->api->GetKey()) )
			continue;	//only call back to the same online service that issued the download reqeust

		OnFinishAPC *data = new OnFinishAPC;
		data->asyncDownloader = downloader->api;
		data->url = _wcsdup(url);
		data->destfilename = _wcsdup(destfilename);
		data->asyncDownloader->AddRef(); // so it doesn't disappear while we're switching threads
		if (threadId == downloader->threadId)
		{
			// same thread! huzzah but I wonder how that happened :)
			CMGR_OnFinishAPC((ULONG_PTR)data);
		}
		else
		{
			// different thread, do an APC
			if (QueueUserAPC(CMGR_OnFinishAPC, downloader->threadHandle, (ULONG_PTR)data) == 0)
			{
				data->asyncDownloader->Release();
				free(data->url);
				free(data->destfilename);
				delete data;
			}
		}
	}
}