///////////////////////////////////////////////////////////
// MahjongTable.cs
// Copyright (c) by Ysama September, 2024
// Free Images come from:
// https://www.ac-illust.com/main/search_result.php?word=
//         麻雀&search_word=麻雀牌&page=0#google_vignette
///////////////////////////////////////////////////////////
using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using System.Reflection;		//Assemblyを使う為
using System.Resources;			//リソース関係クラス等の使用の為

namespace mahjongtable
{
	///////////////////////////////
	//MahjongTable（麻雀台）クラス
	///////////////////////////////
	public class MahjongTable : Panel					//ピクチャー（ボックス）の枠となるPanel
	{
		//メンバーフィールド（変数）
		PictureBox picBox;								//ピクチャーボックスコントロール
		Bitmap pbCanvas;								//貼り付ける描画用ビットマップ
		Graphics cvsGr;									//pbCanvasの描画ハンドル（hDC）

		//メンバープロパティ
		public int cvsWidth {get; private set;}			//pbCanvasの幅
		public int cvsHeight {get; private set;}		//pbCanvasの高さ
		public int msX {get; private set;}				//マウスが押された時のx座標
		public int msY {get; private set;}				//マウスが押された時のx座標
		public MouseButtons msButton {get; private set;}//押されたマウスボタン（MouseButtonEventArgs.ChangedButton）
		public MJTile[] Tiles {get; private set;}		//洗牌牌後の「山」（MJTileインスタンス配列）
		public int NextTile {get; private set;}			//「山」から配られた牌の数

		public MahjongTable(int W = 864, int H = 702)	//コンストラクター
		{
			//メンバープロパティの初期化（自動実装はC# 6.0から）
			cvsWidth = W;								//pbCanvasの幅（初期値暫定－(幅44 + 4) x 18枚）
			cvsHeight = H;								//pbCanvasの高さ（初期値暫定－(高74 + 4) x 9段）
			Tiles = new MJTile[136];					//Tiles配列の添字配列の初期化（(数牌3 x 9 + 風牌4 + 三元牌3) x 4）
			//MahjongTable(Panel)の初期化
			this.Size = new Size(864, 702);				//初期値は描画範囲よりも小さく設定
			this.MinimumSize = new Size(432, 351);		//初期値を最小値とする（暫定）
			this.MaximumSize = new Size(cvsWidth + 8, cvsHeight + 8);	//"+8"はスクロールバーマージン
			this.BorderStyle = BorderStyle.Fixed3D;
			this.AutoScroll = true;						//スクロールバー付
			this.BackColor = Color.DarkGreen;
			//描画ビットマップとデバイスコンテキストの生成
			pbCanvas = new Bitmap(cvsWidth, cvsHeight);
			cvsGr = Graphics.FromImage(pbCanvas);
			//ピクチャーボックスコントロール
			picBox = new PictureBox();
			picBox.Location = new Point(0, 0);		//枠内の位置
			picBox.SizeMode = PictureBoxSizeMode.AutoSize;	//画像サイズでPictureBoxの大きさが変更される
			picBox.Image = pbCanvas;				//pbCanvasを貼る
			picBox.Click += Panel_Click;			//picBox（即ちそのコンテナーであるthis）のClickイベント
			picBox.MouseMove += Panel_Move;			//picBox（即ちそのコンテナーであるthis）のClickイベント
			this.Controls.Add(picBox);				//picBoxを子にする
			//MJTileクラスインスタンスの初期化と洗牌（シーパイ）
			XiPai();
		}

		~MahjongTable()						//デストラクター
		{
			cvsGr.Dispose();				//pbCanvasの描画ハンドル廃棄
			pbCanvas.Dispose();				//描画用ビットマップ廃棄
		}

		//MahjongTable上のクリックイベント（picBox→Panelへ伝達）
		private void Panel_Click(object sender, EventArgs e)
		{
			MouseEventArgs mouseArgs = e as MouseEventArgs;
			Point p = this.PointToClient(Cursor.Position);
			msX = p.X;						//クリックされた際のX座標を記録
			msY = p.Y;						//クリックされた際のY座標を記録
			msButton = mouseArgs.Button;	//クリックされたボタンを記録
			this.OnClick(e);				//picBoxからPanelのClickイベントを呼び出す
		}

		//MahjongTable上のマウス移動イベント（picBox→Panelへ伝達）
		private void Panel_Move(object sender, MouseEventArgs e)
		{
			MouseEventArgs mouseArgs = e as MouseEventArgs;
			Point p = this.PointToClient(Cursor.Position);
			this.OnMouseMove(e);				//picBoxからPanelのClickイベントを呼び出す
		}

		//MJTileインスタンス配列の初期化
		private void InitMJTiles()
		{
			//各牌は4枚
			for(int i = 0; i < 4; i++)
			{
				//数牌は三種＋風牌＋三元牌
				for(int j = 0; j < 5; j++)
				{
					//数牌は三種
					if(j < 3)
					{
						for(int k = 0; k < 9; k++)
						{
							Tiles[i * 34 + j * 9 + k] = new MJTile();
							Tiles[i * 34 + j * 9 + k].Suit = j;			//"Man" - 0, "Pin" - 1, "So" - 2, "Kaze" - 3, "Sangen" - 4
							Tiles[i * 34 + j * 9 + k].Number = k + 1;	//1 - 9
							Tiles[i * 34 + j * 9 + k].Owner = 0;			//0は一人ゲームまたは未配布を意味する
							Tiles[i * 34 + j * 9 + k].Shown = false;
							Tiles[i * 34 + j * 9 + k].Discarded = false;
						}
					}
					//風牌は四種
					if(j == 3)
					{
						for(int k = 0; k < 4; k++)
						{
							Tiles[i * 34 + 27 + k] = new MJTile();
							Tiles[i * 34 + 27 + k].Suit = j;			//"Man" - 0, "Pin" - 1, "So" - 2, "Kaze" - 3, "Sangen" - 4
							Tiles[i * 34 + 27 + k].Number = k + 1;	//1 - 4
							Tiles[i * 34 + 27 + k].Owner = 0;		//0は一人ゲームまたは未配布を意味する
							Tiles[i * 34 + 27 + k].Shown = false;
							Tiles[i * 34 + 27 + k].Discarded = false;
						}
					}
					//数牌は三種
					if(j == 4)
					{
						for(int k = 0; k < 3; k++)
						{
							Tiles[i * 34 + 31 + k] = new MJTile();
							Tiles[i * 34 + 31 + k].Suit = j;			//"Man" - 0, "Pin" - 1, "So" - 2, "Kaze" - 3, "Sangen" - 4
							Tiles[i * 34 + 31 + k].Number = k + 1;	//1 - 3
							Tiles[i * 34 + 31 + k].Owner = 0;			//0は一人ゲームまたは未配布を意味する
							Tiles[i * 34 + 31 + k].Shown = false;
							Tiles[i * 34 + 31 + k].Discarded = false;
						}
					}
				}
			}
			NextTile = 0;
		}

		//MJTileインスタンス配列の洗牌
		public void XiPai()
		{
			int n = 136;	//(数牌3 x 9 + 風牌4 + 三元牌3) x 4
			InitMJTiles();
			//乱数の初期化（発生パターンが時間により変化する）
			Random rand = new Random((int)DateTime.Now.Ticks & 0x0000FFFF);
			while(n > 1)	//麻雀牌が2枚になった回が最後
			{
				n--;						//'0'ベースの配列の最後を指す
				int m = rand.Next(n + 1);	//n + 1より小さい0 以上の整数
				MJTile temp = Tiles[m];
				Tiles[m] = Tiles[n];
				Tiles[n] = temp;
			}
		}

		//MahjongTable（picBox）を緑色でクリアする
		public void Clear()
		{
			cvsGr.Clear(Color.DarkGreen);
		}

		//MJTileインスタンスの表示面指定
		public void TurnFace(int tileNo, bool face)
		{
			Tiles[tileNo].Shown = face;
		}

		//デフォルトサイズ（cvsWidth = 48 x 9, cvsHeight = 78 x 5）のグリッド用
		//コンストラクターでサイズを変更する場合には変更を要する
		public int GeGridtNo(int x, int y)
		{
			int offset = 2;
			if((x - offset < 0 || (x - offset) > 48 * 18)	//グリッドサイズ：W = 48dots (44 + 4)
			 || (y - offset < 0 || (y - offset) > 78 * 9))	//グリッドサイズ：H = 78dots (74 + 4)
			 	return -1;									//エラー
			 else
				return ((y - offset) / 78) * 18 + ((x - offset) / 48);
		}

		//MJTileインスタンスの指定位置表示
		//位置指定はクライアント座標、または横18 x 縦9のグリッド座標
		//グリッドサイズ：W = 48dots (44 + 4)、H = 78dots (74 + 4)
		//offsetサイズ  ：2
		public void ShowTile(int tileNo, int x, int y, bool grid = false)
		{
			//本プログラムの埋め込みリソースのリソースマネージャーを作成
			Assembly asm = Assembly.GetExecutingAssembly();
			ResourceManager rm = new ResourceManager("Mahjong", asm);
			//ピクチャーボックス描画用ビットマップ
			Bitmap bm;
			//リソースID名の作成
			string[] suit = new string[] {"Man", "Pin", "So", "Kaze", "Sangen"};
			string name = suit[Tiles[tileNo].Suit] + Tiles[tileNo].Number.ToString();
			if(Tiles[tileNo].Shown)
				//MJTileインスタンスのビットマップを読み込む
				bm = (Bitmap)rm.GetObject(name);
			else
				//裏面のビットマップを読み込む
				bm = (Bitmap)rm.GetObject("Back");
			bm.MakeTransparent();
			if(grid)
			{
				int offset = 2;
				cvsGr.DrawImage(bm, x * 48 + offset, y * 78 + offset, bm.Width, bm.Height);
			}
			else
			{
				cvsGr.DrawImage(bm, x, y, bm.Width, bm.Height);
			}
			//ビットマップを廃棄
			bm.Dispose();
			//pbCanvasを貼る
			picBox.Image = pbCanvas;
		}

		//MJTileインスタンスの選択（選択の解除は同じ牌のRemoveTileを行い、
		//再描画が必要な場合には再度ShowTileを行う－理由：ビットマップの
		//余白は透明なので枠線が残ってしまう為。）
		//位置指定はクライアント座標、または横18 x 縦9のグリッド座標
		//グリッドサイズ：W = 48dots (44 + 4)、H = 78dots (74 + 4)
		//offsetサイズ  ：2
		public void SelectTile(int tileNo, int x, int y, bool grid = false)
		{
			int Width = 44;
			int Height = 74;
			Pen GridPen = new Pen(Brushes.Red);	//枠線の色
			GridPen.Width = 2.0f;				//枠線の太さ
			if(grid)
			{
				int offset = 2;
				//線の太さが2なので描画位置をイメージサイズ内に十分収まるように（太さ x 2）小さい枠にする。
				cvsGr.DrawRectangle(GridPen, x * 48 + offset + 1, y * 78 + offset + 1, Width - 3, Height - 3);
			}
			else
			{
				cvsGr.DrawRectangle(GridPen, x + 1, y + 1, Width - 3, Height - 3);
			}
			//GridPenの廃棄
			GridPen.Dispose();
			//pbCanvasを貼る
			picBox.Image = pbCanvas;
		}

		//MJTileインスタンスの消去
		//位置指定はクライアント座標、または横18 x 縦9のグリッド座標
		//グリッドサイズ：W = 48dots (44 + 4)、H = 78dots (74 + 4)
		//offsetサイズ  ：2
		public void RemoveTile(int tileNo, int x, int y, bool grid = false, bool flag = true)
		{
			int Width = 48;
			int Height = 78;
			if(grid)
			{
				int offset = 2;
				cvsGr.FillRectangle(Brushes.DarkGreen, x * 48 + offset, y * 78 + offset, Width, Height);
			}
			else
			{
				cvsGr.FillRectangle(Brushes.DarkGreen, x, y, Width, Height);
			}
			//Discardedサインを指定（既定値はtrue）
			Tiles[tileNo].Discarded = flag;
			//pbCanvasを貼る
			picBox.Image = pbCanvas;
		}

		//MJTileインスタンス配列からカードを配る
		public void HaiPai(int no_of_players, int no_of_cards)
		{
			for(int i = 0; i < no_of_cards; i++)
				for(int j = 0; j < no_of_players; j++)
					Tiles[i * no_of_players + j].Owner = j + 1;
			NextTile += no_of_players * no_of_cards;
		}

		//MJTileインスタンス配列からカードを一枚引く
		public int Tsumo(int player)
		{
			Tiles[NextTile++].Owner = player;
			return (136 - NextTile);		//戻り値は残牌数（0ならゲーム終了）
		}
	}

	/////////////////////////
	//MJTile（麻雀牌）構造体
	/////////////////////////
	public struct MJTile					//合計16バイト
	{
		public int Suit {get; set;}			//"Man" - 0, "Pin" - 1, "So" - 2, "Kaze" - 3, "Sangen" - 4
		public int Number {get; set;}		//数牌（1 - 9）、風牌（1 - 4）、三元牌（1 - 3）
		public int Owner {get; set;}		//1から始まるオーナー（プレーヤー）番号－"0"は一人ゲームまたは未配布を意味する
		public bool Shown {get; set;}		//表示を表にするか、裏にするかのフラグ（裏の場合は常にBackを表示）
		public bool Discarded {get; set;}	//捨てられた（true－以降はゲームで使われない）が否か（ポン、チーの場合はfalse）
	}
}
