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

import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
 * The Grid is a model which holds the state of a game board. It is made up of a set of Integer values arranged in a 2D
 * arrow, with rows and columns.
 *
 * Each value inside the Grid is an IntegerProperty can be bound to enable modification and display of the contents of
 * the grid.
 *
 * The Grid contains functions related to modifying the model, for example, placing a piece inside the grid.
 *
 * The Grid should be linked to a GameBoard for its display.
 */
public class Grid {

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

    /**
     * The number of columns in this grid
     */
    private final int cols;

    /**
     * The number of rows in this grid
     */
    private final int rows;

    /**
     * The grid is a 2D arrow with rows and columns of SimpleIntegerProperties.
     */
    private final SimpleIntegerProperty[][] grid;

    /**
     * The preview grid is a 2D arrow with rows and columns of SimpleIntegerProperties, storing which blocks are currently showing previews.
     */
    private final SimpleIntegerProperty[][] previewGrid;

    /**
     * Create a new Grid with the specified number of columns and rows and initialise them
     * @param cols number of columns
     * @param rows number of rows
     */
    public Grid(int cols, int rows) {
        this.cols = cols;
        this.rows = rows;

        //Create the grid itself
        grid = new SimpleIntegerProperty[cols][rows];

        //Add a SimpleIntegerProperty to every block in the grid
        for(var y = 0; y < rows; y++) {
            for(var x = 0; x < cols; x++) {
                grid[x][y] = new SimpleIntegerProperty(0);
            }
        }

        //Create the grid itself
        previewGrid = new SimpleIntegerProperty[cols][rows];

        //Add a SimpleIntegerProperty to every block in the grid
        for(var y = 0; y < rows; y++) {
            for(var x = 0; x < cols; x++) {
                previewGrid[x][y] = new SimpleIntegerProperty(0);
            }
        }
    }

    /**
     * Get the Integer property contained inside the grid at a given row and column index. Can be used for binding.
     * @param x column
     * @param y row
     * @return the IntegerProperty at the given x and y in this grid
     */
    public IntegerProperty getGridProperty(int x, int y) {
        return grid[x][y];
    }

    /**
     * Get the Integer property contained inside the preview grid at a given row and column index. Can be used for binding.
     * @param x column
     * @param y row
     * @return the IntegerProperty at the given x and y in this grid
     */
    public IntegerProperty getPreviewGridProperty(int x, int y) {
        return previewGrid[x][y];
    }

    /**
     * Update the value at the given x and y index within the grid
     * @param x column
     * @param y row
     * @param value the new value
     */
    public void set(int x, int y, int value) {
        grid[x][y].set(value);
    }

    /**
     * Get the value represented at the given x and y index within the grid
     * @param x column
     * @param y row
     * @return the value
     */
    public int get(int x, int y) {
        try {
            //Get the value held in the property at the x and y index provided
            return grid[x][y].get();
        } catch (ArrayIndexOutOfBoundsException e) {
            //No such index
            return -1;
        }
    }

    /**
     * Check whether piece can be placed in grid at given location
     * @param piece piece to check
     * @param placeX x-coordinate to check
     * @param placeY y-coordinate to check
     * @return whether piece can be placed in this location
     */
    public boolean canPlayPiece(GamePiece piece, int placeX, int placeY) {
        logger.info("Checking if piece {} can be played at {},{}", piece, placeX, placeY);

        int[][] blocks = piece.getBlocks();

        for (var blockX = 0; blockX < blocks.length; blockX++) {
            for (var blockY = 0; blockY < blocks.length; blockY++) {
                // check if this block can be placed on grid
                var blockValue = blocks[blockX][blockY];
                var x = placeX + blockX - 1;
                var y = placeY + blockY - 1;
                var gridValue = get(x,y);
                if (blockValue != 0 && gridValue != 0) {
                    logger.info("Unable to place block due to conflict at {},{}", x, y);
                    return false;
                }
            }
        }

        return true;
    }

    /**
     * Play a piece at given location in the grid by updating the grid with the piece blocks
     * @param piece piece to place
     * @param placeX x-coordinate to check
     * @param placeY y-coordinate to check
     */
    public void playPiece(GamePiece piece, int placeX, int placeY) {
        logger.info("Playing piece {} at {},{}", piece, placeX, placeY);
        int value = piece.getValue();
        int[][] blocks = piece.getBlocks();

        // return if piece can't be played
        if (!canPlayPiece(piece, placeX, placeY)) return;

        for (var blockX = 0; blockX < blocks.length; blockX++) {
            for (var blockY = 0; blockY < blocks.length; blockY++) {
                if (blocks[blockX][blockY] > 0) {
                    set(placeX + blockX - 1, placeY + blockY - 1, value);
                }
            }
        }
    }

    /**
     * Clear the grid
     */
    public void clearGrid() {
        for (var x = 0; x < getCols(); x++) {
            for (var y = 0; y < getRows(); y++) {
                if (get(x,y) != 0) {
                    set(x,y,0);
                }
            }
        }
    }

    /**
     * Get the number of columns in this game
     * @return number of columns
     */
    public int getCols() {
        return cols;
    }

    /**
     * Get the number of rows in this game
     * @return number of rows
     */
    public int getRows() {
        return rows;
    }

    /**
     * Preview piece on board
     * @param piece piece to preview
     * @param x x-coordinate of piece centre
     * @param y y-coordinate of piece centre
     */
    public void previewPiece(GamePiece piece, int x, int y) {
        if (!canPlayPiece(piece, x, y)) return;

        logger.info("Previewing piece {} at {},{}", piece, x, y);
        int value = piece.getValue();
        int[][] blocks = piece.getBlocks();

        for (var blockX = 0; blockX < blocks.length; blockX++) {
            for (var blockY = 0; blockY < blocks.length; blockY++) {
                if (blocks[blockX][blockY] > 0) {
                    previewGrid[x + blockX - 1][y + blockY - 1].set(value);
                }
            }
        }
    }

    /**
     * Clear previewed blocks from the grid
     */
    public void clearPreview() {
        for (var resetX = 0; resetX < previewGrid.length; resetX++) {
            for (var resetY = 0; resetY < previewGrid[0].length; resetY++) {
                previewGrid[resetX][resetY].set(0);
            }
        }
    }

}
