package com.haru.tetoru.parts;

import static com.haru.tetoru.parts.Mino.ROTATE_MINO_TYPE.*;

/*
　Minoクラスの管理構造体の管理方法を変更
　　・中心は可変
　　・周囲3点は固定で4種類(0,90,180,270)
　　(固定のため分離して保持して良い)
　　・ミノの種類として1つの形に4種類
　　
　　・回転状態は0〜3
　(方針)
　通信用途を考慮にいれて、Minoクラスが管理する情報を最小限となるようにする
*/

public class Mino extends Block implements Cloneable {

	/****************************/
	/* 管理情報の定義(ここから) */
	/****************************/

	/* 回転状態を含むミノの種類 */
	enum ROTATE_MINO_TYPE {
		/* 規則性を持たせた通し番号にしている */
		/* TODO:定数ベタガキは改善の余地あり */
		ZERO( 0, ROTATE_STATE.r0),   /* TODO:何に使う？ROTATE_STATE.r0でよい？ */
		I0(   1, ROTATE_STATE.r0),   /* (1-1)*4 + 1 */
		I90(  2, ROTATE_STATE.r90),  /* (1-1)*4 + 2 */
		I180( 3, ROTATE_STATE.r180), /* (1-1)*4 + 3 */
		I270( 4, ROTATE_STATE.r270), /* (1-1)*4 + 4 */
		O0(   5, ROTATE_STATE.r0),   /* (2-1)*4 + 1 */
		O90(  6, ROTATE_STATE.r90),  /* (2-1)*4 + 2 */
		O180( 7, ROTATE_STATE.r180), /* (2-1)*4 + 3 */
		O270( 8, ROTATE_STATE.r270), /* (2-1)*4 + 4 */ 
		T0(   9, ROTATE_STATE.r0),   /* (3-1)*4 + 1 */
		T90( 10, ROTATE_STATE.r90),  /* (3-1)*4 + 2 */
		T180(11, ROTATE_STATE.r180), /* (3-1)*4 + 3 */
		T270(12, ROTATE_STATE.r270), /* (3-1)*4 + 4 */
		S0(  13, ROTATE_STATE.r0),
		S90( 14, ROTATE_STATE.r90),
		S180(15, ROTATE_STATE.r180),
		S270(16, ROTATE_STATE.r270),
		Z0(  17, ROTATE_STATE.r0),
		Z90( 18, ROTATE_STATE.r90),
		Z180(19, ROTATE_STATE.r180),
		Z270(20, ROTATE_STATE.r270),
		J0(  21, ROTATE_STATE.r0),
		J90( 22, ROTATE_STATE.r90),
		J180(23, ROTATE_STATE.r180),
		J270(24, ROTATE_STATE.r270),
		L0(  25, ROTATE_STATE.r0),
		L90( 26, ROTATE_STATE.r90),
		L180(27, ROTATE_STATE.r180),
		L270(28, ROTATE_STATE.r270);

		private int type;
		private ROTATE_STATE state; /* ※回転状態(state)は内部でのみ使用予定 */
									/* → stateのインスタンス一致判定にメソッドを追加する */

		private ROTATE_MINO_TYPE(int type, ROTATE_STATE state) {
			this.type = type;
			this.state = state;
		}
		public int getType() {
			return type;
		}

//		public String toString() {
//			return type + "";
//		}

		/* 「ミノの種類」+「回転の状態」→「回転状態を含むミノの種類」 */
		public static ROTATE_MINO_TYPE toRotateMinoType(final MINO_TYPE minoType, final ROTATE_STATE state) {
			ROTATE_MINO_TYPE rotateMinoType = ROTATE_MINO_TYPE.ZERO;

			/* 通し番号は規則性に基づきを演算して取得 */
			final int NUM = (minoType.get() - 1) * ROTATE_STATE.STATE_NUM + (state.get() + 1); /* state.get()は0〜4 */

			for (ROTATE_MINO_TYPE x  : ROTATE_MINO_TYPE.values()) {
				if (x.type == NUM) {
					rotateMinoType = x;
					break;
				}
			}
			return rotateMinoType;
		}

		/* 「回転状態を含むミノの種類」→「ミノの種類」(回転の状態は捨てる) */
		public MINO_TYPE toMinoType() {
			/* 「ミノの種類」の番号を規則性に基づきを演算して取得 */
			final int NUM = (int)Math.ceil(this.type / (double)ROTATE_STATE.STATE_NUM);
			return MINO_TYPE.valueOf(NUM);
		}

		/* 回転状態を変える */
		/* 別のインスタンスを戻り値に設定するため、staticで定義する */
		/* stateの参照のために引数にrotateMinoTypeが必要 */
		/* TODO:内部変数stateの必要性を再検討 */
		public static ROTATE_MINO_TYPE getRotatedMinoType(final ROTATE_MINO_TYPE rotateMinoType, int dr) {
			MINO_TYPE minoType = rotateMinoType.toMinoType();
			ROTATE_STATE state = ROTATE_STATE.getRotatedState(rotateMinoType.state.get(), dr);
			return toRotateMinoType(minoType, state);
		}

		public boolean compareRotateState(ROTATE_STATE target) {
			return (this.state.get()) == (target.get()) ? true : false;
		}

		/* TODO:必要なメソッドか再検討する。Table#rotate()内のみの使用なら要らないかも */
		static ROTATE_MINO_TYPE valueOf(int rotateMinoType) {
			ROTATE_MINO_TYPE ret = ROTATE_MINO_TYPE.ZERO;
			for (ROTATE_MINO_TYPE t : ROTATE_MINO_TYPE.values()) {
				if (rotateMinoType == t.type) {
					ret = t;
					break;
				}
			}
			return ret;
		}

		/************************************************************/
		/* ミノの種類 */
		/************************************************************/
		/* TODO:定義位置は再検討 */
		/* TODO:定数ベタガキは改善の余地あり */
		private enum MINO_TYPE {
			_(0),
			I(1),
			O(2),
			T(3),
			S(4),
			Z(5),
			J(6),
			L(7);

			private int type;

			private MINO_TYPE(int type) {
				this.type = type;
			}

			int get() {
				return type;
			}

//			public String toString() {
//				return type + "";
//			}

			static MINO_TYPE valueOf(int minoType) {
				MINO_TYPE ret = MINO_TYPE._;
				for (MINO_TYPE t : MINO_TYPE.values()) {
					if (minoType == t.type) {
						ret = t;
						break;
					}
				}
				return ret;
			}
		};
		/************************************************************/
		/* 回転の状態 */
		/************************************************************/
		/* 0〜3をラップしてr0,r90,r180,r270でアクセスできるようにする */
		public static final int DR_NO = 0;
		public static final int DR_RIGHT = 1;
		public static final int DR_LEFT = -1;

		enum ROTATE_STATE {
			r0(0),
			r90(1),
			r180(2),
			r270(3);

			static final int STATE_NUM = 4;

			private int state;

			private ROTATE_STATE(int state) {
				this.state = state;
			}
			int get() {
				return state;
			}
			static ROTATE_STATE getRotatedState(int curentState, int dr) {
				/* 状態を変える */
				if (DR_RIGHT == dr) {
					curentState = (curentState + 1) % 4;
				} else {
					curentState = (curentState - 1 + 4) % 4;
				}
				return valueOf(curentState);
			}
			private static ROTATE_STATE valueOf(int rotateState) {
				ROTATE_STATE ret = ROTATE_STATE.r0;
				for (ROTATE_STATE s : ROTATE_STATE.values()) {
					if (rotateState == s.state) {
						ret = s;
						break;
					}
				}
				return ret;
			}
		};
		/************************************************************/
	};


	/* 3点情報テーブル(拡張版) */
	/* 3点の情報を回転の向き毎に定義する */
	private enum ROTATE_MINO_INFO {
		NON(  ZERO, new int[][]{{ 0, 0},{ 0, 0},{ 0, 0}}),
		I_0(    I0, new int[][]{{ 1, 0},{-2, 0},{-1, 0}}),
		I_90(  I90, new int[][]{{ 0, 1},{ 0,-2},{ 0,-1}}),
		I_180(I180, new int[][]{{-1, 0},{ 2, 0},{ 1, 0}}),
		I_270(I270, new int[][]{{ 0,-1},{ 0, 2},{ 0, 1}}),
		O_0(    O0, new int[][]{{ 0,-1},{-1,-1},{-1, 0}}),
		O_90(  O90, new int[][]{{ 0,-1},{-1,-1},{-1, 0}}),
		O_180(O180, new int[][]{{ 0,-1},{-1,-1},{-1, 0}}),
		O_270(O270, new int[][]{{ 0,-1},{-1,-1},{-1, 0}}),
		T_0(    T0, new int[][]{{ 0,-1},{ 1, 0},{-1, 0}}),
		T_90(  T90, new int[][]{{ 1, 0},{ 0, 1},{ 0,-1}}),
		T_180(T180, new int[][]{{ 0, 1},{-1, 0},{ 1, 0}}),
		T_270(T270, new int[][]{{-1, 0},{ 0,-1},{ 0, 1}}),
		S_0(    S0, new int[][]{{ 0,-1},{ 1,-1},{-1, 0}}),
		S_90(  S90, new int[][]{{ 1, 0},{ 1, 1},{ 0,-1}}),
		S_180(S180, new int[][]{{ 0, 1},{-1, 1},{ 1, 0}}),
		S_270(S270, new int[][]{{-1, 0},{-1,-1},{ 0, 1}}),
		Z_0(    Z0, new int[][]{{ 0,-1},{ 1, 0},{-1,-1}}),
		Z_90(  Z90, new int[][]{{ 1, 0},{ 0, 1},{ 1,-1}}),
		Z_180(Z180, new int[][]{{ 0, 1},{-1, 0},{ 1, 1}}),
		Z_270(Z270, new int[][]{{-1, 0},{ 0,-1},{-1, 1}}),
		J_0(    J0, new int[][]{{ 1, 0},{-1, 0},{-1,-1}}),
		J_90(  J90, new int[][]{{ 0, 1},{ 0,-1},{ 1,-1}}),
		J_180(J180, new int[][]{{-1, 0},{ 1, 0},{ 1, 1}}),
		J_270(J270, new int[][]{{ 0,-1},{ 0, 1},{-1, 1}}),
		L_0(    L0, new int[][]{{ 1, 0},{-1, 0},{ 1,-1}}),
		L_90(  L90, new int[][]{{ 0, 1},{ 0,-1},{ 1, 1}}),
		L_180(L180, new int[][]{{-1, 0},{ 1, 0},{-1, 1}}),
		L_270(L270, new int[][]{{ 0,-1},{ 0, 1},{-1,-1}});

		private ROTATE_MINO_TYPE type; /* key */
		private int pos[][];           /* value */

		private ROTATE_MINO_INFO(ROTATE_MINO_TYPE type, int pos[][]) {
			this.type = type;
			this.pos = pos; /* 参照コピー */
		}
		public static int[][] getPos(final ROTATE_MINO_TYPE type) {
			ROTATE_MINO_INFO blockType = ROTATE_MINO_INFO.NON;
			
			for (ROTATE_MINO_INFO i : ROTATE_MINO_INFO.values()) {
				if (type == i.type) {
					blockType = i;
					break;
				}
			}
			return blockType.pos;
		}
	}
	/****************************/
	/* 管理情報の定義(ここまで) */
	/****************************/

	/****************************/
	/* Constant                 */
	/****************************/
	/* 回転の向き */
	public static final int R_NO = ROTATE_MINO_TYPE.DR_NO;
	public static final int R_RIGHT = ROTATE_MINO_TYPE.DR_RIGHT;;
	public static final int R_LEFT =  ROTATE_MINO_TYPE.DR_LEFT;

	private static final int MINO_INFO_NUM = 3; /* テトラ(4) - 1 */

	/****************************/
	/* Member variable          */
	/****************************/
	/* 中心(x, y) */
	/* 親クラスBlockに定義している */

	/* 回転状態を含むミノの種類(0〜28) */
	private ROTATE_MINO_TYPE rotateMinoType;

	/****************************/
	/* Constructor              */
	/****************************/
	/**
	 * 
	 * @param minoType ミノの種類を表す定数(0〜7)
	 * 定数はcom.haru.tetoru.Mino.ROTATE_MINO_TYPE.MINO_TYPEに定義されている
	 * @param x
	 * @param y
	 */
	public Mino(int minoType, int x, int y) {
		this(MINO_TYPE.valueOf(minoType), x, y);
	}
	Mino(int type) {
		this(type, 0, 0);
	}
	Mino(MINO_TYPE minoType, int x, int y) {
		this(ROTATE_MINO_TYPE.toRotateMinoType(minoType, ROTATE_STATE.r0), x, y);
	}

	Mino(ROTATE_MINO_TYPE rotateMinoType, int x, int y) {
		/* ブロックの種類を取得し、ブロックの種類と中心の設定 */
		super(rotateMinoType.toMinoType().get(), x, y);

		/* 回転状態を含むミノの種類(ミノの種類+回転の状態)を設定 */
		this.rotateMinoType = rotateMinoType;
	}
	Mino(ROTATE_MINO_TYPE rotateMinoType) {
		this(rotateMinoType, 0, 0);
	}

	/****************************/
	/* Member method            */
	/****************************/
	/**
	 * 中心の設定
	 * @param x
	 * @param y
	 */
	public void setCenter(int x, int y) {
		setX(x);
		setY(y);
	}

	/**
	 * ミノの種類を取得
	 * @return 回転状態を含むミノの種類(0〜28)
	 */
	/* TODO:getType()のオーバーライドにできないか検討 */
//	public int getType() {
	int getType_Mino() {
		return rotateMinoType.getType();
	}

	ROTATE_MINO_TYPE getRotateMinoType() {
		return rotateMinoType;
	}

	/**
	 * 回転状態を取得
	 * @return
	 */
//	int getState() {
//		return state;
//	}

	/**
	 * 位置情報の回転する
	 * ・回転状態を更新
	 * ・Iミノは、中心も移動する
	 * ・回転したミノの設置可否判定は実施せず、回転を実施する
	 * @param dr
	 * @return 回転しないミノ、引数以上の場合false
	 */
	public boolean rotateMinoType(int dr) {
		/* 引数チェック */
		if (R_RIGHT != dr && R_LEFT != dr) {
			return false;
		}

		/* Oミノは回転しない */
		switch(rotateMinoType) {
			case O0:
			case O90:
			case O180:
			case O270:
				return false;
			default:
				break;
		}

		/* 回転状態を更新(回転状態を含むミノの種類を更新) */
		rotateMinoType = ROTATE_MINO_TYPE.getRotatedMinoType(rotateMinoType, dr);

		/* Iミノの場合、中心の移動(中心の考え方が違うため) */
		switch(rotateMinoType) {
			case I0:
			case I90:
			case I180:
			case I270:
				int dx = 0;
				int dy = 0;
				int state;

				/* stateの決定 */
				/* TODO:とりあえず動くものを作成(rotateMinoType.stateは非公開のまま) */
				state = 
					(rotateMinoType.compareRotateState(ROTATE_STATE.r0)) ? 0 :
					(rotateMinoType.compareRotateState(ROTATE_STATE.r90)) ? 1 :
					(rotateMinoType.compareRotateState(ROTATE_STATE.r180)) ? 2 :
					(rotateMinoType.compareRotateState(ROTATE_STATE.r270)) ? 3 : -1;

				/* TODO:テーブル化するか検討 */
				if (R_RIGHT == dr) {
					switch (state) {
					case 1:
						dy = 1; /* 下 */
						Debug.println("first");
						break;
					case 2:
						dx = -1; /* 左 */
						break;
					case 3:
						dy = -1; /* 上 */
						break;
					case 0:
						dx = 1; /* 右 */
						break;
					}
				} else {
					switch (state) {
					case 3:
						dx = -1; /* 左 */
						break;
					case 2:
						dy = 1; /* 下 */
						break;
					case 1:
						dx = 1; /* 右 */
						break;
					case 0:
						dy = -1; /* 上 */
						break;
					}
				}

				setX(getX() + dx);
				setY(getY() + dy);
				break;
			default:
				break;
		}

		return true;
	}

	/**
	 * 3点の位置情報の取得
	 * @return
	 */
	public int[][] getInfo() {
		/* テーブルから3点の位置情報を取得してくる */
		return ROTATE_MINO_INFO.getPos(rotateMinoType);
	}

	/**
	 * 
	 * @param rotateMinoType
	 */
	void setRotateMinoType(int rotateMinoType) {
		/* TODO:必要なメソッドか再検討する。Table#rotate()内のみの使用なら要らないかも */
		/* clone()可否判定して、設置できると確定した場合に、呼び出している */
		this.rotateMinoType = ROTATE_MINO_TYPE.valueOf(rotateMinoType);
	}

	/**
	 * 
	 */
	public Block[] getBlocks() {
		Block[] blocks = new Block[MINO_INFO_NUM  + 1];
		int[][] pos = ROTATE_MINO_INFO.getPos(rotateMinoType);

		blocks[0] = new Block(getType(), getX(), getY());

		for (int i = 0; i < MINO_INFO_NUM; i++) {
			int x = getX() + pos[i][0];
			int y = getY() + pos[i][1];
			blocks[1 + i] = new Block(getType(), x, y);
		}

		return blocks;
		
	}

	/* クローンの生成 */
	/* TODO:オーバーライドの必要性があったかは再検討 */
	public Mino clone() {
		try {
			Mino cloned = (Mino) super.clone();
			cloned.rotateMinoType = this.rotateMinoType;
			
			return cloned;
		} catch (CloneNotSupportedException e) {
			return null;
		}
	}

	/**************************************************************************/
	/* 等価判定																  */
	/**************************************************************************/
	public boolean equals(Mino m) {
		return this.getX() == m.getX()
				&& this.getY() == m.getY()
				&& this.getType() == m.getType()
				&& this.getType_Mino() == m.getType_Mino();
	}
	/**************************************************************************/
	/* ハッシュ値															  */
	/**************************************************************************/
	/* equals()をオーバーライドした場合はhashCode()もオーバーライド必要。*/
	/*「等価なオブジェクトは等価なハッシュコードを保持する必要がある」 */
	public int hashCode() {
		int result = 17;
		result = 31 * result + getType();
		result = 31 * result + getX();
		result = 31 * result + getY();
		result = 31 * result + getType_Mino();
		return result;
	}
	/**************************************************************************/
	/* 文字列化																  */
	/**************************************************************************/
	public String toString() {
		return "(" + getX() +"," + getY() + "," + getType() + "," + getType_Mino() + ")";
	}
}
