// Projet de Java :  "Ecriture d'un mini tableur"
// Auteur : Baccam Olivier & Roulin Clément
// Responsable : Claudine Moinard


import java.awt.*;
import java.util.*;

/**Classe Cellule :
 * Crée un textfield pour chaque case et lui affecte les méthodes spécifiques
 */
public class Cellule extends TextField {

    // les differents status de la case
    final static int status_Vide = 'v';       // ne contient aucune valeur
    final static int status_Invalide = 'i';  // expression invalide
    final static int status_Normal = 'n';    // normal

    /** La référence de la case */
    public Coord referenceCellule;

    int status;            // le status de la case
    int valeurCellule;        // la valeur de la case
    String formuleString;     // la formule en String
    ExpAr formuleExpAr;        // la formule en ExpAr
    Cellule[] referenceListe;    // liste des cases références dans la formule
    int nombreReference;   // nb de cases référencées
    Cellule[] dependanteListe;   // liste des cases dépendantes de la case
    int nombreDependante;  // nb de cases dépendantes

    /** Constructeur coordonnées "numériques" */
    public Cellule(int colone, int ligne) {
        init(colone, ligne);
    }

    /** initialise les variables d'instances de Cellule */
    void init(int c, int l) {
        referenceCellule = new Coord(c, l);
        formuleString = "Aucune Expression";
        nombreReference = 0;
        referenceListe = null;
        nombreDependante = 0;
        dependanteListe = new Cellule[3]; //  3 cases par defaut
        status = status_Vide;
        setText(""); // pour le TextField

        setEditable(false);
    }

    /** retourne si oui ou non la Cellule fait référence récursivement à elle même
     *  DETECTION DES CYCLES
     */
    public boolean possedeCycle(Cellule c) {
        if (c == null) return false;
        for (int i = 0; i < nombreReference; i++) {
            if (referenceListe[i] == c)
                return true;
            if (referenceListe[i].possedeCycle(c))
                return true;
        }
        return false;
    }

    /** Evalue ligne'expression contenue la case et les cases dépendantes
     * @exception ErrorException si ily a une erreur lors de ligne'évaluation */
    public void evaluer(Cellule[][] gr) throws ErrorException {
        if ((gr == null) || (formuleExpAr == null)) return;
        try {
            valeurCellule = formuleExpAr.valeurCellule(referenceCellule, gr);
        } catch (ErrorException e) {
            invaliderCellule();
            invaliderListeDepdt();
            if (e.errorType() == ErrorException.errorCoordInvalide)
                invaliderListeCoord();
            throw e;
        }
        status = status_Normal;
        setText("" + valeurCellule);
        for (int i = 0; i < nombreDependante; i++)
            dependanteListe[i].evaluer(gr);   // on evalue les cases dépendantes
    }

    /** Retourne le nom de la case sous forme de String */
    public String avoirNom() {
        return referenceCellule.avoirNom();
    }

    /** Retourne la formule de la case */
    public String avoirFormule() {
        return "" + formuleString;
    }

    /** Retourne la valeur de la case
     * @exception ErrorException si ligne'expression est invalide */
    public int avoirValeur() throws ErrorException {
        if (formuleExpAr == null)
            throw new ExceptionInvalide();
        if ((status == status_Vide) || (status == status_Invalide))
            throw new ExceptionInvalide();
        return valeurCellule;
    }

    /** Retourne dans un Point la position [colone,ligne] de la case.
     * @exception ExceptionCoordInvalide si la référence de la case n'est pas valide */
    public Point avoirPosition() throws ExceptionCoordInvalide {
        return referenceCellule.avoirAbsoluePoint();
    }


    /** Enregistre la case en argument comme dépendante de la case courante. */
    public void ajouterDependance(Cellule c) {
        if (nombreDependante == dependanteListe.length) { // si la liste de dépendance est pleine
            Cellule[] li = new Cellule[nombreDependante + 1];
            for (int i = 0; i < nombreDependante; i++) li[i] = dependanteListe[i];
            dependanteListe = li;
        }
        dependanteListe[nombreDependante++] = c;
    }

    /** Retire la case en argument de la liste des cases dépendantes de la case courante  */
    public void retirerDependance(Cellule c) {
        int i;
        if (nombreDependante <= 0) return;
        boolean found = false;
        for (i = 0; ((i < nombreDependante) && (!found)); i++) {
            if (dependanteListe[i] == c) {
                found = true;
                break;
            }
        }
        if (found) {
            if (i < nombreDependante - 1) { // ce n'est pas la derniere dépendante
                for (int j = i; j < (nombreDependante - 1); j++)
                    dependanteListe[j] = dependanteListe[j + 1];
            }
            nombreDependante = nombreDependante - 1;
        }
    }

    /** Invalide la case par son status et sa valeur */
    public void invaliderCellule() {
        status = status_Invalide;
        setText("invalide");
    }

    /** Elimine les dépendances de la case courante */
    public void invaliderListeCoord() {
        for (int i = 0; i < nombreReference; i++) {
            referenceListe[i].retirerDependance(this);
        }
        nombreReference = 0;
        referenceListe = null;
    }

    /** prévenir toutes cases dépendantes de ligne'invalidité de la case courante */
    public void invaliderListeDepdt() {
        for (int i = 0; i < nombreDependante; i++) {
            dependanteListe[i].invaliderCellule();
            dependanteListe[i].invaliderListeDepdt();
        }
    }

    /** Met à jour  la formule de case */
    public String changerFormule(String str, Cellule[][] gr) {
        String message = "all is ok!";
        int i;
        str = Analyse.enleverEspaceDuDebut(str); // on enleve les espaces du début
        formuleExpAr = Analyse.construireExpAr(str); // on  traduitle string en expar
        // ligne'expression est invalide
        if (formuleExpAr == null) {
            formuleString = str;
            invaliderCellule();
            invaliderListeCoord();
            invaliderListeDepdt();
        }
        // ligne'expression est "semantiquement" correcte
        else {
            int nombreReferenceTemp = nombreReference; //  sauvegarde toutes les références au cas ou!
            Cellule[] referenceListeTemp = referenceListe;
            try {
                creerListeCoord(formuleExpAr.listeCoord(), gr); // On construit la nouvelle liste de références
            } catch (ExceptionCoordInvalide ir) {
                nombreReference = nombreReferenceTemp;
                referenceListe = referenceListeTemp;
            }
            // on regarde si il y a des cycles
            if (possedeCycle(this)) {
                nombreReference = nombreReferenceTemp; // si oui on reprends les anciennes références
                referenceListe = referenceListeTemp;
            }
            formuleString = str;
            for (i = 0; i < nombreReferenceTemp; i++) {
                referenceListeTemp[i].retirerDependance(this); // on enleve les anciennes dépendances
            }
            for (i = 0; i < nombreReference; i++) {
                referenceListe[i].ajouterDependance(this); // on prévient les cases référencées
            }
            try {
                evaluer(gr); // Evaluation de la nouvelle formule
            } catch (ErrorException e) {
                return e.errorLabel();
            }
        }
        return message;
    }

    /** Créer la liste des cases référencées
     * Les réferences r et laCase permettent de trouver les cases du tableau
     * @param r les références.
     * @param g le tableau.
     * @exception ExceptionCoordInvalide si une des références est invalide
     */
    public void creerListeCoord(Coord[] r, Cellule[][] g) throws ExceptionCoordInvalide {
        nombreReference = 0;
        if (r == null) {
            referenceListe = null;
            return;
        }
        referenceListe = new Cellule[r.length];
        for (int i = 0; i < r.length; i++) {
            Point p = null;
            if (r[i].avoirType() == Coord.Coord_Absolue) {
                p = r[i].avoirAbsoluePoint();
            }
            if (r[i].avoirType() == Coord.Coord_Relative) {
                p = r[i].avoirRelativePoint(referenceCellule);
            }
            if (p == null)
                throw new ExceptionCoordInvalide(r[i]);
            if (
                    (p.y < 0) || (p.y >= g.length)
                    ||
                    (p.x < 0) || (p.x >= g[0].length)
            )
                throw new ExceptionCoordInvalide(r[i]);
            referenceListe[nombreReference++] = g[p.y][p.x];
        }
        return;
    }

    /** Change le Background de la case sélectionnée (Focus) */
    public void onBackground() {
        setBackground(Color.white);
    }

    /** ReChange le Background de la case Désélectionnée (FocusLost) */
    public void offBackground() {
        setBackground(Color.orange);
    }
}