import java.rmi.*;
import java.rmi.server.*;
import java.awt.*;
import java.util.*;

/** 
La classe Puissance4 decrit l'objet accessible sur le registre rmi.
* Elle gere la construction du tableau des cases, les informations sur les 
* joueurs, la position des pions, les coups des joueurs et determine lorsqu'un 
* joueur a gagne.
@author Sebastien BISMUTH 
@author Yann LENNE
@version 1.1.6
*/

public class Puissance4 extends UnicastRemoteObject implements Puissance4Distant{

  /** Nombres de cases de l'echiquier en hauteur et en largeur */
  private Dimension dim;

  /** Contenu des cases de l'echiquier : 
    * 0 pour le joueur1 ... 3 pour le joueur4.
  */
  private char[][] tab;

  /** Nombre de joueurs dans la partie */
  private int nbJoueurs;

  /** Numero du prochain joueur qui doit se connecter*/
  private char numProchainJoueur;

  /** Numero du joueur gagnant */
  private char joueurGagnant;

  /** Coordonnees du dernier pion ajoute */
  private Point pionAjoute;

  /** Numero du dernier joueur a avoir ajoute un pion */
  private char dernierJoueur;

  /** Coordonnees des pions gagnants */
  private Point[] casesGagnantes;

  /** Vecteur contenant les noms des joueurs */
  private Vector nomsJoueurs;
  
  /** Indique si l'un des deux joueurs a emis le desir de recommencer la partie */
  private boolean recommencer;

  /** Indique si l'un des deux joueurs a emis le desir de quitter le jeu */
  private boolean quitter; 

  /** Nombre de pions joues depuis le debut de la partie*/
  private int nbPions;

  /** Initialise la partie.
    * Initialise le tableau qui memorise la position de tous les pions
    * ainsi que les attributs necessaires a la gestion du jeu
    @param dim Nombres de cases de l'echiquier
    @param nombreJoueurs Nombre de joueurs qui doivent participer.
*/
  public Puissance4(Dimension dim, int nombreJoueurs) throws RemoteException{
    this.dim = dim;
    tab = new char[dim.height][dim.width];
    
    nbJoueurs = nombreJoueurs;
    dernierJoueur = '0';
    numProchainJoueur = '1';
    initPartie();
    pionAjoute = new Point();
    nomsJoueurs = new Vector();
  }

  /** Accesseur de l'attribut tab.
     * Retourne l'attribut tab.
     @return char[][] Tableau qui memorise la position de tous les pions.
     */
  public char[][] getTab() throws RemoteException{
    return tab;
  }

  /** Accesseur de l'attribut dim.
     * Retourne l'attribut dim.
     @return Dimension Largeur et hauteur du tableau de jeu. 
     */
  public Dimension getDim() throws RemoteException{
    return dim;
  }
  
  /** Accesseur de l'attribut joueurGagnant.
    Retourne l'attribut joueurGagnant
     @return char Retourne le numero du joueur vainqueur.
     */
  public char getJoueurGagnant() throws RemoteException {
    return joueurGagnant;
  }

  /** Retourne le nom du joueur dont le numero est passe en parametre.
    @param numJoueur Numero du joueur 
    @return String nom du joueur
  */
  public String getPseudoJoueur(char numJoueur) throws RemoteException {
    String num = new String();
    num = "" + numJoueur + "";
    return (String)nomsJoueurs.elementAt(Integer.parseInt(num)-1);
  }
  
  /** Accesseur de l'attribut pionAjoute.
    Retourne l'attribut pionAjoute.
    @return Point Retourne les coordonnees du dernier pion ajoute.
  */
  public Point getPionAjoute() throws RemoteException {
    return pionAjoute;
  }
  
  /** Accesseur de l'attribut dernierJoueur.
    Retourne l'attribut dernierJoueur
    @return char Retourne le numero du dernier joueur qui a ajoute un pion.
  */
  public char getDernierJoueur() throws RemoteException {
    return dernierJoueur;
  }

  /** Accesseur de l'attribut casesGagnantes.
    Retourne l'attribut casesGagnantes
    @return Point Retourne les coordonnees des pions gagnants.
  */
  public Point[] getCasesGagnantes() throws RemoteException {
    return casesGagnantes;
  }

  /** Accesseur de l'attribut recommencer.
   * Retourne l'attribut recommencer.
   @return boolean  Indique si un joueur desire recommencer 
   */
  public boolean getRecommencer() throws RemoteException {
    return recommencer;
  }

  /** Accesseur de l'attribut quitter.
   * Retourne l'attribut quitter.
   @return boolean  Indique si un joueur desire quitter le jeu 
   */
  public boolean getQuitter() throws RemoteException{
    return quitter;
  }

  /** Accesseur de l'attribut nomsJoueurs.
   * Retourne l'attribut nomsJoueurs.
   @return Vector Retourne la liste des noms des joueurs.
   */
  public Vector getNomsJoueurs()  throws RemoteException{
    return nomsJoueurs;
  }
  
  /** Accesseur de l'attribut nbJoueurs.
   * Retourne l'attribut nbJoueurs.
   @return int Retourne le nombre de joueurs dans la partie.
   */
  public int getNbJoueurs() throws RemoteException{
    return nbJoueurs;
  }

  /** Modificateur de l'attribut recommencer.
    * Modifie l'attribut recommencer.
    @param b Valeur que doit prendre l'attribut recommencer.
   */
  public void setRecommencer(boolean b)  throws RemoteException{
    recommencer = b;
  }

  /** Modificateur de l'attribut quitter.
    * Modifie l'attribut quitter.
    @param b Valeur que doit prendre l'attribut quitter.
   */
  public void setQuitter(boolean b)  throws RemoteException{
    quitter = b;
  }
  
  /** Verifie que tous les joueurs attendus sont enregistres. 
    @return boolean true si tous les joueurs sont connectes, false sinon.
  */
  public boolean connexionTotale() throws RemoteException{
    if (nomsJoueurs.size() == nbJoueurs)
      return true;
    else
      return false;
  }

  /** Renvoie le numero du prochain joueur qui va jouer.
    @return char Numero du joueur dont c'est le tour.
  */
  public char prochainJoueur() throws RemoteException{
    String s = new String();
    s = "" + dernierJoueur + "";

    if (Integer.parseInt(s) == nbJoueurs)
      return '1';
    else
      return (char)(dernierJoueur + 1);
  }

  /** Initialise les parametres pour un debut de partie.
  */
  public void initPartie() throws RemoteException{  
    for (int i = 0; i < dim.height; i++) {
      for (int j = 0; j < dim.width; j++) {
        tab[i][j] = 0;
      }
    }

    joueurGagnant = '0';
    nbPions = 0;
  }
  
  /** Ajoute un pion lorsqu'un joueur a clique dans une colonne du jeu.
    * Recherche la prochaine case vide de la colonne dans laquelle le joueur
    * a clique et ajoute le pion dans le tableau si c'est possible.
    @return boolean retourne true si le pion a pu etre ajoute et false sinon
    @param joueur Numero du joueur qui a clique
    @param colonne Numero de la colonne dans laquelle le joueur a clique (la premiere colonne est la colonne 0).
    */ 
  public boolean ajouterPion (char joueur, int colonne)  throws RemoteException{
    int cpt = dim.height-1;
    
    // recherche de la prochaine case vide de la colonne
    while (cpt >= 0 && tab[cpt][colonne] != 0 ) {
      cpt --;
    }
    if (cpt >= 0) {
      // On enregistre le nouveau pion
      tab[cpt][colonne] = joueur;

      // Enregistre les coordonnees du dernier pion ajoute et le dernier joueur
      pionAjoute.x = colonne;
      pionAjoute.y = cpt;
      dernierJoueur = joueur;
      
      nbPions++;
      casesGagnantes = new Point[4];

      // Verifie si le dernier joueur a gagne la partie
      casesGagnantes = rendreVerdict(tab, dim, joueur);
      if(casesGagnantes[0].x != -1)
        joueurGagnant = joueur;
      else{
        if(matchNul())
          joueurGagnant = '9';
      }
      return true;
    }
    else
      return false;
  }
  
  /** Fonction qui ajoute un nouveau joueur.
    * Attribue un numero a chacun des joueurs a mesure qu'ils s'inscrivent
    * aupres du serveur.
    @param pseudo Pseudo du joueur
    @return char Numero attribue au joueur
    */    

  public char ajouterJoueur(String pseudo) throws RemoteException {
    switch(numProchainJoueur) { 
    case '1':
      numProchainJoueur = '2';
      nomsJoueurs.addElement(pseudo); 
      return '1'; 
    case '2': 
      numProchainJoueur = '3';
      nomsJoueurs.addElement(pseudo);
      return '2'; 
    case '3': 
      numProchainJoueur = '4';
      nomsJoueurs.addElement(pseudo);
      return '3'; 
    case '4': 
      numProchainJoueur = '5';
      nomsJoueurs.addElement(pseudo); 
      return '4'; 
    default: return '0'; 
    } 
  }
  
  /** Determine si la partie est nulle.
    @return true pour un match nul, false sinon
  */
  public boolean matchNul() throws RemoteException {
    if(dim.height*dim.width == nbPions)
      return true;
    else
      return false;
  }

  /** Determine si le joueur qui a ajoute un pion a gagne.
    @param tab Tableau qui memorise la position des pions
    @param dim Largeur et hauteur du tableau de jeu
    @param joueur Numero du joueur qui a joue
    @return Point[] Tableau des 4 pions gagnant si le joueur a gagne (contient les valeurs -1 si le joueur n'a pas gagne)
   */
  private Point[] rendreVerdict(char[][] tab, Dimension dim, char joueur){
    int nbLig = dim.height; 
    int nbCol = dim.width;
    Point[] tabPoints = new Point[4];
    
    boolean gagne = false;
    int cptPions = 0;
    int l = nbLig - 1;
    int c;
    
    tabPoints[0] = new Point();
    tabPoints[1] = new Point();
    tabPoints[2] = new Point();
    tabPoints[3] = new Point();
    
    for(int i=0 ; i<4 ; i++){
      tabPoints[i].x = -1;
      tabPoints[i].y = -1;
    }
    
    // Test de chaque ligne
    while(l >= 0 && gagne == false){
      cptPions = 0;
      c = 0;
      
      while(c < nbCol){
        
        if (tab[l][c] == joueur)
          cptPions++;
        else
          cptPions = 0;
        
        
        if (cptPions == 4){
          gagne = true;
          
          for(int i=0 ; i<4 ; i++){
            tabPoints[i].x = c-(3-i);
            tabPoints[i].y = l;
          }				
          break;
        }
        c++;			
      }
      l--;
    }
    
    // Test de chaque colonne
    c = 0;
    
    while(c < nbCol && gagne == false) {
      
      cptPions = 0;
      l = nbLig - 1;
      
      while(l >= 0){
        if (tab[l][c] == joueur)
          cptPions++;
        else
          cptPions = 0;
        
        if (cptPions == 4){
          gagne = true;
          
          for(int i=0 ; i<4 ; i++){
            tabPoints[i].x = c;
            tabPoints[i].y = l+i;
          }
          break;
        }
        l--;	
      }
      c++;
    }		
    
    // Test de chaque diagonale (/)    
    int c2;
    c = 0;
    
    // Test de chaque diagonale (/ - de haut en bas)
    while(c < nbCol && gagne == false) {
      
      cptPions = 0;
      l = 0;
      c2 = c;
      
      while(l < nbLig && c2 >= 0) {
        if (tab[l][c2] == joueur)
          cptPions++;
        else
          cptPions = 0;
        
        if (cptPions == 4){
          gagne = true;
          
          for(int i=0 ; i<4 ; i++){
            tabPoints[i].x = c2+i;
            tabPoints[i].y = l-i;
          }
          break;
        }
        l++;
        c2--;	
      }
      c++;
    }		
    
    // Test de chaque diagonale (/ - de bas en haut)    
    c = 0;
    
    while(c < nbCol && gagne == false) {
      
      cptPions = 0;
      l = nbLig - 1;
      c2 = c;	
      
      while(l >= 0 && c2 < nbCol){
        if (tab[l][c2] == joueur)
          cptPions++;
        else
          cptPions = 0;
        
        if (cptPions == 4){
          gagne = true;
          
          for(int i=0 ; i<4 ; i++){
            tabPoints[i].x = c2-(3-i);
            tabPoints[i].y = l+(3-i);
          }
          break;
        }
        l--;
        c2++;	
      }
      c++;
    }		
    
    
    // Test de chaque diagonale (\)    
    c = 0;
    
    // Test de chaque diagonale (\ - de haut en bas)
    while(c < nbCol && gagne == false) {
      
      cptPions = 0;
      l = 0;
      c2 = c;
      
      while(l < nbLig && c2 < nbCol) {
        if (tab[l][c2] == joueur)
          cptPions++;
        else
          cptPions = 0;
        
        if (cptPions == 4){
          gagne = true;
          
          for(int i=0 ; i<4 ; i++){
            tabPoints[i].x = c2-(3-i);
            tabPoints[i].y = l-(3-i);
          }
          break;
        }
        l++;
        c2++;
      }
      c++;
    }		
    
    // Test de chaque diagonale (\ - de bas en haut)    
    c = 0;
    
    while(c < nbCol && gagne == false) {
      cptPions = 0;
      l = nbLig - 1;
      c2 = c;	
      
      while(l >= 0 && c2 >= 0){
        if (tab[l][c2] == joueur)
          cptPions++;
        else
          cptPions = 0;
        
        if (cptPions == 4) {
          gagne = true;
          
          for(int i=0 ; i<4 ; i++){
            tabPoints[i].x = c2+(3-i);
            tabPoints[i].y = l+(3-i);
          }
          break;
        }
        l--;
        c2--;
      }
      c++;
    }
    return tabPoints;
  }
  
}
