#include"CSoundDecoderOggVorbis.h"
#include"../../../Auxiliary/Debug/CAssert.h"
#include"../../../Auxiliary/CString.h"
#include"../../../Auxiliary/Debug/CTrace.h"



namespace Maid
{
	/*!
	 	@class	CSoundDecoderOggVorbis CSoundDecoderOggVorbis.h
	 	@brief	OggVorbis ファイルエンコーダー
	 */
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
//! コンストラクタ
/*!
 */
CSoundDecoderOggVorbis::CSoundDecoderOggVorbis()
{
	m_IsOpen = false;
}

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
//! デストラクタ
/*!
 *
 */
CSoundDecoderOggVorbis::~CSoundDecoderOggVorbis()
{
	Close();
}

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
//! ファイルのロード
/*!
 	引数などは ISoundDecoder::Open を参照すること
 */
void CSoundDecoderOggVorbis::Open( const mstring& FileName )
{
	Close();

	m_hFile.Open(FileName);

	ov_callbacks callbacks = {
		fread_emulation,
		fseek_emulation,
		fclose_emulation,
		ftell_emulation
	};
	// ファイルはOggVorbis形式かな？
	int ret = ov_open_callbacks((void*)this, &m_VorbisFile, NULL, 0, callbacks);
	MAID_ASSERT( ret!=0, "oggvorbisでないようです" );

	// シークできないと困る
	ret = ov_seekable(&m_VorbisFile);
	MAID_ASSERT( ret==0, "シークできないようです" );


	m_IsOpen = true;
}

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
//! デコードする
/*!
 	引数などは ISoundDecoder::Read を参照すること
 */
int CSoundDecoderOggVorbis::Read( void* pDst, int dwSize )
{
	MAID_ASSERT( !m_IsOpen, "まだ開いていません" );
	unt32           read = 0;
	OggVorbis_File* pFile = &m_VorbisFile;

	const unt32 len = dwSize;

	while(read<len)
	{
		const long ret = ov_read( pFile, (char*)pDst+read, len-read, 0, 2, 1, NULL);
		MAID_ASSERT( ret==OV_HOLE, "OV_HOLE" );
		MAID_ASSERT( ret==OV_EBADLINK, "OV_EBADLINK" );

		read += ret;
		if( ret==0 ) { break; }
	}

	return read;
}

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
//! デコード開始位置を移動する
/*!
 *	引数などは ISoundDecoder::SetPosition を参照すること
 */
void CSoundDecoderOggVorbis::SetPosition( int Offset, POSITION ePos )
{
	MAID_ASSERT( !m_IsOpen, "まだ開いていません" );
	ogg_int64_t pos;

	const int sample_size = GetBitsPerSamples() / 8 * GetChannels();

	switch( ePos )
	{
	case ISoundDecoder::BEGIN:
		{
			pos = Offset/sample_size;
		}break;
	case ISoundDecoder::END:
		{
			const ogg_int64_t len = ov_pcm_total( &m_VorbisFile, -1 );

			pos = int(len) + (Offset/sample_size); 
		}break;
	case ISoundDecoder::CURRENT:
		{
			pos = (GetPosition()+Offset)/sample_size; 
		}break;
	}

//	MAID_TRACE( "SetPosition " << Offset << " " << ePos );

	ov_pcm_seek( &m_VorbisFile, pos );
}

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
//! 読み込んだファイルの解放
/*!
 *	引数などは ISoundDecoder::Close を参照すること
 */
void	CSoundDecoderOggVorbis::Close()
{
	if( m_IsOpen )
	{
		ov_clear(&m_VorbisFile);
		ZERO( &m_VorbisFile, sizeof(m_VorbisFile) );
		m_hFile.Close();
		m_IsOpen = false;
	}
}

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
//! 現在の位置の取得
/*!
 *	引数などは ISoundDecoder::GetPosition を参照すること
 */
int	CSoundDecoderOggVorbis::GetPosition()		const
{
	MAID_ASSERT( !m_IsOpen, "まだ開いていません" );
	OggVorbis_File* pFile = const_cast<OggVorbis_File*>(&m_VorbisFile);

	const int sample_size = GetBitsPerSamples() / 8 * GetChannels();

	const ogg_int64_t len = ov_pcm_tell( pFile );
	return (int)len * sample_size;
}

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
//! コンバート後のファイルの長さを取得する（バイト単位）
/*!
 *	引数などは ISoundDecoder::GetLength を参照すること
 */
int	CSoundDecoderOggVorbis::GetLength()			const
{
	MAID_ASSERT( !m_IsOpen, "まだ開いていません" );
	OggVorbis_File* pFile = const_cast<OggVorbis_File*>(&m_VorbisFile);

	const ogg_int64_t len = ov_pcm_total( pFile, -1 );

	const int bit = GetBitsPerSamples() / 8;
	const int cha = GetChannels();
	const int ret = int(len) * bit * cha;

	return ret;
}

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
//! １秒あたり何サンプルあるかの取得
/*!
 *	引数などは ISoundDecoder::GetSamplesPerSec を参照すること
 */
int	CSoundDecoderOggVorbis::GetSamplesPerSec()	const
{
	MAID_ASSERT( !m_IsOpen, "まだ開いていません" );
	const int ret = ov_info(const_cast<OggVorbis_File*>(&m_VorbisFile), -1)->rate;

	return ret;
}

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
//! チャンネル数(mono,stereo 等)の取得
/*!
 *	引数などは ISoundDecoder::GetChannels を参照すること
 */
int	CSoundDecoderOggVorbis::GetChannels()		const
{
	MAID_ASSERT( !m_IsOpen, "まだ開いていません" );
	const int ret = ov_info(const_cast<OggVorbis_File*>(&m_VorbisFile), -1)->channels & 0xffff;
	return ret;
}

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
//! １サンプルあたりに使用しているビット量の取得
/*!
 *	引数などは ISoundDecoder::GetBitsPerSamples を参照すること
 */
int	CSoundDecoderOggVorbis::GetBitsPerSamples()	const
{
	MAID_ASSERT( !m_IsOpen, "まだ開いていません" );
	return 16;	//	ogg は16固定
}


/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
//! <stdio.h> の fread() のエミュレーション
/*!
 *	fread は実際に読み出した全項目の数を返します。\n
 *	この数が count より小さい場合は、エラーが発生したか、\n
 *	count 数を読み出す前にファイルの終端に達しているはずです。\n
 *	size または count を 0 にすると、fread 関数は 0 を返します。バッファの内容は変わりません。
 *
 *	\param  buffer		[i ]	データの格納場所
 *	\param  size		[i ]	バイト単位の項目サイズ
 *	\param  count		[i ]	読み出す最大項目数
 *	\param  p			[i ]	COggVorbisDecoder* です。キャストして使う
 *
 *	\return 読み込んだサイズ（バイト単位）
 */
size_t CSoundDecoderOggVorbis::fread_emulation(void* buffer, size_t size, size_t count, void *p)
{

	CSoundDecoderOggVorbis* pThis = (CSoundDecoderOggVorbis*)p;
	if(p==NULL)return 0;

	const int ret = pThis->m_hFile.Read( buffer, unt32(size*count) );

//	MAID_TRACE( MAIDTEXT("fread ") << size << " " << count << " " << ret);

	return ret;
}


/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
//! <stdio.h> の fseek() のエミュレーション
/*!
 *	指定された位置にファイル ポインタを移動します
 *
 *	\param  p			[i ]	COggVorbisDecoder* です。キャストして使う
 *	\param  offset		[i ]	origin からのバイト数
 *	\param  origin		[i ]	初期位置
 *
 *
 *	\return 関数の成功で０
 *			関数の失敗で０以外
 */
int CSoundDecoderOggVorbis::fseek_emulation(void* p, ogg_int64_t offset, int origin)
{
	if(p==NULL)return -1;

	CSoundDecoderOggVorbis* pThis = (CSoundDecoderOggVorbis*)p;
	switch(origin){
	case SEEK_CUR: { pThis->m_hFile.Seek( int(offset), IFileRead::POSITION_CURRENT	); }break;
	case SEEK_END: { pThis->m_hFile.Seek( int(offset), IFileRead::POSITION_END		); }break;
	case SEEK_SET: { pThis->m_hFile.Seek( int(offset), IFileRead::POSITION_BEGIN	); }break;
	default:
		return -1;
	}

//	MAID_TRACE( MAIDTEXT("fseek") << int(offset) << " " << origin );
	return 0;
}

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
//! <stdio.h> の fclose() のエミュレーション
/*!
 *	指定したストリームを閉じます
 *
 *	\param  p			[i ]	COggVorbisDecoder* です。キャストして使う
 *
 *	\return 関数の成功で０
 *			関数の失敗で０以外
 */
int CSoundDecoderOggVorbis::fclose_emulation(void* p)
{

	if(p==NULL) return(EOF);

	CSoundDecoderOggVorbis* pThis = (CSoundDecoderOggVorbis*)p;
	pThis->m_hFile.Close();

//	MAID_TRACE( MAIDTEXT("fclose") );
	return 0;
}

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
//! <stdio.h> の ftell() のエミュレーション
/*!
 *	ファイル ポインタの現在位置を得ます
 *
 *	\param  p			[i ]	COggVorbisDecoder* です。キャストして使う
 *
 *	\return 現在のファイル位置
 */
long CSoundDecoderOggVorbis::ftell_emulation(void* p)
{
	if(p==NULL) return -1;

	CSoundDecoderOggVorbis* pThis = (CSoundDecoderOggVorbis*)p;

	const long pos = pThis->m_hFile.GetPosition();

//	MAID_TRACE( MAIDTEXT("ftell ") << pos);
	return pos;
}
}
