package com.cholling.ygame.model;

import org.apache.commons.lang3.SerializationUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Board {
    private int[][] board;

    public int[][] getBoard() {
        return SerializationUtils.clone(board);
    }

    public void setBoard(int[][] board, boolean enforceValidity) {
        if (!enforceValidity || (getBoardState(board) != BoardState.INVALID)) {
            this.board = SerializationUtils.clone(board);
        } else {
            throw new IllegalArgumentException("Board is not valid");
        }
    }

    public void setBoard(int[][] board) {
        setBoard(board, true);
    }

    public Board(Board board) {
        this.setBoard(board.getBoard());
    }

    public Board(int[][] boardArray) {
        this.setBoard(boardArray, true);
    }

    public Board(int[][] boardArray, boolean enforceValidity) {
        this.setBoard(boardArray, enforceValidity);
    }

    public Board(int size) {
        int[][] boardArray = new int[size][];
        for (int x = size; x > 0; x--) {
            int[] column = new int[x];
            for (int y=0; y < x; y++) {
                column[y] = 0;
            }
            boardArray[size-x] = column;
        }
        this.setBoard(boardArray);
    }

    public Board() {
        // required default constructor
    }

    public List<int[]> getValidMoves() {
        List<int[]> validMoves = new ArrayList<>();
        if (this.getWinner() == 0) {
            for (int x = 0; x < board.length; x++) {
                int[] column = board[x];
                for (int y=0; y < column.length; y++) {
                    if (board[x][y] == 0) {
                        validMoves.add(new int[] {x,y});
                    }
                }
            }
        }
        return validMoves;
    }

    public int getWinner() {
        if (board.length == 1) {
            return board[0][0];
        }
        boolean[] piecesAlreadyFound = new boolean[] {false, false, false};
        if (board.length == 2) {
            for(int x=0;x<board.length;x++) {
                for(int y=0;y<board.length-x;y++) {
                    int pieceValue = board[x][y];
                    if (piecesAlreadyFound[pieceValue]) {
                        return pieceValue;
                    } else {
                        piecesAlreadyFound[pieceValue] = true;
                    }
                }
            }
        } else {
            Board reducedBoard = reduceBoardByOne();
            return reducedBoard == null ? 0 : reducedBoard.getWinner();
        }
        return 0;
    }

    private Board reduceBoardByOne() {
        if (board.length < 3) {
            return null;
        }
        final int newLength = board.length - 1;
        int[][] newBoardArray = new int[newLength][];
        for(int x=0; x<newLength; x++) {
            int[] newColumn = new int[newLength - x];
            for(int y=0; y<newLength-x; y++) {
                int[][] subBoardArray = new int[][]{
                        {board[x][y], board[x][y+1]},
                        {board[x+1][y]}
                };
                Board subBoard = new Board(subBoardArray, false);
                newColumn[y] = subBoard.getWinner();
            }
            newBoardArray[x] = newColumn;
        }
        return new Board(newBoardArray, false);
    }

    public Board move(int[] nextMove) {
        BoardState boardState = getBoardState();
        if(BoardState.INVALID == boardState) {
            throw new IllegalArgumentException("Board is not a valid board.");
        }
        List<int[]> validMoves = getValidMoves();
        int[][] validMovesArray = validMoves.toArray(new int[validMoves.size()][]);
        String validMovesString = Arrays.deepToString(validMovesArray);
        final int x = nextMove[0];
        final int y = nextMove[1];
        if (!validMovesString.contains(String.format("[%d, %d]", x, y))) {
            throw new IllegalArgumentException("Requested move is not a valid move.");
        }
        int[][] newBoardArray = getBoard();

        newBoardArray[x][y] = (BoardState.RED_TO_MOVE == boardState) ? 1 : 2;
        return new Board(newBoardArray);
    }

    @Override
    public String toString() {
        return Arrays.deepToString(board);
    }

    public BoardState getBoardState() {
        return getBoardState(board);
    }

    private BoardState getBoardState(int[][] board) {
        int numberOfOnes = 0;
        int numberOfTwos = 0;
        int columnSize = board.length;
        int numberOfHexes = 0;
        for (int i = columnSize; i > 0; i--) {
            numberOfHexes += i;
        }
        for (int x=0; x < board.length; x++) {
            int[] column = board[x];
            if (column.length != columnSize) {
                return BoardState.INVALID;
            }
            columnSize--;
            for (int y=0; y < column.length; y++) {
                if (board[x][y] < 0 || board[x][y] > 2) {
                    return BoardState.INVALID;
                }
                if (board[x][y] == 1) {
                    numberOfOnes++;
                }
                if (board[x][y] == 2) {
                    numberOfTwos++;
                }
            }
        }
        if (numberOfOnes + numberOfTwos == numberOfHexes) {
            return BoardState.COMPLETED;
        }
        if (numberOfOnes == numberOfTwos) {
            return BoardState.RED_TO_MOVE;
        }
        if (numberOfOnes == numberOfTwos + 1) {
            return BoardState.BLUE_TO_MOVE;
        }
        return BoardState.INVALID;
    }

    public enum BoardState {
        INVALID, RED_TO_MOVE, BLUE_TO_MOVE, COMPLETED
    }
}
