using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows.Forms;
using System.Threading;
using System.IO;
using System.Reflection;
using System.Drawing;
using System.Xml;
using System.Xml.Serialization;
using FDK;

namespace StrokeStyleT
{
	// CActivity::On進行() の戻り値として使う。
	internal enum E進行結果 : int
	{
		継続,
		完了,
		キャンセル,
		曲決定,
		クリア,
		GUI割込・演奏停止,
		GUI割込・演奏開始,
	}

	// 各状態の結果を表すことに使う。
	internal enum E状態結果 : int
	{
		未知,
		完了,
		中止,
	}

	class Global : CApplicationBase
	{
		public static readonly Version Version = Assembly.GetExecutingAssembly().GetName().Version;

		public static Global App { get; private set; }
		public static CDirect3D Direct3D { get; private set; }
		public static Theme Theme { get; set; }
		public static Input Input { get; set; }
		public static Folders Folders { get; private set; }
		public static Users Users { get; private set; }
		public static Config Config { get; private set; }
		public static CSoundDeviceWASAPI Sound { get; private set; }
		public static CSoundTimer SoundTimer { get; private set; }

		public static C起動ステージ 起動ステージ = null;
		public static C選曲ステージ 選曲ステージ = null;
		public static C演奏ステージ 演奏ステージ = null;

		public static bool bWindowがActive中 { get; private set; }

		protected override bool On初期化()
		{
			Global.App = this;
			Global.bWindowがActive中 = false;
			Global.MouseCursor = new MouseCursor();


			// フォルダの初期化。

			Global.Folders = new Folders();


			// ログ出力を開始する。

			#region [ ログファイル出力用の Trace リスナを追加する。]
			//-----------------
			Trace.AutoFlush = true;
			Trace.IndentLevel = 0;

			string stgログファイルパス = Path.Combine( Folders.stgユーザ共通フォルダパス, @"SSTLog.txt" );

			#region [ ログファイルパスへの Trace リスナを追加。失敗したらダイアログは出し、継続か中止かをユーザに委ねる。]
			//-----------------
			bool bTraceリスナの追加について解決した = false;

			while( false == bTraceリスナの追加について解決した )
			{
				try
				{
					var sw出力ストリーム = new StreamWriter( stgログファイルパス, false, System.Text.Encoding.GetEncoding( "shift-jis" ) );
					var listメソッド名として出力させないスタックトレース内の名前または名前空間 = new List<string> { "FDK.CTraceLogListener", "System.Diagnostics.TraceInternal", "System.Diagnostics.Trace", "" };

					Trace.Listeners.Add(
						new CTraceLogListener(
							sw出力ストリーム,
							listメソッド名として出力させないスタックトレース内の名前または名前空間
						)
					);

					bTraceリスナの追加について解決した = true;
				}
				catch( Exception e )
				{
					var dr = MessageBox.Show(
								string.Format( "Traceリスナーの追加に失敗しました。" + Environment.NewLine +
												"理由: {0}" + Environment.NewLine +
												Environment.NewLine +
												"キャンセルをクリックすると、ログファイルへの記録なしで続行します。"
												,e.Message ),
								"ログエラー", MessageBoxButtons.RetryCancel );

					if( DialogResult.Cancel == dr )
					{
						bTraceリスナの追加について解決した = true;
					}
				}
			}
			//-----------------
			#endregion

			// タイトル表示。

			string stgCopyright = "";

			#region [ アセンブリリソースからの Copyright の取得 ]
			//-----------------
			object[] arrCopyright = Assembly.GetExecutingAssembly().GetCustomAttributes( typeof( AssemblyCopyrightAttribute ), false );
			if( ( null != arrCopyright ) && ( 0 < arrCopyright.Length ) )
				stgCopyright = ( (AssemblyCopyrightAttribute) arrCopyright[ 0 ] ).Copyright;
			//-----------------
			#endregion

			Trace.WriteLine( "" );
			Trace.Write( Application.ProductName );
			Trace.WriteLine( string.Format( " {0}.{1} (rev.{2} build.{3})", Version.Major, Version.Minor, Version.Revision, Version.Build ) );
			Trace.WriteLine( string.Format( "{0} All Rights Reserved.", stgCopyright ) );
			Trace.WriteLine( "" );

			Trace.TraceInformation( "ログファイルの出力を開始します。[{0}]", Global.Folders.tファイルパスをマクロ付きパスに変換する( stgログファイルパス ) );
			//-----------------
			#endregion

			Trace.TraceInformation( "アプリケーションを初期化します。" );
			Trace.Indent();


			// コンフィグの初期化。

			Global.Config = new Config();
			Global.Config.Load( Path.Combine( Global.Folders.stgユーザ共通フォルダパス, @"Config.xml" ) );
			Trace.TraceInformation( "コンフィグファイル Config.xml の読み込みに成功しました。" );


			// トップレベルActivityの登録。

			this.listトップレベルActivities.Add( Global.起動ステージ = new C起動ステージ() );
			Trace.TraceInformation( "起動ステージを生成しました。" );

			this.listトップレベルActivities.Add( Global.選曲ステージ = new C選曲ステージ() );
			Trace.TraceInformation( "選曲ステージを生成しました。" );

			this.listトップレベルActivities.Add( Global.演奏ステージ = new C演奏ステージ() );
			Trace.TraceInformation( "演奏ステージを生成しました。" );


			// Direct3D ラッパーインスタンスの生成。

			Global.Direct3D = new CDirect3D( StrokeStyleT.Theme.sz描画サイズ原寸px );
			Trace.TraceInformation( "Direct3D を生成しました。" );


			// ウィンドウ（親クラスで生成済み）の初期設定。

			this.Window.Location = new Point( 10, 10 );
			this.Window.Text = string.Format( "{0} {1}.{2} (rev.{3} build.{4})", Application.ProductName, Version.Major, Version.Minor, Version.Revision, Version.Build );
			this.Window.MaximizeBox = false;
			this.Window.FormBorderStyle = FormBorderStyle.Fixed3D;
			this.Window.MouseDown += Window_MouseDown;
			this.Window.Click += Window_Click;
			this.Window.MouseClick += Window_MouseClick;
			this.Window.MouseUp += Window_MouseUp;
			this.Window.Activated += Window_Activated;
			this.Window.Deactivate += Window_Deactivate;
			Trace.TraceInformation( "ウィンドウを生成しました。" );


			// テーマの生成。内部でウィンドウのあるモニタの表示モードをリストアップするので、Window と Direct3D が生成済みであること。

			Global.Theme = new Theme( this.Window.Handle );
			Trace.TraceInformation( "テーマを生成しました。" );


			// ウィンドウの表示。（クライアントサイズを取得するため、Theme が生成済みであること。）

			this.Window.ClientSize = Global.Theme.t現在の表示モードに最適なクライアントサイズを取得する( this.Window );
			this.Window.Show();
			Trace.TraceInformation( "ウィンドウを表示しました。" );
			Trace.TraceInformation( "  設計サイズの原寸:       {0}x{1}", StrokeStyleT.Theme.sz描画サイズ原寸px.Width, StrokeStyleT.Theme.sz描画サイズ原寸px.Height );
			Trace.TraceInformation( "  ウィンドウの最適サイズ: {0}x{1}", this.Window.ClientSize.Width, this.Window.ClientSize.Height );


			// Direct3D デバイスの作成。（Window が生成済みであること。）

			bool b全画面モード = false;	// Global.Users.SelectedUser.Options.b全画面モード;		この地点ではまだユーザが確定してないので、ひとまずウィンドウモードを指定。
			bool b垂直帰線同期待ち = false;		// 常に false とする。CApplicationBase クラスで独自に垂直帰線同期待ちを行っているため。
			Size sz表示サイズ = ( b全画面モード ) ? this.t最適な全画面サイズを取得する() : this.Window.ClientSize;

			Global.Direct3D.tデバイスを作成する( this.Window.Handle, b全画面モード, sz表示サイズ, b垂直帰線同期待ち );
			Trace.TraceInformation( "Direct3Dデバイスを生成しました。" );
			Trace.TraceInformation( "  全画面モード:{0}", b全画面モード );
			Trace.TraceInformation( "  垂直帰線同期待ち:{0}", b垂直帰線同期待ち );


			// Direct3D デバイスの初期設定。

			#region [ ウィンドウのクライアントサイズから、ビューを設定する。]
			//-----------------
			float f視野角 = 45.0f;	// 度

			Global.Direct3D.SetTransform(
				TransformState.View,
				Matrix.LookAtLH(
					new Vector3( 0f, 0f, (float) ( ( -this.Window.ClientSize.Height / 2.0 ) / Math.Tan( Utils.DegreeToRadian( f視野角 / 2.0 ) ) ) ),	// カメラの位置
					new Vector3( 0f, 0f, 0f ),					// 見ているターゲット
					new Vector3( 0f, 1f, 0f ) ) );				// 上方向
			//-----------------
			#endregion
			#region [ ウィンドウのクライアントサイズから、射影行列を設定する。]
			//-----------------
			Global.Direct3D.SetTransform(
				TransformState.Projection,
				Matrix.PerspectiveFovLH(
					Utils.DegreeToRadian( f視野角 ),												// y 方向の視野角度（ラジアン単位）
					(float) this.Window.ClientSize.Width / this.Window.ClientSize.Height,			// アスペクト比。ビューの幅÷高さ。
					-100.0f,																		// z（近）
					100.0f ) );																		// z（遠）
			//-----------------
			#endregion
			#region [ レンダリングステートを設定する。]
			//-----------------
			Global.Direct3D.SetRenderState( RenderState.Lighting, false );
			Global.Direct3D.SetRenderState( RenderState.ZEnable, false );
			Global.Direct3D.SetRenderState( RenderState.AntialiasedLineEnable, false );
			Global.Direct3D.SetRenderState( RenderState.AlphaTestEnable, true );
			Global.Direct3D.SetRenderState( RenderState.AlphaRef, 10 );
			Global.Direct3D.SetRenderState( RenderState.MultiSampleAntialias, false );
			Global.Direct3D.SetRenderState<RS_CmpFunc>( RenderState.AlphaFunc, RS_CmpFunc.Greater );
			Global.Direct3D.SetRenderState( RenderState.AlphaBlendEnable, true );
			Global.Direct3D.SetRenderState<RS_Blend>( RenderState.SrcBlend, RS_Blend.SourceAlpha );
			Global.Direct3D.SetRenderState<RS_Blend>( RenderState.DestBlend, RS_Blend.InverseSourceAlpha );
			Global.Direct3D.SetTextureStageState<TSS_TextureOperation>( 0, TextureStageState.AlphaOperation, TSS_TextureOperation.Modulate );
			Global.Direct3D.SetTextureStageState( 0, TextureStageState.AlphaArgument1, 2 );
			Global.Direct3D.SetTextureStageState( 0, TextureStageState.AlphaArgument2, 1 );
			//-----------------
			#endregion

			Trace.TraceInformation( "Direct3Dデバイスを初期化しました。" );


			// 入力デバイスの初期化。

			Global.Input = new Input( this.Window.Handle );
			Trace.TraceInformation( "入力デバイスを初期化しました。" );


			// サウンドデバイスとサウンドタイマの初期化。

			Global.Sound = new CSoundDeviceWASAPI(
				CSoundDeviceWASAPI.EWASAPIデバイスモード.排他, 
				Global.Config.nSoundバッファ長ms,
				Global.Config.nSound更新間隔ms,
				b排他モードでの作成失敗時に共有モードでの作成を行う: false );
			
			Trace.TraceInformation( "サウンドデバイスを生成しました。" );

			Global.SoundTimer = new CSoundTimer( Global.Sound );
			Trace.TraceInformation( "サウンドデバイスを利用するサウンドタイマーを生成しました。" );


			// 全チップのサウンドを作成。

			Cチップ.t全チップサウンドの作成();
			Trace.TraceInformation( "全チップのサウンドを作成しました。" );

			// ユーザ情報の初期化。

			Global.Users = new Users();
			Global.Users.Load();
			Trace.TraceInformation( "ユーザ情報を初期化または読み込みました。" );

			#region [ ユーザ "AutoPlay" がいないなら、作成・追加する。 ]
			//-----------------
			if( !Global.Users.Exists( ( user ) => { return user.stg名前.Equals( "AutoPlay" ); } ) )
			{
				User autoUser = new User();
				autoUser.stg名前 = "AutoPlay";
				autoUser.Options.b自動演奏 = true;
				autoUser.Songs.Add( new Song() {
					stg曲名 = "ペリドットとパール",
					stg曲ファイルマクロ無しパス = Path.Combine( Global.Folders.tファイルパス内のマクロを展開する( @"$(SystemFolder)" ), @"Songs\[弟の姉] ペリドットとパール\ペリドットとパール.sstf" ),
					stgサムネイルファイルマクロ無しパス = Path.Combine( Global.Folders.tファイルパス内のマクロを展開する( @"$(SystemFolder)" ), @"Songs\[弟の姉] ペリドットとパール\thumb.png" ),
				} );
				autoUser.Songs.Add( new Song() {
					stg曲名 = "ソンザイノリユウ【定概テクトニクス】",
					stg曲ファイルマクロ無しパス = Path.Combine( Global.Folders.tファイルパス内のマクロを展開する( @"$(SystemFolder)" ), @"Songs\ソンザイノリユウ\ソンザイノリユウ.sstf" ),
					stgサムネイルファイルマクロ無しパス = Path.Combine( Global.Folders.tファイルパス内のマクロを展開する( @"$(SystemFolder)" ), @"Songs\ソンザイノリユウ\thumb.png" ),
				} );
				autoUser.Songs.Add( new Song() {
					stg曲名 = "究極焼肉レストラン！お燐の地獄亭！",
					stg曲ファイルマクロ無しパス = Path.Combine( Global.Folders.tファイルパス内のマクロを展開する( @"$(SystemFolder)" ), @"Songs\究極焼肉レストラン！お燐の地獄亭！\究極焼き肉レストラン！お隣の地獄亭！.sstf" ),
					stgサムネイルファイルマクロ無しパス = Path.Combine( Global.Folders.tファイルパス内のマクロを展開する( @"$(SystemFolder)" ), @"Songs\究極焼肉レストラン！お燐の地獄亭！\thumb.png" ),
				} );

				Global.Users.Add( autoUser );

				Trace.TraceInformation( "自動演奏用ユーザ \"AutoPlay\" を新規登録しました。" );
			}
			//-----------------
			#endregion


			// 完了。

			Trace.Unindent();
			Trace.TraceInformation( "アプリケーションの初期化を完了しました。" );

			return true;
		}
		protected override void On終了()
		{
			Trace.TraceInformation( "アプリケーションの終了処理を開始します。" );
			Trace.Indent();


			// トップレベルActivityの終了。

			this.listトップレベルActivities.Clear();	// CActivity は Dispose を使わない。


			Global.Users.Dispose();

			Cチップ.t全チップサウンドの解放();
			Global.SoundTimer.Dispose();
			Global.Sound.Dispose();

			Global.Input.Dispose();
			Global.Theme.Dispose();
			Global.Direct3D.Dispose();
			
			Global.Config.Save( Path.Combine( Global.Folders.stgユーザ共通フォルダパス, @"Config.xml" ) );
			Global.Config.Dispose();
			
			Global.Folders.Dispose();


			// ログ出力を終了する。

			Trace.Unindent();
			Trace.TraceInformation( "アプリケーションの終了処理を完了しました。" );

			Trace.WriteLine( "" );
			Trace.WriteLine( "遊んでくれてありがとう！" );

			#region [ Traceリスナがあれば削除する。]
			//-----------------
			for( int i = Trace.Listeners.Count - 1; i >= 1; i-- )	// 1つ（出力ウィンドウ）以上の追加リスナがあれば、すべて削除。
			{
				Trace.IndentLevel = 0;
				Trace.Listeners[ i ].Close();
				Trace.Listeners[ i ].Dispose();
				Trace.Listeners.RemoveAt( i );
			}
			//-----------------
			#endregion


			// ウィンドウはこのあと親クラスが解放する。
		}
		protected override void Onフロー制御()
		{
			// アプリの状態遷移図にそってフローが流れるようプログラミングする。
			// this.t状態遷移する() が EXフロー制御スレッド正常終了通知例外 を出せば、本スレッドを終了する。

			try
			{
				// 起動ステージ開始 ------------------------------------------

				this.t状態遷移して待機( E状態.起動ステージ活性化 );
				this.t状態遷移して待機( E状態.起動ステージメイン );
				this.t状態遷移して待機( E状態.起動ステージ非活性化 );


				// 選曲ステージ開始 ------------------------------------------

				this.t状態遷移して待機( E状態.選曲ステージ活性化 );

			L選曲ステージメイン:

				this.t状態遷移して待機( E状態.選曲ステージメイン );

				if( E状態結果.中止 == this.e状態の結果.Get() )
					goto Lアプリ終了;

				
				// この時点で、選択曲を取得できなければならない。
				Debug.Assert( null != Global.Users.SelectedUser.Songs.SelectedSong );

				Trace.TraceInformation( "曲を選択しました:「{0}」({1})",
					Global.Users.SelectedUser.Songs.SelectedSong.stg曲名,
					Global.Folders.tファイルパスをマクロ付きパスに変換する( Global.Users.SelectedUser.Songs.SelectedSong.stg曲ファイルマクロ無しパス ) );


				// 曲を読み込む。---------------------------------------------

				this.t状態遷移して待機( E状態.曲読み込み );

				if( E状態結果.完了 != this.e状態の結果.Get() )	// 曲の読み込みに失敗したら、選曲ステージに戻る。
					goto L選曲ステージメイン;

				// この時点で、Global.演奏ステージ.Song にスコアが読み込まれていなければならない。
				Debug.Assert( null != Global.演奏ステージ.Song.listチップ );

				
				// 成功したら、選曲ステージを非活性化。

				this.t状態遷移して待機( E状態.選曲ステージ非活性化 );


				// 演奏ステージ開始 ------------------------------------------

				this.t状態遷移して待機( E状態.演奏ステージ活性化 );
				this.t状態遷移して待機( E状態.演奏ステージメイン );
				this.t状態遷移して待機( E状態.演奏ステージ非活性化 );

				if( null != Global.Users.SelectedUser.Songs.SelectedSong.ms背景動画 )
				{
					Global.Users.SelectedUser.Songs.SelectedSong.ms背景動画.t停止する();
					Global.Users.SelectedUser.Songs.SelectedSong.ms背景動画.Dispose();
				}
				if( null != Global.Users.SelectedUser.Songs.SelectedSong.sdBGM )
				{
					Global.Users.SelectedUser.Songs.SelectedSong.sdBGM.t停止する();
					Global.Users.SelectedUser.Songs.SelectedSong.sdBGM.Dispose();
				}

				this.t状態遷移して待機( E状態.選曲ステージ活性化 );
				goto L選曲ステージメイン;



			Lアプリ終了:

				this.t状態遷移して待機( E状態.アプリ終了 );

				
				// フロー制御スレッド終了 -------------------------------------

				this.bアプリケーションを終了する.Set( true );
				return;	// スレッドを正常に終了。
			}
			catch( EXフロー制御タスク正常終了通知例外 )
			{
				//this.bアプリケーションを終了する.Set( true );		この例外はこのフラグが true なのを確認して発効されるので、ここでセットする意味はない。
				return;	// スレッドを正常に終了。
			}
			finally
			{
				Trace.TraceInformation( "フロー制御スレッドを終了します。" );
			}

			// その他の例外はそのまま発出。
		}
		protected override bool On進行()
		{
			#region [ アプリ終了チェック ]
			//-----------------
			if( this.bアプリケーションを終了する.Get() )
			{
				Trace.TraceInformation( "進行タスクを終了します。" );
				this.tタスクを完了した( Eタスク.進行 );
	
				return false;	// false を返すと進行スレッドを正常終了する。
			}
			//-----------------
			#endregion

			
			// 入力のポーリング。

			Global.Input.tポーリング( Global.bWindowがActive中, bバッファ入力を使用する: true );


			// 状態別に処理。

			switch( this.eタスクの状態[ (int) Eタスク.進行 ] )
			{
				case E状態.待機: this.On待機・進行(); break;

				case E状態.起動ステージ活性化: this.On起動ステージ活性化・進行(); break;
				case E状態.起動ステージメイン: this.On起動ステージメイン・進行(); break;
				case E状態.起動ステージ非活性化: this.On起動ステージ非活性化・進行(); break;
	
				case E状態.選曲ステージ活性化: this.On選曲ステージ活性化・進行(); break;
				case E状態.選曲ステージメイン: this.On選曲ステージメイン・進行(); break;
				case E状態.選曲ステージ非活性化: this.On選曲ステージ非活性化・進行(); break;
				
				case E状態.曲読み込み: this.On曲読み込み・進行(); break;
				
				case E状態.演奏ステージ活性化: this.On演奏ステージ活性化・進行(); break;
				case E状態.演奏ステージメイン: this.On演奏ステージメイン・進行(); break;
				case E状態.演奏ステージ非活性化: this.On演奏ステージ非活性化・進行(); break;
				
				case E状態.アプリ終了: this.Onアプリ終了・進行(); break;
				
				#region [ 上記以外 → 完了 ]
				//-----------------
				default:
					this.tタスクを完了した( Eタスク.進行 );
					break;
				//-----------------
				#endregion
			}

			return true;
		}
		protected override void On描画前()
		{
			#region [ アプリ終了チェック ]
			//-----------------
			if( this.bアプリケーションを終了する.Get() )
			{
				Trace.TraceInformation( "描画前タスクを終了します。" );
				this.tタスクを完了した( Eタスク.描画前 );

				return;	// これ以降、ずっと「待機」状態で呼び出される。使用しているスレッドはメインスレッドなので終了しない。
			}
			//-----------------
			#endregion

			// 状態別に処理。

			switch( this.eタスクの状態[ (int) Eタスク.描画前 ] )
			{
				case E状態.待機: this.On待機・描画前(); break;

				case E状態.起動ステージ活性化: this.On起動ステージ活性化・描画前(); break;
				case E状態.起動ステージメイン: this.On起動ステージメイン・描画前(); break;
				case E状態.起動ステージ非活性化: this.On起動ステージ非活性化・描画前(); break;

				case E状態.選曲ステージ活性化: this.On選曲ステージ活性化・描画前(); break;
				case E状態.選曲ステージメイン: this.On選曲ステージメイン・描画前(); break;
				case E状態.選曲ステージ非活性化: this.On選曲ステージ非活性化・描画前(); break;
	
				case E状態.曲読み込み: this.On曲読み込み・描画前(); break;
				
				case E状態.演奏ステージ活性化: this.On演奏ステージ活性化・描画前(); break;
				case E状態.演奏ステージメイン: this.On演奏ステージメイン・描画前(); break;
				case E状態.演奏ステージ非活性化: this.On演奏ステージ非活性化・描画前(); break;
				
				case E状態.アプリ終了: this.Onアプリ終了・描画前(); break;
				
				#region [ 上記以外 → 完了 ]
				//-----------------
				default:
					this.tタスクを完了した( Eタスク.描画前 );
					break;
				//-----------------
				#endregion
			}
		}
		protected override void On描画( IntPtr hDevice )
		{
			#region [ アプリ終了チェック ]
			//-----------------
			if( this.bアプリケーションを終了する.Get() )
			{
				Trace.TraceInformation( "描画タスクを終了します。" );
				this.tタスクを完了した( Eタスク.描画 );

				return;	// これ以降、ずっと「待機」状態で呼び出される。使用しているスレッドはメインスレッドなので終了しない。
			}
			//-----------------
			#endregion

			// 以下、状態別に抽出されたメソッドで処理する。

			switch( this.eタスクの状態[ (int) Eタスク.描画 ] )
			{
				case E状態.待機: this.On待機・描画( hDevice ); break;

				case E状態.起動ステージ活性化: this.On起動ステージ活性化・描画( hDevice ); break;
				case E状態.起動ステージメイン: this.On起動ステージメイン・描画( hDevice ); break;
				case E状態.起動ステージ非活性化: this.On起動ステージ非活性化・描画( hDevice ); break;

				case E状態.選曲ステージ活性化: this.On選曲ステージ活性化・描画( hDevice ); break;
				case E状態.選曲ステージメイン: this.On選曲ステージメイン・描画( hDevice ); break;
				case E状態.選曲ステージ非活性化: this.On選曲ステージ非活性化・描画( hDevice ); break;
	
				case E状態.曲読み込み: this.On曲読み込み・描画( hDevice ); break;
				
				case E状態.演奏ステージ活性化: this.On演奏ステージ活性化・描画( hDevice ); break;
				case E状態.演奏ステージメイン: this.On演奏ステージメイン・描画( hDevice ); break;
				case E状態.演奏ステージ非活性化: this.On演奏ステージ非活性化・描画( hDevice ); break;
				
				case E状態.アプリ終了: this.Onアプリ終了・描画( hDevice ); break;
				
				#region [ 上記以外 → 完了 ]
				//-----------------
				default:
					this.tタスクを完了した( Eタスク.描画 );
					break;
				//-----------------
				#endregion
			}
		}

		private Size t最適な全画面サイズを取得する()
		{
			var size = Global.Users.SelectedUser.Options.sz全画面;


			// Theme に登録されている表示モードに、Config の指定と同じサイズのものがあるならそれを返す。

			foreach( var dm in Theme.list現在のデスクトップフォーマットに対応する表示モードのリスト )
			{
				if( dm.Format == Theme.dm現在の表示モード.Format &&
					dm.Width == size.Width &&
					dm.Height == size.Height )
				{
					return size;
				}
			}


			// 同サイズのものがなかった場合、Config の指定より小さい表示モードのうち最大のサイズのものを選択する。

			var newSize = new Size( 0, 0 );

			foreach( var dm in Theme.list現在のデスクトップフォーマットに対応する表示モードのリスト )
			{
				if( dm.Format == Theme.dm現在の表示モード.Format &&
					dm.Width < size.Width &&
					dm.Height < size.Height &&
					dm.Width > newSize.Width &&
					dm.Height > newSize.Height )
				{
					newSize = new Size( dm.Width, dm.Height );
				}
			}

			// 見つからなかった場合は現在のデスクトップのサイズにする。

			if( 0 == newSize.Width * newSize.Height )
			{
				newSize = new Size( Theme.dm現在の表示モード.Width, Theme.dm現在の表示モード.Height );
			}


			// Config にも登録する。

			Global.Users.SelectedUser.Options.sz全画面 = newSize;

			return newSize;
		}

		// ユーティリティ。

		public static MouseCursor MouseCursor;

		public static void tデバイスをロックして処理を行う( Action<IntPtr> action )
		{
			IntPtr hDevice = IntPtr.Zero;

			try
			{
				hDevice = Global.Direct3D.tデバイスをロックする( true );
				action( hDevice );
			}
			finally
			{
				if( IntPtr.Zero != hDevice  )
					Global.Direct3D.tデバイスのロックを解除する( hDevice );
			}
		}

		/// <summary>
		/// &lt;Tag&gt;XXX&lt;/Tag&gt; の先頭タグを reader が示している場合に、XXX の部分を読み込んで action を実行する。
		/// </summary>
		public static void tXMLのテキスト要素を読み込む( XmlTextReader reader, Action<string> action )
		{
			Debug.Assert( null != reader );
			Debug.Assert( null != action );

			if( !reader.IsEmptyElement )	// 空要素ならテキストなし
			{
				while( ( reader.Read() ) && ( XmlNodeType.EndElement != reader.NodeType ) )		// 終端要素が現れるまでループ
				{
					if( XmlNodeType.Text == reader.NodeType )	// Text 要素が現れたらデリゲート呼び出し。
						action( reader.Value );					// （終端までに何度も現れたら何度も呼び出す。）
				}

				// 終端要素を読み込んだら帰還。
			}
		}
		public static void tDispose済みなら例外発出( string name, bool bDisposed )
		{
			if( bDisposed )
				throw new ObjectDisposedException( name );
		}


		// フロー制御関連。

		private enum E状態
		{
			待機,
			起動ステージ活性化,
			起動ステージメイン,
			起動ステージ非活性化,
			オープニングステージ活性化,
			オープニングステージメイン,
			オープニングステージ非活性化,
			選曲ステージ活性化,
			選曲ステージメイン,
			選曲ステージ非活性化,
			曲読み込み,
			演奏ステージ活性化,
			演奏ステージメイン,
			演奏ステージ非活性化,
			クリアステージ活性化,
			クリアステージメイン,
			クリアステージ非活性化,
			結果ステージ活性化,
			結果ステージメイン,
			結果ステージ非活性化,
			演奏動画の停止と解放,
			演奏BGMの停止と解放,
			アプリ終了,

		}
		private enum Eタスク : int
		{
			進行 = 0,
			描画前 = 1,
			描画 = 2,
		}
		private E状態[] eタスクの状態 = new E状態[] 
		{
			E状態.待機,		// [0] 進行
			E状態.待機,		// [1] 描画前
			E状態.待機		// [2] 描画
		};
		private ManualResetEvent[] evタスクの完了イベント = new ManualResetEvent[]
		{
			new ManualResetEvent( false ),	// [0] 進行 
			new ManualResetEvent( false ),	// [1] 描画前
			new ManualResetEvent( false ),	// [2] 描画
		};
		/// <summary>
		/// <para>ev現在の状態のタスクの終了イベントを Set() する前に、このプロパティに結果を格納しておくこと。</para>
		/// <para>フロー制御・進行・描画前・描画のどのタスクがこれを格納するかは、そのときのアプリの状態によって異なる。</para>
		/// </summary>
		private CAtom<E状態結果> e状態の結果 = new CAtom<E状態結果>( E状態結果.未知 );
		private class EXフロー制御タスク正常終了通知例外 : Exception { };
		private Object lockタスク = new Object();	// タスク関連ロック用

		private void t状態遷移して待機( E状態 e次の状態 )
		{
			Trace.TraceInformation( "「" + e次の状態 + "」へ遷移します。" );

			lock( this.lockタスク )
			{
				// 各タスクの現状フラグに次の状態をコピー。

				this.eタスクの状態[ (int) Eタスク.進行 ] = e次の状態;
				this.eタスクの状態[ (int) Eタスク.描画前 ] = e次の状態;
				this.eタスクの状態[ (int) Eタスク.描画 ] = e次の状態;


				// 各スレッドの完了イベントをリセット。

				this.evタスクの完了イベント[ (int) Eタスク.進行 ].Reset();
				this.evタスクの完了イベント[ (int) Eタスク.描画前 ].Reset();
				this.evタスクの完了イベント[ (int) Eタスク.描画 ].Reset();
			}

			// すべてのタスクの完了イベントが発火するのを待つ。

			ManualResetEvent.WaitAll( this.evタスクの完了イベント );

			// アプリ終了チェック。

			if( this.bアプリケーションを終了する.Get() )
				throw new EXフロー制御タスク正常終了通知例外();
		}
		private void tタスクを完了した( Eタスク taskID )
		{
			lock( this.lockタスク )
			{
				this.eタスクの状態[ (int) taskID ] = E状態.待機;	// 状態を「待機」に設定。
			}
			this.evタスクの完了イベント[ (int) taskID ].Set();		// 完了イベントを発火。
		}
		private void t全タスクに完了を通知する()
		{
			this.tタスクを完了した( Eタスク.進行 );
			this.tタスクを完了した( Eタスク.描画前 );
			this.tタスクを完了した( Eタスク.描画 );
		}


		// 状態別に各タスクの処理を見やすくするために、ここに処理を抽出して集める。
		// 各状態において、必ず 進行・描画前・描画のいずれかのタスクが this.e状態の結果 を設定すること。（さもないとステージが終わらない。）

		#region [ 待機 ]
		//-----------------
		private void On待機・進行()
		{
			// 何もしない。

			//this.tタスクを完了した( Eタスク.進行 );		完了もしない。
		}
		private void On待機・描画前()
		{
			// 何もしない。

			//this.tタスクを完了した( Eタスク.描画前 );		完了もしない。
		}
		private void On待機・描画( IntPtr hLockedDevice )
		{
			// 何もしない。

			//this.tタスクを完了した( Eタスク.描画 );		完了もしない。
		}
		//-----------------
		#endregion
		#region [ 起動ステージ活性化 ]
		//-------------------------
		private void On起動ステージ活性化・進行()
		{
			// 何もしないで完了。

			this.tタスクを完了した( Eタスク.進行 );
		}
		private void On起動ステージ活性化・描画前()
		{
			Global.起動ステージ.t活性化();

			this.e状態の結果.Set( E状態結果.完了 );		// 状態結果を格納して
			this.tタスクを完了した( Eタスク.描画前 );	// 完了。
		}
		private void On起動ステージ活性化・描画( IntPtr hLockedDevice )
		{
			// 何もしないで完了。

			this.tタスクを完了した( Eタスク.描画 );
		}
		//-------------------------
		#endregion
		#region [ 起動ステージメイン ]
		//-------------------------
		private void On起動ステージメイン・進行()
		{
			var e結果 = (E進行結果) Global.起動ステージ.t進行();

			switch( e結果 )
			{
				case E進行結果.継続:
					break;

				default:
					this.e状態の結果.Set( E状態結果.完了 );		// 状態結果を格納して
					this.t全タスクに完了を通知する();			// 次の状態へ。
					break;
			}
		}
		private void On起動ステージメイン・描画前()
		{
			// 何もしないで完了。

			this.tタスクを完了した( Eタスク.描画前 );
		}
		private void On起動ステージメイン・描画( IntPtr hLockedDevice )
		{
			Global.起動ステージ.t描画( hLockedDevice );
			this.bPresentスキップ.Set( false );
		}
		//-------------------------
		#endregion
		#region [ 起動ステージ非活性化 ]
		//-------------------------
		private void On起動ステージ非活性化・進行()
		{
			// 何もしないで完了。

			this.tタスクを完了した( Eタスク.進行 );
		}
		private void On起動ステージ非活性化・描画前()
		{
			Global.起動ステージ.t非活性化();

			this.e状態の結果.Set( E状態結果.完了 );		// 状態結果を格納して
			this.t全タスクに完了を通知する();			// 次の状態へ。
		}
		private void On起動ステージ非活性化・描画( IntPtr hLockedDevice )
		{
			// 何もしないで完了。

			this.tタスクを完了した( Eタスク.描画 );
		}
		//-------------------------
		#endregion
		#region [ 選曲ステージ活性化 ]
		//-----------------
		private void On選曲ステージ活性化・進行()
		{
			// 何もしないで完了。

			this.tタスクを完了した( Eタスク.進行 );
		}
		private void On選曲ステージ活性化・描画前()
		{
			Global.選曲ステージ.t活性化();

			this.e状態の結果.Set( E状態結果.完了 );		// 状態結果を格納して
			this.tタスクを完了した( Eタスク.描画前 );	// 完了。
		}
		private void On選曲ステージ活性化・描画( IntPtr hLockedDevice )
		{
			// 何もしないで完了。

			this.tタスクを完了した( Eタスク.描画 );
		}
		//-----------------
		#endregion
		#region [ 選曲ステージメイン ]
		//-----------------
		private void On選曲ステージメイン・進行()
		{
			var e結果 = (E進行結果) Global.選曲ステージ.t進行();
			
			switch( e結果 )
			{
				case E進行結果.継続:
					break;

				case E進行結果.曲決定:
					this.e状態の結果.Set( E状態結果.完了 );		// 状態結果を格納して
					this.t全タスクに完了を通知する();			// 次の状態へ。
					break;

				//case E進行結果.キャンセル:
				default:
					this.e状態の結果.Set( E状態結果.中止);		// 状態結果を格納して
					this.t全タスクに完了を通知する();			// 次の状態へ。
					break;
			}
		}
		private void On選曲ステージメイン・描画前()
		{
			// 何もしないで完了。

			this.tタスクを完了した( Eタスク.描画前 );
		}
		private void On選曲ステージメイン・描画( IntPtr hLockedDevice )
		{
			Global.Direct3D.tデバイスをクリアする( hLockedDevice );
			Global.選曲ステージ.t描画( hLockedDevice );
			this.bPresentスキップ.Set( false );
		}
		//-----------------
		#endregion
		#region [ 選曲ステージ非活性化 ]
		//-----------------
		private void On選曲ステージ非活性化・進行()
		{
			// 何もしないで完了。

			this.tタスクを完了した( Eタスク.進行 );
		}
		private void On選曲ステージ非活性化・描画前()
		{
			Global.選曲ステージ.t非活性化();

			this.e状態の結果.Set( E状態結果.完了 );		// 状態結果を格納して
			this.t全タスクに完了を通知する();			// 次の状態へ。
		}
		private void On選曲ステージ非活性化・描画( IntPtr hLockedDevice )
		{
			// 何もしないで完了。

			this.tタスクを完了した( Eタスク.描画 );
		}
		//-----------------
		#endregion
		#region [ 曲読み込み ]
		//-----------------
		private void On曲読み込み・進行()
		{
			// 何もしないで完了。

			this.tタスクを完了した( Eタスク.進行 );
		}
		private void On曲読み込み・描画前()
		{
			Global.演奏ステージ.Song = new Song( Global.Users.SelectedUser.Songs.SelectedSong );	// 演奏には複製を使用し、原盤はいじらない。
			Options options = Global.Users.SelectedUser.Options;

			try
			{
				// スコアファイルを読み込む。

				Global.演奏ステージ.Song.tスコアを読み込む( Global.演奏ステージ.Song.stg曲ファイルマクロ無しパス, options );
				Trace.TraceInformation( "曲の読み込みに成功しました。" );

				this.e状態の結果.Set( E状態結果.完了 );		// 状態結果＝完了
			}
			catch
			{
				// 読み込み失敗。

				Trace.TraceWarning( "曲の読み込みに失敗しました。" );
				this.e状態の結果.Set( E状態結果.中止 );		// 状態結果＝中止
			}
		
			this.bPresentスキップ.Set( true );
			this.t全タスクに完了を通知する();
		}
		private void On曲読み込み・描画( IntPtr hLockedDevice )
		{
			// 何もしないで完了。

			this.tタスクを完了した( Eタスク.描画 );
		}
		//-----------------
		#endregion
		#region [ 演奏ステージ活性化 ]
		//-----------------
		private void On演奏ステージ活性化・進行()
		{
			// 何もしないで完了。

			this.tタスクを完了した( Eタスク.進行 );
		}
		private void On演奏ステージ活性化・描画前()
		{
			Global.演奏ステージ.t活性化();

			this.e状態の結果.Set( E状態結果.完了 );		// 状態結果を格納して
			this.t全タスクに完了を通知する();			// 次の状態へ。
		}
		private void On演奏ステージ活性化・描画( IntPtr hLockedDevice )
		{
			// 何もしないで完了。

			this.tタスクを完了した( Eタスク.描画 );
		}
		//-----------------
		#endregion
		#region [ 演奏ステージメイン ]
		//-----------------
		private void On演奏ステージメイン・進行()
		{
			var e結果 = (E進行結果) Global.演奏ステージ.t進行();

			switch( e結果 )
			{
				case E進行結果.クリア:
					this.e状態の結果.Set( E状態結果.完了 );		// 状態結果を格納して
					this.t全タスクに完了を通知する();			// 次の状態へ。
					break;

				case E進行結果.キャンセル:
					this.e状態の結果.Set( E状態結果.中止 );		// 状態結果を格納して
					this.t全タスクに完了を通知する();			// 次の状態へ。
					break;
			}
		}
		private void On演奏ステージメイン・描画前()
		{
			Global.演奏ステージ.t描画前();
		}
		private void On演奏ステージメイン・描画( IntPtr hLockedDevice )
		{
			Global.Direct3D.tデバイスをクリアする( hLockedDevice );
			Global.演奏ステージ.t描画( hLockedDevice );
			this.bPresentスキップ.Set( false );
		}
		//-----------------
		#endregion
		#region [ 演奏ステージ非活性化 ]
		//-----------------
		private void On演奏ステージ非活性化・進行()
		{
			// 何もしないで完了。

			this.tタスクを完了した( Eタスク.進行 );
		}
		private void On演奏ステージ非活性化・描画前()
		{
			Global.演奏ステージ.t非活性化();

			this.e状態の結果.Set( E状態結果.完了 );		// 状態結果を格納して
			this.t全タスクに完了を通知する();			// 次の状態へ。
		}
		private void On演奏ステージ非活性化・描画( IntPtr hLockedDevice )
		{
			// 何もしないで完了。

			this.tタスクを完了した( Eタスク.描画 );
		}
		//-----------------
		#endregion
		#region [ アプリ終了 ]
		//-----------------
		private void Onアプリ終了・進行()
		{
			Trace.TraceInformation( "アプリケーションを終了します。" );
	
			this.bアプリケーションを終了する.Set( true );
			
			this.e状態の結果.Set( E状態結果.完了 );		// 状態結果を格納して
			this.t全タスクに完了を通知する();			// 次の状態へ。
		}
		private void Onアプリ終了・描画前()
		{
			// 何もしないで完了。

			this.tタスクを完了した( Eタスク.描画前 );
		}
		private void Onアプリ終了・描画( IntPtr hLockedDevice )
		{
			// 何もしないで完了。

			this.tタスクを完了した( Eタスク.描画 );
		}
		//-----------------
		#endregion


		// Program.App.Window イベント。

		private void Window_MouseDown( object sender, MouseEventArgs e )
		{
			//foreach( var gui in CActGUIBase.Instances )
			//	if( ( null == gui.ParentPanel ) && gui.b活性化してる )
			//		gui.OnWindowMouseDown();
		}
		private void Window_Click( object sender, EventArgs e )
		{
			//foreach( var gui in CActGUIBase.Instances )
			//	if( ( null == gui.ParentPanel ) && gui.b活性化してる )
			//		gui.OnWindowClick();
		}
		private void Window_MouseClick( object sender, MouseEventArgs e )
		{
			//foreach( var gui in CActGUIBase.Instances )
			//	if( ( null == gui.ParentPanel ) && gui.b活性化してる )
			//		gui.OnWindowMouseClick();
		}
		private void Window_MouseUp( object sender, MouseEventArgs e )
		{
			//foreach( var gui in CActGUIBase.Instances )
			//	if( (null == gui.ParentPanel ) && gui.b活性化してる )
			//		gui.OnWindowMouseUp();
		}
		private void Window_Activated( object sender, EventArgs e )
		{
			Global.bWindowがActive中 = true;
		}
		private void Window_Deactivate( object sender, EventArgs e )
		{
			Global.bWindowがActive中 = false;
		}
	}
}
