package com.haru.tetoru.parts;

import static com.haru.tetoru.parts.Block.$;
import static com.haru.tetoru.parts.Block.D;
import static com.haru.tetoru.parts.Block._;

import java.util.ArrayList;

import com.haru.tetoru.parts.Mino.ROTATE_MINO_TYPE.ROTATE_STATE;

public class Table {

	/* サイズ */
	private static final int WIDTH  = 12;
	private static final int HEIGHT  = 21;

	/* 上２段は見えないブロック */
	public static final int OFFSET = 2;

	/* 右端 */
	private static final int L = 1;
	/* 左端 */
	private static final int R = WIDTH - 2;
	
	/* 縦×横の2次元配列 */
	private int[][] table;

	/* ブロックの出現位置 */
	private static final int START_X = 5;
	private static final int START_Y = 0 + OFFSET;

	
	
	/* ネクスト */
	private NextTable nextTable;

	public Table() {
		/* テーブル定義 */
		table = new int[HEIGHT + OFFSET][WIDTH];
		/* ネクストテーブル */
		nextTable = new NextTable();

		/* テーブル初期化 */
		for (int y = 0; y < table.length; y++) {
			if (table.length-1 == y) {
				for (int x = 0; x < table[y].length; x++) {
					table[y][x] = $;
				}
			} else {
				for (int x = 0; x < table[y].length; x++) {
					if (L > x || R < x) {
						table[y][x] = $;
					} else {
						table[y][x] = _;
					}
				}
			}
		}
	}

	/**
	 * テーブル表示用情報を取得
	 */
	public String toString() {
		StringBuilder builder;
		builder = new StringBuilder();

		/* テーブル表示用情報作成(上２段は見えないブロック) */
		for (int i = OFFSET; i < table.length; i++) {
			for (int block : table[i]) {
				builder.append(block);
			}
			builder.append(System.getProperty("line.separator"));
		}

		return builder.toString();
	}

	/**
	 * ネクスト表示用情報を取得。
	 * NextTable#toStringをラップする。
	 * @return
	 */
	public String toStringNext() {
		return nextTable.toString();
	}

	/**************************************************************************/
	/* ミノ																	  */
	/**************************************************************************/
	/**
	 * ミノ生成
	 * @return
	 */
	public Mino initMino() {
		/* 種類を取得 */
		int type = nextTable.getNextType();

		/* 位置の設定 */
		int x = START_X;
		int y = START_Y;

		/* ミノの生成 */
		return new Mino(type, x, y);
	}

	/**
	 * ミノが置けるか判定。
	 * 引数に中心の位置を指定する。
	 * @param current
	 * @param x
	 * @param y
	 * @return
	 */
	public boolean isSetMino(Mino current, int x, int y) {
		int cnt;
		int[][] info = current.getInfo();
		boolean ret = true;


//		Debug.println("x:" + x + " y:" + y);

		/* 中心の判定 */
		if (_ != table[y][x]) {
			ret = false;;
		} else {
			/* 3点の判定 */
			for (cnt = 0; cnt < info.length; cnt++) {
				int offsetX = info[cnt][0];
				int offsetY = info[cnt][1];

//				Debug.println("{" + offsetX + ", " + offsetY + "}");

				/* 判定位置を定義 */
				Point p = new Point(x + offsetX, y + offsetY);

				/* 配列外アクセスをチェック */
				if (isOutOfRange(p)) {
					ret = false;
					break;
				}

				/* 判定 */
				if (_ != table[p.y][p.x]) {
					ret = false;
					break;
				}
			}
		}

		return ret;
	}

	/* テーブルの範囲外か判定 */
	private boolean isOutOfRange(Point p) {
		return p.x < 0 || p.x >= table[0].length
			|| p.y < 0 || p.y >= table.length;
	}


	/**
	 * ミノをテーブルに設定。
	 * 中心の設定済みとする。(Mino#setCenter(int,int)を呼び出しておく)
	 * 	 * @param current
	 */
	public void setMino(Mino current) {
		int type = current.getType();
		int x = current.getX();
		int y = current.getY();
		int[][] info = current.getInfo();

		/* 3点をテーブルに設定 */
		for (int cnt = 0; cnt < info.length; cnt++) {
			int offsetX = info[cnt][0];
			int offsetY = info[cnt][1];
			
			table[y + offsetY][x + offsetX] = type;
		}
		/* 中心をテーブルに設定 */
		table[y][x] = type;
	}
	/**
	 * ミノの削除
	 * @param current
	 */
	public void delMino(Mino current) {
		int cnt;
		int type = _;
		int x = current.getX();
		int y = current.getY();
		int[][] info = current.getInfo();

		/* 3点をテーブルに設定 */
		for (cnt = 0; cnt < info.length; cnt++) {
			int offsetX = info[cnt][0];
			int offsetY = info[cnt][1];
			
			table[y + offsetY][x + offsetX] = type;
		}
		/* 中心をテーブルに設定 */
		table[y][x] = type;
	}
	
	/**
	 * 下に移動
	 * @param current
	 * @return
	 */
	public boolean moveDown(Mino current) {
		int x = current.getX();
		int y = current.getY();

		/* 元の位置から一旦消す */
		delMino(current);

		/* 下に置けるか判定 */
		if (!isSetMino(current, x, y+1)) {
			Debug.println("下に設置中");

			/* 元の位置に設定*/
			setMino(current);
			return false;
		}


		/* １つ下に移動 */
		current.setCenter(x, y+1);

		/* 1つ下に置く */
		setMino(current);

		return true;
	}

	/**
	 * 右に移動
	 * @param current
	 * @return
	 */
	public boolean moveRight(Mino current) {
		int x = current.getX();
		int y = current.getY();

		/* 元の位置から一旦消す */
		delMino(current);

		/* 右に置けるか判定 */
		if (!isSetMino(current, x+1, y)) {
			/* 元の位置に設定*/
			setMino(current);
			return false;
		}

		/* １つ右に移動 */
		current.setCenter(x+1, y);

		/* 1つ右に置く */
		setMino(current);

		return true;
	}

	/**
	 * 左に移動
	 * @param current
	 * @return
	 */
	public boolean moveLeft(Mino current) {
		int x = current.getX();
		int y = current.getY();

		/* 元の位置から一旦消す */
		delMino(current);

		/* 左に置けるか判定 */
		if (!isSetMino(current, x-1, y)) {
			/* 元の位置に設定*/
			setMino(current);
			return false;
		}

		/* １つ左に移動 */
		current.setCenter(x-1, y);

		/* 1つ左に置く */
		setMino(current);

		return true;
	}
	
	/**********************************************************************/
	/* 回転																  */
	/**********************************************************************/
	/**
	 * 回転したミノが設置できるか判定
	 * @param tmpMino
	 * @param dr
	 * @return
	 */
	public boolean isSetRotateMino(Mino tmpMino, int dr) {
		boolean ret = false;

		/* パラメータ取得 */
		int type = tmpMino.getType();
		int dir = dr;

		/* stateの決定(Tableとして扱いやすい形式であればなんでもよい) */
		Mino.ROTATE_MINO_TYPE rotateMinoType = tmpMino.getRotateMinoType();
		final int 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;

		int turn = /* 元の回転状態 */
			(Mino.R_RIGHT == dr)	
			? (state - 1 + 4) % 4
			: (state + 1) % 4;

		/* 取得したパラメータチェック */
		if (-1 == state) {
			return false;
		}

		/* 回転法則を第5候補まで適用 */
		for (int i = 1; i <= 5; i++) {
			Point p = RotateRule.moveDirection(i, type, turn, dir);
			Debug.print("(" + p.x + "," + p.y + ")");

			/* 配置する中心の位置 */
			Point center = new Point(tmpMino.getX() + p.x,
									 tmpMino.getY() + p.y);

			/* 配列外アクセスをチェック */
			if (isOutOfRange(center)) {
				Debug.print("領域外");
				ret = false;
				break;
			}

			/* ミノが配置できたか判定 */
			ret = isSetMino(tmpMino, center.x, center.y); 
			if (ret) {
				/* 中心の移動を行う */
				tmpMino.setCenter(center.x, center.y);
				break;
			}
		}
		Debug.println("");

		return ret;
	}

	/**
	 * 回転したミノをテーブルに設置する
	 * @param current
	 * @param dr
	 * @return
	 */
	public boolean rotate(Mino current, int dr) {
		boolean ret;

		/* 引数チェック */
		if (Mino.R_RIGHT != dr && Mino.R_LEFT != dr) {
			return false;
		}

		/* 元の位置から一旦消す */
		delMino(current);

		{
		/* ミノのコピーを作成 */
		Mino tmpMino = current.clone();

		/* コピーしたミノを回転 */
		ret = tmpMino.rotateMinoType(dr);
		if (!ret) {
			/* 元の位置に設定*/
			setMino(current);
			return false;
		}

		/* 回転したミノが置けるか判定(SRSに従い、中心の移動もする) */
		ret = isSetRotateMino(tmpMino, dr);
		if (!ret) {
			/* 元の位置に設定*/
			setMino(current);
			return false;
		}

		/* 操作中のミノに変更を適用 */
		/* TODO:currentへの反映方法は再検討する(参照の変更のみでどうにかならない？) */
		current.setCenter(tmpMino.getX(), tmpMino.getY());
		current.setRotateMinoType(tmpMino.getType_Mino()); 
		// current = tmpMino.clone();では代替はできない
		}

		/* ミノをテーブルに設定 */
		setMino(current);

		return true;
	}
	
	/**********************************************************************/
	/* ライン削除														  */
	/**********************************************************************/
	/* 1行削除(実際には削除せず、削除対象のミノとして設定) */
	private void delLine(int y) {
		for (int i = L; i <= R; i++) {
			table[y][i] = D;
		}
	}

	/* 1行削除できるか判定 */
	private boolean isDelLine(int y) {
		boolean ret = true;

		for (int i = L; i <= R; i++) {
			if (_ == table[y][i]) {
				ret = false;
				break;
			}
		}

		return ret;
	}

	/**
	 * 複数行を削除
	 * @param mino
	 * @return
	 */
	public boolean delMultiLine(Mino mino) {
		boolean ret = false;
		int[][] info = mino.getInfo();
		int y = mino.getY();
		int delY = y;

		/* 中心あるラインの削除判定 */
		if (isDelLine(delY)) {
			ret = true;
			delLine(delY);
		}

		/* 中心あるラインの削除判定 */
		for (int cnt = 0; cnt < info.length; cnt++) {
			delY = y + info[cnt][1];
			
			if (isDelLine(delY)) {
				ret = true;
				delLine(delY);
			}
		}

		return ret;
	}

	/**
	 * 削除対象ミノのラインを詰める
	 */
	public void pack() {
		int packLineNum = 0; /* 詰めるライン数 */
		final int CHECK_X = 1; /* ライン上で削除対象ミノをチェックする位置

		/* 最下位行が削除対象かチェック */
		packLineNum = (D == table[table.length-2][CHECK_X]) ? 1 : 0;

		/* 各行か削除対象かチェックしながら、詰める */
		for (int y = table.length-3; y >= 0; y--) {
			/* 削除対象の行か判定 */
			if (D == table[y][CHECK_X]) {
				/* 削除対象の行の場合、詰めるライン数を1増加 */
				packLineNum++;
			} else {
				/* 削除対象でない場合、y行目をpackLineNum行、下に詰める */
				if (0 != packLineNum) {
					/* 1ブロックずつ正確に設定 */
					for (int i = 1; i < table[y].length - 1; i++) {
						table[y + packLineNum][i] = table[y][i];
					}
				}
			}
		}

		/* 最上位のpackLineNum行を初期化する */
		for (int y = 0; y < packLineNum; y++) {
			for (int i = L; i < R; i++) {
				table[y][i] = _;
			}
		}
	}


	/**********************************************************************/
	/* AI																  */
	/**********************************************************************/
	/* 第1引数のテーブルに第2引数のミノを設置させる。設置させることが可能なすべてのミノについて返す。*/
	public Mino[] getPuttableMino(Mino current) {
		ArrayList<Mino> puttableMino = new ArrayList<Mino>(); /* 設置可能なミノ */
		/* 種類を取得 */
		int topY = current.getY(); /* 一番上。数は小さい */


		/* 自分の存在は消す */
		delMino(current);

		for (int y = table.length - 1; y >= topY; y--) {

			Mino tmp = current.clone();

			/* TODO:tmp.rotateMinoType()の戻り値判定は？ */
			for (int i = 0; i < 4; i++, tmp.rotateMinoType(Mino.R_LEFT)) { 
				for (int x = L; x <= R; x++) {
					/* 設定できるか判定 */
					if (!isSetMino(tmp, x, y)) {
						continue;
					}
					/* 設置してるか判定 */
					if (isSetMino(tmp, x, y + 1)) {
						continue;
					}
					/* 対象位置まで移動可能か判定 */
					/* TODO:この判定は別の所で行う */
					
					/* 設置可能ミノを追加 */
					Mino addMino = tmp.clone();
					addMino.setCenter(x, y);
					puttableMino.add(addMino);
				}
			}
		}

		/* 存在を認めよう */
		setMino(current);

		return  puttableMino.toArray(new Mino[puttableMino.size()]);
	}

	/* 評価値を求める */
	/* TODO:移動不可能な場合、戻り値を負にしたい */
	public int getEvalValue(Mino mino) {
		int point = 0;
		Block[] blocks = mino.getBlocks();

		/* 最下点が下にあるほど評価を上げる */
		/* 最下点のy座標取得 */
		int bottomY = 0;
		for (int i = 0; i < blocks.length; i++) {
			int y = blocks[i].getY();
			if (bottomY < y) {
				bottomY = y;
			}
		}
		/* 最下点のy座標が下にあるほど、評価値を高くする */
		point += bottomY; /* 比例 */
		
		
		
		for (int i = 0; i < blocks.length; i++) {
			int x = blocks[i].getX();
			int y = blocks[i].getY();
			

			/* 地面に設置していない場合 */
			if (Mino._ == table[y + 1][x]) {
				point -= 1;
			} else {
				point += 5;
			}
			
			/* 右設置 */
			if (Mino._ != table[y][x + 1]) {
				point += 1;
			}
			/* 左設置 */
			if (Mino._ != table[y][x - 1]) {
				point += 1;
			}

			
//			for (int up = y; up-- > 0; up--) {
//			
//				if (Mino.$ != table[up][x]) {
//					point -= 5;
//					continue;
//				}
//			}
		}
		
		
		return point;
	}
	
	
}
