//////////////////////
//	EightPuzzle.cs
// Copyright (c) 2025
//	 By Y-Sama
//////////////////////
using System;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Imaging;	//PixelFormatを使う為
using System.Reflection;		//Assemblyを使う為
using EtPuzzle;					//NBoardとePuzzleを使う為

namespace EightPuzzle
{
	public partial class EtPuzzle : Form
	{
		//メンバー定数
		const string Filter = "イメージファイル|*.bmp;*.jpeg;*.jpg;*.gif;*.tiff;*.png;*.wmf;*.emf;*.ico";
		const int Num = 9;					//8Puzzleの3 x 3盤面用定数
		const int None = -1;				//不存在の定数
		//メンバーグラフィックス
		Bitmap BaseImg;						//ピクチャーボックスサイズ化した基本画像
		Bitmap Canvas;						//Bitmapインスタンスによる仮想画面
		Graphics cvsHandle;					//Canvasのグラフィックハンドル
		Image[] PieceImg = new Image[8];	//分割画像（[0]-[7]）
		//メンバーコントロール
		PictureBox picBox;
		CheckBox chkBox;
		Button btnFile, btnStart, btnUp, btnDown, btnLeft, btnRight, btnHint, btnExit;
		//メンバーフィールド
		int picOrgWidth;					//ピクチャーボックス幅の初期値
		int picOrgHeight;					//ピクチャーボックス高さの初期値
		Single PieceW;						//分割画像幅
		Single PieceH;						//分割画像高さ
		NBoard Board = new NBoard();		//8パズル用NBoardクラスインスタンス（初期値は0→8）
		ePuzzle ep = new ePuzzle();			//8パズル（ePuzzle）クラスインスタンス

		[STAThread]
		public static void Main()
		{
			Application.Run(new EtPuzzle());
		}

		public EtPuzzle()
		{
			Assembly myOwn = Assembly.GetEntryAssembly();
			this.Icon = Icon.ExtractAssociatedIcon(myOwn.Location);	//プログラムアイコンをフォームにつける
			this.Size = new Size(640, 480);
			this.MinimumSize = new Size(320, 190);
			this.FormBorderStyle = FormBorderStyle.FixedDialog;		//ダイアログ枠
			this.MaximizeBox = false;
			this.Text = "8 Puzzle";
			this.Load += EtPuzzle_Load;
			this.Shown += EtPuzzle_Shown;
		}

		private void EtPuzzle_Load(object sender, EventArgs e)
		{
			//画像選択ボタン
			btnFile = new Button();
			btnFile.Location = new Point(ClientSize.Width - btnFile.Width - 10, 10);
			btnFile.Text = "画像を選ぶ";
			btnFile.Anchor = (AnchorStyles.Top | AnchorStyles.Right);
			btnFile.Click += OnFileOpen_Click;
			this.Controls.Add(btnFile);

			//難易度チェックボックス
			chkBox = new CheckBox();
			//chkBox.Width = 40;
			chkBox.Location = new Point(ClientSize.Width - btnFile.Width - 10, btnFile.Height + 20);
			chkBox.Text = "難易度大";
			chkBox.Anchor = (AnchorStyles.Top | AnchorStyles.Right);
			this.Controls.Add(chkBox);

			//開始ボタン
			btnStart = new Button();
			btnStart.Location = new Point(ClientSize.Width - btnStart.Width - 10, btnFile.Height + chkBox.Height + 30);
			btnStart.Text = "開始";
			btnStart.Anchor = (AnchorStyles.Top | AnchorStyles.Right);
			btnStart.Click += OnStart_Click;
			this.Controls.Add(btnStart);

			//↑ボタン
			btnUp = new Button();
			btnUp.Size = new Size(24, 24);
			btnUp.Location = new Point(ClientSize.Width - 58, btnFile.Height + chkBox.Height + btnStart.Height + 40);
			btnUp.Text = "↑";
			btnUp.Anchor = (AnchorStyles.Top | AnchorStyles.Right);
			btnUp.Click += OnUp_Click;
			this.Controls.Add(btnUp);

			//↓ボタン
			btnDown = new Button();
			btnDown.Size = new Size(24, 24);
			btnDown.Location = new Point(ClientSize.Width - 58, btnFile.Height + chkBox.Height + btnStart.Height + 88);
			btnDown.Text = "↓";
			btnDown.Anchor = (AnchorStyles.Top | AnchorStyles.Right);
			btnDown.Click += OnDown_Click;
			this.Controls.Add(btnDown);

			//←ボタン
			btnLeft = new Button();
			btnLeft.Size = new Size(24, 24);
			btnLeft.Location = new Point(ClientSize.Width - 82, btnFile.Height + chkBox.Height + btnStart.Height + 64);
			btnLeft.Text = "←";
			btnLeft.Anchor = (AnchorStyles.Top | AnchorStyles.Right);
			btnLeft.Click += OnLeft_Click;
			this.Controls.Add(btnLeft);

			//→ボタン
			btnRight = new Button();
			btnRight.Size = new Size(24, 24);
			btnRight.Location = new Point(ClientSize.Width - 34, btnFile.Height + chkBox.Height + btnStart.Height + 64);
			btnRight.Text = "→";
			btnRight.Anchor = (AnchorStyles.Top | AnchorStyles.Right);
			btnRight.Click += OnRight_Click;
			this.Controls.Add(btnRight);

			//終了ボタン
			btnExit = new Button();
			btnExit.Location = new Point(ClientSize.Width - btnExit.Width - 6, ClientSize.Height - btnExit.Height - 12);
			btnExit.Text = "終了";
			btnExit.Anchor = (AnchorStyles.Bottom | AnchorStyles.Right);
			btnExit.Click += OnExit_Click;
			this.Controls.Add(btnExit);

			//ヒントボタン
			btnHint = new Button();
			btnHint.Location = new Point(ClientSize.Width - btnHint.Width - 6, ClientSize.Height - btnExit.Height - btnHint.Height - 22);
			btnHint.Text = "ヒント";
			btnHint.Anchor = (AnchorStyles.Bottom | AnchorStyles.Right);
			btnHint.Click += OnHint_Click;
			this.Controls.Add(btnHint);

			//ボタン状態の初期設定
			BtnStatus(0);

			//ピクチャーボックス
			picBox = new PictureBox();
			//位置
			picBox.Location = new Point(10, 10);
			picBox.Anchor = (AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Bottom | AnchorStyles.Right);
			//サイズ
			picBox.Width = ClientSize.Width - btnFile.Width - 30;
			picBox.Height = ClientSize.Height - 20;
			//オリジナルサイズを保存
			picOrgWidth = picBox.Width;
			picOrgHeight = picBox.Height;
			//その他プロパティ
			picBox.BorderStyle = BorderStyle.Fixed3D;
			//picBox.SizeMode = PictureBoxSizeMode.StretchImage;	//クライアントサイズに合わせる
			//picBox.SizeMode = PictureBoxSizeMode.Zoom;			//縦横比が変更されない
			//Drag and Dropイベントハンドラの追加
			picBox.DragEnter += new DragEventHandler(PB_DragEnter);
			picBox.DragDrop += new DragEventHandler(PB_DragDrop);
			picBox.AllowDrop = true;
			//FormにpicBoxを追加
			this.Controls.Add(picBox);
		}

		private void EtPuzzle_Shown(object sender, EventArgs e)
		{
			//起動時の引数をチェックし、引数にファイル名があればそれを読み込む
			string[] arg = System.Environment.GetCommandLineArgs();
			if(arg.Length > 1)
			{
				//画像ファイルチェック
				if(Filter.IndexOf((System.IO.Path.GetExtension(arg[arg.Length - 1])).ToLower()) == -1)
				{
					MessageBox.Show(arg[arg.Length - 1] + "は画像ファイルではありません。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
					return;
				}
 				//フォーム描画が完了した後に呼び出す
				this.BeginInvoke(new Action(() =>
				{
					if(!InitCanvas(arg[arg.Length - 1]))
						MessageBox.Show("初期化に失敗しました。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
					else
						//ボタン状態の変更
						BtnStatus(1);
				}));
			}
			else
				picBox.Image = null;
		}

		//終了処理
		protected override void OnFormClosing(FormClosingEventArgs e)
		{
			base.OnFormClosing(e);
			DialogResult dr = MessageBox.Show("終了しますか？", "確認", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
			if(dr == DialogResult.No)
				e.Cancel = true;
			else	//終了前にグラフィック関係のリソースを開放する
			{
				InitImage();		//グラフィック関係の初期化
				picBox.Dispose();	//PictureBoxリソースを解放
			}
		}

		//画像選択ボタン
		private void OnFileOpen_Click(object sender, EventArgs e)
		{
			OpenFileDialog ofDlg = new OpenFileDialog();
			//ファイルフィルターの指定
			ofDlg.Filter = Filter;
			ofDlg.RestoreDirectory = true;	//初期ディレクトリへ復帰
			ofDlg.CheckPathExists = true;	//ファイルパスの存在チェック
			ofDlg.InitialDirectory = ".";	// デフォルトのフォルダーの指定
			ofDlg.Title = "ファイルを開く";	//ダイアログのタイトルを指定する
			if(ofDlg.ShowDialog() == DialogResult.Cancel)	//ダイアログを表示する
			{
				MessageBox.Show("キャンセルされました。", "キャンセル", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
			}
			else
			{
				//仮想画面処理
				if(!InitCanvas(ofDlg.FileName))
					MessageBox.Show("初期化に失敗しました。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
				else
					//ボタン状態の変更
					BtnStatus(1);
			}
			//オブジェクトを破棄する
			ofDlg.Dispose();
		}

		//開始ボタン
		private void OnStart_Click(object sender, EventArgs e)
		{
			//epを使った乱数の初期化
			if(chkBox.Checked)
				ep.SwapArr(false);		//難易度（false－難解）
			else
				ep.SwapArr(true);		//難易度（true－簡単）
			//epのstBoardをGetBoard(None)で取得し、そのデータをBoard.ptPiecesへコピー
			Board.Copy(ep.GetBoard(None).ptPieces);
			//ピース画像の表示
			ShowPieces();
			//ドラッグアンドドロップの禁止
			picBox.AllowDrop = false;
			//ボタン状態の変更
			BtnStatus(2);
		}

		//↑ボタン
		private void OnUp_Click(object sender, EventArgs e)
		{
			int pos = Board.IndexOf(Num - 1);	//Board.Piecesの空ピース（8）の位置
			int y = pos / 3;	//空ピースのy位置
			if(y < 1)
				Console.Beep();
			else				//posと↑のピース（pos - 3）の値をスワップする
				Board.Swap(pos, pos - 3);
			//ピース画像の表示
			ShowPieces();
			//完成判定
			IsDone();
		}

		//↓ボタン
		private void OnDown_Click(object sender, EventArgs e)
		{
			int pos = Board.IndexOf(Num - 1);	//Board.Piecesの空ピース（8）の位置
			int y = pos / 3;	//空ピースのy位置
			if(y > 1)
				Console.Beep();
			else				//posと↓のピース（pos + 3）の値をスワップする
				Board.Swap(pos, pos + 3);
			//ピース画像の表示
			ShowPieces();
			//完成判定
			IsDone();
		}

		//←ボタン
		private void OnLeft_Click(object sender, EventArgs e)
		{
			int pos = Board.IndexOf(Num - 1);	//Board.Piecesの空ピース（8）の位置
			int x = pos % 3;	//空ピースのx位置
			if(x == 0)
				Console.Beep();
			else				//posと←のピース（pos - 1）の値をスワップする
				Board.Swap(pos, pos - 1);
			//ピース画像の表示
			ShowPieces();
			//完成判定
			IsDone();
		}

		//→ボタン
		private void OnRight_Click(object sender, EventArgs e)
		{
			int pos = Board.IndexOf(Num - 1);	//Board.Piecesの空ピース（8）の位置
			int x = pos % 3;	//空ピースのx位置
			if(x == 2)
				Console.Beep();
			else				//posと→のピース（pos + 1）の値をスワップする
				Board.Swap(pos, pos + 1);
			//ピース画像の表示
			ShowPieces();
			//完成判定
			IsDone();
		}

		//ヒントボタン
/* ePuzzle.Start()メソッドが時間がかかる処理である為、非同期で処理するには、
etPuzzle.dllのStart()メソッドを先ず"Tast<bool>"型に書き直す必要がある。
そのうえで、この処理を
		private async void OnHint_Click(object sender, EventArgs e)
とし、	bool Solved = ep.Start();を	bool Solved = await ep.Start();
に変更するようだ。
*/
		private void OnHint_Click(object sender, EventArgs e)
		{
			//総てのボタンをdisabledにする
			BtnStatus();
			btnFile.Enabled = false;
			//Boardと同じ盤面をepで解く
			bool Solved = ep.Start();
			if(Solved)	//パズルが解けた場合、ループを抜ける
			{
				//パズルの解を取得
				int[] Solution = ep.GetTraceBack();
				//パズルの解を表示
				for(int i = 0; i < Solution.Length; i++)
				{
					//一旦キャンバスをpicBoxの背景色でクリアする
					cvsHandle.Clear(picBox.BackColor);
					NBoard bd = ep.GetBoard(Solution[i]);
					//左上から右へ0-2、3-5、6-8の順に、Board.ptPieces[i]配列の値（0-8）を引数とするPieceImg[]を表示する
					for(int j = 0; j < Num; j++)
					{
						if(bd.ptPieces[j] != 8)
						{
							cvsHandle.DrawImage(PieceImg[bd.ptPieces[j]], (int)PieceW * (j % 3), (int)PieceH * (j / 3));
							cvsHandle.DrawRectangle(Pens.Black, (int)PieceW * (j % 3), (int)PieceH * (j / 3), (int)PieceW, (int)PieceH);
						}
						else
							cvsHandle.DrawRectangle(Pens.Red, (int)PieceW * (j % 3), (int)PieceH * (j / 3), (int)PieceW, (int)PieceH);
					}
					picBox.Image = Canvas;
					int n = Solution.Length - i - 1;
					if(n == 0)
						MessageBox.Show("初期画面に戻りました。", "初手", MessageBoxButtons.OK, MessageBoxIcon.Information);
					else
						MessageBox.Show("Trace backして、完成形から初期画面に戻ります。", "第" + (Solution.Length - i - 1) + "手", MessageBoxButtons.OK, MessageBoxIcon.Information);
				}
			}
			else
				MessageBox.Show("全ての盤面をチェックしましたが、解けませんでした。", "失敗", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
			//開始状態にする
			BtnStatus(2);
			MessageBox.Show("あなたのパズル画面に戻りました。", "通知", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
			ShowPieces();
		}

		private void OnExit_Click(object sender, EventArgs e)
		{
			//終了処理
			Close();
		}

		///////////////////////////
		//コントロール関連メソッド
		///////////////////////////
		//picBoxにドラッグされた時
		private void PB_DragEnter(object sender, DragEventArgs e)
		{
			if(e.Data.GetDataPresent(DataFormats.FileDrop))
				e.Effect = DragDropEffects.Copy;
			else
				e.Effect = DragDropEffects.None;
		}

		//picBoxにドロップされたとき
		private void PB_DragDrop(object sender, DragEventArgs e)
		{
			//ドロップされたファイルパスを取得
			string[] ddlist =  (string[])e.Data.GetData(DataFormats.FileDrop, false);
			//画像ファイルチェック
			if(Filter.IndexOf((System.IO.Path.GetExtension(ddlist[ddlist.Length - 1])).ToLower()) == -1)
			{
				MessageBox.Show(ddlist[ddlist.Length - 1] + "は画像ファイルではありません。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
				return;
			}
			//仮想画面処理
			if(!InitCanvas(ddlist[ddlist.Length - 1]))
				MessageBox.Show("初期化に失敗しました。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
			else
				//ボタン状態の変更
				BtnStatus(1);
		}

		///////////////////
		//ユーザーメソッド
		///////////////////
		//画像ファイルパスを受け取った際の処理
		private bool InitCanvas(string filepath)
		{
			//グラフィック関係の初期化
			InitImage();
			//filepathで指定された画像ファイルを一旦orgに読み込み、サイズを調整してBaseImgを作成
			Image org = Image.FromFile(filepath);
			//画像ファイルの縦横比に合わせてpicBoxのサイズを再設定
			if(org.Width * org.Height == 0)
				return false;
			else
			{
				if(org.Width > org.Height)
				{
					picBox.Width = picOrgWidth;
					picBox.Height = (int)((double)picOrgWidth * (double)org.Height / (double)org.Width);
				}
				else
				{
					picBox.Height = picOrgHeight;
					picBox.Width = (int)((double)picOrgHeight * (double)org.Width / (double)org.Height);
				}
			}
			//修正されたpicBoxのサイズに合わせてBaseImgを生成
			BaseImg = new Bitmap(picBox.Width, picBox.Height);
			Graphics biHandle = Graphics.FromImage(BaseImg);
			//画像ファイルを変更したpicBoxのサイズでBaseImgに描画
			biHandle.DrawImage(org, 0, 0, picBox.Width, picBox.Height);
			//グラフィックハンドルを廃棄（開放）する
			biHandle.Dispose();
			//オリジナル画像を廃棄（開放）する
			org.Dispose();

			//BaseImgでCanvasを生成
			Canvas = new Bitmap(BaseImg);
			//cvsHandleを生成する
			cvsHandle = Graphics.FromImage(Canvas);
			//picBoxに画像ファイルを表示
			picBox.Image = Canvas;

			//分割画像サイズ設定
			PieceW = (Single)picBox.Width / 3;
			PieceH = (Single)picBox.Height / 3;
			//BasdeImgで分割画像を作成
			RectangleF Rect;
			Rect = new RectangleF(0, 0, PieceW, PieceH);
			PixelFormat Format = BaseImg.PixelFormat;
			for(int i = 0; i < 8; i++)
			{
				Single x = PieceW * (i % 3);
				Single y = PieceH * (i / 3);
				Rect = new RectangleF(x, y, PieceW, PieceH);
				PieceImg[i] = BaseImg.Clone(Rect, Format);
			}
			return true;
		}

		//グラフィック関係の初期化
		private void InitImage()
		{
			if(BaseImg != null)
				BaseImg.Dispose();		//基本画像
			if(cvsHandle != null)
				cvsHandle.Dispose();	//仮想画面のグラフィック
			if(Canvas != null)
				Canvas.Dispose();		//仮想画面
			if(PieceImg != null)
			{
				foreach(Image img in PieceImg)
					if(img != null)
						img.Dispose();	//分割画像
			}
			for(int i = 0; i < Num; i++)
				Board.ptPieces[i] = i;

		}

		//PieceImgの表示
		private void ShowPieces()
		{	//一旦キャンバスをpicBoxの背景色でクリアする
			cvsHandle.Clear(picBox.BackColor);
			//左上から右へ0-2、3-5、6-8の順に、Board.ptPieces[i]配列の値（0-8）を引数とするPieceImg[]を表示する
			for(int i = 0; i < Num; i++)
			{
				if(Board.ptPieces[i] != 8)
				{
					cvsHandle.DrawImage(PieceImg[Board.ptPieces[i]], (int)PieceW * (i % 3), (int)PieceH * (i / 3));
					cvsHandle.DrawRectangle(Pens.Black, (int)PieceW * (i % 3), (int)PieceH * (i / 3), (int)PieceW, (int)PieceH);
				}
				else
					cvsHandle.DrawRectangle(Pens.Red, (int)PieceW * (i % 3), (int)PieceH * (i / 3), (int)PieceW, (int)PieceH);
			}
			picBox.Image = Canvas;
		}

		//ボタン状態変更
		private void BtnStatus(int pat = 0)
		{
			//各ボタンのEnabled属性初期値
			btnFile.Enabled = true;
			chkBox.Enabled = false;
			btnStart.Enabled = false;
			btnUp.Enabled = false;
			btnDown.Enabled = false;
			btnLeft.Enabled = false;
			btnRight.Enabled = false;
			btnHint.Enabled = false;
			switch(pat)
			{
				case 1:		//引数1は「開始」ボタンを有効化する
					btnStart.Enabled = true;
					chkBox.Enabled = true;
					break;
				case 2:		//引数2は上下左右ボタンを有効化し、他を無効化する
					btnFile.Enabled = false;
					btnUp.Enabled = true;
					btnDown.Enabled = true;
					btnLeft.Enabled = true;
					btnRight.Enabled = true;
					btnHint.Enabled = true;
					break;
				default:	//初期値0はこの処理
					break;
			}
		}

		//完了判定
		private bool IsDone()
		{
			if(!Board.IsDone())
				return false;
			picBox.Image = BaseImg;	//分割画像ではなく、基本画像に置き換える。
			MessageBox.Show("Your made it!\r\n8 Puzzleが完成しました！\r\n（OKを押すと再初期化されます。）", "Congratulations!", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
			InitImage();
			BtnStatus(0);	//ボタン状態の初期設定
			return true;
		}
	}
}
