#include "stdafx.h"
#include <dmksctrl.h>
#include <process.h>
#include "Port.h"


namespace GuruGuruSmf {	namespace Dxmus
{

	// コンストラクタ
	Dls::Dls(IDirectMusicLoader8* ldr)
	{
		loader = ldr;
		collection = 0;
	}

	// デストラクタ
	Dls::~Dls()
	{
		if (collection){
			collection->Release();
			collection = 0;
		}
	}

	// ファイルから読み込み
	GGSERROR Dls::LoadFromFile(wchar_t *fileName)
	{
		HRESULT hr;
		hr = loader->LoadObjectFromFile(CLSID_DirectMusicCollection, IID_IDirectMusicCollection8,
								fileName, (LPVOID*)&collection );
		if(FAILED(hr))	return GgsError::FileLoadFailed;	// 読み込み失敗

		return GgsError::NoError;	// 成功
	}

	// 音色取り出し
	int Dls::GetInstrument(int voice, IDirectMusicInstrument8** instrument)
	{
		if(collection == 0)	return -1;

		return collection->GetInstrument(voice, instrument);
	}


	// DirectMusic管理スレッド
	unsigned int WINAPI threadPort(void* p_userdata)
	{
		CoInitializeEx(NULL, COINIT_MULTITHREADED);
		//CoInitialize(NULL);
		((Port*)p_userdata)->OnWorker();
		CoUninitialize();
		return 0;
	}

	// 全オブジェクトの操作覚悟で作りましたが
	// OpenとCloseだけでも動いてるので様子見
	void Port::OnWorker()
	{
		while(isWorkerOn)
		{
			//isBusy = true;
			WaitForSingleObject(event_cmd, INFINITE);//by Kobarin
			while(!commands.empty())
			{
				PortCommand cmd = commands.front();
				switch(cmd.type)
				{
				case CommandType_Open:
					resultOpen = Open2(cmd.handle);
					break;
				case CommandType_Close:
					Close2();
					break;
				default:
					break;
				}

				EnterCriticalSection(&lockObject);
				commands.pop();
				LeaveCriticalSection(&lockObject);
			}
			SetEvent(event_done);//by Kobarin
			//isBusy = false;
			//Sleep(1);
		}
		//DeleteCriticalSection(&lockObject);//デストラクタに移動 by Kobarin
		//#ifdef _DEBUG
		//OutputDebugString(L"OnWorker() terminated\n");
		//#endif
		//DllMain 内で後始末すると↑が表示されないのにスレッドが終了している
		//FreeLibrary の前に GGS4Uninitialize_k を呼べば表示される
		//マルチプルインスタンス版の API だけを使った場合も表示される
	}

	// コマンドをキューに入れる
	void Port::PostCommand(PortCommand cmd)
	{
		EnterCriticalSection(&lockObject);
		commands.push(cmd);
		LeaveCriticalSection(&lockObject);
		SetEvent(event_cmd);//by Kobarin
	}

	// コマンドの実行が終わるまでウェイト
	// 急ぐ処理は0 時間がかかるなら1が妥当
	//by Kobarin sleep は無視
	void Port::Wait(int sleep)
	{
		//while(isBusy) Sleep(sleep);
		//コマンドを実行する前にスレッドが終了しても復帰するようにする
		//DllMain 内で後始末されるとスレッドが強制終了されるらしい？
		//DllMain 内で後始末されると ret == WAIT_OBJECT_0 になるが
		//OnWorker() 内の isWorkerOn のループの外が通る形跡がない
		HANDLE hHandles[] = {handle_thread, event_done};
		//DWORD ret =
		WaitForMultipleObjects(2, hHandles, FALSE, INFINITE);
	}

	// コンストラクタ
	Port::Port()
	{
		music = 0;
		port = 0;
		buffer = 0;
		loader = 0;
		defaultDls = 0;
		clock = 0;

		downloadedList = 0;
		downloadedCount = 0;

		isWorkerOn = true;
		InitializeCriticalSection(&lockObject);
		//handle_thread = (HANDLE)_beginthreadex(NULL, 0, threadPort, this, 0, NULL);

		isOpen = false;
		isBusy = false;
		resultOpen = GgsError::Code::NoError;
		event_cmd = CreateEvent(NULL, FALSE, FALSE, NULL);
		event_done = CreateEvent(NULL, FALSE, FALSE, NULL);
		handle_thread = (HANDLE)_beginthreadex(NULL, 0, threadPort, this, 0, NULL);//by Kobarin(全てのメンバを初期化してからスレッド実行)
	}

	// デストラクタ
	Port::~Port()
	{
		Close();

		isWorkerOn = false;
		SetEvent(event_cmd);
		WaitForSingleObject(handle_thread, INFINITE);
		CloseHandle(handle_thread);
		CloseHandle(event_cmd);
		CloseHandle(event_done);
		DeleteCriticalSection(&lockObject);
	}

	// 初期化
	GGSERROR Port::Open(HWND handle)
	{
		PortCommand cmd;
		cmd.type = CommandType_Open;
		cmd.handle = handle;
		PostCommand(cmd);

		Wait(1);
		return resultOpen;
	}
	GGSERROR Port::Open2(HWND handle)
	{
		Close2();

		HRESULT hr;
		hr = CoCreateInstance(CLSID_DirectMusic ,NULL ,
					CLSCTX_INPROC_SERVER ,IID_IDirectMusic8,
					(LPVOID*)&music);
		if(FAILED(hr))	return GgsError::CannotOpenDevice;
		hr = music->SetDirectSound(NULL ,handle);
		if(FAILED(hr))	return GgsError::CannotOpenDevice;

		//ポート作成
		DMUS_PORTPARAMS param;
		ZeroMemory(&param, sizeof(DMUS_PORTPARAMS));
		param.dwSize = sizeof(DMUS_PORTPARAMS);
		param.dwValidParams = DMUS_PORTPARAMS_VOICES
			| DMUS_PORTPARAMS_CHANNELGROUPS
			| DMUS_PORTPARAMS_SAMPLERATE
			| DMUS_PORTPARAMS_EFFECTS;
		param.dwVoices = 128;
		param.dwChannelGroups = 1;
		param.dwSampleRate = 44100;
		param.dwEffectFlags = DMUS_EFFECT_REVERB | DMUS_EFFECT_CHORUS /*| DMUS_EFFECT_DELAY*/;

		GUID guidPort;
		hr = music->GetDefaultPort(&guidPort);
		if(FAILED(hr))	return GgsError::CannotOpenDevice;
		hr = music->CreatePort(guidPort, &param, &port, NULL);
		if(FAILED(hr))	return GgsError::CannotOpenDevice;
		hr = port->Activate(TRUE);
		if(FAILED(hr))	return GgsError::CannotOpenDevice;

		// バッファ作成
		DMUS_BUFFERDESC bufferDesc;

		bufferDesc.dwSize = sizeof(DMUS_BUFFERDESC);
		bufferDesc.dwFlags = 0;
		bufferDesc.guidBufferFormat = GUID_NULL;
		bufferDesc.cbBuffer = 128;
		hr = music->CreateMusicBuffer(&bufferDesc, &buffer, NULL);
		if(FAILED(hr))	return GgsError::CannotOpenDevice;

		// タイマ取得
		hr = port->GetLatencyClock(&clock);
		if(FAILED(hr))	return GgsError::CannotOpenDevice;

		// ローダー作成
		hr = CoCreateInstance(CLSID_DirectMusicLoader, NULL,
					CLSCTX_INPROC_SERVER, IID_IDirectMusicLoader8,
					(LPVOID*)&loader);
		if(FAILED(hr))	return GgsError::CannotOpenDevice;

		wchar_t path[MAX_PATH];
		if(GetCurrentDirectoryW(MAX_PATH, path) == 0){
			DWORD errCode = GetLastError();
			if(errCode == ERROR_CALL_NOT_IMPLEMENTED){	// Windows9x ハードコーディングです
				char pathA[MAX_PATH];
				GetCurrentDirectoryA(MAX_PATH, pathA);
				MultiByteToWideChar(CP_ACP, 0, pathA, -1, path, MAX_PATH);
			}
		}

		loader->SetSearchDirectory(GUID_DirectMusicAllTypes, path, FALSE);


		// デフォルトのDLSをダウンロード
		DMUS_OBJECTDESC objectDesc;
		ZeroMemory(&objectDesc, sizeof(DMUS_OBJECTDESC));
		objectDesc.dwSize = sizeof(DMUS_OBJECTDESC);
		objectDesc.guidClass = CLSID_DirectMusicCollection;
		objectDesc.guidObject = GUID_DefaultGMCollection;
		objectDesc.dwValidData = DMUS_OBJ_CLASS | DMUS_OBJ_OBJECT;
		hr = loader->GetObject(&objectDesc, IID_IDirectMusicCollection8,
					(LPVOID*)&defaultDls);
		if(FAILED(hr))	return GgsError::CannotOpenDevice;

		isOpen = true;
		return GgsError::NoError;
	}

	// 音色リストをリセット
	void Port::ResetVoiceList()
	{
		if(downloadedCount == 0) return;

		for(int i=0; i<downloadedCount; i++){
			if(downloadedList[i]){
				port->UnloadInstrument(downloadedList[i]);
				downloadedList[i]->Release();
			}
		}
		delete[] downloadedList;
		downloadedList = 0;
		downloadedCount = 0;
	}

	// 音色を取り出し
	int Port::GetInstrument(int voice, IDirectMusicInstrument8** instrument)
	{
		// まずはデフォルトから探す
		defaultDls->GetInstrument(voice, instrument);
		if(*instrument != 0) return 0;

		// 見つからなければリストから探す
		for(int i=0; i<(int)dlsList.size(); i++){
			if(dlsList[i]->GetInstrument(voice, instrument) == 0) return 0;
		}
		return -1;
	}

	// 音色リストをセット
	void Port::SetVoiceList(DWORD* list, int count)
	{
		ResetVoiceList();

		IDirectMusicInstrument8* instrument = 0 ;
		IDirectMusicDownloadedInstrument8* downloaded = 0;

		downloadedList = new IDirectMusicDownloadedInstrument8*[count];
		downloadedCount = count;

		// 有効ノートは全範囲
		DMUS_NOTERANGE noteRange;
		noteRange.dwLowNote = 0;
		noteRange.dwHighNote = 127;

		// 音色を読み出してportに設定
		for(int i=0; i<count; i++){
			downloadedList[i] = 0;

			int voice = list[i];

			if(GetInstrument(voice, &instrument)){	// 音色がなかったらバンク0
				defaultDls->GetInstrument(voice&0xff0000ff, &instrument);
				if(instrument == 0) continue;	// これはお手上げ
				instrument->SetPatch(voice);
			}

			port->DownloadInstrument(instrument, &downloaded, &noteRange, 1);
			downloadedList[i] = downloaded;
			downloaded = 0;
			instrument->Release();
			instrument = 0;
		}
		port->Compact();
	}



	// 後始末
	void Port::Close()
	{
		PortCommand cmd;
		cmd.type = CommandType_Close;
		PostCommand(cmd);
		Wait(1);//by Kobarin
	}

	void Port::Close2()
	{
		if(isOpen){
			isOpen = false;
		}

		ResetVoiceList();

		for(int i=0; i<(int)dlsList.size(); i++){
			delete dlsList[i];
		}
		dlsList.clear();

		if(clock){
			clock->Release();
			clock = 0;
		}

		if(buffer){
			buffer->Release();
			buffer = 0;
		}

		if(defaultDls){
			defaultDls->Release();
			defaultDls = 0;
		}

		if(port){
			port->Activate(FALSE);
			port->Release();
			port = 0;
		}

		if(loader){
			loader->Release();
			loader = 0;
		}

		if(music){
			music->Release();
			music = 0;
		}
	}

	// 遅延付き時間を取得
	GGSERROR Port::GetReferenceTime(long long* time)
	{
		if(!isOpen)	return GgsError::NotReady;

		HRESULT hr = clock->GetTime(time);
		if(FAILED(hr)) return GgsError::Failed;
		return GgsError::NoError;
	}

	// 短いメッセージを送る
	GGSERROR Port::SendShortMessage(long long time, int midiMessage)
	{
		if(!isOpen)	return GgsError::NotReady;

		HRESULT hr;
		hr = buffer->PackStructured(time, 0, midiMessage);
		if(FAILED(hr)) return GgsError::Failed;
		hr = port->PlayBuffer(buffer);
		if(FAILED(hr)) return GgsError::Failed;
		buffer->Flush();
			
		return GgsError::NoError;
	}

	// 長いメッセージを送る
	GGSERROR Port::SendLongMessage(long long time, LPBYTE midiMessage, int length)
	{
		if(!isOpen) return GgsError::NotReady;

		HRESULT hr;
		hr = buffer->PackUnstructured(time, 0, length, midiMessage);
		if(FAILED(hr)) return GgsError::Failed;
		hr = port->PlayBuffer(buffer);
		if(FAILED(hr)) return GgsError::Failed;
		buffer->Flush();

		return GgsError::NoError;
	}

	// DLSを追加
	GGSERROR Port::AddDls(wchar_t* fileName)
	{
		Dls* dls = new Dls(loader);
		GGSERROR err = dls->LoadFromFile(fileName);
		if(err == GgsError::NoError){
			dlsList.push_back(dls);
			return err;
		}else{
			delete dls;
			return err;
		}
	}

}}

