package controller;

import javafx.application.Platform;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.*;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.shape.Polygon;
import model.GameState;
import model.Move;
import model.Player;
import model.Tile;
import org.json.JSONException;
import org.json.JSONObject;
import sun.audio.AudioPlayer;
import sun.audio.AudioStream;
import view.TileView;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.URL;
import java.util.List;
import java.util.Random;
import java.util.ResourceBundle;

public class Controller implements Initializable
{
	private static final String iaProgramPath = "../bin/theturk";
	private static final String introMusicFile = "./resource/pingu_theme.wav";
	private static final String clickSoundFile = "./resource/clic.wav";

	private GameState gameState;
	private Process gameProcess;
	private PrintWriter gameInput;
	private Tile[] board;
	private TileView[] boardView;
	private Player humanPlayer;
	private int selectedTile;
	private int penguinGenCounter; //Used when creating a random state

	@FXML
	private Polygon tile0, tile1, tile2, tile3, tile4, tile5, tile6, tile7, tile8, tile9, tile10, tile11, tile12, tile13, tile14, tile15, tile16, tile17, tile18, tile19, tile20, tile21, tile22, tile23, tile24, tile25, tile26, tile27, tile28, tile29, tile30, tile31, tile32, tile33, tile34, tile35, tile36, tile37, tile38, tile39, tile40, tile41, tile42, tile43, tile44, tile45, tile46, tile47, tile48, tile49, tile50, tile51, tile52, tile53, tile54, tile55, tile56, tile57, tile58, tile59;
	@FXML
	private Label nbFish0, nbFish1, nbFish2, nbFish3, nbFish4, nbFish5, nbFish6, nbFish7, nbFish8, nbFish9, nbFish10, nbFish11, nbFish12, nbFish13, nbFish14, nbFish15, nbFish16, nbFish17, nbFish18, nbFish19, nbFish20, nbFish21, nbFish22, nbFish23, nbFish24, nbFish25, nbFish26, nbFish27, nbFish28, nbFish29, nbFish30, nbFish31, nbFish32, nbFish33, nbFish34, nbFish35, nbFish36, nbFish37, nbFish38, nbFish39, nbFish40, nbFish41, nbFish42, nbFish43, nbFish44, nbFish45, nbFish46, nbFish47, nbFish48, nbFish49, nbFish50, nbFish51, nbFish52, nbFish53, nbFish54, nbFish55, nbFish56, nbFish57, nbFish58, nbFish59;

	@FXML
	private BorderPane mainPane;

	@FXML
	private Label scoreRedLabel, scoreBlueLabel, turnLabel, statusLabel;

	@Override
	public void initialize(URL location, ResourceBundle resources)
	{
		Polygon[] fxTiles = {tile0, tile1, tile2, tile3, tile4, tile5, tile6, tile7, tile8, tile9, tile10, tile11, tile12, tile13, tile14, tile15, tile16, tile17, tile18, tile19, tile20, tile21, tile22, tile23, tile24, tile25, tile26, tile27, tile28, tile29, tile30, tile31, tile32, tile33, tile34, tile35, tile36, tile37, tile38, tile39, tile40, tile41, tile42, tile43, tile44, tile45, tile46, tile47, tile48, tile49, tile50, tile51, tile52, tile53, tile54, tile55, tile56, tile57, tile58, tile59};
		Label[] labels = {nbFish0, nbFish1, nbFish2, nbFish3, nbFish4, nbFish5, nbFish6, nbFish7, nbFish8, nbFish9, nbFish10, nbFish11, nbFish12, nbFish13, nbFish14, nbFish15, nbFish16, nbFish17, nbFish18, nbFish19, nbFish20, nbFish21, nbFish22, nbFish23, nbFish24, nbFish25, nbFish26, nbFish27, nbFish28, nbFish29, nbFish30, nbFish31, nbFish32, nbFish33, nbFish34, nbFish35, nbFish36, nbFish37, nbFish38, nbFish39, nbFish40, nbFish41, nbFish42, nbFish43, nbFish44, nbFish45, nbFish46, nbFish47, nbFish48, nbFish49, nbFish50, nbFish51, nbFish52, nbFish53, nbFish54, nbFish55, nbFish56, nbFish57, nbFish58, nbFish59};
		this.board = new Tile[fxTiles.length];
		this.boardView = new TileView[fxTiles.length];
		for (int i = 0; i < fxTiles.length; i++)
		{
			board[i] = new Tile(i, Tile.PenguinPresence.NO_PENGUIN);
			boardView[i] = new TileView(fxTiles[i], labels[i], board[i]);
		}
		this.gameState = new GameState();
		try
		{
			this.gameProcess = new ProcessBuilder(iaProgramPath).start();
			gameInput = new PrintWriter(new OutputStreamWriter(gameProcess.getOutputStream()), true);
			Runtime.getRuntime().addShutdownHook(new Thread()
			{
				public void run()
				{
					gameProcess.destroy();
				}
			});
			UpdateThread upT = new UpdateThread(gameProcess, this.gameState, this.board, this.boardView, scoreRedLabel, scoreBlueLabel, turnLabel, statusLabel);
			upT.setDaemon(true);
			upT.start();
		} catch (IOException e)
		{
			Alert alert = new Alert(Alert.AlertType.ERROR, "Can't run penguin program", ButtonType.OK);
			alert.showAndWait();
			e.printStackTrace();
			System.exit(1);
		}

		//PLAY START SOUND
		new Thread(() -> {
			try
			{
				AudioPlayer.player.start(new AudioStream(new FileInputStream(introMusicFile)));
			} catch (IOException e)
			{
				e.printStackTrace();
			}
		}).start();

		//DO WE WANT TO LOAD A STATE?
		TextInputDialog dialog = new TextInputDialog();
		dialog.setHeaderText("Do you want to load a state?");
		dialog.setContentText("Enter JSON (or leave blank for random)");
		dialog.showAndWait();
		String result = dialog.getResult();
		if (result == null) //If the user clicked exit
			System.exit(0);
		try
		{
			new JSONObject(result); //Verify if it is valid json (if not, an exception is thrown)
			//WE DO WANT TO LOAD A STATE
			gameInput.println(result);
			Platform.runLater(() -> startGame());

		} catch (JSONException e)
		{
			//WE DON'T WANT TO LOAD A STATE
			gameState.clearFish();
			gameState.setScore(Player.Red, 0);
			gameState.setScore(Player.Blue, 0);
			//Generating random fish values
			for (int i = 0; i < 60; i++)
			{
				int nbFish = new Random().nextInt(3) + 1;
				gameState.setFish(i, nbFish);
				board[i].setNbFish(nbFish);
				boardView[i].update();
			}
			//Adding listener to add penguins
			penguinGenCounter = 0;
			for (TileView t : boardView)
			{
				PlacePenguinClickHandler phandl = new PlacePenguinClickHandler(t.getModelTile().getNumber());
				t.getFxTile().setOnMouseClicked(phandl);
				t.getFishLabel().setOnMouseClicked(phandl);
			}
			statusLabel.setText("Click to place RED penguins");
		}
	}

	private void startGame()
	{
		ChoiceDialog<Player> playerChoice = new ChoiceDialog<>(Player.Red, Player.Red, Player.Blue);
		playerChoice.setTitle("Penguin game!");
		playerChoice.setHeaderText("Choose your color (red starts)");
		playerChoice.showAndWait();
		humanPlayer = playerChoice.getResult();
		if (humanPlayer == null)
			System.exit(0);
		if (humanPlayer.equals(Player.Red))
			gameInput.println("h");
		else
			gameInput.println("c");
		gameState.setHumanPlayer(humanPlayer);
		this.selectedTile = -1;
		for (TileView t : boardView)
		{
			GameClickHandler gch = new GameClickHandler(t.getModelTile().getNumber());
			t.getFxTile().setOnMouseClicked(gch);
			t.getFishLabel().setOnMouseClicked(gch);

		}

	}

	private class PlacePenguinClickHandler implements EventHandler<MouseEvent>
	{
		public int tileNumber;

		public PlacePenguinClickHandler(int tileNumber)
		{
			this.tileNumber = tileNumber;
		}

		@Override
		public void handle(MouseEvent event)
		{
			if (!board[tileNumber].getPenguinPresence().equals(Tile.PenguinPresence.NO_PENGUIN))
				return;
			if (penguinGenCounter < 4)
			{
				gameState.setPenguin(Player.Red, penguinGenCounter, tileNumber);
				board[tileNumber].setPenguinPresence(Tile.PenguinPresence.RED_PENGUIN);
				boardView[tileNumber].update();
			} else if (penguinGenCounter < 8)
			{
				gameState.setPenguin(Player.Blue, penguinGenCounter - 4, tileNumber);
				board[tileNumber].setPenguinPresence(Tile.PenguinPresence.BLUE_PENGUIN);
				boardView[tileNumber].update();
			}
			penguinGenCounter++;
			if(penguinGenCounter < 4)
				statusLabel.setText("Red penguins left: "+Integer.toString(4-penguinGenCounter));
			else if (penguinGenCounter == 4)
				statusLabel.setText("Click to place BLUE penguins");
			else if (penguinGenCounter < 8)
				statusLabel.setText("Blue penguins left: "+Integer.toString(8-penguinGenCounter));
			else
				statusLabel.setText("");

			//If we placed all penguins
			if (penguinGenCounter >= 8)
			{
				gameInput.println(gameState.toGameInputJSON());
				Platform.runLater(() -> startGame());
			}
		}
	}

	/**
	 * Event handler that will listen for a click on a tile during the game and move/select the penguin.
	 * It is associated with a TileView
	 */
	private class GameClickHandler implements EventHandler<MouseEvent>
	{
		private int tileNumber;

		public GameClickHandler(int tileNumber)
		{
			this.tileNumber = tileNumber;
		}

		private void unHighlightTiles(List<Integer> tiles)
		{
			for (int tile : tiles)
			{
				boardView[tile].setHighlighted(false);
				boardView[tile].setHighlightMove(null);
				boardView[tile].update();
			}
		}

		private void playClickSound()
		{
			try
			{
				AudioPlayer.player.start(new AudioStream(new FileInputStream(clickSoundFile)));
			} catch (IOException e)
			{
				e.printStackTrace();
			}
		}

		@Override
		public void handle(MouseEvent event)
		{
			if (!gameState.getCurrent_player().equals(humanPlayer))
				return;

			if (selectedTile == tileNumber) //If we clicked again on the selected tile
			{
				playClickSound();
				//UnSelect and un-highlight previously selected and highlighted tiles
				boardView[selectedTile].setSelected(false);
				boardView[selectedTile].update();
				unHighlightTiles(gameState.getReachableTiles(humanPlayer, gameState.getPenguinOnTile(humanPlayer, selectedTile)));
				selectedTile = -1;
				return;
			}

			//We clicked on a tile that wasn't previously selected
			//Selecting the tile
			int penguinNb = gameState.getPenguinOnTile(humanPlayer, tileNumber);
			if (penguinNb != -1) //There is a penguin on this tile: we want to select it
			{
				playClickSound();
				//Unselect previously selected tiles
				if (selectedTile != -1)
				{
					boardView[selectedTile].setSelected(false);
					boardView[selectedTile].update();
					unHighlightTiles(gameState.getReachableTiles(humanPlayer, gameState.getPenguinOnTile(humanPlayer, selectedTile)));
				}
				//Selecting new tile
				boardView[tileNumber].setSelected(true);
				boardView[tileNumber].update();
				selectedTile = tileNumber;
				for (int i = 1; i <= gameState.getNbMoves(humanPlayer, penguinNb, GameState.Direction.A); i++)
				{
					boardView[selectedTile + i * 7].setHighlighted(true);
					boardView[selectedTile + i * 7].setHighlightMove(new Move(GameState.Direction.A, i));
					boardView[selectedTile + i * 7].update();
				}
				for (int i = 1; i <= gameState.getNbMoves(humanPlayer, penguinNb, GameState.Direction.B); i++)
				{
					boardView[selectedTile - i].setHighlighted(true);
					boardView[selectedTile - i].setHighlightMove(new Move(GameState.Direction.B, i));
					boardView[selectedTile - i].update();
				}
				for (int i = 1; i <= gameState.getNbMoves(humanPlayer, penguinNb, GameState.Direction.C); i++)
				{
					boardView[selectedTile - i * 8].setHighlighted(true);
					boardView[selectedTile - i * 8].setHighlightMove(new Move(GameState.Direction.C, i));
					boardView[selectedTile - i * 8].update();
				}
				for (int i = 1; i <= gameState.getNbMoves(humanPlayer, penguinNb, GameState.Direction.D); i++)
				{
					boardView[selectedTile - i * 7].setHighlighted(true);
					boardView[selectedTile - i * 7].setHighlightMove(new Move(GameState.Direction.D, i));
					boardView[selectedTile - i * 7].update();
				}
				for (int i = 1; i <= gameState.getNbMoves(humanPlayer, penguinNb, GameState.Direction.E); i++)
				{
					boardView[selectedTile + i].setHighlighted(true);
					boardView[selectedTile + i].setHighlightMove(new Move(GameState.Direction.E, i));
					boardView[selectedTile + i].update();
				}
				for (int i = 1; i <= gameState.getNbMoves(humanPlayer, penguinNb, GameState.Direction.F); i++)
				{
					boardView[selectedTile + i * 8].setHighlighted(true);
					boardView[selectedTile + i * 8].setHighlightMove(new Move(GameState.Direction.F, i));
					boardView[selectedTile + i * 8].update();
				}
				return;
			}
			else if (boardView[tileNumber].isHighlighted()) //There is no penguin but this is a possible move for the penguin on the selected tile
			{
				playClickSound();
				Move move = boardView[tileNumber].getHighlightMove();
				int moveNb = gameState.getPlayerMoveNumber(humanPlayer, gameState.getPenguinOnTile(humanPlayer, selectedTile), move.getDirection(), move.getSteps());
				//Before playing the move: deselect and de-highlight tiles
				boardView[selectedTile].setSelected(false);
				boardView[selectedTile].update();
				unHighlightTiles(gameState.getReachableTiles(humanPlayer, gameState.getPenguinOnTile(humanPlayer, selectedTile)));
				selectedTile = -1;
				//Play the move
				gameInput.println(moveNb);
			}
		}
	}


}