package uk.mgrove.ac.soton.comp1206.scene;

import javafx.animation.*;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ChangeListener;
import javafx.geometry.Pos;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import javafx.util.Duration;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import uk.mgrove.ac.soton.comp1206.component.GameBlock;
import uk.mgrove.ac.soton.comp1206.component.GameBoard;
import uk.mgrove.ac.soton.comp1206.component.PieceBoard;
import uk.mgrove.ac.soton.comp1206.game.Game;
import uk.mgrove.ac.soton.comp1206.game.GamePiece;
import uk.mgrove.ac.soton.comp1206.ui.GamePane;
import uk.mgrove.ac.soton.comp1206.ui.GameWindow;
import uk.mgrove.ac.soton.comp1206.component.StatsMenu;
import uk.mgrove.ac.soton.comp1206.util.Multimedia;

/**
 * The Single Player challenge scene. Holds the UI for the single player challenge mode in the game.
 */
public class ChallengeScene extends BaseScene {

    /**
     * Logger
     */
    private static final Logger logger = LogManager.getLogger(ChallengeScene.class);

    /**
     * The game that is running
     */
    protected Game game;

    /**
     * PieceBoard to display current piece
     */
    protected PieceBoard currentPieceBoard;

    /**
     * PieceBoard to display next piece
     */
    protected PieceBoard followingPieceBoard;

    /**
     * GameBoard for main game
     */
    protected GameBoard board;

    /**
     * Progress bar to indicate time left until game loop executes (i.e. life is lost)
     */
    protected Rectangle timerCountdownBar;

    /**
     * Fill transition for timer countdown bar
     */
    private FillTransition timerCountdownBarFillTransition;

    /**
     * Scale transition for timer countdown bar
     */
    private ScaleTransition timerCountdownBarScaleTransition;

    /**
     * Main pane for UI
     */
    protected BorderPane mainPane;

    /**
     * Top high score property
     */
    protected final IntegerProperty highScore = new SimpleIntegerProperty(0);

    /**
     * Create a new Single Player challenge scene
     * @param gameWindow the Game Window
     */
    public ChallengeScene(GameWindow gameWindow) {
        super(gameWindow);
        logger.info("Creating Challenge Scene");
    }

    /**
     * Build the Challenge window
     */
    @Override
    public void build() {
        logger.info("Building " + this.getClass().getName());

        setupGame();

        root = new GamePane(gameWindow.getWidth(),gameWindow.getHeight());

        var challengePane = new StackPane();
        challengePane.setMaxWidth(gameWindow.getWidth());
        challengePane.setMaxHeight(gameWindow.getHeight());
        challengePane.getStyleClass().add("menu-background");
        root.getChildren().add(challengePane);

        mainPane = new BorderPane();
        challengePane.getChildren().add(mainPane);

        board = new GameBoard(game.getGrid(),gameWindow.getWidth()/2f,gameWindow.getWidth()/2f);
        board.setFocusTraversable(true);
        board.setOnRightClicked(game::rotateCurrentPiece);
        mainPane.setCenter(board);

        var statsMenu = new StatsMenu(game.scoreProperty(),game.levelProperty(),game.livesProperty(),game.multiplierProperty());

        currentPieceBoard = new PieceBoard(100, 100);
        currentPieceBoard.showMiddleIndicator(true);
        currentPieceBoard.setOnLeftClicked(game::rotateCurrentPiece);
        followingPieceBoard = new PieceBoard(72, 72);

        var topHighScoreText = new Text();
        topHighScoreText.textProperty().bind(highScore.asString());
        topHighScoreText.getStyleClass().add("hiscore");
        highScore.set(getHighScore());
        var topHighScoreContainer = new VBox(new Text("Top high score"), topHighScoreText);

        game.scoreProperty().addListener((ChangeListener<? super Number>) (change, oldValue, newValue) -> {
            if (newValue.intValue() > highScore.get()) highScore.set(newValue.intValue());
        });

        var rightMenu = new VBox();
        rightMenu.setSpacing(16);
        rightMenu.getChildren().addAll(statsMenu,currentPieceBoard,followingPieceBoard,topHighScoreContainer);

        mainPane.setRight(rightMenu);

        timerCountdownBar = new Rectangle(gameWindow.getWidth()/2f,48);
        BorderPane.setAlignment(timerCountdownBar, Pos.BOTTOM_CENTER);

        mainPane.setBottom(timerCountdownBar);

        Multimedia.playMusic("music/game.wav");

        //Handle block on game board grid being clicked
        board.setOnBlockClick(this::blockClicked);
    }

    /**
     * Handle when a block is clicked
     * @param gameBlock the Game Block that was clocked
     */
    protected void blockClicked(GameBlock gameBlock) {
        game.blockClicked(gameBlock);
    }

    /**
     * Handle keypress for game keyboard controls
     * @param event the keypress
     */
    protected void handleKeyboardControls(KeyEvent event) {
        logger.info("Handling keyboard input: {}", event.getCode());
        switch (event.getCode()) {
            case ESCAPE -> returnToMenu();
            case ENTER, X -> game.dropPiece(board.getXFocus(),board.getYFocus());
            case SPACE, R -> game.swapPieces();
            case OPEN_BRACKET, Q, Z -> game.rotateCurrentPieceAnticlockwise();
            case CLOSE_BRACKET, E, C -> game.rotateCurrentPiece();
            case LEFT, A -> board.changeXFocus(-1);
            case RIGHT, D -> board.changeXFocus(1);
            case DOWN, S -> board.changeYFocus(1);
            case UP, W -> board.changeYFocus(-1);
        }
    }

    /**
     * Set up the game object and model
     */
    public void setupGame() {
        logger.info("Starting a new challenge");

        //Start new game
        game = new Game(5, 5);
    }

    /**
     * Initialise the scene and start the game
     */
    @Override
    public void initialise() {
        logger.info("Initialising Challenge");

        scene.setOnKeyPressed(this::handleKeyboardControls);

        game.setNextPieceListener(this::setCurrentPiece);

        game.setLineClearedListener(board::fadeOut);

        game.setOnGameLoop(this::scheduleCountdown);

        game.setShowScoresListener(this::showScores);

        game.start();
    }

    private void returnToMenu() {
        game.endGame();
        
        gameWindow.startMenu();
    }

    protected void showScores(Game game) {
        if (timerCountdownBarFillTransition != null) timerCountdownBarFillTransition.stop();
        if (timerCountdownBarScaleTransition != null) timerCountdownBarScaleTransition.stop();
        gameWindow.startScores(game);
    }

    protected void setCurrentPiece(GamePiece currentPiece, GamePiece followingPiece) {
        currentPieceBoard.displayPiece(currentPiece);
        followingPieceBoard.displayPiece(followingPiece);
        board.setCurrentPiece(currentPiece);
    }

    /**
     * Reset the countdown progress bar with new game timer delay when the game loop is scheduled
     * @param timerDelay delay until game loop is executed
     */
    protected void scheduleCountdown(int timerDelay) {
        logger.info("Scheduling UI countdown timer");

        timerCountdownBar.setArcHeight(32);
        timerCountdownBar.setArcWidth(32);
        timerCountdownBar.setFill(Color.GREEN);
        timerCountdownBarFillTransition = new FillTransition(Duration.millis(timerDelay), timerCountdownBar, Color.GREEN, Color.RED);
        timerCountdownBarScaleTransition = new ScaleTransition(Duration.millis(timerDelay), timerCountdownBar);
        timerCountdownBarScaleTransition.setFromX(1);
        timerCountdownBarScaleTransition.setToX(0);
        timerCountdownBarFillTransition.play();
        timerCountdownBarScaleTransition.play();
    }

    /**
     * Get top high score
     * @return top high score
     */
    protected int getHighScore() {
        return ScoresScene.getScores("scores.txt").get(0).getValue();
    }

}
