/*
===========================================================================

Doom 3 GPL Source Code
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.

This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").

Doom 3 Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Doom 3 Source Code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Doom 3 Source Code.  If not, see <http://www.gnu.org/licenses/>.

In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code.  If not, please request a copy in writing from id Software at the address below.

If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.

===========================================================================
*/

#include "sys/platform.h"
#include "framework/FileSystem.h"

#include "sound/snd_local.h"

//-----------------------------------------------------------------------------
// Name: idWaveFile::idWaveFile()
// Desc: Constructs the class.  Call Open() to open a wave file for reading.
//       Then call Read() as needed.  Calling the destructor or Close()
//       will close the file.
//-----------------------------------------------------------------------------
idWaveFile::idWaveFile( void ) {
	memset( &mpwfx, 0, sizeof( waveformatextensible_t ) );
	mhmmio		= NULL;
	mdwSize		= 0;
	mseekBase	= 0;
	mbIsReadingFromMemory = false;
	mpbData		= NULL;
	ogg			= NULL;
	isOgg		= false;
}

//-----------------------------------------------------------------------------
// Name: idWaveFile::~idWaveFile()
// Desc: Destructs the class
//-----------------------------------------------------------------------------
idWaveFile::~idWaveFile( void ) {
	Close();

	if ( mbIsReadingFromMemory && mpbData ) {
		Mem_Free( mpbData );
	}

	memset( &mpwfx, 0, sizeof( waveformatextensible_t ) );
}

//-----------------------------------------------------------------------------
// Name: idWaveFile::Open()
// Desc: Opens a wave file for reading
//-----------------------------------------------------------------------------
int idWaveFile::Open( const char* strFileName, waveformatex_t* pwfx ) {

	mbIsReadingFromMemory = false;

	mpbData     = NULL;
	mpbDataCur  = mpbData;

	if( strFileName == NULL ) {
		return -1;
	}

	idStr name = strFileName;

	// note: used to only check for .wav when making a build
	name.SetFileExtension( ".ogg" );
	if ( fileSystem->ReadFile( name, NULL, NULL ) != -1 ) {
		return OpenOGG( name, pwfx );
	}

	memset( &mpwfx, 0, sizeof( waveformatextensible_t ) );

	mhmmio = fileSystem->OpenFileRead( strFileName );
	if ( !mhmmio ) {
		mdwSize = 0;
		return -1;
	}
	if ( mhmmio->Length() <= 0 ) {
		mhmmio = NULL;
		return -1;
	}
	if ( ReadMMIO() != 0 ) {
		// ReadMMIO will fail if its an not a wave file
		Close();
		return -1;
	}

	mfileTime = mhmmio->Timestamp();

	if ( ResetFile() != 0 ) {
		Close();
		return -1;
	}

	// After the reset, the size of the wav file is mck.cksize so store it now
	mdwSize = mck.cksize / sizeof( short );
	mMemSize = mck.cksize;

	if ( mck.cksize != 0xffffffff ) {
		if ( pwfx ) {
			memcpy( pwfx, (waveformatex_t *)&mpwfx, sizeof(waveformatex_t));
		}
		return 0;
	}
	return -1;
}

//-----------------------------------------------------------------------------
// Name: idWaveFile::OpenFromMemory()
// Desc: copy data to idWaveFile member variable from memory
//-----------------------------------------------------------------------------
int idWaveFile::OpenFromMemory( short* pbData, int ulDataSize, waveformatextensible_t* pwfx ) {
	mpwfx       = *pwfx;
	mulDataSize = ulDataSize;
	mpbData     = pbData;
	mpbDataCur  = mpbData;
	mdwSize		= ulDataSize / sizeof( short );
	mMemSize	= ulDataSize;
	mbIsReadingFromMemory = true;

	return 0;
}

//-----------------------------------------------------------------------------
// Name: idWaveFile::ReadMMIO()
// Desc: Support function for reading from a multimedia I/O stream.
//       mhmmio must be valid before calling.  This function uses it to
//       update mckRiff, and mpwfx.
//-----------------------------------------------------------------------------
int idWaveFile::ReadMMIO( void ) {
	mminfo_t		ckIn;           // chunk info. for general use.
	pcmwaveformat_t pcmWaveFormat;  // Temp PCM structure to load in.

	memset( &mpwfx, 0, sizeof( waveformatextensible_t ) );

	mhmmio->Read( &mckRiff, 12 );
	assert( !isOgg );
	mckRiff.ckid = LittleLong( mckRiff.ckid );
	mckRiff.cksize = LittleLong( mckRiff.cksize );
	mckRiff.fccType = LittleLong( mckRiff.fccType );
	mckRiff.dwDataOffset = 12;

	// Check to make sure this is a valid wave file
	if( (mckRiff.ckid != fourcc_riff) || (mckRiff.fccType != mmioFOURCC('W', 'A', 'V', 'E') ) ) {
		return -1;
	}

	// Search the input file for for the 'fmt ' chunk.
	ckIn.dwDataOffset = 12;
	do {
		if (8 != mhmmio->Read( &ckIn, 8 ) ) {
			return -1;
		}
		assert( !isOgg );
		ckIn.ckid = LittleLong( ckIn.ckid );
		ckIn.cksize = LittleLong( ckIn.cksize );
		ckIn.dwDataOffset += ckIn.cksize-8;
	} while (ckIn.ckid != mmioFOURCC('f', 'm', 't', ' '));

	// Expect the 'fmt' chunk to be at least as large as <PCMWAVEFORMAT>;
	// if there are extra parameters at the end, we'll ignore them
	if( ckIn.cksize < sizeof(pcmwaveformat_t) ) {
		return -1;
	}

	// Read the 'fmt ' chunk into <pcmWaveFormat>.
	if( mhmmio->Read( &pcmWaveFormat, sizeof(pcmWaveFormat) ) != sizeof(pcmWaveFormat) ) {
		return -1;
	}
	assert( !isOgg );
	pcmWaveFormat.wf.wFormatTag = LittleShort( pcmWaveFormat.wf.wFormatTag );
	pcmWaveFormat.wf.nChannels = LittleShort( pcmWaveFormat.wf.nChannels );
	pcmWaveFormat.wf.nSamplesPerSec = LittleLong( pcmWaveFormat.wf.nSamplesPerSec );
	pcmWaveFormat.wf.nAvgBytesPerSec = LittleLong( pcmWaveFormat.wf.nAvgBytesPerSec );
	pcmWaveFormat.wf.nBlockAlign = LittleShort( pcmWaveFormat.wf.nBlockAlign );
	pcmWaveFormat.wBitsPerSample = LittleShort( pcmWaveFormat.wBitsPerSample );

	// Copy the bytes from the pcm structure to the waveformatex_t structure
	memcpy( &mpwfx, &pcmWaveFormat, sizeof(pcmWaveFormat) );

	// Allocate the waveformatex_t, but if its not pcm format, read the next
	// word, and thats how many extra bytes to allocate.
	if( pcmWaveFormat.wf.wFormatTag == WAVE_FORMAT_TAG_PCM ) {
		mpwfx.Format.cbSize = 0;
	} else {
		return -1;	// we don't handle these (32 bit wavefiles, etc)
#if 0
		// Read in length of extra bytes.
		word cbExtraBytes = 0L;
		if( mhmmio->Read( (char*)&cbExtraBytes, sizeof(word) ) != sizeof(word) )
			return -1;

		mpwfx.Format.cbSize = cbExtraBytes;

		// Now, read those extra bytes into the structure, if cbExtraAlloc != 0.
		if( mhmmio->Read( (char*)(((byte*)&(mpwfx.Format.cbSize))+sizeof(word)), cbExtraBytes ) != cbExtraBytes ) {
			memset( &mpwfx, 0, sizeof( waveformatextensible_t ) );
			return -1;
		}
#endif
	}

	return 0;
}

//-----------------------------------------------------------------------------
// Name: idWaveFile::ResetFile()
// Desc: Resets the internal mck pointer so reading starts from the
//       beginning of the file again
//-----------------------------------------------------------------------------
int idWaveFile::ResetFile( void ) {
	if( mbIsReadingFromMemory ) {
		mpbDataCur = mpbData;
	} else  {
		if( mhmmio == NULL ) {
			return -1;
		}

		// Seek to the data
		if( -1 == mhmmio->Seek( mckRiff.dwDataOffset + sizeof(fourcc), FS_SEEK_SET ) ) {
			return -1;
		}

		// Search the input file for for the 'fmt ' chunk.
		mck.ckid = 0;
		do {
			byte ioin;
			if ( !mhmmio->Read( &ioin, 1 ) ) {
				return -1;
			}
			mck.ckid = (mck.ckid>>8) | (ioin<<24);
		} while (mck.ckid != mmioFOURCC('d', 'a', 't', 'a'));

		mhmmio->Read( &mck.cksize, 4 );
		assert( !isOgg );
		mck.cksize = LittleLong( mck.cksize );
		mseekBase = mhmmio->Tell();
	}

	return 0;
}

//-----------------------------------------------------------------------------
// Name: idWaveFile::Read()
// Desc: Reads section of data from a wave file into pBuffer and returns
//       how much read in pdwSizeRead, reading not more than dwSizeToRead.
//       This uses mck to determine where to start reading from.  So
//       subsequent calls will be continue where the last left off unless
//       Reset() is called.
//-----------------------------------------------------------------------------
int idWaveFile::Read( byte* pBuffer, int dwSizeToRead, int *pdwSizeRead ) {

	if ( ogg != NULL ) {

		return ReadOGG( pBuffer, dwSizeToRead, pdwSizeRead );

	} else if ( mbIsReadingFromMemory ) {

		if( mpbDataCur == NULL ) {
			return -1;
		}
		if( (byte*)(mpbDataCur + dwSizeToRead) > (byte*)(mpbData + mulDataSize) ) {
			dwSizeToRead = mulDataSize - (int)(mpbDataCur - mpbData);
		}
		SIMDProcessor->Memcpy( pBuffer, mpbDataCur, dwSizeToRead );
		mpbDataCur += dwSizeToRead;

		if ( pdwSizeRead != NULL ) {
			*pdwSizeRead = dwSizeToRead;
		}

		return dwSizeToRead;

	} else {

		if( mhmmio == NULL ) {
			return -1;
		}
		if( pBuffer == NULL ) {
			return -1;
		}

		dwSizeToRead = mhmmio->Read( pBuffer, dwSizeToRead );
		// this is hit by ogg code, which does it's own byte swapping internally
		if ( !isOgg ) {
			LittleRevBytes( pBuffer, 2, dwSizeToRead / 2 );
		}

		if ( pdwSizeRead != NULL ) {
			*pdwSizeRead = dwSizeToRead;
		}

		return dwSizeToRead;
	}
}

//-----------------------------------------------------------------------------
// Name: idWaveFile::Close()
// Desc: Closes the wave file
//-----------------------------------------------------------------------------
int idWaveFile::Close( void ) {
	if ( ogg != NULL ) {
		return CloseOGG();
	}
	if( mhmmio != NULL ) {
		fileSystem->CloseFile( mhmmio );
		mhmmio = NULL;
	}
	return 0;
}

//-----------------------------------------------------------------------------
// Name: idWaveFile::Seek()
//-----------------------------------------------------------------------------
int idWaveFile::Seek( int offset ) {

	if ( ogg != NULL ) {

		common->FatalError( "idWaveFile::Seek: cannot seek on an OGG file\n" );

	} else if( mbIsReadingFromMemory ) {

		mpbDataCur = mpbData + offset;

	} else {
		if( mhmmio == NULL ) {
			return -1;
		}

		if ((int)(offset+mseekBase) == mhmmio->Tell()) {
			return 0;
		}
		mhmmio->Seek( offset + mseekBase, FS_SEEK_SET );
		return 0;
	}
	return -1;
}