import java.util.*;

import javafx.application.*;
import javafx.stage.*;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.canvas.*;
import javafx.scene.shape.*;
import javafx.scene.layout.*;
import javafx.scene.paint.*;
import javafx.scene.text.*;
import javafx.event.*;
import javafx.scene.input.*;
import javafx.scene.image.*;
import javafx.geometry.*;

//TODO:
//	Prevent moving into check
//	Add castling
//	Add en passant
//	Give feedback for check, checkmate, and stalemate.

//Main class and GUI

public class Chess extends Application {
	private ArrayList<GameState> gameStateHistory; //Stack of GameBoard states for the move undo.
	private ArrayList<Move> moveHistory;  //Stack of tuples (moves) so they can be displayed.
	private ArrayList<String> moveNotationHistory;  //Stack of move notations.

	//User's first click for a move.
	private Integer clickRow;  //null means no first click made currently.
	private Integer clickCol;

	private GraphicsContext graphics;
	private TextArea moveHistoryTextArea;
	private Label feedback;

	//Load images
	private static final Image W_ROOK = new Image("img/w_rook_png_shadow_128px.png");
	private static final Image B_ROOK = new Image("img/b_rook_png_shadow_128px.png");
	private static final Image W_KNIGHT = new Image("img/w_knight_png_shadow_128px.png");
	private static final Image B_KNIGHT = new Image("img/b_knight_png_shadow_128px.png");
	private static final Image W_BISHOP = new Image("img/w_bishop_png_shadow_128px.png");
	private static final Image B_BISHOP = new Image("img/b_bishop_png_shadow_128px.png");
	private static final Image W_QUEEN = new Image("img/w_queen_png_shadow_128px.png");
	private static final Image B_QUEEN = new Image("img/b_queen_png_shadow_128px.png");
	private static final Image W_KING = new Image("img/w_king_png_shadow_128px.png");
	private static final Image B_KING = new Image("img/b_king_png_shadow_128px.png");
	private static final Image W_PAWN = new Image("img/w_pawn_png_shadow_128px.png");
	private static final Image B_PAWN = new Image("img/b_pawn_png_shadow_128px.png");

	public static void main(String[] args) {
		launch(args);
	}

	public void start(Stage stage) {
		//Boilerplate code
		VBox root = new VBox();
		Scene scene = new Scene(root);
		stage.setTitle("Chess");
		stage.setScene(scene);

		//Set up canvas for the board
		Canvas board = new Canvas(400, 400);
		graphics = board.getGraphicsContext2D();

		//Setup move history text area
		moveHistoryTextArea = new TextArea();
		moveHistoryTextArea.setFont(Font.font("Courier New", FontWeight.BOLD, 12));
		moveHistoryTextArea.setPrefColumnCount("129. b1Rxb8R+ a1Rxa8R++".length());
		moveHistoryTextArea.setEditable(false);
		moveHistoryTextArea.setPrefHeight(400);
		moveHistoryTextArea.setMinHeight(400);
		moveHistoryTextArea.setMaxHeight(400);

		//Horizontal box for board and move history text area
		HBox hb = new HBox(20, board, moveHistoryTextArea);
		hb.setAlignment(Pos.CENTER);
		root.getChildren().add(hb);

		//Add a label for feedback
		feedback = new Label("");
		HBox hb2 = new HBox(20, feedback);
		hb2.setAlignment(Pos.CENTER);
		root.getChildren().add(hb2);

		//Set up buttons
		Button undo = new Button("Undo");
		Button reset = new Button("Reset");
		Button aiMove = new Button("AI move");
		HBox hb3 = new HBox(20, undo, reset, aiMove);
		hb3.setAlignment(Pos.CENTER);
		hb3.setPadding(new Insets(10,10,10,10));
		root.getChildren().add(hb3);

		//Set up empty move history, game history,  notation history.
		gameStateHistory = new ArrayList<GameState>();
		gameStateHistory.add(new GameState()); //Set up the initial board
		moveHistory = new ArrayList<Move>();
		moveNotationHistory = new ArrayList<String>();

		//No clicks yet
		clickRow = clickCol = null;

		//Set up mouse/click events
		board.setOnMousePressed(this::mouseClick);
		undo.setOnAction(this::undo);
		reset.setOnAction(this::reset);
		aiMove.setOnAction(this::moveAI);

		//Refresh the board
		updateScreen();

		//Fix mysterious broken width and prevent shrinking window
		stage.setWidth(664);
		stage.setMinWidth(stage.getWidth());
		stage.setMinHeight(stage.getHeight());

		stage.show();
	}

	private void updateScreen() {
		//Draw the board
		for(int x = 0; x < 8; x++) {
			for(int y = 0; y < 8; y++) {
				graphics.setFill((x + y) % 2 == 0 ? Color.BURLYWOOD : Color.FORESTGREEN);
				graphics.fillRect(50*x, 50*y, 50, 50);
			}
		}

		//Show the last move made in red
		if (!moveHistory.isEmpty()) {
			graphics.setFill(new Color(1, 0, 0, .6));
			Move m = moveHistory.get(moveHistory.size() - 1);
			graphics.fillRect(50*m.getSourceCol(), 50*m.getSourceRow(), 50, 50);
			graphics.fillRect(50*m.getDestCol(), 50*m.getDestRow(), 50, 50);
		}

		//Draw the pieces
		for(int x = 0; x < 8; x++) {
			for(int y = 0; y < 8; y++) {
				Square s = board().getSquare(x, y);
				if ( s.getType() ==  Square.PAWN && s.isWhite()) graphics.drawImage(W_PAWN, 50*x, 50*y, 50, 50);
				if ( s.getType() ==  Square.PAWN && !s.isWhite()) graphics.drawImage(B_PAWN, 50*x, 50*y, 50, 50);
				if ( s.getType() ==  Square.KNIGHT && s.isWhite()) graphics.drawImage(W_KNIGHT, 50*x, 50*y, 50, 50);
				if ( s.getType() ==  Square.KNIGHT && !s.isWhite()) graphics.drawImage(B_KNIGHT, 50*x, 50*y, 50, 50);
				if ( s.getType() ==  Square.BISHOP && s.isWhite()) graphics.drawImage(W_BISHOP, 50*x, 50*y, 50, 50);
				if ( s.getType() ==  Square.BISHOP && !s.isWhite()) graphics.drawImage(B_BISHOP, 50*x, 50*y, 50, 50);
				if ( s.getType() ==  Square.ROOK && s.isWhite()) graphics.drawImage(W_ROOK, 50*x, 50*y, 50, 50);
				if ( s.getType() ==  Square.ROOK && !s.isWhite()) graphics.drawImage(B_ROOK, 50*x, 50*y, 50, 50);
				if ( s.getType() ==  Square.QUEEN && s.isWhite()) graphics.drawImage(W_QUEEN, 50*x, 50*y, 50, 50);
				if ( s.getType() ==  Square.QUEEN && !s.isWhite()) graphics.drawImage(B_QUEEN, 50*x, 50*y, 50, 50);
				if ( s.getType() ==  Square.KING && s.isWhite()) graphics.drawImage(W_KING, 50*x, 50*y, 50, 50);
				if ( s.getType() ==  Square.KING && !s.isWhite()) graphics.drawImage(B_KING, 50*x, 50*y, 50, 50);

			}
		}

		//Highlight the selected square (if any) in yellow
		if (clickRow != null) {
			graphics.setFill(new Color(1, 1, 0, .5));
			graphics.fillRect(50*clickCol, 50*clickRow, 50, 50);
		}

		//Update feedback
		feedback.setText(board().getFeedback());
	}

	private GameState board() {
		return gameStateHistory.get(gameStateHistory.size()-1);
	}

	//Reset to the intial game position.
	public void reset(ActionEvent e) {
		//Clear out all the histories
		gameStateHistory = new ArrayList<GameState>();
		gameStateHistory.add(new GameState()); //Set up the initial board
		moveHistory = new ArrayList<Move>();
		moveNotationHistory = new ArrayList<String>();

		//Clear out clickRow and clickCol
		clickRow = clickCol = null;
		moveHistoryTextArea.setText("");
		updateScreen();
	}

	//AI makes a move
	public void moveAI(ActionEvent e) {
		//Ask the gameState for a list of all legal moves.
		ArrayList<Move> a = board().getAllPossibleMoves();
		if (a.size() <= 0) return; //no moves.

		//Get one of those in the list uniformly at random.
		int r = (int)(Math.random() * a.size());
		makeMove(a.get(r));
	}

	private void makeMove(Move move) {
		//Clone the gameState object for the current state of the game, tell it to make the move, add it to the move history
		GameState g = board().getStateAfterMove(move);
		if (g==null) return; //didn't actually move it; must not have been legal.
		gameStateHistory.add(g);

		//add the move to the history as well
		moveHistory.add(move);

		//add the notation for it
		moveNotationHistory.add(move+"");

		//Refresh the board
		updateScreen();

		writeMoveHistoryForIndex(moveNotationHistory.size()-1);
	}

	//For the human moving
	public void mouseClick(MouseEvent e) {
		//If it is the first click (null check) then record it, updtae the GUI to show highlighting
		//else attempt the move:
		int x = (int)(e.getX() / 50);
		int y = (int)(e.getY() / 50);

		if (board().getSquare(x,y).isWhite() == board().isWhitesTurn() && board().getSquare(x,y).getType() != Square.EMPTY) { //if clicking on own piece...
			clickRow = y;
			clickCol = x;
		}
		else if (clickRow != null) {
			makeMove(new Move(clickCol, clickRow, x, y));
			clickRow = clickCol = null;
		}
		updateScreen();
	}

	//Undo button for the most recent move
	public void undo(ActionEvent e) {
		if (gameStateHistory.size() <= 1) return; //can't pop the initial state.

		//Pop those 3 history object things and update the GUI.
		gameStateHistory.remove(gameStateHistory.size() - 1);
		moveHistory.remove(moveHistory.size() - 1);
		moveNotationHistory.remove(moveNotationHistory.size() - 1);
		clickRow = clickCol = null;
		refreshMoveHistoryDisplay();
		updateScreen();
	}

	private void refreshMoveHistoryDisplay() {
		moveHistoryTextArea.setText("");
		for(int i = 0; i < moveHistory.size(); i++) {
			writeMoveHistoryForIndex(i);
		}
	}

	private void writeMoveHistoryForIndex(int ply) {
		GameState g = gameStateHistory.get(ply);
		Move m = moveHistory.get(ply);
		if (g.isWhitesTurn()) moveHistoryTextArea.appendText("\n" + (ply/2+1) +". "+m);
		else moveHistoryTextArea.appendText("  " +m);
	}

}