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

#include "quakedef.h"
#include "sv_client.h"
#include "logofile_shared.h"
#include "server.h"
#include "filetransfermgr.h"
#include "filesystem_engine.h"


ConVar sv_logo_rate( "sv_logo_rate", "1024", 0, "How fast (bytes per second) the server sends logo files to clients." );


class CPendingFile
{
public:
	CRC32_t m_nLogoFileCRC;
	bool m_bWaitingForServerToGetFile;
};


class CPerClientLogoInfo
{
public:
	CPerClientLogoInfo()
	{
		m_bLogoFileCRCValid = false;
		m_bSendFileInProgress = false;
	}

	// This client's logo info.
	bool m_bLogoFileCRCValid;
	int m_nLogoFileCRC;

	// Are we sending this client a file right now?
	bool m_bSendFileInProgress;

	// Files that this client has requested but we aren't able to send yet.	
	CUtlVector<CPendingFile> m_PendingFiles;
};


class CServerFileTransferMgr : public CFileTransferMgr
{
public:
	virtual bool SendChunk( INetChannel *pDest, const void *pData, int len )
	{
		SVC_LogoFileData fileData;
		fileData.m_Data.CopyArray( (const char*)pData, len );
		return pDest->SendNetMsg( fileData, true );
	}

	virtual void OnSendCancelled( FileTransferID_t id )
	{
	}

	CGameClient* GetClientByNetChannel( INetChannel *pChan )
	{
		for ( int i=0; i < sv.clients.Count(); i++ )
		{
			CGameClient *pClient = sv.Client( i );
			if ( pClient && pClient->GetNetChannel() == pChan )
				return pClient;
		}
		return NULL;
	}
	
	virtual void OnFinishedSending( 
		INetChannel *pDest, 
		const void *pUserData, 
		int userDataLen, 
		FileTransferID_t id )
	{
		// Start sending the next file to this guy.
		CGameClient *pClient = GetClientByNetChannel( pDest );
		if ( pClient )
		{
			pClient->m_pLogoInfo->m_bSendFileInProgress = false;
			UpdatePendingFiles();
		}
		else
		{
			Warning( "OnFinishedSending: can't get CGameClient from INetChannel.\n" );
		}
	}

	virtual void OnFileReceived( 
		INetChannel *pChan,
		const void *pUserData,
		int userDataLength,
		const char *pFileData, 
		int fileLength )
	{
		// Ok, now the server has received a file the client sent. First, validate the VTF.
		if ( !LogoFile_IsValidVTFFile( pFileData, fileLength ) )
		{
			Warning( "CServerFileTransferMgr::OnFileReceived: received an invalid logo file from a client.\n" );
			return;
		}

		if ( userDataLength < sizeof( CRC32_t ) )
		{
			Warning( "CServerFileTransferMgr::OnFileReceived: invalid userDataLength (%d).\n", userDataLength );
		}

		CRC32_t crcValue = *((CRC32_t*)pUserData);

		// Save this file in our cache.
		if ( SaveCRCFileToCache( crcValue, pFileData, fileLength ) )
		{
			// Start transfers to any clients that we can now.
			MarkPendingFilesWithCRC( crcValue );
			UpdatePendingFiles();
		}
	}


	// If any clients are waiting on this file, mark them so they know they can be sent the file now.
	void MarkPendingFilesWithCRC( CRC32_t crcValue )
	{
		for ( int i=0; i < sv.clients.Count(); i++ )
		{
			CGameClient *pClient = sv.Client( i );
			if ( !pClient || !pClient->m_pLogoInfo )
				continue;
		
			for ( int i=0; i < pClient->m_pLogoInfo->m_PendingFiles.Count(); i++ )
			{
				CPendingFile *pFile = &pClient->m_pLogoInfo->m_PendingFiles[i];
				if ( pFile->m_nLogoFileCRC == crcValue )
					pFile->m_bWaitingForServerToGetFile = false;
			}
		}
	}


	bool SaveCRCFileToCache( CRC32_t crcValue, const void *pFileData, int fileLength )
	{
		CLogoFilename logohex( crcValue, true );
		
		FileHandle_t hFile = g_pFileSystem->Open( logohex.m_Filename, "wb" );
		if ( hFile == FILESYSTEM_INVALID_HANDLE )
		{
			Warning( "SaveCRCFileToCache: couldn't open '%s' for writing.\n", logohex.m_Filename );
			return false;
		}
		else
		{
			int writeRet = g_pFileSystem->Write( pFileData, fileLength, hFile );
			g_pFileSystem->Close( hFile );

			// If we couldn't write it, then delete it.
			if ( writeRet == fileLength )
			{
				return true;
			}
			else
			{
				Warning( "SaveCRCFileToCache: couldn't write data (%d should be %d).\n", writeRet, fileLength );
				return false;
			}
		}
	}

	void UpdatePendingFiles()
	{
		CUtlVector<char> fileData;
		CRC32_t lastCRC = 0;

		// Find clients who want to receive this file.
		for ( int i=0; i < sv.clients.Count(); i++ )
		{
			CGameClient *pClient = sv.Client( i );
			if ( !pClient || !pClient->m_pLogoInfo )
				continue;
		
			// Are we already sending the client a file?
			if ( pClient->m_pLogoInfo->m_bSendFileInProgress )
				continue;

			for ( int iFile=0; iFile < pClient->m_pLogoInfo->m_PendingFiles.Count(); iFile++ )
			{
				CPendingFile *pFile = &pClient->m_pLogoInfo->m_PendingFiles[iFile];

				// If we still have to wait for the server to get this file, then stop.
				if ( pFile->m_bWaitingForServerToGetFile )
					continue;

				pClient->m_pLogoInfo->m_PendingFiles.Remove( iFile );

				// Load the file, if we haven't already.
				if ( fileData.Count() == 0 || lastCRC != pFile->m_nLogoFileCRC )
				{
					// Remember the last CRC so we don't have to reopen the file if 
					// this one is going to a bunch of clients in a row.
					lastCRC = pFile->m_nLogoFileCRC;
					if ( !LogoFile_ReadFile( pFile->m_nLogoFileCRC, fileData ) )
						break;
				}

				StartSending(
					pClient->GetNetChannel(),
					&lastCRC,
					sizeof( lastCRC ),
					fileData.Base(),
					fileData.Count(),
					sv_logo_rate.GetInt() 
					);

				pClient->m_pLogoInfo->m_bSendFileInProgress = true;
				break;
			}
		}
	}
};
CServerFileTransferMgr g_ServerFileTransferMgr;


bool SV_LogoFile_HasLogoFile( CRC32_t crcValue )
{
	CLogoFilename logohex( crcValue, true );
	return g_pFileSystem->FileExists( logohex.m_Filename );
}


PROCESS_MSG_SERVER( CLC_LogoFileData )
{
	g_ServerFileTransferMgr.HandleReceivedData( m_Client->GetNetChannel(), m_Data.Base(), m_Data.Count() );
	return true;
} };


PROCESS_MSG_SERVER( CLC_LogoFileRequest )
{
	// The client is requesting that we send it a specific logo file.
	int index = m_Client->m_pLogoInfo->m_PendingFiles.AddToTail();
	CPendingFile &file = m_Client->m_pLogoInfo->m_PendingFiles[index];
	file.m_nLogoFileCRC = m_nLogoFileCRC;
	file.m_bWaitingForServerToGetFile = SV_LogoFile_HasLogoFile( file.m_nLogoFileCRC );

	// Start sending it if it's time..
	g_ServerFileTransferMgr.UpdatePendingFiles();
	return true;	
} };


CPerClientLogoInfo* SV_LogoFile_CreatePerClientLogoInfo()
{
	CPerClientLogoInfo *pInfo = new CPerClientLogoInfo;
	pInfo->m_bLogoFileCRCValid = false;
	return pInfo;
}


void SV_LogoFile_DeletePerClientLogoInfo( CPerClientLogoInfo *pInfo )
{
	delete pInfo;
}


void SV_LogoFile_HandleClientDisconnect( CGameClient *pClient )
{
	g_ServerFileTransferMgr.HandleClientDisconnect( pClient->GetNetChannel() );
}


void SV_LogoFile_NewConnection( INetChannel *chan, CGameClient *pGameClient )
{
	REGISTER_MSG_SERVER( CLC_LogoFileRequest );
}


bool SV_LogoFile_IsDownloadingLogoFile( CRC32_t crcValue )
{
	for ( int i=g_ServerFileTransferMgr.FirstIncoming(); i != g_ServerFileTransferMgr.InvalidIncoming(); i=g_ServerFileTransferMgr.NextIncoming( i ) )
	{
		const void *pData;
		int dataLen;
		g_ServerFileTransferMgr.GetIncomingUserData( i, pData, dataLen );

		CRC32_t *pTestValue = (CRC32_t*)pData;
		if ( *pTestValue == crcValue )
			return true;
	}
	return false;
}


void SV_LogoFile_OnConnect( CGameClient *pSenderClient, bool bValid, CRC32_t crcValue )
{
	pSenderClient->m_pLogoInfo->m_bLogoFileCRCValid = bValid;
	pSenderClient->m_pLogoInfo->m_nLogoFileCRC = crcValue;

	if ( bValid )
	{
		// Does the server need this file? If so, request it.
		if ( !SV_LogoFile_HasLogoFile( crcValue ) && !SV_LogoFile_IsDownloadingLogoFile( crcValue ) )
		{
			SVC_LogoFileRequest fileRequest;
			fileRequest.m_nLogoFileCRC = crcValue;
			if ( !pSenderClient->SendNetMsg( fileRequest, true ) )
			{
				Host_Error( "SV_LogoFile_OnConnect: Reliable broadcast message would overflow client" );
				return;
			}
		}

		// Tell all clients (except the sending client) about this logo.
		SVC_LogoFileCRC logoNotify;
		logoNotify.m_nLogoFileCRC = crcValue;

		for ( int i=0; i < sv.clients.Count(); i++ )
		{
			CGameClient *pClient = sv.Client( i );
			if ( !pClient || pClient == pSenderClient )
				continue;
			
			bool bReliable = true;
			if ( !pClient->SendNetMsg( logoNotify, bReliable ) )
			{
				Host_Error( "SV_LogoFile_OnConnect: Reliable broadcast message would overflow client" );
				return;
			}
		}
	}

	// Also, tell this client about all other client CRCs so it can aks for the one it needs.
	for ( int i=0; i < sv.clients.Count(); i++ )
	{
		CGameClient *pClient = sv.Client( i );
		if ( !pClient || pClient == pSenderClient || !pClient->m_pLogoInfo->m_bLogoFileCRCValid )
			continue;

		SVC_LogoFileCRC logoNotify;
		logoNotify.m_nLogoFileCRC = pClient->m_pLogoInfo->m_nLogoFileCRC;

		bool bReliable = true;
		if ( !pSenderClient->SendNetMsg( logoNotify, bReliable ) )
		{
			Host_Error( "SV_LogoFile_OnConnect: Reliable broadcast message would overflow client" );
			return;
		}
	}
}