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

#if defined( WIN32 ) && !defined( _X360 )
#include "winlite.h"
#include <WinInet.h>
#endif

#include "youtubeapi.h"

#include "platform.h"
#include "convar.h"
#include "fmtstr.h"
#include "igamesystem.h"
#include "strtools.h"
#include "cdll_util.h"
#include "utlmap.h"
#include "utlstring.h"
#include "vstdlib/jobthread.h"
#include <steam/steam_api.h>
#include <steam/isteamhttp.h>
#include "cdll_client_int.h"
#include "utlbuffer.h"
#include "filesystem.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"


//=============================================================================

static ConVar youtube_http_proxy( "youtube_http_proxy", "", FCVAR_ARCHIVE, "HTTP proxy.  Specify if you have have problems uploading to YouTube." );

//=============================================================================

static bool GetStandardTagValue( const char *pTag, const char *pXML, CUtlString &strResult )
{
	const char *pStart = strstr( pXML, pTag );
	if ( pStart != NULL )
	{
		pStart = strstr( pStart, ">" );
		if ( pStart != NULL )
		{
			pStart += 1;
			const char *pEnd = strstr( pStart, CFmtStr1024( "</%s", pTag ) );
			if ( pEnd != NULL )
			{
				strResult.SetDirect( pStart, pEnd - pStart );
				return true;
			}
		}
	}
	return false;
}

//=============================================================================

class CYouTubeRetrieveInfoJob;

namespace
{
	class CYouTubeSystem : public CAutoGameSystemPerFrame
	{
	public:

		CYouTubeSystem();
		~CYouTubeSystem();

		// CAutoGameSystem
		virtual bool Init();
		virtual void PostInit();
		virtual void Shutdown();
		virtual bool IsPerFrame() { return true; }
		virtual void Update( float frametime );

		void Login( const char *pUserName, const char *pPassword, const char *pSource );
		void LoginCancel();
		YouTubeUploadHandle_t Upload( const char* pFilePath, const char *pMimeType, const char *pTitle, const char *pDescription, const char *pCategory, const char *pKeywords, eYouTubeAccessControl access );

		bool IsUploadFinished( YouTubeUploadHandle_t handle );
		bool GetUploadProgress( YouTubeUploadHandle_t handle, double &ultotal, double &ulnow );
		bool GetUploadResults( YouTubeUploadHandle_t handle, bool &bSuccess, CUtlString &strURLToVideo, CUtlString &strURLToVideoStats );
		void ClearUploadResults( YouTubeUploadHandle_t handle );
		void CancelUpload( YouTubeUploadHandle_t handle );
		void SetUploadFinished( YouTubeUploadHandle_t handle, bool bSuccess, const char *pURLToVideo, const char *pURLToVideoStats );

		void SetUserProfile( const char *pUserProfile );
		bool GetProfileURL( CUtlString &strProfileURL ) const;

		YouTubeInfoHandle_t GetInfo( const char *pURLToVideoStats, CYouTubeResponseHandler &responseHandler );
		void CancelGetInfo( YouTubeInfoHandle_t handle );

		const char *GetDeveloperKey() const;
		const char *GetDeveloperTag() const;
		void SetDeveloperSettings( const char *pDeveloperKey, const char *pDeveloperTag );

		const char *GetLoginName() const;
		eYouTubeLoginStatus GetLoginStatus() const;
		void SetLoginStatus( eYouTubeLoginStatus status );

		const char* GetAuthToken() const;
		void SetAuthToken( const char *pAuthToken );

	private:
		struct uploadstatus_t
		{
			bool bFinished;
			bool bSuccess;
			CUtlString strURLToVideo;
			CUtlString strURLToVideoStats;
		};

		uploadstatus_t *GetStatus( YouTubeUploadHandle_t handle );

		eYouTubeLoginStatus m_eLoginStatus;
		CUtlString m_strYouTubeUserName;
		CUtlString m_strDeveloperKey;
		CUtlString m_strDeveloperTag;
		CUtlString m_strAuthToken;
		CUtlString m_strUserProfile;
		CThreadMutex m_Mutex;
		IThreadPool* m_pThreadPool;
		CUtlMap< YouTubeUploadHandle_t, uploadstatus_t > m_mapUploads;
		CUtlVector< CYouTubeRetrieveInfoJob * > m_vecRetrieveInfoJobs;
	};
};

static CYouTubeSystem gYouTube;

static ISteamHTTP *GetISteamHTTP()
{
	if ( steamapicontext != NULL && steamapicontext->SteamHTTP() )
	{
		return steamapicontext->SteamHTTP();
	}
#ifndef CLIENT_DLL
	if ( steamgameserverapicontext != NULL )
	{
		return steamgameserverapicontext->SteamHTTP();
	}
#endif
	return NULL;
}

//=============================================================================

// Base class for all YouTube jobs
class CYouTubeJob : public CJob
{
public:
	CYouTubeJob( CYouTubeSystem *pSystem )
	{
		SetFlags( JF_IO );
		// store local ones so we don't have to go through a mutex
		m_strDeveloperKey = pSystem->GetDeveloperKey();
		m_strDeveloperTag = pSystem->GetDeveloperTag();
	}

	virtual ~CYouTubeJob()
	{
	}

	void CancelUpload()
	{
		m_bCancelled = true;
	}

	bool IsCancelled() const
	{
		return m_bCancelled;
	}


protected:
	void OnHTTPRequestCompleted( HTTPRequestCompleted_t *pParam, bool bIOFailure )
	{
		m_bHTTPRequestPending = false;
		if ( (!m_bAllowRequestFailure && pParam->m_eStatusCode != k_EHTTPStatusCode200OK) || bIOFailure || !pParam->m_bRequestSuccessful )
		{
			Warning( "Failed to get youtube url: HTTP status %d fetching %s\n", pParam->m_eStatusCode, m_sURL.String() );
		}
		else
		{
			OnHTTPRequestCompleted( pParam );
		}
	}

	virtual void OnHTTPRequestCompleted( HTTPRequestCompleted_t *pParam ) { Assert( false ); }

	void DoRequest( HTTPRequestHandle hRequest, const char *pchRequestURL )
	{
		m_sURL = pchRequestURL;

		SteamAPICall_t hSteamAPICall;
		if ( GetISteamHTTP()->SendHTTPRequest( hRequest, &hSteamAPICall ) )
		{
			m_HTTPRequestCompleted.Set( hSteamAPICall, this, &CYouTubeJob::OnHTTPRequestCompleted );
		}

		// Wait for it to finish.
		while ( m_bHTTPRequestPending && !m_bCancelled )
		{
			ThreadSleep( 100 );
		}

		GetISteamHTTP()->ReleaseHTTPRequest( hSteamAPICall );
	}


	CCallResult<CYouTubeJob, HTTPRequestCompleted_t> m_HTTPRequestCompleted;

	CUtlString m_strDeveloperKey;
	CUtlString m_strDeveloperTag;
	CUtlString m_sURL;
	CUtlString m_strResponse;
	bool m_bHTTPRequestPending = true;
	bool m_bAllowRequestFailure = false;
	bool m_bCancelled = false;
};

//=============================================================================

class CYouTubeRetrieveUserProfile : public CYouTubeJob
{
public:
	CYouTubeRetrieveUserProfile( CYouTubeSystem *pSystem ) 
		: CYouTubeJob( pSystem )
	{
	}

private:
	void OnHTTPRequestCompleted( HTTPRequestCompleted_t *pParam ) OVERRIDE
	{
		uint32 unBodySize;
		if ( !GetISteamHTTP()->GetHTTPResponseBodySize( pParam->m_hRequest, &unBodySize ) )
		{
			Assert( false );
		}
		else
		{
			m_strResponse.SetLength( unBodySize );

			if ( GetISteamHTTP()->GetHTTPResponseBodyData( pParam->m_hRequest, (uint8*)m_strResponse.String(), unBodySize ) )
			{
				gYouTube.SetUserProfile( m_strResponse.Get() );
			}
		}
	}

	virtual JobStatus_t	DoExecute()
	{
		HTTPRequestHandle hRequest = GetISteamHTTP()->CreateHTTPRequest( k_EHTTPMethodGET, "http://gdata.youtube.com/feeds/api/users/default" );
		GetISteamHTTP()->SetHTTPRequestNetworkActivityTimeout( hRequest, 30 );
		GetISteamHTTP()->SetHTTPRequestHeaderValue( hRequest, "Authorization", CFmtStr1024( "GoogleLogin auth=%s", gYouTube.GetAuthToken() ) );

		DoRequest( hRequest, "http://gdata.youtube.com/feeds/api/users/default" );

		return JOB_OK;
	}
};

//=============================================================================

class CYouTubeRetrieveInfoJob : public CYouTubeJob
{
public:
	CYouTubeRetrieveInfoJob( CYouTubeSystem *pSystem, const char *pVideoURL, CYouTubeResponseHandler &responseHandler ) 
		: CYouTubeJob( pSystem )
		, m_strURL( pVideoURL )
		, m_responseHandler( responseHandler )
	{
	}

	void NotifyResponseHandler()
	{
		m_responseHandler.HandleResponse( 200, m_strResponse.Get() );
	}

private:
	void OnHTTPRequestCompleted( HTTPRequestCompleted_t *pParam ) OVERRIDE
	{
		uint32 unBodySize;
		if ( !GetISteamHTTP()->GetHTTPResponseBodySize( pParam->m_hRequest, &unBodySize ) )
		{
			Assert( false );
		}
		else
		{
			m_strResponse.SetLength( unBodySize );
			GetISteamHTTP()->GetHTTPResponseBodyData( pParam->m_hRequest, (uint8*)m_strResponse.String(), unBodySize );
		}
	}

	virtual JobStatus_t	DoExecute()
	{
		HTTPRequestHandle hRequest = GetISteamHTTP()->CreateHTTPRequest( k_EHTTPMethodGET, m_strURL.Get() );
		GetISteamHTTP()->SetHTTPRequestNetworkActivityTimeout( hRequest, 30 );

		DoRequest( hRequest, m_strURL.Get() );

		return JOB_OK;
	}

	// data
	CUtlString m_strURL;
	CYouTubeResponseHandler &m_responseHandler;
};

class CYouTubeLoginJob : public CYouTubeJob
{
public:
	CYouTubeLoginJob( CYouTubeSystem *pSystem, const char *pUserName, const char *pPassword, const char *pSource ) 
		: CYouTubeJob( pSystem )
		, m_strUserName( pUserName )
		, m_strPassword( pPassword )
		, m_strSource( pSource )
	{
	}

private:
	
	void SetLoginResults( const char *pLoginResults )
	{
		const char *pStart = strstr( pLoginResults, "Auth=" );
		if ( pStart != NULL )
		{
			pStart += strlen( "Auth=" );			
			const char *pEnd = strstr( pStart, "\r\n" );
			if ( pEnd == NULL )
			{
				pEnd = strstr( pStart, "\n" );
			}
			CUtlString strAuthToken;
			if ( pEnd != NULL )
			{
				strAuthToken.SetDirect( pStart, pEnd - pStart );
			}
			else
			{
				strAuthToken.SetDirect( pStart, strlen( pStart ) );
			}	
			gYouTube.SetAuthToken( strAuthToken.Get() );
		}
	}

	void OnHTTPRequestCompleted( HTTPRequestCompleted_t *pParam ) OVERRIDE
	{
		eYouTubeLoginStatus loginStatus = kYouTubeLogin_LoggedIn;

		if ( pParam->m_eStatusCode == 403 )
		{
			loginStatus = kYouTubeLogin_Forbidden;
		}
		else if ( pParam->m_eStatusCode != 200 )
		{
			loginStatus = kYouTubeLogin_GenericFailure;
		}
		else
		{
			uint32 unBodySize;
			if ( !GetISteamHTTP()->GetHTTPResponseBodySize( pParam->m_hRequest, &unBodySize ) )
			{
				Assert( false );
			}
			else
			{
				m_strResponse.SetLength( unBodySize );
				if ( GetISteamHTTP()->GetHTTPResponseBodyData( pParam->m_hRequest, (uint8*)m_strResponse.String(), unBodySize ) )
				{
					SetLoginResults( m_strResponse );
				}
			}
		}

		gYouTube.SetLoginStatus( loginStatus );

	}

	virtual JobStatus_t	DoExecute()
	{
		m_bAllowRequestFailure = true;

		HTTPRequestHandle hRequest = GetISteamHTTP()->CreateHTTPRequest( k_EHTTPMethodPOST, "https://www.google.com/accounts/ClientLogin" );
		GetISteamHTTP()->SetHTTPRequestNetworkActivityTimeout( hRequest, 30 );

	//	GetISteamHTTP()->SetHTTPRequestHeaderValue( hRequest, "Content-Type", "application/x-www-form-urlencoded" );
		GetISteamHTTP()->SetHTTPRequestHeaderValue( hRequest, "X-GData-Key", CFmtStr1024( "key=%s", m_strDeveloperKey.Get() ) );

		char szUserNameEncoded[256];
		char szPasswordEncoded[256];
		char szSourceEncoded[256];
		Q_URLEncode( szUserNameEncoded, sizeof( szUserNameEncoded ), m_strUserName.Get(), m_strUserName.Length() );
		Q_URLEncode( szPasswordEncoded, sizeof( szPasswordEncoded ), m_strPassword.Get(), m_strPassword.Length() );
		Q_URLEncode( szSourceEncoded, sizeof( szSourceEncoded ), m_strSource.Get(), m_strSource.Length() );

		CFmtStr1024 data( "Email=%s&Passwd=%s&service=youtube&source=%s", szUserNameEncoded, szPasswordEncoded, szSourceEncoded );

		GetISteamHTTP()->SetHTTPRequestRawPostBody( hRequest, "application/x-www-form-urlencoded", (uint8 *)data.Access(), data.Length() );

		DoRequest( hRequest, "https://www.google.com/accounts/ClientLogin" );

		return JOB_OK;
	}

	// data
	CUtlString m_strUserName;
	CUtlString m_strPassword;
	CUtlString m_strSource;
};

// Job for uploading a file
class CYouTubeUploadJob : public CYouTubeJob
{
public:
	CYouTubeUploadJob( CYouTubeSystem *pSystem, const char* pFilePath, const char *pMimeType, const char *pTitle, const char *pDescription, const char *pCategory, const char *pKeywords, eYouTubeAccessControl access )
		: CYouTubeJob( pSystem )
		, m_strFilePath( pFilePath )
		, m_strMimeType( pMimeType )
		, m_strTitle( pTitle )
		, m_strDesc( pDescription )
		, m_strCategory( pCategory )
		, m_strKeywords( pKeywords )
		, m_eAccess( access )
	{
	}

	void GetProgress( double &ultotal, double &ulnow  )
	{
		ultotal = 0;
		ulnow = 0;
	}

private:

	virtual JobStatus_t	DoExecute()
	{
		m_bAllowRequestFailure = true;

		HTTPRequestHandle hRequest = GetISteamHTTP()->CreateHTTPRequest( k_EHTTPMethodPUT, "http://uploads.gdata.youtube.com/feeds/api/users/default/uploads" );
		GetISteamHTTP()->SetHTTPRequestNetworkActivityTimeout( hRequest, 30 );

		const char *pFileName = Q_UnqualifiedFileName( m_strFilePath.Get() );

		GetISteamHTTP()->SetHTTPRequestHeaderValue( hRequest, "Authorization", CFmtStr1024( "GoogleLogin auth=%s", gYouTube.GetAuthToken() ) );
		GetISteamHTTP()->SetHTTPRequestHeaderValue( hRequest, "X-GData-Key", CFmtStr1024( "key=%s", m_strDeveloperKey.Get() ) );
		GetISteamHTTP()->SetHTTPRequestHeaderValue( hRequest, "GData-Version", "2" );
		//GetISteamHTTP()->SetHTTPRequestHeaderValue( hRequest, "Content-Type", "multipart/form-data;boundary=-x" );
		GetISteamHTTP()->SetHTTPRequestHeaderValue( hRequest, "Slug", CFmtStr1024( "%s", pFileName ) );

		const char *pPrivateString = "";
		const char *pAccessControlString = "";

		switch ( m_eAccess )
		{
		case kYouTubeAccessControl_Public:
			break;
		case kYouTubeAccessControl_Private:
			pPrivateString = "<yt:private/>";
			break;
		case kYouTubeAccessControl_Unlisted:
			pAccessControlString = "<yt:accessControl action=\"list\" permission=\"denied\"/>";
			break;
		}

		CFmtStr1024 strAPIRequest( "<?xml version=\"1.0\"?>"
			"<entry "
			"xmlns=\"http://www.w3.org/2005/Atom\" "
			"xmlns:media=\"http://search.yahoo.com/mrss/\" "
			"xmlns:yt=\"http://gdata.youtube.com/schemas/2007\">"
			"<media:group>"
			"<media:title type=\"plain\">%s</media:title>"
			"<media:description type=\"plain\">%s</media:description>"
			"<media:category scheme=\"http://gdata.youtube.com/schemas/2007/categories.cat\">"
			"%s"
			"</media:category>"
			"%s"
			"<media:keywords>%s</media:keywords>"
			"<media:category scheme=\"http://gdata.youtube.com/schemas/2007/developertags.cat\">"
			"%s"
			"</media:category>"
			"</media:group>"
			"%s"
			"</entry>",
			m_strTitle.Get(),
			m_strDesc.Get(),
			m_strCategory.Get(),
			pPrivateString,
			m_strKeywords.Get(),
			m_strDeveloperTag.Get(),
			pAccessControlString );

		CFmtStr1024 fmtstrBody(
			"\r\nContent-Type: application/atom+xml; charset=UTF-8\r\n"
			"\r\n---x\r\nContent-Disposition: form-data; name=\"apirequest\"\r\n\r\n%s"
			"\r\n---x\r\nContent-Disposition: form-data; name=\"video\"\r\nContent-Type: %s\r\nContent-Transfer-Encoding: binary\r\n\r\n",
			strAPIRequest.Access(), m_strMimeType.Get()
		);

		CUtlBuffer postDataRaw( 0, 0, 0 );

		postDataRaw.Put( fmtstrBody.Access(), fmtstrBody.Length() );

		CUtlBuffer fileData( 0, 0, 0 );
		bool bReadFileOK = g_pFullFileSystem->ReadFile( m_strFilePath, nullptr, fileData );

		if ( bReadFileOK )
		{
			postDataRaw.Put( fileData.Base(), fileData.TellPut() );

			fileData.Clear();

			static const char rgchFooter[] = "\r\n---x--\r\n";

			postDataRaw.Put( rgchFooter, V_strlen( rgchFooter ) );

			GetISteamHTTP()->SetHTTPRequestRawPostBody( hRequest, "multipart/form-data;boundary=-x", (uint8 *)postDataRaw.Base(), postDataRaw.TellPut() );

			// BUGBUG: use SendHTTPRequestAndStreamResponse
			DoRequest( hRequest, "http://uploads.gdata.youtube.com/feeds/api/users/default/uploads" );
		}	

		return JOB_OK;
	}

	void OnHTTPRequestCompleted( HTTPRequestCompleted_t *pParam ) OVERRIDE
	{
		bool bSuccess = false;

		CUtlString strURLToVideoStats;
		CUtlString strURLToVideo;

		if ( pParam->m_eStatusCode == 200 )
		{
			bSuccess = true;

			uint32 unBodySize;
			if ( !GetISteamHTTP()->GetHTTPResponseBodySize( pParam->m_hRequest, &unBodySize ) )
			{
				Assert( false );
			}
			else
			{
				m_strResponse.SetLength( unBodySize );
				if ( GetISteamHTTP()->GetHTTPResponseBodyData( pParam->m_hRequest, (uint8*)m_strResponse.String(), unBodySize ) )
				{
					// @note Tom Bui: wish I had an xml parser...
					{
						strURLToVideo = "";
						// "<link rel='alternate' type='text/html' href='http://www.youtube.com/watch?v=7D4pb3irM_0&feature=youtube_gdata' /> ";
						const char *kLinkStartTag = "<link rel='alternate' type='text/html' href='";
						const char *kLinkTagEnd = "'/>";
						const char *pStart = strstr( m_strResponse.Get(), kLinkStartTag );
						if ( pStart != NULL )
						{
							pStart += strlen( kLinkStartTag );
							const char *pEnd = strstr( pStart, kLinkTagEnd );
							if ( pEnd != NULL )
							{
								strURLToVideo.SetDirect( pStart, pEnd - pStart );
							}
						}
					}
					{
						strURLToVideoStats = "";
						// "<link rel='self' type='text/html' href='http://www.youtube.com/watch?v=7D4pb3irM_0&feature=youtube_gdata' /> ";				
						const char *kLinkStartTag = "<link rel='self' type='application/atom+xml' href='";
						const char *kLinkTagEnd = "'/>";
						const char *pStart = strstr( m_strResponse.Get(), kLinkStartTag );
						if ( pStart != NULL )
						{
							pStart += strlen( kLinkStartTag );
							const char *pEnd = strstr( pStart, kLinkTagEnd );
							if ( pEnd != NULL )
							{
								strURLToVideoStats.SetDirect( pStart, pEnd - pStart );
								// @note Tom Bui: we want at least version 2
								if ( V_strstr( strURLToVideoStats.Get(), "?v=" ) == NULL )
								{
									strURLToVideoStats += "?v=2";
								}
							}
						}
					}
				}
			}
		}

		gYouTube.SetUploadFinished( (YouTubeUploadHandle_t)this, bSuccess, strURLToVideo.Get(), strURLToVideoStats.Get() );
	}

	// data 
	CUtlString m_strFilePath;
	CUtlString m_strMimeType;
	CUtlString m_strTitle;
	CUtlString m_strDesc;
	CUtlString m_strCategory;
	CUtlString m_strKeywords;
	eYouTubeAccessControl m_eAccess;
};

//=============================================================================

CYouTubeSystem::CYouTubeSystem()
	: m_eLoginStatus( kYouTubeLogin_NotLoggedIn )
	, m_pThreadPool( NULL )
	, m_mapUploads( DefLessFunc( YouTubeUploadHandle_t ) )
{
}

CYouTubeSystem::~CYouTubeSystem()
{
}

bool CYouTubeSystem::Init()
{
	m_pThreadPool = CreateThreadPool();
	m_pThreadPool->Start( ThreadPoolStartParams_t( false, 4 ), "YouTubeSystem" );
	return true;
}

void CYouTubeSystem::PostInit()
{
}

void CYouTubeSystem::Shutdown()
{
	DestroyThreadPool( m_pThreadPool );
	m_pThreadPool = NULL;
}

void CYouTubeSystem::Update( float frametime )
{
	AUTO_LOCK( m_Mutex );
	FOR_EACH_VEC( m_vecRetrieveInfoJobs, i )
	{
		CYouTubeRetrieveInfoJob *pJob = m_vecRetrieveInfoJobs[i];
		if ( pJob->IsFinished() )
		{
			pJob->NotifyResponseHandler();
			// cleanup the job
			pJob->Release();
			// and remove
			m_vecRetrieveInfoJobs.FastRemove( i );
		}
		else
		{
			++i;
		}
	}
}

void CYouTubeSystem::Login( const char *pUserName, const char *pPassword, const char *pSource )
{
	m_eLoginStatus = kYouTubeLogin_NotLoggedIn;
	CYouTubeLoginJob *pJob = new CYouTubeLoginJob( this, pUserName, pPassword, pSource );
	m_pThreadPool->AddJob( pJob );
	pJob->Release();
}

void CYouTubeSystem::LoginCancel()
{
	m_eLoginStatus = kYouTubeLogin_Cancelled;
}

YouTubeUploadHandle_t CYouTubeSystem::Upload( const char* pFilePath, const char *pMimeType, const char *pTitle, const char *pDescription, const char *pCategory, const char *pKeywords, eYouTubeAccessControl access )
{
	if ( m_eLoginStatus != kYouTubeLogin_LoggedIn )
	{
		return NULL;
	}
	CYouTubeUploadJob *pJob = new CYouTubeUploadJob( this, pFilePath, pMimeType, pTitle, pDescription, pCategory, pKeywords, access );
	m_pThreadPool->AddJob( pJob );
	uploadstatus_t status = { false, false, "", "" };
	m_mapUploads.Insert( (YouTubeUploadHandle_t)pJob, status );
	return (YouTubeUploadHandle_t)pJob;
}

CYouTubeSystem::uploadstatus_t *CYouTubeSystem::GetStatus( YouTubeUploadHandle_t handle )
{
	int idx = m_mapUploads.Find( handle );
	if ( m_mapUploads.IsValidIndex( idx ) )
	{
		return &m_mapUploads[idx];
	}
	return NULL;
}

bool CYouTubeSystem::IsUploadFinished( YouTubeUploadHandle_t handle )
{
	AUTO_LOCK( m_Mutex );
	uploadstatus_t *pStatus = GetStatus( handle );
	if ( pStatus != NULL )
	{
		return pStatus->bFinished;
	}
	return true;
}

bool CYouTubeSystem::GetUploadProgress( YouTubeUploadHandle_t handle, double &ultotal, double &ulnow )
{
	AUTO_LOCK( m_Mutex );
	uploadstatus_t *pStatus = GetStatus( handle );
	if ( pStatus != NULL )
	{
		CYouTubeUploadJob *pJob = (CYouTubeUploadJob*)handle;
		pJob->GetProgress( ultotal, ulnow );
		return true;
	}
	return false;
}

bool CYouTubeSystem::GetUploadResults( YouTubeUploadHandle_t handle, bool &bSuccess, CUtlString &strURLToVideo, CUtlString &strURLToVideoStats )
{
	AUTO_LOCK( m_Mutex );
	uploadstatus_t *pStatus = GetStatus( handle );
	if ( pStatus != NULL )
	{
		bSuccess = pStatus->bSuccess;
		strURLToVideo = pStatus->strURLToVideo;
		strURLToVideoStats = pStatus->strURLToVideoStats;
		return true;
	}
	return false;
}

void CYouTubeSystem::ClearUploadResults( YouTubeUploadHandle_t handle )
{
	AUTO_LOCK( m_Mutex );
	if ( m_mapUploads.Remove( handle ) )
	{
		CYouTubeUploadJob *pJob = (CYouTubeUploadJob*)handle;
		pJob->Release();
	}
}

void CYouTubeSystem::CancelUpload( YouTubeUploadHandle_t handle )
{
	AUTO_LOCK( m_Mutex );
	if ( m_mapUploads.Remove( handle ) )
	{
		CYouTubeUploadJob *pJob = (CYouTubeUploadJob*)handle;
		pJob->CancelUpload();
		pJob->Release();
	}
}

void CYouTubeSystem::SetUploadFinished( YouTubeUploadHandle_t handle, bool bSuccess, const char *pURLToVideo, const char *pURLToVideoStats )
{
	AUTO_LOCK( m_Mutex );
	uploadstatus_t *pStatus = GetStatus( handle );
	if ( pStatus )
	{
		pStatus->bFinished = true;
		pStatus->bSuccess = bSuccess;
		pStatus->strURLToVideo = pURLToVideo;
		pStatus->strURLToVideoStats = pURLToVideoStats;
	}
}

void CYouTubeSystem::SetUserProfile( const char *pUserProfile )
{
	AUTO_LOCK( m_Mutex );
	m_strUserProfile = pUserProfile;
	CUtlString author;
	GetStandardTagValue( "author", m_strUserProfile.Get(), author );
	GetStandardTagValue( "name", author.Get(), m_strYouTubeUserName );
}

YouTubeInfoHandle_t CYouTubeSystem::GetInfo( const char *pURLToVideoStats, CYouTubeResponseHandler &responseHandler )
{
	AUTO_LOCK( m_Mutex );
	CYouTubeRetrieveInfoJob *pJob = new CYouTubeRetrieveInfoJob( this, pURLToVideoStats, responseHandler );
	m_pThreadPool->AddJob( pJob );
	m_vecRetrieveInfoJobs.AddToTail( pJob );
	return (YouTubeInfoHandle_t)pJob;
}

void CYouTubeSystem::CancelGetInfo( YouTubeInfoHandle_t handle )
{
	AUTO_LOCK( m_Mutex );
	int idx = m_vecRetrieveInfoJobs.Find( (CYouTubeRetrieveInfoJob*)handle );
	if ( idx >= 0 && idx < m_vecRetrieveInfoJobs.Count() )
	{
		((CYouTubeRetrieveInfoJob*)handle)->Release();
		m_vecRetrieveInfoJobs.FastRemove( idx );
	}
}

void CYouTubeSystem::SetDeveloperSettings( const char *pDeveloperKey, const char *pDeveloperTag )
{
	AUTO_LOCK( m_Mutex );
	m_strDeveloperKey = pDeveloperKey;
	m_strDeveloperTag = pDeveloperTag;
}

const char *CYouTubeSystem::GetDeveloperKey() const
{ 
	AUTO_LOCK( m_Mutex );
	return m_strDeveloperKey.Get(); 
}

const char *CYouTubeSystem::GetDeveloperTag() const
{ 
	AUTO_LOCK( m_Mutex );
	return m_strDeveloperTag.Get(); 
}

const char *CYouTubeSystem::GetLoginName() const
{
	return m_strYouTubeUserName.Get();
}

eYouTubeLoginStatus CYouTubeSystem::GetLoginStatus() const
{
	return m_eLoginStatus;
}

bool CYouTubeSystem::GetProfileURL( CUtlString &strProfileURL ) const
{
	if ( m_eLoginStatus == kYouTubeLogin_LoggedIn )
	{
		strProfileURL = CFmtStr1024( "http://www.youtube.com/profile?user=%s", m_strYouTubeUserName.Get() );
		return true;
	}
	return false;
}

void CYouTubeSystem::SetLoginStatus( eYouTubeLoginStatus status )
{
	AUTO_LOCK( m_Mutex );
	m_eLoginStatus = status;
	if ( m_eLoginStatus == kYouTubeLogin_LoggedIn )
	{
		CYouTubeRetrieveUserProfile *pJob = new CYouTubeRetrieveUserProfile( this );
		m_pThreadPool->AddJob( pJob );
		pJob->Release();
	}
}	

void CYouTubeSystem::SetAuthToken( const char *pAuthToken )
{
	AUTO_LOCK( m_Mutex );
	m_strAuthToken = pAuthToken;
}

const char* CYouTubeSystem::GetAuthToken() const
{
	AUTO_LOCK( m_Mutex );
	return m_strAuthToken.Get();
}

//=============================================================================
// Public API

void YouTube_SetDeveloperSettings( const char *pDeveloperKey, const char *pDeveloperTag )
{
	gYouTube.SetDeveloperSettings( pDeveloperKey, pDeveloperTag );
}

void YouTube_Login( const char *pUserName, const char *pPassword, const char *pSource )
{
	if ( gYouTube.GetLoginStatus() == kYouTubeLogin_LoggedIn )
	{
		return;
	}
	gYouTube.Login( pUserName, pPassword, pSource );
}

void YouTube_LoginCancel()
{
	gYouTube.LoginCancel();
}

const char *YouTube_GetLoginName()
{
	return gYouTube.GetLoginName();
}

eYouTubeLoginStatus YouTube_GetLoginStatus()
{
	return gYouTube.GetLoginStatus();
}

bool YouTube_GetProfileURL( CUtlString &strProfileURL )
{
	return gYouTube.GetProfileURL( strProfileURL );
}

YouTubeUploadHandle_t YouTube_Upload( const char* pFilePath, const char *pMimeType, const char *pTitle, const char *pDescription, const char *pCategory, const char *pKeywords, eYouTubeAccessControl access )
{
	return gYouTube.Upload( pFilePath, pMimeType, pTitle, pDescription, pCategory, pKeywords, access );
}

bool YouTube_IsUploadFinished( YouTubeUploadHandle_t handle )
{
	return gYouTube.IsUploadFinished( handle );
}

bool YouTube_GetUploadProgress( YouTubeUploadHandle_t handle, double &ultotal, double &ulnow )
{
	return gYouTube.GetUploadProgress( handle, ultotal, ulnow );
}

bool YouTube_GetUploadResults( YouTubeUploadHandle_t handle, bool &bSuccess, CUtlString &strURLToVideo, CUtlString &strURLToVideoStats )
{
	return gYouTube.GetUploadResults( handle, bSuccess, strURLToVideo, strURLToVideoStats );
}

void YouTube_ClearUploadResults( YouTubeUploadHandle_t handle )
{
	gYouTube.ClearUploadResults( handle );
}

void YouTube_CancelUpload( YouTubeUploadHandle_t handle )
{
	gYouTube.CancelUpload( handle );
}

YouTubeInfoHandle_t YouTube_GetVideoInfo( const char *pURLToVideoStats, CYouTubeResponseHandler &responseHandler )
{
	return gYouTube.GetInfo( pURLToVideoStats, responseHandler );
}

void YouTube_CancelGetVideoInfo( YouTubeInfoHandle_t handle )
{
	gYouTube.CancelGetInfo( handle );
}