#include <string.h>
#include <strsafe.h>

#include <iostream>
#include <cstdio>

#include <QtCore/qglobal.h>

#include <QWebEngineProfile>
#include <QtWebEngineWidgets/QtWebEngineWidgets>
#include <QWebEnginePage>
#include <QWebEngineSettings>

#include "api__wac_downloadManager.h"

#include "wac_downloadManager.h"
#include "wac_download_http_receiver_api.h"

#include "..\wac_network\wac_network_http_receiver_api.h"

#include "api/service/waservicefactory.h"

#include "../nu/threadname.h"
#include "../nu/AutoChar.h"
#include "../nu/threadpool/timerhandle.hpp"

#include "..\WAT\WAT.h"

#include "..\Winamp\buildType.h"

static const GUID internetConfigGroupGUID =
{
	0xc0a565dc, 0xcfe, 0x405a, { 0xa2, 0x7c, 0x46, 0x8b, 0xc, 0x8a, 0x3a, 0x5c }
};

#define DOWNLOAD_TIMEOUT_MS    60000 // 60 second timeout
#define DOWNLOAD_SLEEP_MS         50
#define DOWNLOAD_BUFFER_SIZE 1310720 // gives a maximum download rate of 25 mb/sec per file


/**********************************************************************************
 **********************************************************************************/


/**********************************************************************************
 *                                    PUBLIC                                      *
 **********************************************************************************/
wa::Components::WAC_DownloadData::WAC_DownloadData( api_wac_download_manager_http_receiver *p_http, const char *p_url, int p_flags, ifc_downloadManagerCallback *p_callback )
{
	_http          = p_http;

	strcpy_s( this->_url, 1024, p_url );

	_flags         = p_flags;
	_callback      = p_callback;

	if ( _callback )
		_callback->AddRef();

	_hFile         = INVALID_HANDLE_VALUE;
	_filepath[ 0 ] = 0;
	_fileext       = 0;

	int download_method = ( api_downloadManager::DOWNLOADEX_MASK_DOWNLOADMETHOD & _flags );
	switch ( download_method )
	{
		case api_downloadManager::DOWNLOADEX_TEMPFILE:
		{
			wchar_t temppath[ MAX_PATH - 14 ] = { 0 }; // MAX_PATH-14 'cause MSDN said so
			GetTempPathW( MAX_PATH - 14, temppath );
			GetTempFileNameW( temppath, L"wdl", 0, _filepath );
			_hFile = CreateFileW( _filepath, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, CREATE_ALWAYS, 0, 0 );
		}
		break;
		case api_downloadManager::DOWNLOADEX_CALLBACK:
			if ( _callback )
				_callback->GetLocation( _filepath, MAX_PATH );
			break;
	}

	

	_source[ 0 ] = 0;
	_title[ 0 ]  = 0;

	if ( _flags & api_downloadManager::DOWNLOADEX_CALLBACK )
	{
		if ( _callback )
		{
			_callback->GetSource( _source, 1024 );
			_callback->GetTitle( _title, 1024 );
		}
	}

	_connectionStart = _lastDownloadTick = GetTickCount();
	_last_status     = HTTP_RECEIVER_STATUS_ERROR;
	_pending         = ( _flags & api_downloadManager::DOWNLOADEX_PENDING ) > 0;
}

wa::Components::WAC_DownloadData::~WAC_DownloadData()
{
	ServiceRelease( _http, httpreceiverGUID2 );

	_http = NULL;

	if ( _fileext )
		delete _fileext;

	int download_method = ( api_downloadManager::DOWNLOADEX_MASK_DOWNLOADMETHOD & _flags );
	if ( download_method == api_downloadManager::DOWNLOADEX_TEMPFILE && _filepath[ 0 ] )
		DeleteFileW( _filepath );

	if ( _callback )
		_callback->Release();

	_callback = NULL;
}


void wa::Components::WAC_DownloadData::Retain()
{
	this->_refCount.fetch_add( 1 );
}

void wa::Components::WAC_DownloadData::Release()
{
	if ( this->_refCount.fetch_sub( 1 ) == 0 )
		delete this;
}


void wa::Components::WAC_DownloadData::Close( ifc_downloadManagerCallback **callbackCopy )
{
	if ( _hFile != INVALID_HANDLE_VALUE )
		CloseHandle( _hFile );

	_hFile = INVALID_HANDLE_VALUE;

	if ( callbackCopy != NULL )
	{
		*callbackCopy = _callback;
		if ( _callback != NULL )
			_callback->AddRef();
	}
	else if ( _callback != NULL )
	{
		_callback->Release();
		_callback = NULL;
	}

	// don't want to close http here, because someone might still want to get the headers out of it
}

bool wa::Components::WAC_DownloadData::getExtention()
{
	if ( _fileext && *_fileext )
		return _fileext;

	char l_header_name_content_type[] = "Content-Type";

	char *l_content_type = _http->getheader( l_header_name_content_type );
	if ( l_content_type && *l_content_type )
	{
		if ( _CONTENT_TYPES_EXTENSIONS.count( l_content_type ) == 1 )
			_fileext = _strdup( _CONTENT_TYPES_EXTENSIONS.find( l_content_type )->second.c_str() );
	}

	return _fileext;
}




/**********************************************************************************
 *                                    PRIVATE                                     *
 **********************************************************************************/



/**********************************************************************************
 **********************************************************************************/

/**********************************************************************************
 *                                    PUBLIC                                      *
 **********************************************************************************/
wa::Components::WAC_DownloadManager::WAC_DownloadManager( QObject *parent )	: QNetworkAccessManager( parent )
{
	this->setObjectName( "DownloadManagerService" );

	this->init();
}

wa::Components::WAC_DownloadManager::~WAC_DownloadManager()
{
	disconnect( _connection_authentication_required );
}


DownloadToken wa::Components::WAC_DownloadManager::Download( const char *p_url, ifc_downloadManagerCallback *p_callback )
{
	return DownloadEx( p_url, p_callback, api_downloadManager::DOWNLOADEX_TEMPFILE );
}

DownloadToken wa::Components::WAC_DownloadManager::DownloadEx( const char *p_url, ifc_downloadManagerCallback *p_callback, int p_flags )
{



	return DownloadToken();
}





/**********************************************************************************
 *                                    PRIVATE                                     *
 **********************************************************************************/
void wa::Components::WAC_DownloadManager::init()
{
	QString l_winamp_user_agent = QString( "%1 Winamp/%2" ).arg( QWebEngineProfile::defaultProfile()->httpUserAgent(), STR_WINAMP_PRODUCTVER ).replace( ",", "." );

	QWebEngineProfile::defaultProfile()->setHttpUserAgent( l_winamp_user_agent );


	_connection_authentication_required = connect( this, &QNetworkAccessManager::authenticationRequired,   this, &wa::Components::WAC_DownloadManager::on_s_authentication_required );


}


QNetworkReply *wa::Components::WAC_DownloadManager::createRequest( Operation p_operation, const QNetworkRequest &p_request, QIODevice *p_outgoing_data )
{
	return QNetworkAccessManager::createRequest( p_operation, p_request, p_outgoing_data );
}



/**********************************************************************************
 *                                 PRIVATE SLOTS                                  *
 **********************************************************************************/
void wa::Components::WAC_DownloadManager::on_s_authentication_required( QNetworkReply *p_reply, QAuthenticator *p_authenticator )
{
	Q_UNUSED( p_reply );
	Q_UNUSED( p_authenticator );
}





/**********************************************************************************
 **********************************************************************************/
DownloadData::DownloadData( api_httpreceiver *p_http, const char *p_url, int p_flags, ifc_downloadManagerCallback *p_callback )
{
	flags    = p_flags;
	http     = p_http;
	callback = p_callback;

	if ( callback )
		callback->AddRef();

	hFile         = INVALID_HANDLE_VALUE;
	filepath[ 0 ] = 0;
	fileext       = 0;

	int download_method = ( api_downloadManager::DOWNLOADEX_MASK_DOWNLOADMETHOD & flags );
	switch ( download_method )
	{
		case api_downloadManager::DOWNLOADEX_TEMPFILE:
		{
			wchar_t temppath[ MAX_PATH - 14 ] = { 0 }; // MAX_PATH-14 'cause MSDN said so
			GetTempPathW( MAX_PATH - 14, temppath );
			GetTempFileNameW( temppath, L"wdl", 0, filepath );
			hFile = CreateFileW( filepath, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, CREATE_ALWAYS, 0, 0 );
		}
		break;
		case api_downloadManager::DOWNLOADEX_CALLBACK:
			if ( callback )
				callback->GetLocation( filepath, MAX_PATH );
			break;
	}

	strcpy_s( this->url, 1024, p_url );
	source[ 0 ] = 0;
	title[ 0 ] = 0;
	if ( flags & api_downloadManager::DOWNLOADEX_CALLBACK )
	{
		if ( callback )
		{
			callback->GetSource( source, 1024 );
			callback->GetTitle( title, 1024 );
		}
	}

	connectionStart = lastDownloadTick = GetTickCount();
	last_status     = HTTPRECEIVER_STATUS_ERROR;
	pending         = ( flags & api_downloadManager::DOWNLOADEX_PENDING ) > 0;
}

DownloadData::~DownloadData()
{
	ServiceRelease( http, httpreceiverGUID );

	http = NULL;

	if ( fileext )
		delete fileext;

	int download_method = ( api_downloadManager::DOWNLOADEX_MASK_DOWNLOADMETHOD & flags );
	if ( download_method == api_downloadManager::DOWNLOADEX_TEMPFILE && filepath[ 0 ] )
		DeleteFileW( filepath );

	if ( callback )
		callback->Release();

	callback = NULL;
}


void DownloadData::Retain()
{
	this->_refCount.fetch_add( 1 );
}

void DownloadData::Release()
{
	if ( this->_refCount.fetch_sub( 1 ) == 0 )
		delete this;
}


void DownloadData::Close( ifc_downloadManagerCallback **callbackCopy )
{
	if ( hFile != INVALID_HANDLE_VALUE )
		CloseHandle( hFile );

	hFile = INVALID_HANDLE_VALUE;

	if ( callbackCopy != NULL )
	{
		*callbackCopy = callback;
		if ( callback != NULL )
			callback->AddRef();
	}
	else if ( callback != NULL )
	{
		callback->Release();
		callback = NULL;
	}

	// don't want to close http here, because someone might still want to get the headers out of it
}

bool DownloadData::getExtention()
{
	if ( fileext && *fileext )
		return fileext;

	char l_header_name_content_type[] = "Content-Type";
	char *l_content_type = http->getheader( l_header_name_content_type );
	if ( l_content_type && *l_content_type )
	{
		if ( _CONTENT_TYPES_EXTENSIONS.count( l_content_type ) == 1 )
			fileext = _strdup( _CONTENT_TYPES_EXTENSIONS.find( l_content_type )->second.c_str() );
	}

	return fileext;
}



/**********************************************************************************
 **********************************************************************************/

 /**********************************************************************************
 *                                    PUBLIC                                      *
 **********************************************************************************/
DownloadManager::DownloadManager()
{
	download_thread = NULL;
	killswitch      = CreateEvent( NULL, TRUE, FALSE, NULL );

	InitializeCriticalSection( &downloadsCS );
}


void DownloadManager::Kill()
{
	SetEvent( killswitch );
	if ( download_thread )
	{
		WaitForSingleObject( download_thread, 3000 );
		CloseHandle( download_thread );
	}

	DeleteCriticalSection( &downloadsCS );
	CloseHandle( killswitch );
}


static void SetUserAgent( api_httpreceiver *p_http )
{
	char agent[ 256 ] = { 0 };
	StringCchPrintfA( agent, 256, "User-Agent: %S/%S", WASABI_API_APP->main_getAppName(), WASABI_API_APP->main_getVersionNumString() );
	p_http->addheader( agent );

	//QString l_winamp_user_agent = QString( "User-Agent: %1 Winamp/%2" ).arg( QWebEngineProfile::defaultProfile()->httpUserAgent(), STR_WINAMP_PRODUCTVER ).replace( ",", "." );

	//http->addheader( l_winamp_user_agent.toStdString().c_str() );
}


DownloadToken DownloadManager::Download( const char *url, ifc_downloadManagerCallback *callback )
{
	return DownloadEx( url, callback, api_downloadManager::DOWNLOADEX_TEMPFILE );
}

DownloadToken DownloadManager::DownloadEx( const char *url, ifc_downloadManagerCallback *callback, int flags )
{
	if ( InitDownloadThread() )
	{
		api_httpreceiver *http = NULL;

		ServiceBuild( http, httpreceiverGUID );

		if ( http )
		{
			DownloadData *downloadData = new DownloadData( http, url, flags, callback );

			int  use_proxy = 1;
			bool proxy80   = AGAVE_API_CONFIG->GetBool( internetConfigGroupGUID, L"proxy80", false );

			if ( proxy80 && strstr( url, ":" ) && ( !strstr( url, ":80/" ) && strstr( url, ":80" ) != ( url + strlen( url ) - 3 ) ) )
				use_proxy = 0;

			const wchar_t *proxy = use_proxy ? AGAVE_API_CONFIG->GetString( internetConfigGroupGUID, L"proxy", 0 ) : 0;

			http->open( API_DNS_AUTODNS, DOWNLOAD_BUFFER_SIZE, ( proxy && proxy[ 0 ] ) ? (const char *)AutoChar( proxy ) : NULL );

			SetUserAgent( http );

			if ( callback )
				callback->OnInit( downloadData );

			if ( downloadData->flags & api_downloadManager::DOWNLOADEX_UI )
			{
				for ( ifc_downloadManagerCallback *l_status : status_callbacks )
					l_status->OnInit( downloadData );
			}

			//only call http->connect when it is not pending download request
			if ( !( flags & DOWNLOADEX_PENDING ) )
				http->connect( url, 1 );

			//http->run(); // let's get this party started
			EnterCriticalSection( &downloadsCS );
			downloads.push_back( downloadData );
			LeaveCriticalSection( &downloadsCS );

			return downloadData;
		}
	}

	return 0;
}


void DownloadManager::ResumePendingDownload( DownloadToken p_token )
{
	if ( !p_token )
		return;

	DownloadData *data = (DownloadData *)p_token;
	if ( data->pending )
	{
		data->pending = false;
		data->connectionStart = data->lastDownloadTick = GetTickCount();

		data->http->connect( data->url );
	}
}

void DownloadManager::CancelDownload( DownloadToken p_token )
{
	if ( !p_token )
		return;

	DownloadData *data = (DownloadData *)p_token;
	EnterCriticalSection( &downloadsCS );

	if ( downloads.end() != std::find( downloads.begin(), downloads.end(), data ) )
	{
		ifc_downloadManagerCallback *callback;
		data->Close( &callback );

		//downloads.eraseObject(p_data);
		auto it = std::find( downloads.begin(), downloads.end(), data );
		if ( it != downloads.end() )
		{
			downloads.erase( it );
		}

		LeaveCriticalSection( &downloadsCS );

		if ( callback )
		{
			callback->OnCancel( p_token );
			if ( data->flags & api_downloadManager::DOWNLOADEX_UI )
			{
				for ( ifc_downloadManagerCallback *l_status : status_callbacks )
					l_status->OnCancel( p_token );
			}

			callback->Release();
		}

		data->Release();
	}
	else
		LeaveCriticalSection( &downloadsCS );
}

void DownloadManager::RetainDownload( DownloadToken p_token )
{
	if ( !p_token )
		return;

	DownloadData *data = (DownloadData *)p_token;
	if ( data )
		data->Retain();
}

void DownloadManager::ReleaseDownload( DownloadToken p_token )
{
	if ( !p_token )
		return;

	DownloadData *data = (DownloadData *)p_token;
	if ( data )
		data->Release();
}


void DownloadManager::RegisterStatusCallback( ifc_downloadManagerCallback *callback )
{
	EnterCriticalSection( &downloadsCS );
	status_callbacks.push_back( callback );
	LeaveCriticalSection( &downloadsCS );
}

void DownloadManager::UnregisterStatusCallback( ifc_downloadManagerCallback *callback )
{
	EnterCriticalSection( &downloadsCS );

	auto it = std::find( status_callbacks.begin(), status_callbacks.end(), callback );
	if ( it != status_callbacks.end() )
		status_callbacks.erase( it );

	LeaveCriticalSection( &downloadsCS );
}


bool DownloadManager::IsPending( DownloadToken token )
{
	DownloadData *data = (DownloadData *)token;
	if ( data )
		return data->pending;
	else
		return FALSE;
}


/**********************************************************************************
 *                                    PRIVATE                                     *
 **********************************************************************************/
bool DownloadManager::DownloadThreadTick()
{
	unsigned int i = 0;
	char *downloadBuffer = (char *)malloc( DOWNLOAD_BUFFER_SIZE );

	while ( WaitForSingleObject( killswitch, 0 ) != WAIT_OBJECT_0 )
	{
		EnterCriticalSection( &downloadsCS );
		if ( downloads.empty() )
		{
			// TODO: might be nice to dynamically increase the sleep time if this happens
			// (maybe to INFINITE and have Download() wake us?)
			LeaveCriticalSection( &downloadsCS );

			free( downloadBuffer );

			return true;
		}

		if ( i >= downloads.size() )
		{
			LeaveCriticalSection( &downloadsCS );
			free( downloadBuffer );

			return true;
		}

		DownloadData *thisDownload = downloads[ i ];
		if ( thisDownload->pending )
		{
			LeaveCriticalSection( &downloadsCS );
		}
		else
		{
			thisDownload->Retain();
			LeaveCriticalSection( &downloadsCS );


			INT tick = Tick( thisDownload, downloadBuffer, DOWNLOAD_BUFFER_SIZE );
			switch ( tick )
			{
				case TICK_NODATA:
					// do nothing
					break;

				case TICK_CONNECTING:
					break;

				case TICK_CONNECTED:
					if ( thisDownload->callback )
						thisDownload->callback->OnConnect( thisDownload );
					if ( thisDownload->flags & api_downloadManager::DOWNLOADEX_UI )
					{
						for ( ifc_downloadManagerCallback *l_status : status_callbacks )
							l_status->OnConnect( thisDownload );
					}
					break;

				case TICK_SUCCESS:
					if ( thisDownload->callback )
						thisDownload->callback->OnTick( thisDownload );
					if ( thisDownload->flags & api_downloadManager::DOWNLOADEX_UI )
					{
						for ( ifc_downloadManagerCallback *l_status : status_callbacks )
							l_status->OnTick( thisDownload );
					}

					// TODO: send out update l_callback
					break;

				case TICK_FINISHED:
				case TICK_FAILURE:
				case TICK_TIMEOUT:
				case TICK_CANT_CONNECT:
				case TICK_WRITE_ERROR:
					FinishDownload( thisDownload, tick );
					break;
			}
			thisDownload->Release();
		}

		i++;
	}

	free( downloadBuffer );

	return false; // we only get here when killswitch is set
}

int DownloadManager::DownloadTickThreadPoolFunc( HANDLE handle, void *user_data, intptr_t id )
{
	DownloadManager *dlmgr = (DownloadManager *)user_data;
	if ( dlmgr->DownloadThreadTick() )
	{
		TimerHandle t( handle );
		t.Wait( DOWNLOAD_SLEEP_MS );
	}
	else
	{
		WASABI_API_THREADPOOL->RemoveHandle( 0, handle );
		SetEvent( dlmgr->download_thread );
	}

	return 0;
}

bool DownloadManager::InitDownloadThread()
{
	if ( download_thread == NULL )
	{
		download_thread = CreateEvent( 0, FALSE, FALSE, 0 );
		TimerHandle t;
		WASABI_API_THREADPOOL->AddHandle( 0, t, DownloadTickThreadPoolFunc, this, 0, api_threadpool::FLAG_LONG_EXECUTION );
		t.Wait( DOWNLOAD_SLEEP_MS );
	}

	return ( download_thread != NULL );
}

void DownloadManager::FinishDownload( DownloadData *p_data, int code )
{
	if ( p_data == NULL )
		return;

	ifc_downloadManagerCallback *l_callback = NULL;

	EnterCriticalSection( &downloadsCS );
	p_data->Close( &l_callback );
	LeaveCriticalSection( &downloadsCS );

	if ( l_callback != NULL )
	{
		if ( code == TICK_FINISHED )
		{
			l_callback->OnFinish( p_data );
			if ( p_data->flags & api_downloadManager::DOWNLOADEX_UI )
			{
				for ( ifc_downloadManagerCallback *l_data : status_callbacks )
					l_data->OnFinish( p_data );
			}
		}
		else
		{
			l_callback->OnError( p_data, code );
			if ( p_data->flags & api_downloadManager::DOWNLOADEX_UI )
			{
				for ( ifc_downloadManagerCallback *l_data : status_callbacks )
					l_data->OnError( p_data, code );
			}
		}

		l_callback->Release();
	}

	EnterCriticalSection( &downloadsCS );

	auto it = std::find( downloads.begin(), downloads.end(), p_data );
	if ( it != downloads.end() )
		downloads.erase( it );

	LeaveCriticalSection( &downloadsCS );

	p_data->Release();
}

int DownloadManager::Tick( DownloadData *thisDownload, void *buffer, int bufferSize )
{
	if ( !thisDownload )
		return api_downloadManager::TICK_FAILURE;

	int state = thisDownload->http->run();
	if ( state == HTTPRECEIVER_RUN_ERROR || thisDownload == NULL )
		return api_downloadManager::TICK_FAILURE;

	if ( !thisDownload->fileext )
		thisDownload->getExtention();


	int downloaded = thisDownload->http->get_bytes( buffer, bufferSize );
	if ( downloaded )
	{
		switch ( thisDownload->flags & DOWNLOADEX_MASK_DOWNLOADMETHOD )
		{
			case api_downloadManager::DOWNLOADEX_BUFFER:
			{
				thisDownload->buffer.reserve( thisDownload->http->content_length() );
				thisDownload->buffer.add( buffer, downloaded );
			}
			break;
			case api_downloadManager::DOWNLOADEX_TEMPFILE:
			{
				DWORD written = 0;
				WriteFile( thisDownload->hFile, buffer, downloaded, &written, NULL );
				if ( written != downloaded )
					return api_downloadManager::TICK_WRITE_ERROR;
			}
			break;
			case api_downloadManager::DOWNLOADEX_CALLBACK:
			{
				if ( thisDownload->flags & api_downloadManager::DOWNLOADEX_UI )
				{
					for ( ifc_downloadManagerCallback *l_status : status_callbacks )
						l_status->OnData( thisDownload, buffer, downloaded );
				}

				if ( thisDownload->callback )
					thisDownload->callback->OnData( thisDownload, buffer, downloaded );
			}
		}

		thisDownload->lastDownloadTick = GetTickCount();
		thisDownload->bytesDownloaded += downloaded;

		return api_downloadManager::TICK_SUCCESS;
	}
	else // nothing in the buffer
	{
		if ( state == HTTPRECEIVER_RUN_CONNECTION_CLOSED ) // see if the connection is closed
		{
			return api_downloadManager::TICK_FINISHED; // yay we're done
		}

		if ( GetTickCount() - thisDownload->lastDownloadTick > DOWNLOAD_TIMEOUT_MS ) // check for timeout
			return api_downloadManager::TICK_TIMEOUT;

		switch ( thisDownload->http->get_status() )
		{
			case HTTPRECEIVER_STATUS_CONNECTING:
				if ( thisDownload->last_status != HTTPRECEIVER_STATUS_CONNECTING )
				{
					thisDownload->last_status = HTTPRECEIVER_STATUS_CONNECTING;
					return api_downloadManager::TICK_CONNECTING;
				}
				else
				{
					return api_downloadManager::TICK_NODATA;
				}
			case HTTPRECEIVER_STATUS_READING_HEADERS:
				if ( thisDownload->last_status != HTTPRECEIVER_STATUS_READING_HEADERS )
				{
					thisDownload->last_status = HTTPRECEIVER_STATUS_READING_HEADERS;
					return api_downloadManager::TICK_CONNECTED;
				}
				else
				{
					return api_downloadManager::TICK_NODATA;
				}
		}

		if ( !thisDownload->replyCode )
			thisDownload->replyCode = thisDownload->http->getreplycode();

		switch ( thisDownload->replyCode )
		{
			case 0:
			case 100:
			case 200:
			case 201:
			case 202:
			case 203:
			case 204:
			case 205:
			case 206:
				return api_downloadManager::TICK_NODATA;
			default:
				return api_downloadManager::TICK_CANT_CONNECT;
		}
	}
}





#define CBCLASS DownloadManager
START_DISPATCH;
CB(  API_DOWNLOADMANAGER_DOWNLOAD,                 Download )
CB(  API_DOWNLOADMANAGER_DOWNLOADEX,               DownloadEx )
CB(  API_DOWNLOADMANAGER_GETRECEIVER,              GetReceiver )
CB(  API_DOWNLOADMANAGER_GETLOCATION,              GetLocation )
VCB( API_DOWNLOADMANAGER_SETLOCATION,              SetLocation )
CB(  API_DOWNLOADMANAGER_GETEXTENTION,             GetExtention )
CB(  API_DOWNLOADMANAGER_GETURL,                   GetUrl )
CB(  API_DOWNLOADMANAGER_GETBYTESDOWNLOADED,       GetBytesDownloaded );
CB(  API_DOWNLOADMANAGER_GETBUFFER,                GetBuffer );
VCB( API_DOWNLOADMANAGER_RESUMEPENDINGDOWNLOAD,    ResumePendingDownload );
VCB( API_DOWNLOADMANAGER_CANCELDOWNLOAD,           CancelDownload );
VCB( API_DOWNLOADMANAGER_RETAINDOWNLOAD,           RetainDownload );
VCB( API_DOWNLOADMANAGER_RELEASEDOWNLOAD,          ReleaseDownload );
VCB( API_DOWNLOADMANAGER_REGISTERSTATUSCALLBACK,   RegisterStatusCallback );
VCB( API_DOWNLOADMANAGER_UNREGISTERSTATUSCALLBACK, UnregisterStatusCallback );
CB(  API_DOWNLOADMANAGER_GETSOURCE,                GetSource );
CB(  API_DOWNLOADMANAGER_GETTITLE,                 GetTitle );
CB(  API_DOWNLOADMANAGER_ISPENDING,                IsPending );
END_DISPATCH;
#undef CBCLASS