/*  DecMPA - simple MPEG Audio decoding library.
    Copyright (C) 2002  Hauke Duden

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library 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
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

	For more information look at the file License.txt in this package.

	email: hazard_hd@users.sourceforge.net
*/

//This file is a heavily modified version of SPlayPlugin.cpp from the original
//mpeglib. See Readme.txt for details.

#include "DefInc.h"
#include "MPADecoder.h"

#include "DecodeEngine.h"

#include <exception>
#include <memory>

#include <math.h>


CMPADecoder::CMPADecoder(const DecMPA_Callbacks& Callbacks,void* pCallbackContext)
{
	pow(6.0,3.0);            // fixes bug in __math.h

	m_Callbacks=Callbacks;
	m_pCallbackContext=pCallbackContext;

	m_pFileAccess=new CDecMPAFileAccess(Callbacks,pCallbackContext);

	m_nResyncCounter=0;

	m_nBufferBytes=0;
	m_nUsedBufferBytes=0;
	m_pBuffer=NULL;

	m_OutputFormat.nType=0;
	m_OutputFormat.nFrequency=0;
	m_OutputFormat.nChannels=0;
	m_bOutputFormatChanged=false;

	memset(&m_MPEGHeader,0,sizeof(m_MPEGHeader));

	m_bDoFloat=false;
	m_pPCMFrame=new PCMFrame(MP3FRAMESIZE);
	m_pFloatFrame=new FloatFrame(MP3FRAMESIZE);
	m_pAudioFrame=NULL;
	
	m_pFormatFrame=new AudioFrame;

	m_nReadTime=0;
	m_nBufferStartTime=0;
	m_nDecodeTime=0;

	m_bPrepared=false;
	m_bHasFrame=false;

	m_bFirstDecode=true;
	
	m_pDecodeEngine=DecodeEngine_Create();

	m_bStoreID3v2=false;
	m_pID3v2Data=(unsigned char*)0;
	m_nID3v2DataSize=0;

	m_pDestroyNotify=(void (*)(void*))0;
	m_pDestroyNotifyContext=(void*)0;

	m_aParams[DECMPA_PARAM_OUTPUT]=DECMPA_OUTPUT_INT16;
	m_aParams[DECMPA_PARAM_PROVIDEID3V2]=DECMPA_NO;

	m_bNotMPEG=false;

	m_bDecoderNeedsFlush=false;
}

CMPADecoder::~CMPADecoder()
{
	if(m_pDestroyNotify!=(void (*)(void*))0)
		m_pDestroyNotify(m_pDestroyNotifyContext);

	delete m_pPCMFrame;
	delete m_pFloatFrame;

	delete m_pFileAccess;
	delete m_pFormatFrame;

	DecodeEngine_Destroy(m_pDecodeEngine);
}

int CMPADecoder::DecodeNoData(long& nDecodedBytes)
{
	return Decode(NULL,-1,nDecodedBytes);
}

int CMPADecoder::Decode(void* pBuffer,long nBufferBytes,long& nBytesDecoded)
{
	int Result=DECMPA_OK;

	if(pBuffer==NULL)
		nBufferBytes=-1;

	nBytesDecoded=0;
	m_bOutputFormatChanged=false;	

	if((Result=EnsurePrepared())!=DECMPA_OK)
		return Result;

	try
	{
		while(!ReadDecodedData(pBuffer,nBufferBytes,nBytesDecoded))
		{
			Result=DecodeNextFrame(pBuffer==NULL);
			if(Result!=DECMPA_OK)
				break;
		}
	}
	catch(std::bad_alloc)
	{
		return DECMPA_ERROR_MEMORY;
	}
	catch(std::exception)
	{
		return DECMPA_ERROR_INTERNAL;
	}

	return Result;
}

int CMPADecoder::DecodeNextFrame(bool bNoData)
{
	int Result=DECMPA_OK;

	//find and read the next frame
	//we may still have one frame buffered that was read to find out
	//the file information. In that case don't read another one
	if(!m_bHasFrame)
		m_bHasFrame=ReadNextFrame();
	if(m_bHasFrame)
	{
		bool bDecodeOK;

		//don't use this frame again
		m_bHasFrame=false;

		//if bNoData==true we don't decode any audio data but only
		//parse the frame headers
		//this allows the user to do a quick pass over the file and
		//get an accurate estimate of the files properties
		//(actual duration, decoded size, etc...).
		if(!bNoData)
		{
			if(m_bDecoderNeedsFlush)
			{
				DecodeEngine_Flush(m_pDecodeEngine);
				m_bDecoderNeedsFlush=false;
			}

			bDecodeOK=DecodeEngine_Decode(m_pDecodeEngine,m_pAudioFrame,m_FrameFinder.GetFrameData(),m_FrameFinder.GetFrameSize());
		}
		else
		{
			MpegAudioHeader* pHeader=m_FrameFinder.GetFrameHeader();
			long nDecodedFrameSize;

			m_bDecoderNeedsFlush=true;

			nDecodedFrameSize=pHeader->getpcmperframe();
			if(pHeader->getInputstereo()==1)
				nDecodedFrameSize*=2;

			//pretend that we have decoded something
			m_pAudioFrame->setLen(nDecodedFrameSize);
			m_pAudioFrame->setFrameFormat(pHeader->getInputstereo(),pHeader->getFrequencyHz());

			//note that this "ghost data" will never be retrieved because the call
			//to ReadDecodedData that follows will always remove all of it
			//(see Decode)

			bDecodeOK=true;
		}
		
		if(bDecodeOK)
		{
			//make the decoded data available to the "user"
			HandleDecodedData(m_pAudioFrame);			
		}
		//ignore if the decoding of the frame failed. Just continue with
		//the next frame
	}
	else
	{
		if(m_FrameFinder.IsStreamInvalid())
			Result=DECMPA_ERROR_DECODE;
		else
		{
			if(m_pFileAccess->IsEndOfFile())
			{
				//end of file reached
				if(!m_FrameFinder.KnowsCharacteristics())
				{
					//the end was reached without being able to find out
					//essential stream characteristics
					//=> not an MPEG Audio file (or one that has less than 4 frames)
					Result=DECMPA_ERROR_DECODE;
				}
				else
					Result=DECMPA_END;
			}
			else
			{
				if(m_pFileAccess->GetLastError()!=DECMPA_OK)
					Result=m_pFileAccess->GetLastError();
				else
				{
					//should never happen - an invalid stream, end of file
					//or a read error are the only things that could cause
					//ReadNextFrame to fail
					Result=DECMPA_ERROR_INTERNAL;
				}
			}
		}
	}

	return Result;
}

void CMPADecoder::Flush()
{
	m_FrameFinder.Flush();
	DecodeEngine_Flush(m_pDecodeEngine);

	m_nBufferBytes=0;
	m_nUsedBufferBytes=0;
}

bool CMPADecoder::ReadDecodedData(void* pDest,long nBytes,long& nRead)
{
	long nAvailBytes=m_nBufferBytes-m_nUsedBufferBytes;

	if(nAvailBytes>0)
	{
		if(nBytes>nAvailBytes || nBytes==-1)
			nBytes=nAvailBytes;
		nBytes&=~m_nOutputBlockSize;	//only full samples

		if(pDest!=NULL)
			memcpy(pDest,m_pBuffer+m_nUsedBufferBytes,nBytes);
		m_nUsedBufferBytes+=nBytes;

		nRead=nBytes;

		m_nReadTime=m_nBufferStartTime+(((double)(m_nUsedBufferBytes/m_nOutputBlockSize))*1000)/m_pFormatFrame->getFrequenceHZ();

		return true;
	}
	else	
		return false;
}

long CMPADecoder::GetFilePositionFromTime(long Millis)
{
	int nResult;

	if((nResult=EnsurePrepared())!=DECMPA_OK)
		return nResult;

	return m_Info.GetFilePositionFromTime(Millis);
}

int CMPADecoder::SeekToTime(long Millis)
{
	int Result=DECMPA_OK;

	if(!m_pFileAccess->CanSeek())
		Result=DECMPA_ERROR_UNSUPPORTED;
	else
	{
		long nFilePos;

		if((Result=EnsurePrepared())==DECMPA_OK)
		{
			nFilePos=m_Info.GetFilePositionFromTime(Millis);
			if(nFilePos==-1)
				Result=DECMPA_ERROR_UNSUPPORTED;
			else
			{
				if(!m_pFileAccess->Seek(nFilePos))
					Result=DECMPA_ERROR_SEEK;
				else
				{
					//throw away buffered data
					Flush();

					if(Millis!=0)
					{
						//initiate resync (skip next 5 frames until back
						//references are ok again)
						m_nResyncCounter=5;
					}

					m_nDecodeTime=Millis;
					m_nBufferStartTime=Millis;				
					m_nReadTime=Millis;
				}
			}
		}
	}

	return Result;
}

int CMPADecoder::GetTime(long& Time)
{
	Time=(long)m_nReadTime;

	return DECMPA_OK;
}

int CMPADecoder::GetDuration(long& Duration)
{
	int nResult;

	try
	{
		if((nResult=EnsurePrepared())!=DECMPA_OK)
			return nResult;

		Duration=m_Info.GetDuration();

		return DECMPA_OK;
	}
	catch(std::bad_alloc)
	{
		return DECMPA_ERROR_MEMORY;
	}
	catch(std::exception)
	{
		return DECMPA_ERROR_INTERNAL;
	}
}

bool CMPADecoder::ReadNextFrame()
{
	while(!m_FrameFinder.ReadNextFrame())
	{
		if(m_FrameFinder.IsStreamInvalid())
			return false;

		if(!m_FrameFinder.ReadInput(m_pFileAccess))
			return false;
	}

	return true;
}

void CMPADecoder::SetOutputFormat(AudioFrame* pNewFormatFrame)
{
	pNewFormatFrame->copyFormat(m_pFormatFrame);

	m_OutputFormat.nFrequency=m_pFormatFrame->getFrequenceHZ();
	m_OutputFormat.nChannels=m_pFormatFrame->getStereo() ? 2 : 1;	

	m_bOutputFormatChanged=true;

	m_nOutputBlockSize=m_pFormatFrame->getSampleSize()/8;
	m_nOutputBlockSize*=m_OutputFormat.nChannels;
}

void CMPADecoder::UpdateMPEGHeader(MpegAudioHeader* pHeader)
{
	memcpy(m_MPEGHeader.aRawData,pHeader->getHeader(),4);
	m_MPEGHeader.bProtection=pHeader->getProtection()!=0;

	m_MPEGHeader.nLayer=pHeader->getLayer();
	m_MPEGHeader.nVersion=pHeader->getVersion();
	m_MPEGHeader.bPadding=pHeader->getPadding()!=0;
	m_MPEGHeader.nFrequencyIndex=pHeader->getFrequency();
	m_MPEGHeader.nFrequency=pHeader->getFrequencyHz();
	m_MPEGHeader.nBitRateIndex=pHeader->getBitrateindex();
	m_MPEGHeader.nExtendedMode=pHeader->getExtendedmode();
	m_MPEGHeader.nMode=pHeader->getMode();
	m_MPEGHeader.bInputStereo=pHeader->getInputstereo()!=0;
	m_MPEGHeader.bMPEG25=pHeader->getLayer25()!=0;

	m_MPEGHeader.nFrameSize=pHeader->getFramesize();
	m_MPEGHeader.nDecodedSamplesPerFrame=pHeader->getpcmperframe();
	m_MPEGHeader.nBitRateKbps=pHeader->GetBitRateKbps();
}

bool CMPADecoder::OutputFormatChanged()
{
	return m_bOutputFormatChanged;
}

void CMPADecoder::GetOutputFormat(DecMPA_OutputFormat& Format)
{
	Format=m_OutputFormat;
}

void CMPADecoder::GetMPEGHeader(DecMPA_MPEGHeader& Header)
{
	Header=m_MPEGHeader;
}


void CMPADecoder::HandleDecodedData(AudioFrame* pDecodedFrame)
{
	int nDecodedSamples;

	if(m_bFirstDecode)
	{
		SetOutputFormat(pDecodedFrame);
		m_bFirstDecode=false;
	}

	nDecodedSamples=pDecodedFrame->getLen();
	if(pDecodedFrame->getStereo()==1)
		nDecodedSamples/=2;

	m_nBufferStartTime=m_nDecodeTime;
	m_nDecodeTime+=(((double)nDecodedSamples)*1000)/pDecodedFrame->getFrequenceHZ();

	if(m_nResyncCounter>0)
	{
		//we need to resync = read a few frames to make sure
		//that the back references are ok again
		//do not output any of the decoded data
		m_nResyncCounter--;
	}
	else
	{
		if(!m_pFormatFrame->isFormatEqual(pDecodedFrame))
			SetOutputFormat(pDecodedFrame);

		UpdateMPEGHeader(m_FrameFinder.GetFrameHeader());

		if(m_bDoFloat)
		{
			m_nBufferBytes=m_pFloatFrame->getLen()*sizeof(float);
			m_pBuffer=(unsigned char*)m_pFloatFrame->getData();
		}
		else
		{
			m_nBufferBytes=m_pPCMFrame->getLen()*sizeof(short);
			m_pBuffer=(unsigned char*)m_pPCMFrame->getData();
		}
		m_nUsedBufferBytes=0;
	}
		
}

int CMPADecoder::EnsurePrepared()
{
	int Result=DECMPA_OK;

	if(!m_bPrepared)
	{
		if(m_bNotMPEG)
			Result=DECMPA_ERROR_DECODE;
		else
		{
			m_bStoreID3v2=(m_aParams[DECMPA_PARAM_PROVIDEID3V2]==DECMPA_YES);

			m_bDoFloat=(m_aParams[DECMPA_PARAM_OUTPUT]==DECMPA_OUTPUT_FLOAT);
			m_nOutputBlockSize=1;
			if(m_bDoFloat)
				m_pAudioFrame=m_pFloatFrame;
			else
				m_pAudioFrame=m_pPCMFrame;

			m_OutputFormat.nType=m_aParams[DECMPA_PARAM_OUTPUT];

			//Skip or store any leading ID3 tags - some tags are not
			//correctly escaped and contain "sync" codes that can confuse
			//the decoder
			HandleID3Tag();

			if(!m_Info.InitInfo(&m_FrameFinder,m_pFileAccess))
			{
				m_bNotMPEG=true;
				Result=DECMPA_ERROR_DECODE;
			}
			else
			{	
				//info reads up to one frame.
				//we can use this in decoding, we only have to remember not
				//to read a fresh one in the first iteration of Decode
				m_bHasFrame=m_FrameFinder.HasFrame();

				m_bPrepared=true;
			}
		}
	}

	return Result;
}

void CMPADecoder::HandleID3Tag()
{
	unsigned char aHeaderData[10];
	struct ID3v2Header
	{
		char sID[3];
		unsigned char Version;
		unsigned char Revision;
		unsigned char Flags;
		unsigned long Size;
	} Header;

	int nBytesRead;
	long UndoID;

	UndoID=m_pFileAccess->StartUndoRecording();

	if((nBytesRead=m_pFileAccess->Read((char*)&aHeaderData,10))==10)
	{
		//we do not read directly into the header because the fields may
		//have been padded by the compiler
		memcpy(Header.sID,aHeaderData,3);
		Header.Version=aHeaderData[3];
		Header.Revision=aHeaderData[4];
		Header.Flags=aHeaderData[5];
		Header.Size=aHeaderData[6];
		Header.Size<<=8;
		Header.Size|=aHeaderData[7];
		Header.Size<<=8;
		Header.Size|=aHeaderData[8];
		Header.Size<<=8;
		Header.Size|=aHeaderData[9];

		//make sure its an ID3 header
		if(strncmp(Header.sID,"ID3",3)==0 && Header.Version!=0xff && Header.Revision!=0xff
			&& (Header.Size & 0x80808080)==0)
		{
			unsigned long TagSize=Header.Size;

			//ok, we have an ID3 tag - no need for undo
			m_pFileAccess->EndUndoRecording(UndoID,false);
			UndoID=-1;
			
			//correct "unsyncing" of tag size
			TagSize=(TagSize & 0x7f) | ((TagSize & 0x7f00)>>1)
						| ((TagSize & 0x7f0000)>>2) | ((TagSize & 0x7f000000)>>3);

			if(m_bStoreID3v2)
			{
				//read ID3 tag

				m_pID3v2Data=new unsigned char[10+TagSize];
				memcpy(m_pID3v2Data,aHeaderData,10);
				
				if(m_pFileAccess->Read(m_pID3v2Data+10,TagSize)==(long)TagSize)
					m_nID3v2DataSize=TagSize+10;
				else
				{
					delete[] m_pID3v2Data;
					m_pID3v2Data=(unsigned char*)0;
				}
			}
			else
			{
				//skip the ID3 tag
				m_pFileAccess->Skip(TagSize);
			}
		}
	}

	if(UndoID!=-1)
	{
		//no ID3 tag found
		//=> undo read operations
		m_pFileAccess->EndUndoRecording(UndoID,true);
	}
}

int CMPADecoder::GetID3v2Data(unsigned char*& pData,long& nDataSize)
{
	int nResult;

	if((nResult=EnsurePrepared())!=DECMPA_OK)
		return nResult;

	if(m_pID3v2Data==(unsigned char*)0)
		return DECMPA_ERROR_NOTAVAILABLE;
	
	pData=m_pID3v2Data;
	nDataSize=m_nID3v2DataSize;

	return DECMPA_OK;
}


int CMPADecoder::SetParam(int ID,long Value)
{
	bool bOK=false;

	if(m_bPrepared)
		return DECMPA_ERROR_WRONGSTATE;
	
	switch(ID)
	{
	case DECMPA_PARAM_OUTPUT:		bOK=(Value==DECMPA_OUTPUT_INT16 || Value==DECMPA_OUTPUT_FLOAT);
		break;
	case DECMPA_PARAM_PROVIDEID3V2:	bOK=(Value==DECMPA_YES || Value==DECMPA_NO);
		break;
	}
	if(!bOK)
		return DECMPA_ERROR_PARAM;

	m_aParams[ID]=Value;
		
	return DECMPA_OK;

}

long CMPADecoder::GetParam(int ID)
{
	if(ID<0 || ID>=DECMPA_PARAMCOUNT)
		return 0;

	return m_aParams[ID];
}