package boardgame.model;

import boardgame.util.GameResult;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;

import java.io.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

/**
 * The {@code BoardGameModel} class represents a board game model.
 * It manages the game logic, board state, and player moves.
 */
public class BoardGameModel {

    private File file = new File("game_result.json");
    private ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule());
    private GameResult result = new GameResult();
    private String player1;
    private String player2;

    private static final int BOARD_SIZE = 5;
    private static final int WINNING_CONDITION = 3;

    private boolean gameEnded;

    private Square[][] board;
    private ObjectProperty<Square>[][] squareProperties;
    private ObjectProperty<Square> currentPlayer;

    /**
     * Constructs a new {@code BoardGameModel} object.
     * Initializes the board, square properties, and the current player.
     */
    public BoardGameModel() {
        board = new Square[BOARD_SIZE][BOARD_SIZE];
        squareProperties = new ObjectProperty[BOARD_SIZE][BOARD_SIZE];
        currentPlayer = new SimpleObjectProperty<>(Square.RED);
        initializeBoard();
        initializeSquareProperties();
    }

    /**
     * Writes the game result to JSON.
     *
     * @param result the game result to be written
     * @throws IOException if an error occurs while writing to JSON
     */
    private void writeToJson(GameResult result) throws IOException {
        result.setWinner(currentPlayer.get() == Square.RED ? player1 : player2);
        result.setPlayer1Name(player1);
        result.setPlayer2Name(player2);
        List<GameResult> resultList = new ArrayList<>();

        if (file.exists()) {
            try (FileReader reader = new FileReader(file)) {
                resultList = mapper.readValue(reader, new TypeReference<ArrayList<GameResult>>() {
                });
            }
        }

        resultList.add(result);

        try (FileWriter writer = new FileWriter(file)) {
            mapper.writeValue(writer, resultList);
        }
    }

    /**
     * Sets the names of the players.
     *
     * @param name1 the name of player 1
     * @param name2 the name of player 2
     */
    public void setNames(String name1, String name2) {
        player1 = name1;
        player2 = name2;
    }

    /**
     * Initializes the game board with empty squares.
     */
    private void initializeBoard() {
        for (int i = 0; i < BOARD_SIZE; i++) {
            for (int j = 0; j < BOARD_SIZE; j++) {
                board[i][j] = Square.NONE;
            }
        }

        result.setStartDate(LocalDateTime.now());
    }

    @SuppressWarnings("unchecked")
    private void initializeSquareProperties() {
        for (int i = 0; i < BOARD_SIZE; i++) {
            for (int j = 0; j < BOARD_SIZE; j++) {
                squareProperties[i][j] = new SimpleObjectProperty<>(Square.NONE);
            }
        }
    }

    /**
     * Makes a move on the game board at the specified row and column.
     *
     * @param row the row index
     * @param col the column index
     */
    public void makeMove(int row, int col) {
        try {
            if (!gameEnded && isValidMove(row, col)) {
                board[row][col] = currentPlayer.get();
                squareProperties[row][col].set(board[row][col]);

                if (currentPlayer.get() == Square.RED) {
                    result.player1count++;
                } else {
                    result.player2count++;
                }

                if (checkWin()) {
                    System.out.println("Player " + currentPlayer.get() + " wins!");
                    gameEnded = true;
                    result.setEndDate(LocalDateTime.now());
                } else if (checkDraw()) {
                    System.out.println("It's a draw!");
                    gameEnded = true;
                    result.setEndDate(LocalDateTime.now());
                } else {
                    currentPlayer.set(currentPlayer.get() == Square.RED ? Square.BLUE : Square.RED);
                }

                if (gameEnded) {
                    writeToJson(result);
                }
            }
        } catch (IOException e) {
            System.err.println("An error occurred while writing to JSON: " + e.getMessage());
        }
    }

    private boolean isValidMove(int row, int col) {
        return row >= 0 && row < BOARD_SIZE && col >= 0 && col < BOARD_SIZE && board[row][col] == Square.NONE;
    }

    /**
     * Checks if a player has won the game.
     *
     * @return {@code true} if a player has won, {@code false} otherwise
     */
    public boolean checkWin() {
        return checkRows() || checkColumns() || checkDiagonals();
    }

    private boolean checkRows() {
        for (int i = 0; i < BOARD_SIZE; i++) {
            int redCount = 0;

            for (int j = 0; j < BOARD_SIZE; j++) {
                if (board[i][j] == Square.RED) {
                    redCount++;
                    if (redCount >= WINNING_CONDITION) {
                        return true;
                    }
                } else {
                    redCount = 0;
                }
            }
        }

        return false;
    }

    private boolean checkColumns() {
        for (int j = 0; j < BOARD_SIZE; j++) {
            int redCount = 0;

            for (int i = 0; i < BOARD_SIZE; i++) {
                if (board[i][j] == Square.RED) {
                    redCount++;
                    if (redCount >= WINNING_CONDITION) {
                        return true;
                    }
                } else {
                    redCount = 0;
                }
            }
        }

        return false;
    }

    private boolean checkDiagonals() {
        for (int i = 0; i < BOARD_SIZE; i++) {
            for (int j = 0; j < BOARD_SIZE; j++) {
                if (checkDiagonalFromPosition(i, j)) {
                    return true;
                }
            }
        }

        return false;
    }

    private boolean checkDiagonalFromPosition(int row, int col) {
        // Check diagonal from top-left to bottom-right
        if (checkDiagonalFromTopLeft(row, col)) {
            return true;
        }

        // Check diagonal from top-right to bottom-left
        if (checkDiagonalFromTopRight(row, col)) {
            return true;
        }

        return false;
    }

    private boolean checkDiagonalFromTopLeft(int startRow, int startCol) {
        if (startRow + WINNING_CONDITION > BOARD_SIZE || startCol + WINNING_CONDITION > BOARD_SIZE) {
            return false;
        }

        int redCount = 0;
        for (int i = 0; i < WINNING_CONDITION; i++) {
            if (board[startRow + i][startCol + i] == Square.RED) {
                redCount++;
            } else {
                break;
            }
        }

        return redCount >= WINNING_CONDITION;
    }

    private boolean checkDiagonalFromTopRight(int startRow, int startCol) {
        if (startRow + WINNING_CONDITION > BOARD_SIZE || startCol - WINNING_CONDITION + 1 < 0) {
            return false;
        }

        int redCount = 0;
        for (int i = 0; i < WINNING_CONDITION; i++) {
            if (board[startRow + i][startCol - i] == Square.RED) {
                redCount++;
            } else {
                break;
            }
        }

        return redCount >= WINNING_CONDITION;
    }

    /**
     * Checks if the game is a draw (all squares are filled).
     *
     * @return {@code true} if the game is a draw, {@code false} otherwise
     */
    public boolean checkDraw() {
        for (int i = 0; i < BOARD_SIZE; i++) {
            for (int j = 0; j < BOARD_SIZE; j++) {
                if (board[i][j] == Square.NONE) {
                    return false;
                }
            }
        }

        return true;
    }

    /**
     * Returns the square property at the specified row and column.
     *
     * @param row the row index
     * @param col the column index
     * @return the square property at the specified row and column
     */
    public ObjectProperty<Square> squareProperty(int row, int col) {
        return squareProperties[row][col];
    }

    /**
     * Resets the game board to its initial state.
     */
    public void resetBoard() {
        gameEnded = false;
        initializeBoard();
        initializeSquareProperties();
        currentPlayer.set(Square.RED);
    }

    /**
     * Returns the current player.
     *
     * @return the current player
     */
    public Square getCurrentPlayer() {
        return currentPlayer.get();
    }
}
okay you see there is board that 5x5 which is like [0, 0], [0, 1], [0, 2], [0, 3], [0,4];  [1, 0], [1 1], [1, 2], [1, 3], [1,4];  [2, 0], [2, 1], [2, 2], [2, 3], [2,4];  [3, 0], [3, 1], [3, 2], [3, 3], [3,4];  [4, 0], [4, 1], [4, 2], [4, 3], [4, 4]. even when red stone place on [0, 0], [2, 2], [4,4] or  [0, 2], [2, 2], [4, 2] or [2, 0], [2, 2], [2, 4] or [0, 4], [2, 2], [4, 0], or [4, 0], [4, 2], [4, 4] it is considered winning. he game is over when a row, a column, or a diagonal contains 3 stones of the same color. (You must consider all possible diagonals, not only the main diagonal and the antidiagonal). The three stones do not have to be adjacent. The player to which these stones belong loses the game. Don't need to be ADJACENT!!! Give me full developed code