#include "stdafx.h"
#include "refCApplicationBase.h"
#include "共通\refCTimer.h"
#include "MediaFoundation\refMFPlatform.h"
#include "MediaFoundation\refCMediaSession.h"
#include "Direct3D9\CDirect3DNative.h"

namespace FDK
{
// public:

	void CApplicationBase::Run()
	{
		this->Disposed->tDispose済みまたは実行中なら例外発生( "CApplicationBase" );

		HRESULT hr = S_OK;


		// アプリ開始。

		#pragma region [ 初期化。]
		//-----------------
		::CoInitializeEx( 0, COINIT_MULTITHREADED );

		MFPlatform::Startup();

		this->Window = gcnew CWindow();		// 必ずプロパティを通すこと(this->_hWindowsも設定されるため）
		this->bアプリケーションを終了する = gcnew CAtom<bool>( false );
		this->listトップレベルActivities = gcnew List<CActivity^>();
		this->bPresentスキップ = gcnew CAtom<bool>( false );

		this->Window->FormClosed += gcnew FormClosedEventHandler( this, &CApplicationBase::Window_FormClosed );		// FormClosing は派生クラスで使い、このベースクラスでは FormClosed を使う。

		// 派生クラスでの初期化。
		// Direct3Dデバイスの生成とデバイスマネージャへの設定、ウィンドウの設定などを行う。
		// false を返すか例外を出すとアプリ終了。

		if( !this->On初期化() )
			return;
		//-----------------
		#pragma endregion


		// 各スレッドの実行開始。

		#pragma region [ フロー制御スレッドの生成・実行開始。]
		//-----------------
		Thread^ thフロー制御スレッド = gcnew Thread( gcnew ThreadStart( this, &CApplicationBase::tフロー制御スレッド処理 ) );
		thフロー制御スレッド->Name = "制御";
		thフロー制御スレッド->Priority = Threading::ThreadPriority::Normal;
		thフロー制御スレッド->Start();
		while( !thフロー制御スレッド->IsAlive ) ;		// 起動するまでスピンロック；MSDN にこうしろと書いてある。
		//-----------------
		#pragma endregion
		#pragma region [ 進行スレッドの生成・実行開始。]
		//-----------------
		Thread^ th進行スレッド = gcnew Thread( gcnew ThreadStart( this, &CApplicationBase::t進行スレッド処理 ) );
		th進行スレッド->Name = "進行";
		th進行スレッド->Priority = Threading::ThreadPriority::Normal;
		th進行スレッド->Start();
		while( !th進行スレッド->IsAlive ) ;		// 起動するまでスピンロック；MSDN にこうしろと書いてある。
		//-----------------
		#pragma endregion
		#pragma region [ MediaSessionスレッドの生成・実行開始。]
		//-----------------
		Thread^ thMediaSessionスレッド = gcnew Thread( gcnew ThreadStart( this, &CApplicationBase::tMediaSessionスレッド処理 ) );
		thMediaSessionスレッド->Name = "ＭＳ";
		thMediaSessionスレッド->Priority = Threading::ThreadPriority::Normal;
		thMediaSessionスレッド->Start();
		while( !thMediaSessionスレッド->IsAlive );		// 起動するまでスピンロック；MSDN にこうしろと書いてある。
		//-----------------
		#pragma endregion
		#pragma region [ 描画メソッドをメインスレッドで実行開始。アプリが終わるとメソッドから戻ってくる。]
		//-----------------
		Thread::CurrentThread->Name = "描画";
		this->t描画スレッド処理();
		//-----------------
		#pragma endregion
		

		// 各スレッドの終了を待つ。タイムアウトしたら強制終了する。

		#pragma region [ フロー制御スレッドの終了待ち。]
		//-----------------
		if( thフロー制御スレッド->IsAlive )
		{
			if( !thフロー制御スレッド->Join( 3 * 1000 ) )		// 最大3秒待つ。
			{
				Trace::TraceError( "フロー制御スレッドの停止待ちがタイムアウトしました。フロー制御スレッドを強制終了します。" );
				thフロー制御スレッド->Abort();
			}
		}
		//-----------------
		#pragma endregion
		#pragma region [ 進行スレッドの終了待ち。]
		//-----------------
		if( th進行スレッド->IsAlive )
		{
			if( !th進行スレッド->Join( 3 * 1000 ) )		// 最大3秒待つ。
			{
				Trace::TraceError( "進行スレッドの停止待ちがタイムアウトしました。進行スレッドを強制終了します。" );
				th進行スレッド->Abort();
			}
		}
		//-----------------
		#pragma endregion
		#pragma region [ MediaSessionスレッドへの終了指示と終了待ち。]
		//-------------------------
		if( thMediaSessionスレッド->IsAlive )
		{
			::PostThreadMessage( this->_dwMediaSessionスレッドID, WM_APP_MEDIASESSION_EXIT, 0, 0 );

			int n = 0;
			while( thMediaSessionスレッド->IsAlive )
			{
				Thread::Sleep( 100 );
				
				if( ++n > 30 )		// 最大3秒待つ
				{
					Trace::TraceError( "MediaSessionイベントスレッドの停止待ちがタイムアウトしました。スレッドを強制終了します。" );
					thMediaSessionスレッド->Abort();
					break;
				}
			}
		}
		//-------------------------
		#pragma endregion

		
		// アプリ終了。

		#pragma region [ 終了処理。]
		//-----------------
		CMediaSession::t全インスタンスを停止する();

		this->On終了();			// 派生クラスでの終了処理。

		this->Window->Close();	// デバイスが解放されてからウィンドウを閉じる。

		MFPlatform::Shutdown();

		::CoUninitialize();
		//-----------------
		#pragma endregion
	}

// protected:

	void CApplicationBase::On全デバイスリソースの作成()
	{
		this->Disposed->tDispose済みまたは実行中なら例外発生( "CApplicationBase" );
		Debug::Assert( nullptr != this->listトップレベルActivities );

		for each( CActivity^ act in this->listトップレベルActivities )
			act->tデバイスリソースの作成();
	}
	void CApplicationBase::On全デバイスリソースの解放()
	{
		this->Disposed->tDispose済みまたは実行中なら例外発生( "CApplicationBase" );
		Debug::Assert( nullptr != this->listトップレベルActivities );

		for each( CActivity^ act in this->listトップレベルActivities )
			act->tデバイスリソースの解放();
	}

	void CApplicationBase::tフロー制御スレッド処理()
	{
		this->Disposed->tDispose済みまたは実行中なら例外発生( "CApplicationBase" );

		HRESULT hr = ::CoInitializeEx( NULL, COINIT_MULTITHREADED );
		Trace::Assert( SUCCEEDED( hr ) );

		this->Onフロー制御();	// ループごと子クラスに丸投げ。

		::CoUninitialize();
	}
	void CApplicationBase::t進行スレッド処理()
	{
		this->Disposed->tDispose済みまたは実行中なら例外発生( "CApplicationBase" );

		HRESULT hr = ::CoInitializeEx( NULL, COINIT_MULTITHREADED );
		Trace::Assert( SUCCEEDED( hr ) );

		while( this->On進行() )
			Thread::Sleep( 1 );

		::CoUninitialize();
	}
	void CApplicationBase::t描画スレッド処理()
	{
		this->Disposed->tDispose済みまたは実行中なら例外発生( "CApplicationBase" );

		//::CoInitializeEx( NULL, COINIT_MULTITHREADED );	Run() で実行済み

		HRESULT hr = S_OK;
		HANDLE hDev = NULL;
		IDirect3DDevice9Ex *pDevice;

		// スレッド優先レベルを +2 UP。
		::SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_HIGHEST );

		CTimer^ timer = gcnew CTimer( CTimer::E種別::MultiMedia );

		Int64 n開始 = CTimer::n未使用;
		Int64 nPresent開始 = CTimer::n未使用;
		Int64 n終了 = CTimer::n未使用;
		Int64 n期間 = CTimer::n未使用;

		while( true )
		{
			n開始 = timer->nシステム時刻ms;


			// メッセージ処理。

			#pragma region [ ウィンドウ＆スレッドメッセージをあるだけ処理。]
			//-----------------
			MSG msg;
			while( ::PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )	// カレントスレッドと CApplicationBase::Window のイベントが拾われる。
			{
				if( WM_QUIT == msg.message )
					return;

				::TranslateMessage( &msg );
				::DispatchMessage( &msg );
			}
			//-----------------
			#pragma endregion


			// 描画。

			#pragma region [ デバイスの状態を確認と対処。]
			//-----------------
			if( false == this->bアプリケーションを終了する->Get() )		// Window_Closing された場合、デバイスは消失ではなく破棄されている。
			{
				switch( hr = CDirect3DNative::tデバイスの状態を取得する() )
				{
				case D3D_OK:
					break;

				case S_PRESENT_OCCLUDED:								// 最小化、または完全に描画領域が隠れている。
					Thread::Sleep( 100 );
					continue;

				case S_PRESENT_MODE_CHANGED:							// デスクトップの表示モード（解像度、サイズなど）が変更された。
					CDirect3DNative::tデバイスをリセットする();
					continue;

				case DXVA2_E_NOT_INITIALIZED:							// デバイスマネージャにデバイスが登録されていない。
					CDirect3DNative::tデバイスをデバイスマネージャーに登録する();
					Thread::Sleep( 500 );	// 念のため
					continue;

				case D3DERR_DEVICELOST:									// デバイスが消失した。
					this->On全デバイスリソースの解放();
					CDirect3DNative::tデバイスを再構築する();
					this->On全デバイスリソースの作成();
					continue;

				case D3DERR_DEVICEHUNG:
				case D3DERR_DEVICEREMOVED:
				case D3DERR_OUTOFVIDEOMEMORY:
				default:
					this->bアプリケーションを終了する->Set( true );		// 例外発出より先にセット。
					tHRESULT付き例外発生( hr, L"デバイスに異常が発生しました。終了します。" );
					break;
				}
			}
			//-----------------
			#pragma endregion

			#pragma region [ 描画前 ]
			//-----------------
			//if( false == this->bアプリケーションを終了する->Get() )		アプリ終了フラグが立っていても、終了処理を行わせるために、一度だけ On描画前() を呼び出す。
			this->On描画前();
			//-----------------
			#pragma endregion

			#pragma region [ 描画、表示 ]
			//-----------------
			if( this->bアプリケーションを終了する->Get() )		// アプリ終了フラグが立っていても、終了処理を行わせるために、一度だけ On描画() を呼び出す。
			{
				this->On描画( IntPtr::Zero );	// デバイスロックハンドルはゼロ
			}
			else
			{
				try
				{
					// デバイスをロック。

					hDev = NULL;
					pDevice = NULL;
					CDirect3DNative::tデバイスをロックする( true, &hDev, &pDevice );


					// 描画 

					tFAILEDなら例外発生( L"デバイスの BeginScene ", hr = pDevice->BeginScene() );

					this->On描画( IntPtr( hDev ) );

					tFAILEDなら例外発生( L"デバイスの EndScene ", hr = pDevice->EndScene() );


					// デバイス表示。

					if( !this->bPresentスキップ->Get() )
					{
						nPresent開始 = timer->nシステム時刻ms;

						tFAILEDなら例外発生( L"デバイスのPresentEx()",
							hr = pDevice->PresentEx( NULL, NULL, NULL, NULL, 0 ) );

						n終了 = timer->nシステム時刻ms;

						#pragma region [ 18ミリ秒以上かかってるとデバッグ表示 ]
						//-----------------
						n期間 = n終了 - nPresent開始;

						if( (Int64) 18 < n期間 )
						{
							Debug::WriteLine( String::Format( "{0}(描画)+{1}(Present) = {2}",
								nPresent開始 - n開始,
								n期間,
								n終了 - n開始
								) );
						}
						//-----------------
						#pragma endregion
					}
				}
				finally
				{
					// デバイスのロックを解除。

					if( NULL != hDev )
						CDirect3DNative::tデバイスのロックを解除する( hDev );
				}
			}
			//-----------------
			#pragma endregion


			// 時間調整＆垂直同期帰線待ち。
			// ※ 現在は、単純に垂直帰線同期を 60Hz と想定している。

			if( false == this->bアプリケーションを終了する->Get() )
				CMediaSession::t全インスタンスへ次のサンプルを要求する();	// スリープするより先に指示。リソースがもったいないから。


			#pragma region [ 終了チェック。]
			//-----------------
			if( this->bアプリケーションを終了する->Get() )
			{
				Trace::TraceInformation( L"描画前＆描画スレッドを終了します。" );
				break;
			}
			//-----------------
			#pragma endregion


			n終了 = timer->nシステム時刻ms;

			#pragma region [ 時間調整。0〜14ms の間スリープする。]
			//-----------------
			n期間 = n終了 - n開始;
			n開始 = timer->nシステム時刻ms;

			if( 15 > n期間 )
				Sleep( (DWORD)( 15 - n期間 ) );
			//-----------------
			#pragma endregion

			n期間 = timer->nシステム時刻ms - n開始;

			#pragma region [ 垂直帰線同期待ち。スピンロックで実現。]
			//-----------------
			if( 16 > n期間 )	// スリープしすぎてたら、垂直帰線同期を待たないでとっとと描画する。
			{
				CDirect3DNative::tデバイスをロックする( true, &hDev, &pDevice );

				#pragma region [ スピンロックで垂直同期帰線を待つ。]
				//-----------------
				D3DRASTER_STATUS status;
				while( pDevice->GetRasterStatus( 0, &status ) == S_OK )
				{
					if( status.InVBlank )
						break;
				}
				//-----------------
				#pragma endregion

				CDirect3DNative::tデバイスのロックを解除する( hDev );
			}
			//-----------------
			#pragma endregion
		}

		//::CoUninitialize();	Run() で実行する
	}
	void CApplicationBase::tMediaSessionスレッド処理()
	{
		this->Disposed->tDispose済みまたは実行中なら例外発生( "CApplicationBase" );

		HRESULT hr = ::CoInitializeEx( NULL, COINIT_MULTITHREADED );
		Trace::Assert( SUCCEEDED( hr ) );

		this->_dwMediaSessionスレッドID = ::GetCurrentThreadId();


		// MediaSession イベントを CMediaSessionNative へ投げ返す。

		MSG msg;
		while( ::GetMessage( &msg, (HWND) NULL, WM_APP_MEDIASESSION_EVENT, WM_APP_MEDIASESSION_EXIT ) )
		{
			if( WM_APP_MEDIASESSION_EXIT == msg.message )
			{
				break;
			}
			if( WM_APP_MEDIASESSION_EVENT == msg.message )
			{
				CMediaSessionNative::STMSEVENT *pstEvent = ( CMediaSessionNative::STMSEVENT* ) msg.wParam;
				if( ( NULL != pstEvent ) && ( NULL != pstEvent->pMediaSessionNative ) && ( NULL != pstEvent->pEvent ) )
				{
					pstEvent->pMediaSessionNative->tMediaSessionイベントを処理する( pstEvent->pEvent );
					free( pstEvent );	// WM_APP_MEDIASESSION_EVENT 発行元で malloc() されているので、free() するのを忘れないこと。
				}
				continue;
			}
		}

		::CoUninitialize();
	}

// private:

	void CApplicationBase::Window_FormClosed( Object^ sender, FormClosedEventArgs^ e )
	{
		this->Disposed->tDispose済みまたは実行中なら例外発生( "CApplicationBase" );

		this->bアプリケーションを終了する->Set( true );
	}
}