package beans;

/**
 * @author MACHIZAUD Andréa
 * @version 1.0 06/04/11
 */
public class Deminor {
    private static final java.util.logging.Logger logger =
            java.util.logging.Logger.getLogger(Deminor.class.getCanonicalName());

    //Constant
    public static final int DEFAULT_SIZE = 10;
    public static final int DEFAULT_BOMB_NUMBER = 3;

    public static final int BOMB = -1;
    public static final int NONE = 0;
    public static final int DIGGED = 1;
    public static final int FLAG = 2;

    public static final int GAME_EVENT_WIN = 1;
    public static final int GAME_EVENT_CONTINUE = 0;
    public static final int GAME_EVENT_LOSE = -1;

    //class attribute
    //Deminor with a default size
    private int[][] bombGrid;
    private int[][] deminorGrid;
    private int gameState = GAME_EVENT_CONTINUE;

    //Required by JavaBeans spec
    public Deminor() {
        this(DEFAULT_SIZE,DEFAULT_SIZE, DEFAULT_BOMB_NUMBER);
    }

    public Deminor(int rowsize, int columnsize, int bombNumber) {
        if (rowsize < 1 || columnsize < 1)
            throw new IllegalArgumentException("Deminor size is less than 1");
        if (bombNumber > rowsize * columnsize)
            throw new IllegalArgumentException("There are too many bombs for this grid");

        bombGrid = new int[rowsize][columnsize];
        deminorGrid = new int[rowsize][columnsize];

        //Initialisation
        for (int row = 0; row < bombGrid.length; row++)
            for (int column = 0; column < bombGrid[row].length; column++)
                bombGrid[row][column] = NONE;
        for (int row = 0; row < deminorGrid.length; row++)
            for (int column = 0; column < deminorGrid[row].length; column++)
                deminorGrid[row][column] = NONE;

        //place bombs
        int remainingBomb = bombNumber;
        while (remainingBomb > 0) {
            //pick a random case
            int randomRow = (int) (Math.random() * bombGrid.length);
            int randomColumn = (int) (Math.random() * bombGrid[0].length);

            //if the case is free set a bomb there
            if (bombGrid[randomRow][randomColumn] != BOMB) {
                bombGrid[randomRow][randomColumn] = BOMB;

                //handle border and if there is a bomb close to position
                for (int rowStep = -1; rowStep <= 1; rowStep++)
                    for (int columnStep = -1; columnStep <= 1; columnStep++)
                        try {
                            if (bombGrid[randomRow + rowStep][randomColumn + columnStep] != BOMB)
                                bombGrid[randomRow + rowStep][randomColumn + columnStep]++;
                        } catch (ArrayIndexOutOfBoundsException e) {
                            //Expected
                            //Means that we cannot increase close bomb number here
                        }

                remainingBomb--;
            }
        }

    }

    /**
     * Render a custom view grid with mix user grid (on which he digged and flagged) and
     * bombGrid to show hints on bomb number
     */
    public int[][] getGrid() {
        int[][] viewGrid = new int[bombGrid.length][bombGrid[0].length];
        for (int row = 0; row < viewGrid.length; row++)
            for (int column = 0; column < viewGrid[0].length; column++)
                if (gameState == GAME_EVENT_LOSE)
                    switch (bombGrid[row][column]) {
                        case BOMB:
                            viewGrid[row][column] = -3;
                            break;
                        default:
                            viewGrid[row][column] = bombGrid[row][column];
                            break;
                    }
                else
                    switch (deminorGrid[row][column]) {
                        case NONE:
                            viewGrid[row][column] = -1;
                            break;
                        case FLAG:
                            viewGrid[row][column] = -2;
                            break;
                        case DIGGED:
                            viewGrid[row][column] = bombGrid[row][column];
                            break;
                    }
        logger.info("Rendered grid : \n" + renderGrid(viewGrid));
        return viewGrid;
    }

    public int dig(int row, int column) throws InvalidCoordinateException {
        try {
            deminorGrid[row][column] = DIGGED;
            switch (bombGrid[row][column]) {
                case NONE:
                    gameState = GAME_EVENT_CONTINUE;
                    break;
                case BOMB:
                    gameState = GAME_EVENT_LOSE;
                    break;
            }
        } catch (ArrayIndexOutOfBoundsException e) {
            throw new InvalidCoordinateException("Coordinate out of bounds", row, column);
        }
        return gameState;
    }

    public int flag(int row, int column) throws InvalidCoordinateException {
        try {
            if (deminorGrid[row][column] != DIGGED) {
                deminorGrid[row][column] = FLAG;
                //Check for the win
                if (gameEnded())
                    gameState = GAME_EVENT_WIN;
                else
                    gameState = GAME_EVENT_CONTINUE;
            } else throw new InvalidCoordinateException("Case already digged");
        } catch (ArrayIndexOutOfBoundsException e) {
            throw new InvalidCoordinateException("Coordinate out of bounds", row + 1, column + 1);
        }
        return gameState;
    }

    private int countBombsAround(int row, int column) {
        int counter = 0;
        for (int rowStep = -1; rowStep <= 1; rowStep++)
            for (int columnStep = -1; columnStep <= 1; columnStep++)
                try {
                    if (bombGrid[row + rowStep][column + columnStep] == BOMB)
                        counter++;
                } catch (ArrayIndexOutOfBoundsException e) {
                    //Expected
                    //Means that we cannot increase close bomb number here
                }
        return counter;
    }

    private boolean gameEnded() {
        for (int row = 0; row < bombGrid.length; row++)
            for (int column = 0; column < bombGrid[0].length; column++)
                if (bombGrid[row][column] == BOMB &&
                        deminorGrid[row][column] != FLAG)
                    return false;
        return true;
    }

    private static final String renderGrid(int[][] viewGrid) {
        StringBuilder sb = new StringBuilder(viewGrid.length * viewGrid.length);
        for (int[] row : viewGrid) {
            sb.append('[');
            for (int caseFlag : row) {
                sb.append(String.valueOf(caseFlag) + " ");
            }
            sb.append("]\n");
        }
        return sb.toString();
    }
}
