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> tour;
    private int playerNumber;
    /**
     * Crée le modèle et l'initialise avec des cases vides
     */
    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 = 0;
        this.tour = new LinkedList<Integer>();
        this.tour.add(Constants.PLAYER_ONE);
        this.tour.add(Constants.PLAYER_TWO);
        if(playerNumber==3){
            this.tour.add(Constants.PLAYER_THREE);
        }
        this.playerNumber=playerNumber;
        this.reset();
    }

    /**
     * Crée le modèle et l'initialise avec des les cases du tableau donné en arguments
     */
    public GrilleModel(int[][] tab, int playerNumber) {
        //Faire un test pour le nombre de colonnes/lignes
        super();
        this.column = Constants.COLUMN_COUNT;
        this.row = Constants.ROW_COUNT;
        this.grille = tab;
        this.playerOneScore = 0;
        this.playerTwoScore = 0;
        this.playerThreeScore = 0;
        this.tour=new LinkedList<Integer>();
        this.tour.add(Constants.PLAYER_ONE);
        this.tour.add(Constants.PLAYER_TWO);
        if(playerNumber==3){
            this.tour.add(Constants.PLAYER_THREE);
        }
        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.tour.clear();
        this.tour.add(Constants.PLAYER_ONE);
        this.tour.add(Constants.PLAYER_TWO);
        if(this.playerNumber==3){
            this.tour.add(Constants.PLAYER_THREE);
        }
        this.switchPlayer();
        this.piecePlayed = 0;
        this.setPartyStatus(GameStatus.PLAYING);
    }

    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 tour 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.switchPlayer();
                }
                this.verifyDraw();
                return;
            }
        } 
    }

    /**
     * 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 = this.playerTurn;
        int diagonalColumn = column;
        int diagonalRow = row;
        //Vérification horizontal et vertical
        if(verifyAlignedPiece(column, 0, 0, 1, playerColor) || verifyAlignedPiece(0, row, 1, 0, playerColor)) {
            if(this.tour.size()==3){
                this.Player3();      
            }else{
                this.setPartyStatus(GameStatus.WIN);
            }
            return true;
        }

        while(diagonalColumn > 0 && diagonalRow > 0) {
            diagonalColumn--;
            diagonalRow--;
        }
        //Vérification diagonale haut à gauche -> bas à droite
        if(verifyAlignedPiece(diagonalColumn, diagonalRow, 1, 1, playerColor)) {
            if(this.tour.size()==3){
                this.Player3();
            }else{
                this.setPartyStatus(GameStatus.WIN);
            }
            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)) {
            if(this.tour.size()==3){
                this.Player3();
            }else{
                this.setPartyStatus(GameStatus.WIN);
            }
            return true;
        }
        return false;
    }

    private void Player3(){
        switch(this.playerTurn){
            case Constants.PLAYER_ONE:
                this.playerOneScore++;
                break;

            case Constants.PLAYER_TWO:
                this.playerTwoScore++;
                break;
            
            case Constants.PLAYER_THREE:
                this.playerThreeScore++;
                break;
        }
        this.removePlayerPion();
        this.firePlayerChanged(this.playerTurn, this.grille);
        this.tour.pollLast();
        this.switchPlayer();
    }

    /**
     * Retourne 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 Retourne 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.tour.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;
	}

    private void verifyDraw() {
        if(this.piecePlayed >= this.column * this.row)
            this.setPartyStatus(GameStatus.DRAW);
    }
    /**
     * Change le tour du joueur et notifie tous les listeners
     */
    public void switchPlayer() {
        this.playerTurn=(int)this.tour.pollFirst();
        this.tour.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) {
            if(this.playerTurn == Constants.PLAYER_ONE)
                playerOneScore++;
            else if(this.playerTurn == Constants.PLAYER_TWO)
                playerTwoScore++;
            else
                playerThreeScore++;
        }   

        fireStatusChanged(this.gameStatus, this.playerTurn, this.playerOneScore, this.playerTwoScore, this.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(this.grille[i], t);
                    this.piecePlayed--;
                }
            }
        }
    }

    private void dropColumn(int[] col, int ligne){
        for(int i=ligne; i>=0; i--){
            if(i>0){
                col[i]=col[i-1]; 
            }else{  
                col[0]=Constants.EMPTY_PLAYER;
            }
        }
    }

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

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

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

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

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

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

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


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

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

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

    /**
     * Retourne 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.tour.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;
    }

    public void setPlayerNumber(int i) {
        this.playerNumber = i;
    }
}
