//
// Tools_Vag_Conversion.cpp
//
// Created by Richard Rice - 31/05/2002
// 
// Copyright (C) 2002 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"

// This file's header
#include "Sound/Conversion/Tools_Vag_Conversion.h"

// Asura
#include "Debugging/Asura_Assert.h"

// Tools
#include "Load&Save/LevelExport/Tools_Level_Export.h" //for text output
#include "Load&Save/Tools_Sound_Export.h"
#include "PC/FileSystem/Tools_FileSystem.h"
#include "Sound/Conversion/EncVag.h"

#include <float.h>

#define lEndian(X) (((X >> 24) & 0xff) | ((X >> 8) & 0xff00) | ((X & 0xff00) << 8) | ((X & 0xff) << 24))

///////////////////////////////////////////////////////////////////////////////////////////////////

typedef void (__stdcall *ENCVAGINIT_PTR)(short conversion_mode);
typedef void (__stdcall *ENCVAG_PTR)( short x[], short y[], short block_attribute );
typedef void (__stdcall *ENCVAGFIN_PTR)(short y[]);
//pointers to functions to extract from dll
ENCVAGINIT_PTR _EncVagInit = 0;
ENCVAG_PTR _EncVag = 0;
ENCVAGFIN_PTR _EncVagFin = 0;


static bool GetVagEncodeFunctionsFromDll()
{
	static struct RuntimeDll
	{
		RuntimeDll()
		{
			hDLL = 0;

		}
		
		~RuntimeDll()
		{
			if(hDLL)
			{
				FreeLibrary(hDLL);
			}
		}
		HMODULE hDLL;
	} dll;
		
	if(!dll.hDLL)
	{
		//try loading the dll
		dll.hDLL = LoadLibrary("EncVag.dll");


		if (dll.hDLL == NULL)
		{
			Tools_Level_Export::AddMessage(Tools_Level_Export::MT_ERROR,"Failed to load EncVag.dll");
			return false;
		}

		//grab the functions
		_EncVagInit = (ENCVAGINIT_PTR)GetProcAddress(dll.hDLL,"EncVagInit");
		_EncVag = (ENCVAG_PTR)GetProcAddress(dll.hDLL,"EncVag");
		_EncVagFin = (ENCVAGFIN_PTR)GetProcAddress(dll.hDLL,"EncVagFin");

		if(!_EncVagInit || !_EncVag || !_EncVagFin)
		{
			Tools_Level_Export::AddMessage(Tools_Level_Export::MT_ERROR,"Failed to get all the functions from EncVag.dll");
			return false;
		
		}
	}
	
	return _EncVagInit && _EncVag && _EncVagFin;
}



///////////////////////////////////////////////////////////////////////////////////////////////////

#define READ_WAV_FILE(buffer,size)\
ReadFile(inputFile,(long*)buffer,(size),&bytes_read,0);\
if(bytes_read!=(unsigned)(size))\
	goto EndWavFileRead;

#define WRITE_VAG_FILE(buffer,size)\
WriteFile(outputFile,(long*)buffer,(size),&bytes_read,0);\
if(bytes_read!=(unsigned)(size))\
	goto EndVagWrite;

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

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



bool ConvertVagFile(const char* szInputName, const char* szOutputName, int iMaxFrequency, 
                    bool bLooped, u_int uFirstSample /* = 0 */, u_int uMaxSamples /* = 0 */,
                    u_int* puFailureCode /* = 0 */, float fMinLength /* = 0.0f */)
{
	//make sure the vag encode functions are available from  the dll
	if(!GetVagEncodeFunctionsFromDll()) 
    {
        if (puFailureCode) *puFailureCode = Tools_ConvertVagFile_FailedToLoadDLL;
        return false;
    }

	// (MH - 18.07.2003) moved declaration of these locals here to cope with goto init problem
	u_int uNumWavSamples = 0;
	u_int uNumWavBytesPerSample = 0;
	float fWavSampleRate = 0.0f;
	u_int inputSamples = 0;
	int iNumPadSamples = 0;
	bool okay = false;
	unsigned char* readBuffer = 0;
	unsigned long bytes_read;
	
	//open the the wav file
	HANDLE inputFile = CreateFile(szInputName,GENERIC_READ,FILE_SHARE_READ,0,OPEN_EXISTING,FILE_FLAG_RANDOM_ACCESS,0);
	
	if(inputFile == INVALID_HANDLE_VALUE)
	{
		Tools_Level_Export::AddMessage(Tools_Level_Export::MT_ERROR,"Failed to  open %s",szInputName);
        if (puFailureCode) *puFailureCode = Tools_ConvertVagFile_FailedToOpenWav;
		return false;
	}

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

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

	PWAVRIFFHEADER myRiffHeader;
	PCMWAVEFORMAT wavFormat;
	READ_WAV_FILE(&myRiffHeader,sizeof(myRiffHeader));

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

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

		SetFilePointer(inputFile,myChunkHeader.chunkLength,0,FILE_CURRENT);

	}while(TRUE);

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

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

	}
	else
	{
		goto EndWavFileRead;
	}

	//file shouldn't be stereo
	if(wavFormat.wf.nChannels>1)
	{
		Tools_Level_Export::AddMessage(Tools_Level_Export::MT_ERROR,"%s has more than one channel",szInputName);
		goto EndWavFileRead;
	}
	if(wavFormat.wBitsPerSample % 8)
	{
		Tools_Level_Export::AddMessage(Tools_Level_Export::MT_ERROR,"%s doesn't have a multiple of 8 bits per sample",szInputName);
		goto EndWavFileRead;
	}
	if(wavFormat.wBitsPerSample==8)
	{
		Tools_Level_Export::AddMessage(Tools_Level_Export::MT_WARNING,"%s is 8 bit (16 bit is recommended)",szInputName);
	}

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

		SetFilePointer(inputFile,myChunkHeader.chunkLength,0,FILE_CURRENT);

	}while(true);

	//how many samples to read in?
	uNumWavBytesPerSample = wavFormat.wf.nBlockAlign;
	uNumWavSamples = myChunkHeader.chunkLength/uNumWavBytesPerSample;
	fWavSampleRate = (float)wavFormat.wf.nSamplesPerSec;
	inputSamples = uNumWavSamples;

	// (MH - 18.07.2003) pad end of input silence?
	{
		float fWavLength = (float)inputSamples/fWavSampleRate;
		if (fWavLength < fMinLength)
		{
			iNumPadSamples = (int)((fMinLength - fWavLength) * fWavSampleRate);
		}
	}

	inputSamples += iNumPadSamples;

	readBuffer = new unsigned char[inputSamples * uNumWavBytesPerSample];

	//read in the data chunk
	READ_WAV_FILE(readBuffer,uNumWavSamples * uNumWavBytesPerSample);
	
	// add silence
	if (iNumPadSamples)
	{
		for (u_int u = uNumWavSamples * uNumWavBytesPerSample; u < inputSamples * uNumWavBytesPerSample; ++u)
		{
			readBuffer[u] = 0;
		}
	}
	
	okay = true;

EndWavFileRead :

	//finished with the file now
	if(inputFile != INVALID_HANDLE_VALUE) CloseHandle(inputFile);
	if(!okay)
	{
		if(readBuffer) delete [] readBuffer;
		Tools_Level_Export::AddMessage(Tools_Level_Export::MT_ERROR,"Error reading %s",szInputName);
        if (puFailureCode) *puFailureCode = Tools_ConvertVagFile_FailedToReadWav;
		return false;
	}

#ifndef DISABLE_R8BRAIN_DLL
	// -- SK - Do resampling here --
	R8BInputFormat xInFormat;

	xInFormat.IEEE = 0;
	xInFormat.pBuffer = readBuffer;
	xInFormat.iRate = wavFormat.wf.nSamplesPerSec;
	xInFormat.iChannels = wavFormat.wf.nChannels;
	xInFormat.iBitDepth = wavFormat.wBitsPerSample;
	xInFormat.iLength = inputSamples;//myChunkHeader.chunkLength/wavFormat.wf.nBlockAlign;
	int iNewSize = 0;
	if( xInFormat.iRate > iMaxFrequency || wavFormat.wBitsPerSample>16)
	{
		unsigned char* pNewData = (unsigned char*)Tools_Sound_Export::ResampleSound_R8Brain( &xInFormat, iMaxFrequency, 16, &iNewSize);
	
		if( (pNewData != NULL) && (iNewSize > 0))
		{
			inputSamples = iNewSize;

			wavFormat.wf.nSamplesPerSec = iMaxFrequency;
			wavFormat.wBitsPerSample = 16;

			wavFormat.wf.nAvgBytesPerSec = wavFormat.wf.nSamplesPerSec * (wavFormat.wBitsPerSample/8);

			delete []readBuffer;

			readBuffer = pNewData;
		}
	}

#endif // DISABLE_R8BRAIN_DLL
	//determine the number of samples wanted
	u_int outputSamples = inputSamples;
	
	int vagFrequency = wavFormat.wf.nSamplesPerSec;
	if(wavFormat.wf.nSamplesPerSec > (unsigned)iMaxFrequency)
	{
		outputSamples = (int)(outputSamples * ((float)iMaxFrequency/wavFormat.wf.nSamplesPerSec));
		vagFrequency = iMaxFrequency;
	}

	//must be a multiple of 28
	outputSamples += BLKSIZ-1;
	outputSamples -= outputSamples%BLKSIZ;

	float freqScale = outputSamples/(float)inputSamples;

    // check the args for creating part of a sound file
    if (uMaxSamples)
    {
        Asura_Assert(uFirstSample == 0, "");
        Asura_Assert(uMaxSamples % BLKSIZ == 0, "uMaxSamples not multiple of 28");
        if (uMaxSamples < outputSamples)
        {
            // set num output samples
            outputSamples = uMaxSamples;
        }
    }

    u_int uFirstInputSample = 0;
    if (uFirstSample)
    {
        Asura_Assert(uMaxSamples == 0, "");
        Asura_Assert(uFirstSample % BLKSIZ == 0, "uFirstSample not multiple of 28");
        if (uFirstSample > outputSamples)
        {
		    if(readBuffer) delete [] readBuffer;
		    Tools_Level_Export::AddMessage(Tools_Level_Export::MT_INFORMATION,"Skipping conversion of %s. Input file too small.",szInputName);
            if (puFailureCode) *puFailureCode = Tools_ConvertVagFile_FirstSampleTooBig;
            return false;
        }
        else
        {
            outputSamples -= uFirstSample;
			uFirstInputSample = (u_int)(uFirstSample/freqScale);
        }
    }

	//allocate a buffer for the converted wav file
	short* wavBuffer = new short[outputSamples];
	
	//fill in structures for resampling
	Tools_SoundResampleData xInput,xOutput;
	xInput.m_uNumberOfChannels = 1;
	xInput.m_uBitsPerSample = wavFormat.wBitsPerSample;
	xInput.m_uNumberOfSamples = (u_int)((uFirstSample+outputSamples)/freqScale) - uFirstInputSample;

	xOutput.m_uNumberOfChannels = 1;
	xOutput.m_uBitsPerSample = 16;
	xOutput.m_uNumberOfSamples = outputSamples;

	//get the start for the input buffer (skipping samples if needed)
	unsigned char* pucInputStart=readBuffer;
	if(xInput.m_uBitsPerSample==8)
	{
		pucInputStart += uFirstInputSample;
	}
	else
	{
		pucInputStart += 2*uFirstInputSample;
	}

	Tools_Sound_Export::Resample_Sound(xInput,xOutput,pucInputStart,wavBuffer);

	//finished with the input buffer
	delete [] readBuffer;

	VAGheader vagHeader;
	memset(&vagHeader,0,sizeof(vagHeader));
	okay = false;

	//open the vag file for writing
    Tools_FileSystem::EnsureDirectoryExists(szOutputName);
	HANDLE outputFile = CreateFile(szOutputName,GENERIC_READ+GENERIC_WRITE,0,0,CREATE_ALWAYS,FILE_FLAG_RANDOM_ACCESS,0);
	
	if(outputFile == INVALID_HANDLE_VALUE)
	{
		Tools_Level_Export::AddMessage(Tools_Level_Export::MT_ERROR,"Failed to open %s for writing",szOutputName);
		delete [] wavBuffer;
		if (puFailureCode) *puFailureCode = Tools_ConvertVagFile_FailedToCreateVag;
        return false;
	}

	//write a dummy header for now , to be filled in properly later
	WRITE_VAG_FILE(&vagHeader,sizeof(vagHeader));

	//do the encoding
	_EncVagInit( ENC_VAG_MODE_NORMAL );
	short vagBuffer[8];
	
	//write out initial zero block
	memset(vagBuffer,0,16);
	WRITE_VAG_FILE(vagBuffer,16);
	vagHeader.size+=16;

	for(u_int i=0;i<outputSamples;i+=BLKSIZ)
	{
		short blockAttribute;
		if(bLooped)
		{
			if(i==0)
			{
				blockAttribute = ENC_VAG_LOOP_START;
			}
			else if(i+BLKSIZ>=outputSamples)
			{
				blockAttribute = ENC_VAG_LOOP_END;
			}
			else
			{
				blockAttribute = ENC_VAG_LOOP_BODY;
			}
		}
		else
		{
			if(i+BLKSIZ>=outputSamples)
			{
				blockAttribute = ENC_VAG_1_SHOT_END;
			}
			else
			{
				blockAttribute = ENC_VAG_1_SHOT;
			}
		}
		
		_EncVag( wavBuffer+i, vagBuffer, blockAttribute );

		WRITE_VAG_FILE(vagBuffer,16);
		vagHeader.size+=16;
	}

	if(!bLooped)
	{
		//add the terminator block for non-looping bags
		_EncVagFin(vagBuffer);
		WRITE_VAG_FILE(vagBuffer,16);
		vagHeader.size+=16;
	}
	//finally need to fill in the header and write it back at the beginning
	vagHeader.format = lEndian(0x56414770); /* "VAGp" */
	vagHeader.ver = lEndian(0x20L);
	vagHeader.ssa = 0;
	vagHeader.fs = lEndian(vagFrequency);
	vagHeader.size = lEndian(vagHeader.size);

	SetFilePointer(outputFile,0,0,FILE_BEGIN);
	WRITE_VAG_FILE(&vagHeader,sizeof(vagHeader));

	okay = true;
	
EndVagWrite :
	if(outputFile) CloseHandle(outputFile);
	if(wavBuffer) delete [] wavBuffer;
	if(!okay)
	{
		DeleteFile(szOutputName);//kill the partially written file
		Tools_Level_Export::AddMessage(Tools_Level_Export::MT_ERROR,"Error writing %s",szOutputName);
        if (puFailureCode) *puFailureCode = Tools_ConvertVagFile_FailedToWriteVag;
		return false;
	}


	return true;	
}

