/**
 * @file
 * @brief Sound関連の実装クラス・それに関する定数定義などを行う。
 * 
 * DirectSound DirectMusic を使用して実装しています。
 * @author S.F.
 * @version $Id:
 *
 * Copyright (C) 2000-2002 Satoshi Fujiwara. All Rights Reserved.
 */

// メモリーリーク検出用
#include "sfdebug.h"

// SYSTEM INCLUDES
//
#define WIN32_LEAN_AND_MEAN
#include "windows.h"
#include "windowsx.h"
//#include <dmusici.h>
#include "Xact3.h"
#include "dxerr8sf.h"
#include <string>

// PROJECT INCLUDES
//
#include "exception.h"
#include "System.h"
#include "Input.h"
#include "Sound.h"
#include "Console.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

#include "SoundImpl.h"

// namespace 
using namespace sf::system::sound;

// ---------------------------------------------------------------------------
//
// SoundImpl クラス
//
// ---------------------------------------------------------------------------

struct SoundImpl::impl 
{
	// LIFECYCLE
public:
	/// コンストラクタ
	impl()
	{
		mbEnabled = false;
		mpLoader = NULL;
		mpPerformance = NULL;
		mpDefaultAudioPath = NULL;

		for(int i = 0;i < OBJECT_MAX;i++)
		{
			mpSegment[i] = NULL;
			mpAudioPath[i] = NULL;
			mbSegInUse[i] = false;
			mFileName[i] = "";
		}
		
		mVolumeMax = volume::MAX;
		mMasterVolume = volume::MIN;

		initialize();

	};
	/// デストラクタ
	~impl()
	{
		unload();
		
		if(mpDefaultAudioPath)
			mpDefaultAudioPath->Release();

		if(mpLoader){
			mpLoader->Release();
			mpLoader = NULL;
		}
		
		if(mpPerformance)
		{
			mpPerformance->CloseDown();
			mpPerformance->Release();
			mpPerformance = NULL;
			
		}
	};
	// OPERATORS //
	// OPERATIONS //
	void load(const int objNo,const char * const pFileName)
	{
		if(!isEnabled())
			return;

		if(objNo > OBJECT_MAX && objNo < 0){
			throw RecoverbleErrorException(L"Sound Buffer is full",__FILE__,__LINE__,sf::system::Exception::BUFFER_IS_FULL);
		}
		
		mpPerformance->Stop(NULL,NULL,0,0);
		
		if(mpSegment[objNo]){
			mpSegment[objNo]->Release();
		}
		
		// Load the segment from the file
		WCHAR wstrFileName[MAX_PATH];
		MultiByteToWideChar( CP_ACP, 0, pFileName, -1, 
			wstrFileName, MAX_PATH );
		
		if( FAILED( mpLoader->LoadObjectFromFile( CLSID_DirectMusicSegment,
			IID_IDirectMusicSegment8,
			wstrFileName,
			(LPVOID*) &mpSegment[objNo] ) ) )
		{
			throw FatalErrorException("Media not found",__FILE__,__LINE__);
		}
		
		IUnknown* pconfig = NULL;
	    
		if( SUCCEEDED( mpSegment[objNo]->GetAudioPathConfig( &pconfig ) ) )
		{
			mpPerformance->CreateAudioPath( pconfig, TRUE, &mpAudioPath[objNo] );
			pconfig->Release();
			mpSegment[objNo]->Download(mpAudioPath[objNo]);
		} else {
			mpSegment[objNo]->Download(mpPerformance);
		}

		char *pf = strupr(strdup(const_cast<char *>(pFileName)));
		mFileName[objNo] = pf;
		free(pf);
	};
	const int load(const char * const pFileName)
	{

		std::string tmp_str;
		char *pf = strupr(strdup(const_cast<char *>(pFileName)));
		tmp_str = pf;
		free(pf);
		int i;

		// 同じファイル名のオブジェクトがすでにロード済みか調査
		// あればその番号を返す 
		for(i = 0;i < OBJECT_MAX;i++){
			if(mFileName[i] == tmp_str){
				return i;
			}
		}

		// 同じ物が無ければ、空きを検索してロード
		for(i = 0;i < OBJECT_MAX;i++){
			if(mpSegment[i] == NULL){
				load(i,pFileName);
				return i;
			}
		}

		return LOAD_ERROR;

	}
	void unload()
	{
		if(mpPerformance)
			mpPerformance->Stop(NULL,NULL,0,0);
		
		for(int i = 0;i < OBJECT_MAX;i++)
		{
			// セグメントがNULLでないならリリース
			if(mpSegment[i]){
				// オーディオパスのリリース
				if(mpAudioPath[i]){
					mpSegment[i]->Unload(mpAudioPath[i]);
					mpAudioPath[i]->Release();
					mpAudioPath[i] = NULL;
					mpSegment[i]->Release();
				} else {
					mpSegment[i]->Unload(mpPerformance);
					mpSegment[i]->Release();
				}
			}
			mpSegment[i] = NULL;
			mbSegInUse[i] = false;
		}
	}
	// サウンドの再生
	void play(const int ObjNo,const DWORD Repeat,const bool bPrimary,const bool bSync)
	{
		if(isEnabled() && !mbSegInUse[ObjNo] && mpSegment[ObjNo])
		{
			if(mQueue.set(ObjNo,Repeat,bPrimary,bSync))
				mbSegInUse[ObjNo] = true;
		}
	}
	void stop(const int ObjNo = 0)
	{
		if(!isEnabled()) return;

		if(!ObjNo)
		{
			mQueue.clear();
			mpPerformance->Stop(NULL,NULL,0,0);
		} else {
			mpPerformance->Stop(mpSegment[ObjNo],NULL,0,0);
		}
	}
	void update()
	{
		if(!mQueue.isEmpty()){
			Queue::Data data;
			mQueue.get(data);

			if(!isEnabled())
				return;

			IDirectMusicAudioPath8* paudio_path = mpAudioPath[data.mObjNo];
			
			if(paudio_path == NULL)
				paudio_path = mpDefaultAudioPath;
			
			DWORD flags = NULL;

			if(!data.mbPrimary){
				flags |= DMUS_SEGF_SECONDARY;
	//			flags |= DMUS_SEGF_CONTROL;
			}

			if(data.mbSync){
				flags |= DMUS_SEGF_BEAT;
	//			flags &= !DMUS_SEGF_CONTROL;
			}

			if(!data.mRepeat)
				mpSegment[data.mObjNo]->SetRepeats(DMUS_SEG_REPEAT_INFINITE);
			else
				mpSegment[data.mObjNo]->SetRepeats(data.mRepeat - 1);

			

			if(mpSegment[data.mObjNo]){
				mpPerformance->PlaySegmentEx(
					mpSegment[data.mObjNo],  // 再生するセグメント。
					NULL,       // ソングに使用するパラメータ。実装されていない。
					NULL,       // トランジションに関するパラメータ。 
					flags,		// フラグ。
					0,          // 開始タイム。0 は直ちに開始。
					NULL,       // セグメント状態を受け取るポインタ。
					NULL,       // 停止するオブジェクト。
					paudio_path         // デフォルトでない場合はオーディオパス。
					);
				mbSegInUse[data.mObjNo] = false;
			}
		}
	
	}
	// ACCESS //
	void masterVolume(const int value)
	{
		if(!isEnabled())
			return;
		int tmp_value = value;

		if(tmp_value > mVolumeMax) tmp_value = mVolumeMax;
		if(tmp_value < volume::MIN) tmp_value = volume::MIN;

		mMasterVolume = tmp_value;
		mpDefaultAudioPath->SetVolume(value,0);
	}
	const int masterVolume() const {return mMasterVolume;}
	void volumeMax(const int value){mVolumeMax = value;};
	const int volumeMax() const {return mVolumeMax;};
	// INQUIRY //
	const bool isEnabled(void) const{return mbEnabled;};
	// 初期化
	void initialize(void)
	{
		HRESULT hr = E_FAIL;
		std::string errTmp = "Sound Initialize Error:";

		CoCreateInstance(CLSID_DirectMusicLoader, NULL, 
			CLSCTX_INPROC, IID_IDirectMusicLoader8,
			(void**)&mpLoader);
		
		CoCreateInstance(CLSID_DirectMusicPerformance, NULL,
			CLSCTX_INPROC, IID_IDirectMusicPerformance8,
			(void**)&mpPerformance );
		
		// Initialize the performance with the standard audio path.
		// This initializes both DirectMusic and DirectSound and 
		// sets up the synthesizer. 
		hr = mpPerformance->InitAudio( NULL, NULL, NULL, 
			DMUS_APATH_SHARED_STEREOPLUSREVERB, 64,
			0, NULL );
		
		if(FAILED(hr)){
			errTmp += DXGetErrorString8(hr);
			throw FatalErrorException(errTmp,__FILE__,__LINE__);
		}
		
		char strPath[MAX_PATH] = ".\\media\\";
		
		// Tell DirectMusic where the default search path is
		WCHAR wstrSearchPath[MAX_PATH];
		MultiByteToWideChar( CP_ACP, 0, strPath, -1, 
			wstrSearchPath, MAX_PATH );
		
		hr = mpLoader->SetSearchDirectory( GUID_DirectMusicAllTypes, 
			wstrSearchPath, FALSE );
		
		if(FAILED(hr)){
			errTmp += DXGetErrorString8(hr);
			throw FatalErrorException(errTmp,__FILE__,__LINE__);
		}
		
		// デフォルトのオーディオパスの設定
		hr = mpPerformance->CreateStandardAudioPath(
			DMUS_APATH_DYNAMIC_STEREO  , 
			64, 
			TRUE,
			&mpDefaultAudioPath);
		
		if(FAILED(hr)){
			errTmp += DXGetErrorString8(hr);
			throw FatalErrorException(errTmp,__FILE__,__LINE__);
		}
		
		mpDefaultAudioPath->SetVolume(mMasterVolume,0);
 		mbEnabled = true;
	};

private:


	IDirectMusicLoader8*		mpLoader;///< ローダー 
	IDirectMusicPerformance8*	mpPerformance;///< パフォーマンス
	IDirectMusicSegment8*		mpSegment[OBJECT_MAX];///< セグメント配列
	IDirectMusicAudioPath8*		mpAudioPath[OBJECT_MAX];///< オーディオパス
	IDirectMusicAudioPath8*		mpDefaultAudioPath;///< 既定のオーディオパス
	std::string					mFileName[OBJECT_MAX];///< サウンドのPATH配列

	bool mbSegInUse[OBJECT_MAX];///< 現在セグメントを使用しているかを保持している配列
	int mMasterVolume;///< マスターボリューム
	int mVolumeMax;///< 再生時に使用する音量の値

    /** Queueクラス 
	 * サウンドは再生指定時にこのキューに一旦蓄えられ、フレーム毎に１つずつこのqueueから取り出し、update()内で再生されます */
	class SFDLL Queue {
	public:
		/// コンストラクタ
		Queue();
		/// デストラクタ
		~Queue();
		/// Queueパラメータ構造体
		struct Data
		{
			int mObjNo;		///< セグメント番号
			long mRepeat;	///< リピート回数 0 は無限回
			bool mbPrimary;	///< プライマリかどうか
			bool mbSync;	///<  プライマリと拍で同期するか
		};
		// OPERATIONS//
		
		/** Queueから情報を取得し、バッファから消す. 
		 * @param rData Data構造体の参照*/
		void get(Data& rData);

		/** Queueにデータをセットする.
		 * @param ObjNo サウンド番号 
		 * @param Repeat リピート回数(0は∞回) 
		 * @param bPrimary プライマリバッファで再生するかどうか 
		 * @param bSync プライマリサウンドと同期するかどうか  */
		const bool set(const int ObjNo,const DWORD Repeat,const bool bPrimary,const bool bSync);
		
		/// キューをクリアする 
		void clear();
		// INQUIRY //

		/** queue(@ref mBuffer)が空かどうか問い合わせる.
		 @retval true 空である
		 @retval false 空でない */
		const bool isEmpty() const {return (mStart == mEnd);};
	private:
		Data mBuffer[Q_MAX];	///< Queueバッファ配列
		int mStart;			///< リングバッファ開始位置
		int mEnd;			///< リングバッファ終了位置

	};// Queue
	
	Queue mQueue;	///< サウンド用キューオブジェクト
	bool mbEnabled;	///< サウンドは使用可能かどうかを保持する
};

// コンストラクタ -------------------------------------------------------------
SoundImpl::SoundImpl() : impl_(new SoundImpl::impl())
{
				
}// SoundImpl()

// デストラクタ ---------------------------------------------------------------
SoundImpl::~SoundImpl()
{
	

} // ~SoundImpl()

// 初期化 ---------------------------------------------------------------------
void SoundImpl::initialize(void)
{
	impl_->initialize();
	

}// initialize() 

// サウンドの再生 -----------------------------------------------
void SoundImpl::play(const int ObjNo,const DWORD Repeat,const bool bPrimary,const bool bSync)
{
	impl_->play(ObjNo,Repeat,bPrimary,bSync);

	
}// play

// キューからサウンドNoを取り出し、再生する -----------------------
void SoundImpl::update()
{
	impl_->update();
}// update

// サウンドファイルのロード ---------------------------------------
void SoundImpl::load(const int objNo,const char * const pFileName)
{
	impl_->load(objNo,pFileName);

}// load

//　サウンドオブジェクトのロード −−−−−−−−−−−−−−−−−−−−−−
const int SoundImpl::load(const char * const pFileName)
{
	impl_->load(pFileName);
}// load

// アンロード -----------------------------------------------------
void SoundImpl::unload()
{
	impl_->unload();
}// unload

void SoundImpl::masterVolume(const int value)
{
	impl_->masterVolume(value);
}// masterVolume


// ---------------------------------------------------------------------------
//
// SoundImpl::Queue クラス
//
// ---------------------------------------------------------------------------

// コンストラクタ -------------------------------------------------
SoundImpl::impl::Queue::Queue()
{
	clear();
}// Queue

// デストラクタ ---------------------------------------------------
SoundImpl::impl::Queue::~Queue(){;};

// リングバッファにサウンド番号をセットする -------------------------
const bool SoundImpl::impl::Queue::set(const int ObjNo,const DWORD Repeat,const bool bPrimary,const bool bSync)
{
	if(mBuffer[mEnd].mObjNo == 0){
		mBuffer[mEnd].mObjNo = ObjNo + 1;
		mBuffer[mEnd].mRepeat = Repeat;
		mBuffer[mEnd].mbPrimary = bPrimary;
		mBuffer[mEnd].mbSync = bSync;
		mEnd = (mEnd + 1) & (Q_MAX - 1);
		return true;
	} else {
		return false;
	}
}// set

// リングバッファからサウンドデータを取り出す -----------------------
void SoundImpl::impl::Queue::get(Data& rData)
{
	if(mStart == mEnd)
	{ rData.mObjNo =0;
		return;
	}
	
	rData = mBuffer[mStart];
	rData.mObjNo -= 1;
	mBuffer[mStart].mObjNo = 0;
	
	mStart = (mStart + 1) & (Q_MAX - 1);

}// get

// リングバッファをクリアする -------------------------------------
void SoundImpl::impl::Queue::clear()
{
	mStart = mEnd = 0;
	ZeroMemory(mBuffer,sizeof(mBuffer));
}// clear

void SoundImpl::stop(int ObjNo)
{
	impl_->stop(ObjNo);
}
