package fr.iutfbleau.projetAgile.Puissance4.Model;

import java.util.LinkedList;

import fr.iutfbleau.projetAgile.Puissance4.Event.AbstractGridInitiater;
import fr.iutfbleau.projetAgile.Puissance4.Utils.Constants;
import fr.iutfbleau.projetAgile.Puissance4.Utils.GameStatus;

/**
 * Classe qui représente le modèle de la grille du Puissance 4
 */
public class GrilleModel extends AbstractGridInitiater{
    private int[][] grille;
    private GameStatus gameStatus = GameStatus.PLAYING;
    private int piecePlayed = 0;
    private int playerTurn;
    private int column, row;
    private String playerOneName;
    private String playerTwoName;
    private String playerThreeName;
    private int playerOneScore;
    private int playerTwoScore;
    private int playerThreeScore;
    private LinkedList<Integer> playersList;
    private int playerNumber;

    /**
     * Crée le modèle et l'initialise avec des cases vides
     * @param playerNumber le nombre de joueurs
     */
    public GrilleModel(int playerNumber) {
        super();
        this.column = Constants.COLUMN_COUNT;
        this.row = Constants.ROW_COUNT;
        this.grille = new int[this.column][this.row];
        this.playerOneScore = 0;
        this.playerTwoScore = 0;
        this.playerThreeScore = -1;
        this.playersList = new LinkedList<Integer>();
        this.resetList();
        if(playerNumber == 3){
            this.playerThreeScore = 0;
        }
        this.playerNumber=playerNumber;
        this.reset();
    }

    /**
     * Réinitialise le modèle avec des cases vides et met à jour son status à {@link fr.iutfbleau.projetAgile.Puissance4.Utils.GameStatus#PLAYING PLAYING} 
     */
    public void reset() {
        for(int i = 0; i < this.column; i++) {
            for(int j = 0; j < this.row; j++) {
                this.grille[i][j] = Constants.EMPTY_PLAYER;
            }
        }
        this.resetList();
        this.switchPlayer();
        this.piecePlayed = 0;
        this.setPartyStatus(GameStatus.PLAYING);
    }

    /**
     * Remet la liste des joueurs dans l'ordre d'origine
     */
    private void resetList() {
        this.playersList.clear();
        this.playersList.add(Constants.PLAYER_ONE);
        this.playersList.add(Constants.PLAYER_TWO);
        if(this.playerNumber==3){
            this.playersList.add(Constants.PLAYER_THREE);
        }
    }

    /**
     * Ajoute un pion dans la grille
     * @param column colonne de la grille
     * @param row ligne de la grille
     */
    private void addPiece(int column, int row) {
        grille[column][row] = this.playerTurn;
        fireGridChanged(column, row, this.playerTurn);
        this.piecePlayed++;
    }  

    /**
     * Ajoute un pion dans une colonne si elle n'est pas complète, met à jour le status de la partie et change le playersList du joueur si nécessaire
     * @param column La colonne dans laquelle ajouter le pion
     */
    public void verifyColumn(int column) {
        for (int row = this.row - 1; row >= 0; row--) {
            if (grille[column][row] == Constants.EMPTY_PLAYER) {
                this.addPiece(column, row);
                if(this.verifyWin(column, row, this.playerTurn)){ 
                    if(this.playersList.size() > 2){
                        this.removePlayer();
                    } else{
                        this.setPartyStatus(GameStatus.WIN);
                    }
                    return;
                }
                this.switchPlayer();
                this.verifyDraw();
                return;
            }
        } 
    }

    /**
     * Supprime de la liste le joueur qui vient de gagner
     */
    private void removePlayer(){
        this.increaseScore();
        this.removePlayerPion();
        this.firePlayerWin(this.playerTurn);
        this.playersList.pollLast();
        this.switchPlayer();
        this.verifyNewWin();
    }

    /**
     * Vérifie si tous les joueurs ont gagnés lors du passage de 3 à 2 joueurs
     */
    private void verifyNewWin() {
        boolean verifyFirstWin = this.verifyWinAll(this.playerTurn);
        this.switchPlayer();
        boolean verifySecondWin = this.verifyWinAll(this.playerTurn);
        if(verifyFirstWin && verifySecondWin) {
            this.setPartyStatus(GameStatus.DRAW);
        }
        else if(verifyFirstWin || verifySecondWin) {
            if(verifyFirstWin)
                switchPlayer();
            this.setPartyStatus(GameStatus.WIN);
        }
    }

    /**
     * Vérifie la grille en fonction de la couleur du pion
     * @param playerColor joueur associé à une couleur
     */
    public boolean verifyWinAll(int playerColor) {
        for(int y = 0; y < this.column; y++) {
             for(int x = this.row - 1; x >= 0; x--) {
                 if(this.grille[y][x] == playerColor)
                     if(verifyWin(y, x, playerColor))
                         return true;
             }
        }
        return false;
     }

     /**
     * Vérifie si le jeton qui vient d'être joué termine la partie
     * @param column La colonne du pion qui vient d'être joué
     * @param row La ligne du pion qui vient d'être joué
     * @return retourne true si le jeu est fini
     */
    private boolean verifyWin(int column, int row, int playerColor) {
        int diagonalColumn = column;
        int diagonalRow = row;
        //Vérification horizontal et vertical
        if(verifyAlignedPiece(column, 0, 0, 1, playerColor) || verifyAlignedPiece(0, row, 1, 0, playerColor)) {
            
            return true;
        }

        while(diagonalColumn > 0 && diagonalRow > 0) {
            diagonalColumn--;
            diagonalRow--;
        }
        //Vérification diagonale haut à gauche -> bas à droite
        if(verifyAlignedPiece(diagonalColumn, diagonalRow, 1, 1, playerColor)) {
           
            return true;
        }

        diagonalColumn = column;
        diagonalRow = row;
        while(diagonalColumn < this.column - 1 && diagonalRow > 0) {
            diagonalColumn++;
            diagonalRow--;
        }

        //Vérification diagonale haut à droite -> bas à gauche
        if(verifyAlignedPiece(diagonalColumn, diagonalRow, -1, 1, playerColor)) {
            
            return true;
        }
        return false;
    }


    /**
     * renvoie true si 4 jetons de la même couleur ou plus sont alignés
     * @param numColumn La colonne où commencer
     * @param numRow La ligne où commencer
     * @param columnOffset l'offset de déplacement vertical
     * @param rowOffset l'offset de déplacement horizontal
     * @param playerColor La couleur du joueur (entier)
     * @return renvoie true si 4 jetons ou plus sont alignés
     */
    private boolean verifyAlignedPiece(int numColumn, int numRow, int columnOffset, int rowOffset, int playerColor) {
		int couleur = playerColor;
        int max = 0;
	    int compteur = 0;
        if(this.playersList.size() == 2)
            max = Constants.ALIGNED_NUMBER_TWO;
        else 
            max = Constants.ALIGNED_NUMBER_THREE;

	    while ((numColumn >= 0) && (numColumn < this.column) && (numRow >= 0) && (numRow < this.row)) {
	        if (grille[numColumn][numRow] != couleur) 
		        compteur = 0;
            else 
		        compteur++;
            if (compteur >= max) 
                return true;
            numColumn += columnOffset;
            numRow += rowOffset;
	    }
	    return false;
	}

    /**
     * Vérifie s'il y a une égalité
     */
    private void verifyDraw() {
        if(this.piecePlayed >= this.column * this.row)
            this.setPartyStatus(GameStatus.DRAW);
    }

    /**
     * Change le playersList du joueur et notifie tous les listeners
     */
    public void switchPlayer() {
        this.playerTurn=(int)this.playersList.remove();
        this.playersList.add((Integer)this.playerTurn);
        fireStatusChanged(this.gameStatus, this.playerTurn, this.playerOneScore, this.playerTwoScore, this.playerThreeScore);
    }
    
     /**
     * Change le status de la partie et notifie tous les listeners
     * @param gameStatus le status de la partie
     */
    public void setPartyStatus(GameStatus gameStatus) {
        this.gameStatus = gameStatus;
        if(gameStatus == GameStatus.WIN) {
            this.increaseScore();
        }   
        fireStatusChanged(this.gameStatus, this.playerTurn, this.playerOneScore, this.playerTwoScore, this.playerThreeScore);
    }

    /**
     * Augmente le score du joueur victorieux
     */
    private void increaseScore() {
        if(this.playerTurn == Constants.PLAYER_ONE)
                playerOneScore++;
        else if(this.playerTurn == Constants.PLAYER_TWO)
            playerTwoScore++;
        else
            playerThreeScore++;
    }

    /** 
     * retire les pions du joueur qui viens de gagner
    */
    private void removePlayerPion(){
        for(int i=0; i<this.grille.length; i++){
            for(int t=0; t<this.grille[i].length; t++){
                if(this.grille[i][t] == this.playerTurn){
                    this.dropColumn(i, t);
                    this.piecePlayed--;
                }
            }
        }
    }

    /**
     * Fait tomber les pions de la colonne à partir de la ligne sélectionnée
     * @param col colonne de la grille
     * @param ligne ligne de la grille
     */
    private void dropColumn(int col, int ligne){
        for(int i=ligne; i>=0; i--){
            if(i>0){
                this.grille[col][i]=this.grille[col][i-1]; 
            }else{  
                this.grille[col][0]=Constants.EMPTY_PLAYER;
            }
            this.fireGridChanged(col, i, this.grille[col][i]);
        }
    }

    // ----------------------- Getter et Setter --------------------------

    /**
     * renvoie le playersList du joueur
     * @return Le playersList du joueur
     */
    public int getPlayerTurn() {
        return this.playerTurn;
    }

    /**
     * renvoie le tableau d'entier qui représente l'état de la grille
     * @return Le tableau d'entier
     */
    public int[][] getTab() {
        return this.grille;
    }

    /**
     * renvoie le nombre de colonne de la grille
     * @return Le nombre de colonne 
     */
    public int getColumn() {
        return this.column;
    }

    /**
     * renvoie le nombre de ligne de la grille
     * @return Le nombre de ligne 
     */
    public int getRow() {
        return this.row;
    }

    /**
     * renvoie le nom du joueur 1
     * @return Un string du nom du joueur 1
     */
    public String getPlayerOneName() {
        return this.playerOneName;
    }

    /**
     * renvoie le nom du joueur 2
     * @return Un string du nom du joueur 2
     */
    public String getPlayerTwoName() {
        return this.playerTwoName;
    }

    /**
     * renvoie le nom du joueur 3
     * @return Un string du nom du joueur 2
     */
    public String getPlayerThreeName() {
        return this.playerThreeName;
    }


    /**
     * renvoie le score du joueur 1
     * @return Le score du joueur 1
     */
    public int getPlayerOneScore() {
        return playerOneScore;
    }

    /**
     * renvoie le score du joueur 2
     * @return Le score du joueur 2
     */
    public int getPlayerTwoScore() {
        return playerTwoScore;
    }

    /**
     * renvoie le score du joueur 3
     * @return Le score du joueur 3
     */
    public int getPlayerThreeScore() {
        return playerTwoScore;
    }

    /**
     * renvoie le {@link fr.iutfbleau.projetAgile.Puissance4.Utils.GameStatus status} de la partie
     * @return Le status de la partie
     */
    public GameStatus getGameStatus() {
        return this.gameStatus;
    }

    public int getPlayerNumber() {
        return this.playersList.size();
    }

    /**
     * Définit le nom du joueur 1 
     * @param playerOneName Le nom du joueur 1
     */
    public void setPlayerOneName(String playerOneName) {
        this.playerOneName = playerOneName;
    }

    /**
     * Définit le nom du joueur 2
     * @param playerTwoName Le nom du joueur 2
     */
    public void setPlayerTwoName(String playerTwoName) {
        this.playerTwoName = playerTwoName;
    }

    /**
     * Définit le nom du joueur 2
     * @param playerThreeName Le nom du joueur 2
     */
    public void setPlayerThreeName(String playerThreeName) {
        this.playerThreeName = playerThreeName;
    }
}
