package com.haru.tetoru.sample.frame.AI;

import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;

import com.haru.tetoru.parts.Mino;
import com.haru.tetoru.parts.Table;
import com.haru.tetoru.template.Player;

public class AIPlayer extends Player implements KeyListener {

	/* 入力された操作情報 */
	private int inputType = OP_NO;

	/********************/
	/* ユーザによる入力 */
	/********************/
	/**
	 *  画面に入力リスナーを登録
	 */
	public void registInputListener(MyGameFrame display) {
		display.addKeyListener(this);
	}


	/* 操作の種類を読み取る（開始操作待ちのとき）*/
	private int readStartOperate() {
		int type = OP_NO;

		/* 入力を読み取り */
		/* 入力がなかった場合は、OP_NOが取得できる */
		type = inputType;

		/* 入力初期化 */
		inputType = OP_NO;

		return type;
	}


	@Override
	public void keyPressed(KeyEvent e) {
		int code = e.getKeyCode(); /* キーコード */

		switch (code) {
		case KeyEvent.VK_DOWN:
			inputType = OP_DOWN;
			break;
		case KeyEvent.VK_RIGHT:
			inputType = OP_RIGHT;
			break;
		case KeyEvent.VK_LEFT:
			inputType = OP_LEFT;
			break;
		case KeyEvent.VK_SPACE:
		case KeyEvent.VK_Z:
			inputType = OP_ROTATE_RIGHT;
			break;
		case KeyEvent.VK_X:
			inputType = OP_ROTATE_LEFT;
			break;
		case KeyEvent.VK_ENTER:
			inputType = OP_START;
			debugFlg = !debugFlg;
			break;
		default:
			inputType = OP_NO;
			break;
		}
	}
	@Override
	public void keyReleased(KeyEvent arg0) {
	}
	@Override
	public void keyTyped(KeyEvent arg0) {
	}


	/**
	 * ゲーム開始合図待ち
	 */
	@Override
	public boolean waitStartSign() {
		int retOperate = OP_NO;

		/* 開始操作入力待ち */
		while (OP_START != retOperate) {
			/* 入力を少し待つ */
			try {
				Thread.sleep(20);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}

			retOperate = readStartOperate();
		}

		return true;
	}


	/****************/
	/* AIによる入力 */
	/****************/
	Table table; /* 現在のテーブルの状態 */
	Mino current; /* 現在のミノ */
	Queue<Integer> ops = new LinkedBlockingQueue<Integer>(1000); /* AI入力パターンキュー */
											/* 引数の値は、キューのサイズの初期値。(固定ではない) */

	boolean debugFlg = false;
	
	public void setTable(Table table) {
		this.table = table;
	}
	public void setCurrentMino(Mino current) {
		this.current = current;
	}
	
	MyGameFrame display;
	public void setDisplay(MyGameFrame display) {
		this.display = display;
	}
	
	private void dispMino(Mino mino, int ms) {
		int type = mino.getType();
		mino.setType(Mino.$);
		table.setMino(mino);
		display.disp(table.toString());
		try {
			Thread.sleep(ms);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		/* 仮置きしたミノを削除 */
		table.delMino(mino);
		mino.setType(type);
	}

	
	/* ミノが設置した後に、再計画 */
	public void plan() {

		/* 現在のミノが配置可能なすべてのミノの位置を取得 */
		/* TODO:物理的に移動不可な位置についても取得される */
		Mino[] minos = table.getPuttableMino(current);


		int[] points = new int[minos.length]; /* 評価値 */
		Mino target = null; /* 移動先のミノ(位置と状態) */
		int maxPoint = 0;
		int maxIdx = 0;
		
		for (int i = 0; i < minos.length; i++) {
			Mino mino = minos[i];
			/* 評価値を求める */
			points[i] = table.getEvalValue(mino);
			
			{
				System.out.println("p : " + points[i]);
//				dispMino(mino, 50);
			}
			
			if (maxPoint < points[i]) {
				maxPoint = points[i];
				maxIdx = i;
			}
		}
		
		/* 評価が一番高いミノを取得(目標ミノ) */
		target = minos[maxIdx];
		
		
		/* 目標ミノが存在しなければ終了する */
		if (null == target) {
			return;
		}
		
		/* 操作列を初期化 */
		initializeOperates(table, current, target);
	}
	
	
	/* 定期的にact */
	public void act() {
		if (!ops.isEmpty()) {
			inputType = ops.poll();
		}
	}
	
	/**
	 *  操作の種類を読み取る
	 */
	@Override
	public int readOperate() {
		int type = OP_NO;

		/* 入力を読み取り */
		/* 入力がなかった場合は、OP_NOが取得できる */
		type = inputType;

		/* 入力初期化 */
		inputType = OP_NO;

		return type;
	}

	
	/* 下に落下したことを知らせる */
	public void notifyDowned() {
		/* 下入力を削除する */
		
		/* 削除できなかったか判定 */
		
			/* 削除できなかった場合、入力配列を再初期化する */
	}
	
	
	
	/**************************************************************************/
	/* 操作列取得															  */
	/**************************************************************************/
	/**
	 * 現在ミノ(current)が目標ミノ(target)に位置するための操作列を求める。
	 * 幅優先探索により最短操作列を求める。
	 * 　操作の種類：左、右、下、右回転
	 * @param table
	 * @param current
	 * @param target
	 */
	private void initializeOperates(Table table, Mino current, Mino target) {
		
		/* 探索する位置に現在ミノが存在した場合、配置できないとみなされる */
		/* ため、現在のミノを削除しておく。 */
		table.delMino(current);

		/* デバッグ用に目標ミノ表示 */
		{
			dispMino(target, 100);
		}

		/**************************************************************************/
		/**************************************************************************/
		Queue<Node> queue = new LinkedBlockingQueue<Node>(1000);
		List<Integer> result = null;
		NodeArray closed = new NodeArray();

		/* 根をエンキュー */
		Node root = new Node(OP_NO, current);
		queue.offer(root);
		closed.add(root);
		
		while (true) {
			Node node = queue.poll();

			if (null == node) {
				/* すんなり諦めればいいけど、
				 * 置く努力をしている。
				 * 
				 */
				
				
				/* 最後に追加したnodeを取得 */
				/* TODO:この仕様が良くない */
				/* 選択肢は２つ。
				 * 1.targetを変更させるために、この関数を異常終了する。
				 * 2.closedからtargetに最も近いNodeを取得。
				 */
				Node last = closed.get(closed.size() - 1);
				
				/* デバッグ用に表示 */
				{
					dispMino(last.getMino(), 500);
				}
				
				/* 操作を適用 */
				Mino tmp = operateNode(last.getMino(), last.getOp());
				if (OP_ROTATE_RIGHT == last.getOp()) {
					if (!table.isSetRotateMino(tmp, Mino.R_RIGHT)) {
						/* 配置できなければ、操作を取り消す */
						tmp = last.getMino();
						last.delLatestHistory();
						System.err.println("回転失敗");
						/* デバッグ用に表示 */
						{
						dispMino(tmp, 500);
						}
					}
				} else {
					/* downだった場合は、１つ多くなるけど気にしない */
				}
				
				last.set(OP_DOWN, tmp);
				
				while (true) {
					tmp = operateNode(last.getMino(), last.getOp());
					if (OP_DOWN == last.getOp()) {
						if (!table.isSetMino(tmp, tmp.getX(), tmp.getY())) {
							
							System.out.println("downできなかったので終了");
							break;
						}
					}
					System.out.println("wait a minute!");
					/* デバッグ用に表示 */
					{
						dispMino(tmp, 500);
					}
					last.set(OP_DOWN, tmp);
				}
				
				/* 操作履歴を取得 */
				result = last.getHistory();
				System.out.println("近似解");
				break;
			}
			
			
			
			
			/* 取得したノードに操作を適用 */
			Mino tmp = operateNode(node.getMino(), node.getOp());

			System.out.println(node.getMino() + " " + node.getOp());

			
			/* tmpがテーブル上に配置可能か判定 */
			if (OP_ROTATE_RIGHT == node.getOp()) {
				if (!table.isSetRotateMino(tmp, Mino.R_RIGHT)) {
					/* 配置できなければ、他の経路を探索する */
					node.delLatestHistory();
					continue;
				}
			} else {
				if (!table.isSetMino(tmp, tmp.getX(), tmp.getY())) {
					/* 配置できなければ、他の経路を探索する */
					node.delLatestHistory();
					continue;
				}
			}
			
			if (debugFlg) {			
				/* デバッグ用に仮置き */
				table.setMino(tmp);
				display.disp(table.toString());
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				/* 仮置きしたミノを削除 */
				table.delMino(tmp);
			}
			
			if (tmp.equals(target)) {
				/* 一致 */
				result = node.getHistory(); /* 最短経路を1つ見つけたら終了 */
				break;
			}
			
			
			/* 操作候補が未訪であればエンキュー(追加する順番=操作の優先度) */
			/* 下操作 */
			Node down = new Node(OP_DOWN, tmp);
			if (!closed.contains(down)) {
				Node cloned = node.clone();
				cloned.set(OP_DOWN, tmp);
				queue.offer(cloned);
				/* 既訪とする */
				closed.add(cloned);
			}
			Node left = new Node(OP_LEFT, tmp);
			if (!closed.contains(left)) {
//					&& OP_RIGHT != node.getOp()) {
				Node cloned = node.clone();
				cloned.set(OP_LEFT, tmp);
				queue.offer(cloned);
				/* 既訪とする */
				closed.add(cloned);
			}

			Node right = new Node(OP_RIGHT, tmp);
			if (!closed.contains(right)) {
//					&& OP_LEFT != node.getOp())	{
				Node cloned = node.clone();
				cloned.set(OP_RIGHT, tmp);
				queue.offer(cloned);
				/* 既訪とする */
				closed.add(cloned);
			}
			Node rotateR = new Node(OP_ROTATE_RIGHT, tmp);
			if (!closed.contains(rotateR)) {
				Node cloned = node.clone();
				cloned.set(OP_ROTATE_RIGHT, tmp);
				queue.offer(cloned);
				/* 既訪とする */
				closed.add(cloned);
			}
		}
		/**************************************************************************/
		/**************************************************************************/
		
		System.out.println(result);
		
		/* 現在のミノを復活させる */
		table.setMino(current);
		
		/* 探索失敗したか判定 */
		if (null == result) {
			return;
		}

		/* 取得した結果をキューに変換して、操作列に設定 */
		ops.addAll(result);
		/* 	下操作を追加する(下移動をなくしているため) */
		ops.offer(OP_DOWN);
		
		return;
	}
	
	private Mino operateNode(Mino node , int op) {
		
		Mino tmp = node.clone();
		
		switch (op) {
		case OP_LEFT:
			tmp.setX(node.getX() - 1);
			break;
		case OP_RIGHT:
			tmp.setX(node.getX() + 1);
			break;
		case OP_DOWN:
			tmp.setY(node.getY() + 1);
			break;
		case OP_ROTATE_RIGHT:
			tmp.rotateMinoType(Mino.R_RIGHT);
			break;
		case OP_NO:
			/* do nothing */
			break;
		}
		
		return tmp;
	}
	
	
	class Node implements Cloneable {
		private Integer op;
		private Mino mino;
		
		private List<Integer> history;
		
		Node(Integer op, Mino mino) {
			this.op = op;
			this.mino = mino;
			
			history = new ArrayList<Integer>();
			history.add(op);
		}
		
		public boolean equals(Node node) {
			return op.equals(node.op) && mino.equals(node.mino);
		}

		public int hashCode() {
			int result = 17;
			result = 31 * result + op;
			result = 31 * result + mino.hashCode();
			return result;
		}
		
		
		public String toString() {
			return op.toString() + "(" + mino.getX() + ", " + mino.getY() +")\t" + history.toString();
		}
		
		Integer getOp() {
			return op;
		}
		Mino getMino() {
			return mino;
		}
		
		void set(Integer i, Mino p) {
			this.op = i;
			this.mino = p;
			history.add(this.op);
		}
		
		public Node clone() {  
			try {
				Node cloned = (Node)super.clone();  
				
				/* historyは正確にコピー */
				cloned.history = new ArrayList<Integer>();
				cloned.history.addAll(history);
				
				return cloned;
			} catch (CloneNotSupportedException e) {  
				return null;  
			}  
		}
		
		List<Integer> getHistory() {
			return history;
		}
		
		void delLatestHistory() {
			history.remove(history.size() - 1);
		}
	}

	/* ArrayListにcontainsを自前で実装 */
	class NodeArray {
		ArrayList<Node> nodes;
		NodeArray() {
			nodes = new ArrayList<Node>();
		}
		boolean add(Node n) {
			return nodes.add(n);
		}
		int size() {
			return nodes.size();
		}
		Node get(int index) {
			return nodes.get(index);
		}
		
		boolean contains(Node n) {
			for (int i = 0; i < nodes.size(); i++) {
				if (n.equals(nodes.get(i))) {
					return true;
				}
			}
			return false;
		}
	}
}

