//////////////////////////////////////////////////////////////////////////////
//
// Tools_AT3_Conversion.cpp
//
// Copyright (c) 2007 Rebellion. All rights reserved.
//
//////////////////////////////////////////////////////////////////////////////

/******************************************************************************/
/* Included Files                                                             */
/******************************************************************************/

#include "Core/Asura.h"

#include "Sound/Conversion/Tools_AT3_Conversion.h"

// Asura Platform-independent
#include "Core/Asura_String.h"
#include "Debugging/Asura_Assert.h"

// Tools
#include "Load&Save/LevelExport/Tools_Level_Export.h"
#include "Load&Save/Tools_Sound_Export.h"

#include "PC/FileSystem/Tools_FileSystem.h"


// Misc
#include <windows.h>

/******************************************************************************/
/* Functions                                                                  */
/******************************************************************************/

static bool Tools_ResampleInputFileForAT3IfNeeded(const char* szInputName, Asura_String& xOutputName, u_int* const puFailureCode /*= 0*/);


////////////////////////////////////////////////////////////////////////////////
//
// Tools_ConvertAT3File()
//
// Converts a wave file into an AT3 (PSP streaming format). Does nothing if the 
// file already exists and doesn't need reconverting
//
// Parameters:		szInputFileName - Path of input file
//					szOutputFileName - Path of output file
//					pxConvertData - Pointer to conversion options structure
//					puFailureCode - Optional pointer to output failure code
//
// Return value:	true for success, false for failure
//
////////////////////////////////////////////////////////////////////////////////

bool Tools_ConvertAT3File( const char* const szInputFileName,
						   const char* const szOutputFileName,
						   const ToolsSoundConversionData* const pxConvertData,
						   u_int* const puFailureCode /*= 0*/ )
{
	// Parameter check

	if( !szInputFileName || !szOutputFileName )
	{
		Asura_Assert( false, "NULL parameter passed to Tools_ConvertAT3File()." );

		if( puFailureCode )
		{
			*puFailureCode = Tools_ConvertAT3File_FailedToOpenWav;
		}

		return false;
	}

	//Resample to 44100 if needed
	Asura_String xTempFileName;
	if(!Tools_ResampleInputFileForAT3IfNeeded(szInputFileName, xTempFileName, puFailureCode)) return false;

	const char* szInputFileNameToUse = xTempFileName.IsBlank() ? szInputFileName : xTempFileName.GetString();


	// Execute the AT3 encoder tool
	char szCommand[1024];
	Asura_PrintfToBfr(szCommand,"at3tool.exe -e -br %d \"%s\" \"%s\"", sizeof(szCommand), true, pxConvertData->m_uAT3BitRate, szInputFileNameToUse, szOutputFileName);

	PROCESS_INFORMATION xProcessInfo;
	STARTUPINFO xStartupInfo = {0};
	xStartupInfo.cb = sizeof( STARTUPINFO );

	if( !CreateProcess( NULL, szCommand, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &xStartupInfo, &xProcessInfo ) )
	{
		#if defined ASURA_MAIN
			Tools_Level_Export::AddMessage( Tools_Level_Export::MT_ERROR, "Failed to encode sound. Check that at3tool.exe is present." );
		#else
			Asura_Assert( false, "Failed to encode sound. Check that at3tool.exe is present." );
		#endif

		if( puFailureCode )
		{
			*puFailureCode = Tools_ConvertAT3File_FailedToLoadExe;
		}

		return false;
	}

	WaitForSingleObject( xProcessInfo.hProcess, INFINITE );

	// TODO: Check the exit code for failure

	CloseHandle( xProcessInfo.hThread );
	CloseHandle( xProcessInfo.hProcess );

	if(!xTempFileName.IsBlank())
	{	
		//delete the temp file
		DeleteFile(xTempFileName.GetString());
	}

	//Make sure the resultant file isn't zero bytes 
	//This occurs when it doesn't like the format of the input file.
	u_int uFileSize;
	bool bGotSize = Tools_FileSystem::GetSizeOfFile(szOutputFileName,uFileSize);
	if(!bGotSize || !uFileSize)
	{
		#if defined ASURA_MAIN
		Tools_Level_Export::AddMessage( Tools_Level_Export::MT_ERROR, "Failed to encode %s.", szInputFileName);
		#else
		Asura_Assertf( false, ("Failed to encode %s.", szInputFileName) );
		#endif

		//delete zero sized file if it exists
		if(bGotSize) DeleteFile(szOutputFileName);
		if( puFailureCode )
		{
			*puFailureCode = Tools_ConvertAT3File_FailedToCreateAT3;
		}

		return false;
		
	}


	if( puFailureCode )
	{
		*puFailureCode = Tools_ConvertAT3File_Success;
	}

	return true;
}


/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/*
AT3Tools.exe only works with files using 44100 as their sample rate , the stuff below deals with resampling the files as needed
RAR 02/07/2007
*/

#define READ_WAV_FILE(pBuffer,iSize)\
ReadFile(hInputFile,(long*)pBuffer,(iSize),&uBytesRead,0);\
if(uBytesRead!=(unsigned)(iSize))\
	goto EndWavFileRead;

#define WRITE_WAV_FILE(pBuffer,iSize)\
WriteFile(hOutputFile,(long*)pBuffer,(iSize),&uBytesWritten,0);\
if(uBytesWritten!=(unsigned)(iSize))\
	goto EndWavFileWrite;


typedef struct pwavchunkheader
{
	char chunkName[4];
	int chunkLength;	
} PWAVCHUNKHEADER;

typedef struct pwavriffheader
{
	char type[4];
} PWAVRIFFHEADER;

static bool Tools_ResampleInputFileForAT3IfNeeded(const char* szInputName, Asura_String& xOutputName, u_int* const puFailureCode /*= 0*/)
{
	HANDLE hInputFile = CreateFile(szInputName,GENERIC_READ,FILE_SHARE_READ,0,OPEN_EXISTING,FILE_FLAG_RANDOM_ACCESS,0);
	
	if(hInputFile == INVALID_HANDLE_VALUE)
	{
		#if defined ASURA_MAIN
		Tools_Level_Export::AddMessage(Tools_Level_Export::MT_ERROR,"Failed to open %s for at3 conversion",szInputName);
		#else
		Asura_Assertf(0,("Failed to open %s for at3 conversion",szInputName));
		#endif
		if(puFailureCode) *puFailureCode = Tools_ConvertAT3File_FailedToReadWav;
		return false;
	}

	bool bOkay = false;
	bool bNeedToConvert = false;

	u_int uNumWavSamples = 0;
	u_int uNumWavBytesPerSample = 0;
	unsigned char* pucReadBuffer = 0;
	unsigned long uBytesRead = 0;


	//read the initial headers
	PWAVCHUNKHEADER xChunkHeader;
	READ_WAV_FILE(&xChunkHeader,sizeof(xChunkHeader));

	if((xChunkHeader.chunkName[0]!='R')||(xChunkHeader.chunkName[1]!='I')||
	   (xChunkHeader.chunkName[2]!='F')||(xChunkHeader.chunkName[3]!='F'))
	{
		goto EndWavFileRead;
	}

	PWAVRIFFHEADER xRiffHeader;
	PCMWAVEFORMAT xWavFormat;
	READ_WAV_FILE(&xRiffHeader,sizeof(xRiffHeader));

	if(xRiffHeader.type[0]!='W'||xRiffHeader.type[1]!='A'||xRiffHeader.type[2]!='V'||xRiffHeader.type[3]!='E')
	{
		goto EndWavFileRead;
	}

	//find the format chunk
	do
	{
		READ_WAV_FILE(&xChunkHeader,sizeof(xChunkHeader));
		if((xChunkHeader.chunkName[0]=='f')&&(xChunkHeader.chunkName[1]=='m')&&
	   		(xChunkHeader.chunkName[2]=='t')&&(xChunkHeader.chunkName[3]==' '))
		{
			break;
		}

		SetFilePointer(hInputFile,xChunkHeader.chunkLength,0,FILE_CURRENT);

	}while(TRUE);

	//read the wav format chunk
	if(xChunkHeader.chunkLength==16)
	{
		/* a standard PCM wave format chunk */
		READ_WAV_FILE(&xWavFormat,sizeof(xWavFormat));
	}
	else if(xChunkHeader.chunkLength>=18)
	{
		/* an extended PCM wave format chunk */
		WAVEFORMATEX temp;
		READ_WAV_FILE(&temp,sizeof(temp));
		
		xWavFormat = *(PCMWAVEFORMAT*)&temp;

		//skip the rest of the chunk
		if(xChunkHeader.chunkLength>18)
		{
			SetFilePointer(hInputFile,xChunkHeader.chunkLength-18,0,FILE_CURRENT);
		}

	}
	else
	{
		goto EndWavFileRead;
	}

	if(xWavFormat.wf.nSamplesPerSec == 44100 && xWavFormat.wBitsPerSample <= 16)
	{
		//don't need to convert if already 44100
		bOkay = true;
		bNeedToConvert = false;
		goto EndWavFileRead;
	}

	//now skip until we reach the data chunk
	do
	{
		READ_WAV_FILE(&xChunkHeader,sizeof(xChunkHeader));
		if((xChunkHeader.chunkName[0]=='d')&&(xChunkHeader.chunkName[1]=='a')&&
	   		(xChunkHeader.chunkName[2]=='t')&&(xChunkHeader.chunkName[3]=='a'))
		{
			break;
		}

		SetFilePointer(hInputFile,xChunkHeader.chunkLength,0,FILE_CURRENT);

	}while(true);

	//how many samples to read in?
	uNumWavBytesPerSample = xWavFormat.wf.nBlockAlign;
	uNumWavSamples = xChunkHeader.chunkLength/uNumWavBytesPerSample;

	pucReadBuffer = new unsigned char[uNumWavSamples*uNumWavBytesPerSample];

	//read in the data chunk
	READ_WAV_FILE(pucReadBuffer,uNumWavSamples * uNumWavBytesPerSample);
	
	bOkay = true;
	bNeedToConvert = true;

EndWavFileRead :

	//finished with the file now
	if(hInputFile != INVALID_HANDLE_VALUE) CloseHandle(hInputFile);
	if(!bOkay)
	{
		if(pucReadBuffer) delete [] pucReadBuffer;
		#ifdef ASURA_MAIN
		Tools_Level_Export::AddMessage(Tools_Level_Export::MT_ERROR,"Error reading %s for at3 conversion",szInputName);
		#else
		Asura_Assertf(0,("Error reading %s for at3 conversion",szInputName));
		#endif
		if(puFailureCode) *puFailureCode = Tools_ConvertAT3File_FailedToReadWav;
		return false;
	}
	if(!bNeedToConvert)
	{
		return true;
	}

	//Do the resampling thing
	R8BInputFormat xInFormat;

	xInFormat.IEEE = 0;
	xInFormat.pBuffer = pucReadBuffer;
	xInFormat.iRate = xWavFormat.wf.nSamplesPerSec;
	xInFormat.iChannels = xWavFormat.wf.nChannels;
	xInFormat.iBitDepth = xWavFormat.wBitsPerSample;
	xInFormat.iLength = uNumWavSamples;
	int iNewSize = 0;

	u_int uOutputBitsPerSample =  Asura_Maths::Min(static_cast<int>(xWavFormat.wBitsPerSample),16);

	unsigned char* pucNewData = (unsigned char*)Tools_Sound_Export::ResampleSound_R8Brain( &xInFormat, 44100, uOutputBitsPerSample, &iNewSize);
	delete [] pucReadBuffer;
	
	if(!pucNewData || !iNewSize)
	{
		#ifdef ASURA_MAIN
		Tools_Level_Export::AddMessage(Tools_Level_Export::MT_ERROR,"Error resampling %s to 44100 for at3 conversion",szInputName);
		#else
		Asura_Assertf(0,("Error resampling %s to 44100 for at3 conversion",szInputName));
		#endif
		delete [] pucNewData;
        if (puFailureCode) *puFailureCode = Tools_ConvertAT3File_FailedToResample;
		return false;
	}
	
	uNumWavSamples = iNewSize;
	xWavFormat.wBitsPerSample = (WORD)uOutputBitsPerSample;
	xWavFormat.wf.nBlockAlign = (xWavFormat.wBitsPerSample*xWavFormat.wf.nChannels)/8;
	xWavFormat.wf.nSamplesPerSec = 44100;
	xWavFormat.wf.nAvgBytesPerSec = xWavFormat.wf.nSamplesPerSec * (xWavFormat.wBitsPerSample/8);
	xWavFormat.wf.wFormatTag = 0x0001;
	uNumWavBytesPerSample = xWavFormat.wf.nBlockAlign;


	//Generate a filename for the temporary file.
	//If in the main tools then plonk it in the levelexporttemp directory , otherwise use the current dir
	#ifdef ASURA_MAIN
	char szTemp[MAX_PATH];
	Asura_PrintfToBfr(szTemp,"%s\\TempWavForAT3.wav",sizeof(szTemp),true,Tools_Level_Export::GetTempDirectory());
	xOutputName = szTemp;
	#else
	xOutputName = "TempWavForAT3.wav";//TBD , pick name in a better way
	#endif

	HANDLE hOutputFile = CreateFile(xOutputName.GetString(),GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_FLAG_RANDOM_ACCESS, 0);
	if(hOutputFile == INVALID_HANDLE_VALUE)
	{
		#ifdef ASURA_MAIN
		Tools_Level_Export::AddMessage(Tools_Level_Export::MT_ERROR,"Failed to create %s (resampled file for at3 conversion)",xOutputName.GetString());
		#else
		Asura_Assertf(0,("Failed to create %s (resampled file for at3 conversion)",xOutputName.GetString()));
		#endif
        if (puFailureCode) *puFailureCode = Tools_ConvertAT3File_FailedToWriteTemporaryFile;
		delete [] pucNewData;
		return false;
	}

	bOkay = false;
 	unsigned long uBytesWritten;
	int iLength;
	
		
	//create header and write it
	xChunkHeader.chunkName[0] = 'R';
	xChunkHeader.chunkName[1] = 'I';
	xChunkHeader.chunkName[2] = 'F';
	xChunkHeader.chunkName[3] = 'F';
	xChunkHeader.chunkLength = 0; //filled in at end
	WRITE_WAV_FILE(&xChunkHeader,sizeof(xChunkHeader));

	//write RiffHeader
	xRiffHeader.type[0] = 'W';
	xRiffHeader.type[1] = 'A';
	xRiffHeader.type[2] = 'V';
	xRiffHeader.type[3] = 'E';
	WRITE_WAV_FILE(&xRiffHeader,sizeof(xRiffHeader));

	//write the chunk header , and format
	xChunkHeader.chunkName[0] = 'f'; 
	xChunkHeader.chunkName[1] = 'm'; 
	xChunkHeader.chunkName[2] = 't'; 
	xChunkHeader.chunkName[3] = ' '; 
	xChunkHeader.chunkLength = sizeof(PCMWAVEFORMAT); 
	WRITE_WAV_FILE(&xChunkHeader,sizeof(xChunkHeader));
	WRITE_WAV_FILE(&xWavFormat,sizeof(xWavFormat));

	//write the header for the data block
	xChunkHeader.chunkName[0] = 'd'; 
	xChunkHeader.chunkName[1] = 'a'; 
	xChunkHeader.chunkName[2] = 't'; 
	xChunkHeader.chunkName[3] = 'a'; 
	xChunkHeader.chunkLength = uNumWavBytesPerSample*uNumWavSamples; 
	WRITE_WAV_FILE(&xChunkHeader,sizeof(xChunkHeader));

	//write the samles
	WRITE_WAV_FILE(pucNewData, uNumWavBytesPerSample*uNumWavSamples);


	//get the file size
	iLength = GetFileSize(hOutputFile,0);
	if(!iLength) goto EndWavFileWrite;
	iLength -= sizeof(PWAVCHUNKHEADER);
	
	//seek to RIFF chunk to write file size minus header size
	SetFilePointer(hOutputFile,4,0,FILE_BEGIN);
	WRITE_WAV_FILE(&iLength,sizeof(int));

	bOkay = true;

EndWavFileWrite :

	if(hOutputFile != INVALID_HANDLE_VALUE) CloseHandle(hOutputFile);
	delete [] pucNewData;
	if(!bOkay)
	{
		#ifdef ASURA_MAIN
		Tools_Level_Export::AddMessage(Tools_Level_Export::MT_ERROR,"Error writing %s (resampled file for at3 conversion)",xOutputName.GetString());
		#else
		Asura_Assertf(0,("Error writing %s (resampled file for at3 conversion)",xOutputName.GetString()));
		#endif
        if (puFailureCode) *puFailureCode = Tools_ConvertAT3File_FailedToWriteTemporaryFile;
		return false;
	}
		
	return true;
}

