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

// OggVOrbis
//#pragma comment (lib,"ogg_static.lib" )
//#pragma comment (lib,"vorbis_static.lib" )
//#pragma comment (lib,"vorbisfile_static.lib" )


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

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

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

	if(m_hFile.Open(FileName)!=MRESULT_OK){ return 1; }

	ov_callbacks callbacks = {
		fread_emulation,
		fseek_emulation,
		fclose_emulation,
		ftell_emulation
	};
	// ファイルはOggVorbis形式かな？
	int ret = ov_open_callbacks((LPVOID)this, &m_VorbisFile, NULL, 0, callbacks);
	if(ret!=0){ return 2; }

	// シークできないと困る
	if(ov_seekable(&m_VorbisFile)==0){ return 3; }

	m_IsOpen = true;

	return MRESULT_OK;
}

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

	const uint32 len = dwSize*GetBytePerSample();

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

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

	return read/GetBytePerSample();
}

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

	switch( ePos )
	{
	case ISoundDecoder::BEGIN:	{ pos = Offset; }break;
	case ISoundDecoder::END:	{ pos = GetLength()+Offset; }break;
	case ISoundDecoder::CURRENT:{ pos = GetPosition()+Offset; }break;
	}

	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 を参照すること
 */
uint32	CSoundDecoderOggVorbis::GetPosition()		const
{
	DEN_ASSERT( !m_IsOpen, "まだ開いていません" );
	OggVorbis_File* pFile = const_cast<OggVorbis_File*>(&m_VorbisFile);

	const ogg_int64_t len = ov_pcm_tell( pFile );
	return (sint32)len;
}

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

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

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

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

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

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
//! このクラスで読み込みが出来るか調べる
/*!
 *	引数などは ResourceFile::Test を参照すること
 */
MRESULT  CSoundDecoderOggVorbis::Test( const MySTL::wstring& FileName )const
{
//	MySTL::wstring s = CString::tolower(CString::GetExtension(FileName));
	if( CString::tolower(CString::GetExtension(FileName))==L"ogg" ) { return MRESULT_OK; }

/*
	CFileRead hFile;
	if( hFile.Open( FileName )!=0 ) { return 1; }

	uint32 HeadCode;
	hFile.Read( &HeadCode, sizeof(HeadCode) );

	if( HeadCode!=Str2Binary32('O','g','g','S') ) { return 2; }
*/
	return 1;
}


/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
//! <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;

	return pThis->m_hFile.Read( buffer, uint32(size*count) );
}


/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
//! <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( sint32(offset), IFileRead::CURRENT	); }break;
	case SEEK_END: { pThis->m_hFile.Seek( sint32(offset), IFileRead::END		); }break;
	case SEEK_SET: { pThis->m_hFile.Seek( sint32(offset), IFileRead::BEGIN	); }break;
	default:
		return -1;
	}
	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();

	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;
	return pThis->m_hFile.GetPosition();
}
}
