/* ***** BEGIN LICENSE BLOCK ***** 
 * Version: RCSL 1.0/RPSL 1.0 
 *  
 * Portions Copyright (c) 1995-2002 RealNetworks, Inc. All Rights Reserved. 
 *      
 * The contents of this file, and the files included with this file, are 
 * subject to the current version of the RealNetworks Public Source License 
 * Version 1.0 (the "RPSL") available at 
 * http://www.helixcommunity.org/content/rpsl unless you have licensed 
 * the file under the RealNetworks Community Source License Version 1.0 
 * (the "RCSL") available at http://www.helixcommunity.org/content/rcsl, 
 * in which case the RCSL will apply. You may also obtain the license terms 
 * directly from RealNetworks.  You may not use this file except in 
 * compliance with the RPSL or, if you have a valid RCSL with RealNetworks 
 * applicable to this file, the RCSL.  Please see the applicable RPSL or 
 * RCSL for the rights, obligations and limitations governing use of the 
 * contents of the file.  
 *  
 * This file is part of the Helix DNA Technology. RealNetworks is the 
 * developer of the Original Code and owns the copyrights in the portions 
 * it created. 
 *  
 * This file, and the files included with this file, is distributed and made 
 * available on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 
 * EXPRESS OR IMPLIED, AND REALNETWORKS HEREBY DISCLAIMS ALL SUCH WARRANTIES, 
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS 
 * FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 
 * 
 * Technology Compatibility Kit Test Suite(s) Location: 
 *    http://www.helixcommunity.org/content/tck 
 * 
 * Contributor(s): 
 *  
 * ***** END LICENSE BLOCK ***** */

// rma includes
#include "ihxtprofile.h"
#ifdef _MAC_MACHO
#include <Carbon/Carbon.h>
#endif
#include "hxcom.h"				

// rta includes 
#include "ihxtbase.h"				

// required for reader 
#include <stdio.h>				
#include <sys/stat.h>
#include "InWAVReader.h"


// wave format constants
const int	WAVE_HEADER_SIZE = 12;
const int	TAG_SIZE = 4;
const int	TAG_DATA_LENGTH_SIZE = 4;
const char* FORMAT_TAG = "fmt ";
const char* DATA_TAG = "data";

/////////////////////////////////////////////////////////////////////////
// Method:
//	CRSInWAVReader ( Fill in as necessary )
// Purpose:
//	constructor
CRSInWAVReader::CRSInWAVReader(void)
	: m_fd( NULL )
	, m_bFileIsOpen( false )
	, m_pLogWriter( NULL)
{
	ResetUncompAudioVars();
}

/////////////////////////////////////////////////////////////////////////
// Method:
//	~CRSInWAVReader ( Fill in as necessary )
// Purpose:
//	destructor
CRSInWAVReader::~CRSInWAVReader(void)
{
	if ( m_bFileIsOpen && m_fd )
	{
		fclose( m_fd );
		m_bFileIsOpen = false;
	}
	HX_RELEASE( m_pLogWriter );
}

/////////////////////////////////////////////////////////////////////////
// Method:
//	ResetUncompAudioVars ( Fill in as necessary )
// Purpose:
//	helper initializer
void CRSInWAVReader::ResetUncompAudioVars()
{
	if ( m_bFileIsOpen && m_fd )
	{
		fclose( m_fd );
		m_bFileIsOpen = false;
	}

	m_uStreams = 0;
	m_uOneWAVFrameSize = 4096;

	m_uPercentDone = 0;
	m_llTotalDuration = 0;

	m_uChannels = 0;
	m_uBitsPerSample  = 0;
	m_uSampleRate  = 0;

	// new
	m_dwTotalBytes = 0;
	m_dwCurrentPosition = 0;
	m_bByteSwap = FALSE;

}

/////////////////////////////////////////////////////////////////////////
// Method:
//	DoOpen 
// Purpose:
//	opens file passed in from agent
HX_RESULT CRSInWAVReader::DoOpen( const char *pURL )
{
	ResetUncompAudioVars();
	HX_RESULT res = HXR_OK;
	
	// open file
	m_fd = fopen( pURL, "rb" );
	if ( !m_fd )
	{
		res = HXR_INVALID_FILE;
		m_bFileIsOpen = false;	
	}
	else
	{
		if (!IsValidSource())
		{
			fclose( m_fd );
			m_fd = NULL;
			res = HXR_ENC_BAD_FILETYPE;
		}
		else
		{
			res  = Open();
			if( HXR_OK != res )
			{
				// error occurred opening so delete the object
				fclose( m_fd );
				m_fd = NULL;
			}
			if(SUCCEEDED(res))
			{
				m_llTotalDuration = ByteOffsetToTime( m_dwTotalBytes );
				m_bFileIsOpen = true;
			}
		}
	}
	return res;
}


/////////////////////////////////////////////////////////////////////////
// Method:
//	IsValidSource ( fill in for your media source )
// Purpose:
//	opens file passed in from agent and checks for validity
BOOL CRSInWAVReader::IsValidSource( )
{
	HX_RESULT res = HXR_FAIL;
    int       nBytes  = WAVE_HEADER_SIZE;	// Number of bytes to read into file
	BOOL      bReturn = TRUE;				// Assume file is valid
	UCHAR* pBuf = new UCHAR[ WAVE_HEADER_SIZE ];

	// read nBytes bytes of data
	INT32 nBytesRead = fread( pBuf, sizeof( UCHAR ), WAVE_HEADER_SIZE, m_fd );
	
	// make sure read made it and we got the bytes we asked for
	if (NULL == pBuf || WAVE_HEADER_SIZE != nBytesRead)
	{
		bReturn = FALSE;
	}

	if (bReturn)
	{
		const void* pRiff = (const void*)pBuf;
		const void* pWave = (const void*)(pBuf+8);

		// look for RIFF and WAVE
		if (memcmp("RIFF",pRiff,4) || memcmp("WAVE",pWave,4))
			bReturn = FALSE;
	}

	// restore the stream pointer to its original position
	if (pBuf)
	{
		MySetPos( 0 );
		HX_VECTOR_DELETE( pBuf );
	}
	HX_VECTOR_DELETE( pBuf );
	return bReturn;
}


/////////////////////////////////////////////////////////////////////////
// Method:
//	Open 
// Purpose:
//	opens file passed in from agent 
HX_RESULT CRSInWAVReader::Open()
{
	//UCHAR*			pBuff   = NULL;        // Pointer to start of buffer  
	UCHAR*			pFmt    = NULL;        // This points to the location of the 'fmt ' tag
	HX_RESULT		err		= HXR_OK;	
	ULONG32*		pNum;

	UCHAR* pBuf = new UCHAR[ WAVE_HEADER_SIZE ];

	// read the first 4 bytes of the wave header to determine
	// if byte swapping is required
	int nBytes  = TAG_SIZE;
	
	int nRead = fread( pBuf, sizeof( UCHAR ), TAG_SIZE, m_fd );
	
	if ( nRead == TAG_SIZE) 
	{
		// Determine if we need to do byte swapping
		pNum = (ULONG32*)pBuf;	
		
		if (*pNum == BIG_ENDIAN_WAV)
			m_bByteSwap = TRUE;

		// locate the format tag 
		ULONG32 ulFmtTagPos = 0;
		int nFmtDataLen = 0;
		err = FindTag( FORMAT_TAG, &ulFmtTagPos, &nFmtDataLen );
		if( HXR_OK == err )
		{
			// reposition file pointer to beginning of format data
			// and read the data (skip the fmt tag and data length value)
			ULONG32 ulFmtDataPos = ulFmtTagPos + TAG_SIZE + TAG_DATA_LENGTH_SIZE;
			nBytes = nFmtDataLen;
			MySetPos( 0 );
			MySetPos( ulFmtDataPos );
			UCHAR* pFmtDataBuffer = new UCHAR[ nBytes ];
			int nReadBytes = fread( pFmtDataBuffer, sizeof( UCHAR ), nBytes, m_fd );
			if( pFmtDataBuffer && ( nFmtDataLen == nBytes ) )
			{
				// get the format information
				PN_WAVE_FORMAT* pFormatStruct = (PN_WAVE_FORMAT*)pFmtDataBuffer;
				UINT16 fmtTag;
				if (m_bByteSwap)
				{
					fmtTag    = WLToBEndian(pFormatStruct->wFormatTag);
					m_uChannels      = WLToBEndian(pFormatStruct->wNumChannels);
					m_uSampleRate = DwLToBEndian(pFormatStruct->dwSamplesPerSec);
					m_uBitsPerSample = WLToBEndian(pFormatStruct->wBitsPerSample);
				}
				else
				{
					fmtTag    =			pFormatStruct->wFormatTag;
					m_uChannels      = pFormatStruct->wNumChannels;
					m_uSampleRate = pFormatStruct->dwSamplesPerSec;
					m_uBitsPerSample = pFormatStruct->wBitsPerSample;
				}

				// Check for valid data. This may be a compressed format we
				// don't understand.
				if( fmtTag != PN_WAVE_FORMAT_PCM )
				{
					// We only support WAV formatted data.
					err = HXR_INVALID_FILE;
					char szMessage[256];
					sprintf(szMessage, "WAV write can\'t handle format tag %u", (char*)&fmtTag);
					m_pLogWriter->LogMessage( kRealNetworks, LC_SDK_WARN, FILEREAD, NO_TRANSLATE, szMessage);
				}
				else if( m_uChannels > 0 && m_uSampleRate > 0 
					&& m_uBitsPerSample > 0 )
				{
					// locate the 'data' tag
					ULONG32 ulDataTagPos = 0;
					int nDataLen = 0;
					err = FindTag( DATA_TAG, &ulDataTagPos, &nDataLen );
					if( HXR_OK == err )
					{
						if( nDataLen > 0 )
						{
							// reposition file pointer and read up to the beginning
							// of the data area in the file
							ULONG32 ulDataPos = ulDataTagPos + TAG_SIZE + TAG_DATA_LENGTH_SIZE;
							MySetPos( ulDataPos );
							m_dwTotalBytes = nDataLen;
						}
						else 
						{
							// a data length of zero is not allowed
							err = HXR_NO_DATA;
							m_pLogWriter->LogMessage( kRealNetworks, LC_APP_WARN, FILEREAD, NO_TRANSLATE, "WAV reader read file with zero data length");
						}
					}
				}
				else 
				{
					// We will not be able to encode this file.
					err = HXR_INVALID_FILE;
				}

			}
			
			HX_VECTOR_DELETE( pFmtDataBuffer );	
		}

	}					
	HX_VECTOR_DELETE( pBuf );
    return err;
}


////////////////////////////////////////////////////////////////
//
//	Method:
//
//		CPNAudioWAVFile::FindTag
//
//	Access:
//
//		protected
//
//	Description:
//
//		Searches the wave file for the requested tag and
//		returns its position in the file and length of the
//		data associated with the tag.
//
//	Parameters:
//		
//		pcszTag (in)	- name of the tag, i.e. "fmt "
//		pulPos (out)	- position of the tag in the file
//		nDataLen (out) - length of the data following the flag
//
//	Returns:
//
//		HX_RESULT	- if successful, HXR_OK
//					- PN_INVALID_WAVE_FILE if tag is not found
//
HX_RESULT CRSInWAVReader::FindTag( const char* pcszTag, ULONG32* pulPos, int* pnDataLen )
{
	ULONG32*	pulNum = NULL;
	ULONG32		ulCurTagPos = 0;
	ULONG32		ulNextTagPos = 0;
	int			nCurTagDataLen = 0;
	HX_RESULT	err = HXR_ENC_UNKNOWN_FILE;
	
	// initialize parameters in case of error
	*pulPos = 0;
	*pnDataLen = 0;

	// reposition file pointer to beginning of the file
	MySetPos( 0 ); //&pos

	// seek to first tag past the header 
	//m_pSio->read_seek( WAVE_HEADER_SIZE, SEEK_SET );
	MySetPos( WAVE_HEADER_SIZE ); //&pos
	ulCurTagPos = WAVE_HEADER_SIZE;
	// get the offset value of the tag
	//ulCurTagPos = m_pSio->read_offset();

	// read the tag
	int	nBytes = TAG_SIZE;
	UCHAR* pBuf = new UCHAR[ nBytes ];
	fread( pBuf, sizeof( UCHAR ), nBytes, m_fd );

	// while the tag requested is not found, seek to
	// the next one and compare it
	while( pBuf && ( memcmp(pBuf, pcszTag, TAG_SIZE) != 0 ) )
	{
		// free the buffer for the next allocation
		HX_VECTOR_DELETE( pBuf );

		// get the tag's data length value
		GetLengthTagData( ulCurTagPos, &nCurTagDataLen );

		if( nCurTagDataLen >= 0 )
		{
			// seek past data length value and the actual data to the next tag
			ULONG32 ulNextTagPos = ulCurTagPos + TAG_SIZE + TAG_DATA_LENGTH_SIZE + nCurTagDataLen;

			// get the next potential tag
			nBytes = TAG_SIZE;
			MySetPos( ulNextTagPos ); //&pos
			pBuf = new UCHAR[ nBytes ];
			int nReadBytes = fread( pBuf, sizeof( UCHAR ), nBytes, m_fd );
			
			// get the offset value of the tag
			ulCurTagPos = ulNextTagPos;
		}
	}

	if (pBuf != NULL)
	{
		// get the tag's data length value
		err = GetLengthTagData( ulCurTagPos, pnDataLen );
		*pulPos = ulCurTagPos;
	}
	HX_VECTOR_DELETE( pBuf );
	return err;
}


////////////////////////////////////////////////////////////////
//
//	Method:
//
//		CRSInWAVReader::GetLengthTagData
//
//	Access:
//
//		protected
//
//	Description:
//
//		Calculates the length of the data associated with the
//		current tag.
//
//	Parameters:
//		
//		ulTagPos (in)	- position of the tag in the file
//		pnDataLen (out) - length of the data following the flag
//
//	Returns:
//
//		HX_RESULT	- if successful, HXR_OK
//					- PN_INVALID_WAVE_FILE if data length could
//					  could not be found.
//
HX_RESULT CRSInWAVReader::GetLengthTagData( ULONG32 ulTagPos, int* pnDataLen )
{	
	HX_RESULT	err = HXR_NO_DATA;
	*pnDataLen = 0;

	// seek to position following the tag and read the length value
	int nBytes = TAG_DATA_LENGTH_SIZE;
	UCHAR* pBuf = new UCHAR[ nBytes ];
	MySetPos( ulTagPos + TAG_SIZE ); //&pos
	int nBytesRead = fread( pBuf, sizeof( UCHAR ), nBytes, m_fd );
	if( pBuf && ( TAG_DATA_LENGTH_SIZE == nBytesRead ) )
	{
		ULONG32* pulNum = (ULONG32*)pBuf;

		// calculate the length of the data associated with this tag
		*pnDataLen = (m_bByteSwap) ? DwLToBEndian(*pulNum) : *pulNum;
		
		// free the buffer
		HX_VECTOR_DELETE( pBuf );
		err = HXR_OK;
	}
	HX_VECTOR_DELETE( pBuf );

	return err;
}

 		
/////////////////////////////////////////////////////////////////////////
// Method:
//	ByteOffsetToTime
// Purpose:
//	millisecond converter
ULONG32 CRSInWAVReader::ByteOffsetToTime(ULONG32 cBytes)
{
	ULONG32 ulMillis = 0;
	
	if (GetActualFrameSize() && m_uSampleRate)
	{
		ULONG32 coeff = 1000, divider = 1;
		while(cBytes/GetActualFrameSize() >= _MAX_LONG / coeff)
		{
			divider *= 10;
			coeff /= 10;
		}

	    ulMillis = (ULONG32)(coeff * (cBytes/(float)GetActualFrameSize()) / (m_uSampleRate / (float)divider));
	}
	return ulMillis;
}


/////////////////////////////////////////////////////////////////////////
// Method:
//	ReadFile
// Purpose:
//	Encapsulates reading functionality for filter layer above
UINT32 CRSInWAVReader::ReadFile( UCHAR *pBuffer, UINT32 uBufferSize, HXT_TIME *pStart, HXT_TIME *pEnd )
{
	*pStart = ByteOffsetToTime( m_dwCurrentPosition );
	UINT32 uRead = fread( pBuffer, sizeof( UCHAR ), uBufferSize, m_fd );
	*pEnd = ByteOffsetToTime( m_dwCurrentPosition += uRead );
	return uRead;
}

/////////////////////////////////////////////////////////////////////////
// Method:
//	ReaderClose
// Purpose:
//	closes file
HX_RESULT CRSInWAVReader::ReaderClose()
{
	if ( m_bFileIsOpen && m_fd )
	{
		fclose( m_fd );
		m_fd = NULL;
	}
	m_bFileIsOpen = false;
	return HXR_OK;
}


/////////////////////////////////////////////////////////////////////////
// Method:
//	MySetPos 
// Purpose:
//	hide cross platform setpos functionality
void CRSInWAVReader::MySetPos( UINT32 uPos )
{
	fseek( m_fd, uPos, SEEK_SET );
//#if defined ( WIN32 ) || defined ( _WIN32 )	
//	INT64 pos = uPos;
//	fsetpos( m_fd, &pos );

//#elif defined( UNIX )
//	__off64_t pos = uPos;
//	fpos_t fpos;
//	fpos.__pos == pos;
//	fsetpos( m_fd, &fpos );
//#endif
}


/////////////////////////////////////////////////////////////////////////
// Method:
//	ENDIAN Helpers

// Little to Big Endian word swap
UINT16 CRSInWAVReader::WLToBEndian(UINT16 wHost) 
{
	UINT16 wNet;
	wNet = ((wHost >> 8) | (wHost << 8)) & 0xffff;
	return wNet;
} 	


// Little to Big Endian double word swap
ULONG32 CRSInWAVReader::DwLToBEndian(ULONG32 dwHost) 
{
	ULONG32 dwNet;
	dwNet =  (dwHost >> 24) | 
			((dwHost >> 8) & 0xff00) | 
			((dwHost << 8) & 0xff0000) | 
			(dwHost << 24);

	return dwNet;
} 	

	
/////////////////////////////////////////////////////////////////////////
// Method:
//	SetLogWriter 
// Purpose:
//	provide logging functionality for all levels of a plugin
HX_RESULT CRSInWAVReader::SetLogWriter(IHXTLogWriter* pILogWriter)
{
	if( pILogWriter != NULL )
	{
		m_pLogWriter = pILogWriter;
		m_pLogWriter->AddRef();
		return HXR_OK;
	}
	return HXR_FAIL;
}

