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


/**
 * The board of the game Gomoku.
 */
public class GomokuBoard{
    /** The firstcell in the board, at the top left of the board. */
    private GomokuCell firstCell;
    /** The width of the GomokuBoard.*/
    private int boardWidth;
    /**  The height of the GomokuBoard.*/
    private int boardHeight;

    public static void main(String[] args) {
        Color[][] colors = {{Color.BLACK,Color.BLACK,Color.BLACK},
                            {Color.BLACK,Color.BLACK,Color.BLACK},
                            {Color.WHITE,Color.WHITE,Color.WHITE}};
        GomokuBoard test = new GomokuBoard(3,3, colors);
        test.get(1,1).setState(Color.BLACK);
        System.out.println(test);
        System.out.println(test.getPlayableCells());

        System.out.println(Cardinal.NE.rotate90CW());
        System.out.println(Cardinal.NE.rotate90CCW());
        System.out.println(Cardinal.NE.rotate(1));
        System.out.println(Cardinal.NE.rotate(-1));
        System.out.println(Cardinal.NE.inverse());

        System.out.println(test);
        test.expandBoard(Cardinal.N);
        System.out.println(test);
        test.expandBoard(Cardinal.S);
        System.out.println(test);
        test.expandBoard(Cardinal.W);
        System.out.println(test);
        test.expandBoard(Cardinal.SE);
        System.out.println(test);
    }

    //------------------Constructors--------------------------

    /**
     * This constructor take the width and the height to creat a board of gomoku.
     * @param width Size of width.
     * @param height Size of height.
     */
    public GomokuBoard(int width, int height){
        this(width, height, null);
    }

    /**
     * This constructor take the width and the height to creat a board of gomoku.
     * @param width Size of width.
     * @param height Size of height.
     * @param colors Is colors of cells after load a game.
     */
    public GomokuBoard(int width, int height, Color[][] colors){
        this.firstCell = new GomokuCell(Color.NIL);
        this.boardWidth = width;
        this.boardHeight = height;
        this.generateCells(width, height, colors);
    }


    /**
     * This method generate all cells in the board and link each other.
     * @param width Size of width.
     * @param height Size of height.
     * @param colors Array of Color.
     */
    private void generateCells(int width, int height, Color[][] colors){
        this.firstCell = new GomokuCell(colors == null ? Color.NIL : colors[0][0]);
        GomokuCell act = this.firstCell;
        GomokuCell top = null;

        for (int i = 0; i < height; i++){
            GomokuCell nextLine = null;
            if (i != height-1){
                nextLine = new GomokuCell(colors == null ? Color.NIL: colors[i+1][0]);
                GomokuCell.link(act, nextLine, Cardinal.S);
            }
            for (int j = 0; j < width; j++) {
                if (top != null) {
                    GomokuCell.link(act, top.getNeighbour(Cardinal.W), Cardinal.NW);
                    GomokuCell.link(act, top, Cardinal.N);
                    top = top.getNeighbour(Cardinal.E);
                    GomokuCell.link(act, top , Cardinal.NE);

                }
                if (j != width -1 ){
                    GomokuCell right = new GomokuCell(colors == null ? Color.NIL : colors[i][j+1] );
                    GomokuCell.link(act, right, Cardinal.E);
                    act = right;
                }
            }
            act = nextLine;
            if (act == null) break;
            top = act.getNeighbour(Cardinal.N);
        }
    }
    
    //------------------Gets--------------------------

    /**
     * Returns the width of the board.
     * @return The width of the board.
     */
    public int getWidth() {
        return this.boardWidth;
    }

    /**
     * Returns the height of the board.
     * @return The height of the board.
     */
    public int getHeight() {
        return this.boardHeight;
    }

    /**
     * This method get a cell in specific position in the board.
     * @param x The position x on the board.
     * @param y The position y on the board.
     * @return GomokuCell in the position.
     */
    public GomokuCell get(int x, int y){
        int i = 0, j = 0;
        GomokuCell act = this.firstCell;
        while (i != x || j != y){
            Cardinal c = Cardinal.SE;
            if (i < x) i++;
            else c = Cardinal.S;
            if (j < y) j++;
            else c = Cardinal.E;
            act = act.getNeighbour(c);
        }
        return act;

    }

    /**
     * This method return all cells in the board in the order.
     * @return All cells with a array 2D.
     */
    private GomokuCell[][] getAllCells(){
        GomokuCell[][] cells = new GomokuCell[this.boardHeight][this.boardWidth];
        GomokuCell act = this.firstCell;
        GomokuCell nextLine = this.firstCell.getNeighbour(Cardinal.S);
        for (int i = 0; i < this.boardHeight; i++) {
            for (int j = 0; j < this.boardWidth; j++) {
                cells[i][j] = act;
                act = act.getNeighbour(Cardinal.E);
            }
            act = nextLine;
            if (act != null){
                nextLine = act.getNeighbour(Cardinal.S);
            }
        }
        return cells;
    }

    /**
     * This method return a list of playable cell.
     * @return List of GomokuCell wich all is playable.
     */

    public List<GomokuCell> getPlayableCells(){
        List<GomokuCell> output = new ArrayList<>();
        for (GomokuCell[] line : this.getAllCells()) {
            for (GomokuCell c : line) {
                if (c.isPlayable() && c.getState() == Color.NIL)
                    output.add(c);
            }
        }

        return output;

    }

    //------------------Gets--------------------------

    /**
     * Set the width of the board.
     * @param width The new width of the board.
     */
    public void setWidth(int width) {
        this.boardWidth = width;
    }

    /**
     * Set the height of the board.
     * @param height The new height of the board.
     */
    public void setHeight(int height) {
        this.boardHeight = height;
    }

    //------------------Methods--------------------------
    
    /**
    * This method return a Map of number aligned cells.
    * @param cell A cell.
    * @return Map of number aligned cells.
    */
    public Map<Cardinal, Integer> countAlignedCells(GomokuCell cell){

        Map<Cardinal, Integer> map = new HashMap<>();
        Map<Cardinal, Integer> mapColor = cell.getSameColorNeighbour();

        map.put(Cardinal.N, mapColor.get(Cardinal.N)+mapColor.get(Cardinal.S)+1);
        map.put(Cardinal.W,mapColor.get(Cardinal.W)+mapColor.get(Cardinal.E)+1);
        map.put(Cardinal.NW, mapColor.get(Cardinal.NW)+mapColor.get(Cardinal.SE)+1);
        map.put(Cardinal.SW, mapColor.get(Cardinal.SW)+mapColor.get(Cardinal.NE)+1);

        return map;
    }

    /**
    * This method return the number max of the aligned Cells.
    * @param mapColor A map of number aligned cells.
    * @return int, the number max of the aligned Cells.
    */
    public int countMax(Map<Cardinal, Integer> mapColor){

        Map<Cardinal, Integer> map = new HashMap<>();
        int max = 0;
        for (Map.Entry<Cardinal, Integer> entry : map.entrySet()) {
            if(entry.getValue() > max){
                max = entry.getValue();
            }
        }
        return max;
    }

    /**
     * Expands the board in the specified direction.
     * This method is used to add a new row, column, or both to the board.
     * It creates new cells in the specified direction and links them to the existing cells.
     * It also updates the board dimensions accordingly.
     * @param direction The cardinal direction in which to expand the board.
     * @see Cardinal
     */
    public void expandBoard(Cardinal direction) {
        // for composed directions, expand in both components
        // e.g. SE -> expand in S and E
        if ((direction.asInt() % 2) != 0) {
            // if the direction is not even, it is a composed direction
            // call the same function in both components
            this.expandBoard(direction.rotate(-1));
            this.expandBoard(direction.rotate(1));
            return;
        }

        // find the suitable corner cell to expand from
        Cardinal cornerCellDirection = direction.rotate(-1);

        // move to the corner cell
        // go to the center of the board
        GomokuCell cornerCell = this.get(this.boardWidth / 2, this.boardHeight / 2);
        GomokuCell nextCell;
        // move while possible to the desired corner of the board
        do {
            nextCell = cornerCell.getNeighbour(cornerCellDirection);
            // if the next cell is null, then attempt to move
            // in the original direction
            if (nextCell == null) {
                cornerCellDirection = direction;
                nextCell = cornerCell.getNeighbour(cornerCellDirection);
                // if we already reached the end of the board, try the
                // direction perpendicular to the original direction
                if (nextCell == null) {
                    cornerCellDirection = direction.rotate90CCW();
                    nextCell = cornerCell.getNeighbour(cornerCellDirection);
                }
            } else {
                cornerCell = nextCell;
            }
        } while (nextCell != null);

        // then, add (n - 1) cells in the direction rotated by 90 degrees CW,
        // where n is either the board width or board height.
        // e.g. for an expansion to the North, add (n - 1) cells to the EAST
        // of the anchor cell
        GomokuCell anchor = new GomokuCell(Color.NIL);
        GomokuCell nextTo = cornerCell;
        GomokuCell before = cornerCell.getNeighbour(direction.rotate90CCW());
        GomokuCell after  = cornerCell.getNeighbour(direction.rotate90CW());
        Cardinal forwardDirection = direction.rotate90CW();
        
        while (nextTo != null) {
            // link the anchor cell with its neighbours
            GomokuCell.link(before, anchor, direction.rotate(1));
            GomokuCell.link(nextTo, anchor, direction);
            GomokuCell.link(after,  anchor, direction.rotate(-1));
            // add a new cell next to the anchor and link it to the anchor,
            // if the end of the board is not reached (to avoid creating an
            // additional cell)
            nextCell = null;
            if (after != null) nextCell = new GomokuCell(Color.NIL);
            GomokuCell.link(anchor, nextCell, forwardDirection);
            // set the anchor to the next cell and update the neighbours
            anchor = nextCell;
            before = nextTo;
            nextTo = after;
            if (after != null) after = after.getNeighbour(forwardDirection);
        }

        // update the board dimensions;
        if (direction == Cardinal.N || direction == Cardinal.S) {
            this.boardHeight++;
        } else {
            this.boardWidth++;
        }

        // for the North and West expansions, update the first cell
        // (top-left cell reference) of the entire board
        if (direction == Cardinal.N || direction == Cardinal.W) {
            this.firstCell = this.firstCell.getNeighbour(direction);
        }
    }

    //------------------Overides--------------------------

    /**
     * This method print the board cell by cell with change line when 
     * the line is finished.
     */
    @Override
    public String toString() {
        StringBuilder out = new StringBuilder();
        GomokuCell[][] cells = this.getAllCells();
        for (GomokuCell[] line : cells) {
            for (GomokuCell c : line) {
                out.append(c.toString());
            }
            out.append("\n");
        }
        return out.toString();
    }
}
