import java.awt.*;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;

/**
 * Cette classe permet d'afficher les éléments UML du projet.
 */
public class UmlDiagram extends Composition {

    private UmlToolbar _umlToolbar;
    private Object _selected;
    private String cornerSelection = null;

    private int _shiftX;
    private int _shiftY;

    private Class _mainClass = null;
    private List<Class> _classes;
    private List<Link> _links;

    /**
     * Constructeur - Crée un nouveau diagramme UML à partir d'un projet.
     * @param project Le projet contenant ce diagramme UML.
     */
    public UmlDiagram(Project project){
        super(project);
        this._toolbar = new UmlToolbar(this);
        this._type = UML_DIAGRAM;
        this._name = "Composition " + _compositionId + " (" + _type + ")";
        _classes = new ArrayList<Class>();
        _links = new ArrayList<Link>();
    }

    /**
     * Dessine les classes et les liens UML.
     * @param g L'objet graphique
     */
    @Override
    public void paintComponent(Graphics g){
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;
        //Activation de l'antialiasing pour les dessins
        RenderingHints rh = new RenderingHints(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        g2.setRenderingHints(rh);

        Font font1 = new Font("Montserrat Light", Font.PLAIN, 14);
        FontMetrics metrics1 = this.getFontMetrics(font1);
        Font font2 = new Font("Montserrat Light", Font.PLAIN, 10);
        FontMetrics metrics2 = this.getFontMetrics(font2);

        g2.setColor(new Color(127, 158, 178));
        g2.drawString(_name, 10, 20);
        for(Class a : _classes){
            g2.setFont(font1);
            int posX = a.getPosX() - (a.getSizeX()/2);
            int posY = a.getPosY() - (a.getSizeY()/2);
            //Dessin du rectangle
            g2.setColor(new Color(127, 158, 178));
            g2.fillRect(posX, posY, a.getSizeX(), a.getSizeY());
            if(a == _mainClass) {
                g2.setColor(new Color(173, 37, 8));
                g2.drawRect(posX, posY, a.getSizeX(), a.getSizeY());
            }
            g2.setColor(Color.BLACK);
            //Dessin du nom de la classe
            g2.setColor(new Color(39, 76, 94));
            int posCounter = posY + font1.getSize();
            g2.drawString(a.getName(), posX + a.getSizeX()/2 - metrics1.stringWidth(a.getName())/2, posCounter);
            posCounter += 5;
            g2.setColor(new Color(218, 233, 244));
            //Ligne de séparation
            g2.drawLine(posX, posY + font1.getSize() + 5, posX + a.getSizeX()-1, posY + font1.getSize() + 5);
            g2.setFont(font2);
            posCounter += font2.getSize();
            g2.drawString("attributes", posX + a.getSizeX()/2 - metrics2.stringWidth("attributes")/2, posCounter);
            //Affichage des attributs
            g2.setColor(new Color(39, 76, 94));
            for(Attribute b : a.getAttributes()){
                posCounter += font2.getSize();
                g2.drawString(b.getAccess() + " " + b.getName() + " : " + b.getType(), posX, posCounter);
                //Si l'attribut est statique, on le souligne
                if(b.isStatic()) g2.drawLine(a.getPosX()-a.getSizeX()/2+10, posCounter+1, a.getPosX()+a.getSizeX()/2-10, posCounter+1);
            }
            posCounter += 5;
            g2.setColor(new Color(218, 233, 244));
            //Ligne de séparation
            g2.drawLine(posX, posCounter, posX + a.getSizeX()-1, posCounter);
            posCounter += font2.getSize();
            g2.drawString("methods", posX + a.getSizeX()/2 - metrics2.stringWidth("methods")/2, posCounter);
            //Dessin des méthodes
            for(Method m : a.getMethods()){
                posCounter += font2.getSize();
                //Si la méthode est un constructeur, on l'affiche en rouge, sinon en bleu
                System.out.println(a.getName());
                if(m.isConstructor()) g2.setColor(new Color(187, 11, 11));
                else g2.setColor(new Color(39, 76, 94));
                String chain = m.getAccess() + " " + m.getName() + "(";
                ArrayList<String> listArguments = new ArrayList<String>();
                for(Argument ar : m.getArguments()){
                    listArguments.add(ar.getName() + " : " + ar.getType());
                }
                chain += String.join(", ", listArguments) + ") : " + m.getType();
                g2.drawString(chain, posX, posCounter);
                //Si l'attribut est statique, on le souligne
                if(m.isStatic()) g2.drawLine(a.getPosX()-a.getSizeX()/2+10, posCounter+1, a.getPosX()+a.getSizeX()/2-10, posCounter+1);
            }
            //Si la classe est sélectionnée
            if(a == (Class)_selected){
                g2.setColor(new Color(39, 76, 94));
                //Top Left
                g2.fillOval(posX-4, posY-4, 8, 8);
                //Top
                g2.fillRect(posX + (a.getSizeX()/2)-4, posY-4, 8, 8);
                //Top Right
                g2.fillOval(posX + a.getSizeX()-4, posY-4, 8, 8);
                //Right
                g2.fillRect(posX + a.getSizeX()-4, posY + (a.getSizeY()/2)-4, 8, 8);
                //Bottom Right
                g2.fillOval(posX + a.getSizeX()-4, posY + a.getSizeY()-4, 8, 8);
                //Bottom
                g2.fillRect(posX + (a.getSizeX()/2)-4, posY + a.getSizeY()-4,8, 8);
                //Bottom Left
                g2.fillOval(posX - 4, posY + a.getSizeY()-4, 8, 8);
                //Left
                g2.fillRect(posX -4, posY + (a.getSizeY()/2)-4, 8, 8);
                //Total rectangle
                g2.drawRect(posX, posY, a.getSizeX(), a.getSizeY());
            }
        }
    }

    /**
     * Retourne la classe principale du diagramme.
     * @return La classe principale du diagramme.
     */
    public Class getMainClass(){
        return _mainClass;
    }

    /**
     * Paramétre la classe principale du diagramme.
     * @param a La nouvelle classe principale du diagramme.
     */
    public void setMainClass(Class a){
        _mainClass = a;
    }

    /**
     * Récupère la toolbar du diagramme.
     * @return La toolbar du diagramme.
     */
    public UmlToolbar getToolbar(){
        return (UmlToolbar) this._toolbar;
    }

    /**
     * Récupère toutes les classes du projet.
     * @return Les classes du projet.
     */
    public List<Class> getClasses(){
        return this._classes;
    }

    /**
     * Ajoute une classe au projet.
     * @param c La classe à ajouter.
     */
    public void addClass(Class c){
        this._classes.add(c);
        //On vient d'ajouter la 1ère classe, définition en classe principale
        if(_classes.size() == 1) _mainClass = c;
    }

    /**
     * Retire une classe du projet.
     * @param c La classe à retirer.
     */
    public void removeClass(Class c){
        //On supprime la classe principale. Par défaut, la 1ère classe créée devient la classe principale.
        this._classes.remove(c);
        if(_mainClass == c && _classes.size() != 0) _mainClass = _classes.get(0);
        _selected = null;
    }

    /**
     * Supprime toutes les classes du projet.
     */
    public void clearClasses(){
        this._classes.clear();
    }

    /**
     * Récupère tous les liens du projet.
     * @return Les liens du projet.
     */
    public List<Link> getLinks(){
        return this._links;
    }

    /**
     * Ajoute un lien au projet.
     * @param l Le lien à ajouter.
     */
    public void addLink(Link l){
        this._links.add(l);
    }

    /**
     * Retire un lien du projet.
     * @param l Le lien à retirer.
     */
    public void removeLink(Link l){
        this._links.remove(l);
    }

    /**
     * Retire un lien du projet.
     * @param index L'index du lien à retirer.
     */
    public void removeLink(int index){
        this._links.remove(index);
    }

    /**
     * Supprime tous les liens du projet.
     */
    public void clearLinks(){
        this._links.clear();
    }

    //Evenements souris
    /**
     * Récupère l'élément sélectionné dans la grille.
     * @return L'élément sélectionné dan sla grille.
     */
    public Object getSelected(){
        return this._selected;
    }

    /**
     * Récupération de l'objet cliqué
     * @param getX Les coordonnées de la souris sur l'axe X.
     * @param getY Les coordonnées de la souris sur l'axe Y.
     */
    public void select(int getX, int getY){
        //On cherche l'objet sélectionné
        boolean classSelected = false;
        boolean linkSelected = false;
        for(Class a : _classes){
            int posX = a.getPosX()-(a.getSizeX()/2);
            int posY = a.getPosY()-(a.getSizeY()/2);
            //Si la souris est dans la classe
            if (getX >= posX && getX <= (posX + a.getSizeX())) {
                if (getY >= posY && getY <= (posY + a.getSizeY())) {
                    this.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
                    //On sélectionne la classe
                    classSelected = true;
                    _selected = a;
                    break;
                }
            }
        }
        if(classSelected == false && linkSelected == false && cornerSelection == null) _selected = null;
        //Si la classe est sélectionnée, on enregistre le décalage entre la souris et le centre de la classe
        AppThinker.getWindow().getStatusbar().setSizeLabel(0, 0);
        if(_selected instanceof Class){
            Class a = (Class) _selected;
            AppThinker.getWindow().getStatusbar().setSizeLabel(a.getSizeX(), a.getSizeY());
            _shiftX = getX - a.getPosX();
            _shiftY = getY - a.getPosY();
        }
        this.repaint();
    }

    /**
     * Modifie les propriétés de la classe.
     * @param a La classe a modifier.
     */
    public void editClass(Class a){
        ClassPropertiesWindow prop = new ClassPropertiesWindow(this, a);
    }

    //Actions de la souris sur le diagramme UML
    /**
     * Action du clic de la souris sur le diagramme.
     * @param e L'événement souris.
     */
    @Override
    public void mouseClicked(MouseEvent e) {
        //Si il s'agit d'un double-clic et que l'outil sélection est sélectionné, on ouvre la fenêtre d'édition de la classe
        UmlToolbar toolbar = (UmlToolbar) _toolbar;
        if(e.getClickCount() == 2 && toolbar.getCurrentTool() == UmlToolbar.SELECT_TOOL){
            if(_selected instanceof Class){
                this.editClass((Class) _selected);
            }
        }
    }

    /**
     * Bouton de la souris pressé sur la grille. On récupère l'outil sélectionné pour parvenir à l'action.
     * @param e L'événement souris.
     */
    @Override
    public void mousePressed(MouseEvent e) {
        UmlToolbar toolbar = (UmlToolbar) _toolbar;
        int tool = toolbar.getCurrentTool();
        switch(tool){
            //On essaie de sélectionner un élément
            case UmlToolbar.EDIT_TOOL:
                System.out.println("On édite un élément.");
                this.select(e.getX(), e.getY());
                if(_selected instanceof Class) this.editClass((Class) _selected);
                break;
            case UmlToolbar.DELETE_TOOL:
                System.out.println("On supprime un élément.");
                this.select(e.getX(), e.getY());
                if(_selected instanceof Class) removeClass((Class)_selected);
                break;
            case UmlToolbar.COPY_TOOL:
                System.out.println("On copie un élément.");
                break;
            case UmlToolbar.PASTE_TOOL:
                System.out.println("On colle un élément.");
                break;
            //On essaie d'ajouter une classe
            case UmlToolbar.CLASS_TOOL:
                this.addClass(new Class(e.getX(), e.getY(), Class.RECTANGLE));
                break;
            case UmlToolbar.STRONG_TOOL:
                System.out.println("On ajoute une relation forte.");
                break;
            case UmlToolbar.WEAK_TOOL:
                System.out.println("On ajoute une relation faible.");
                break;
            case UmlToolbar.COMPOSITION_TOOL:
                System.out.println("On ajoute une relation de composition.");
                break;
            case UmlToolbar.AGGREGATION_TOOL:
                System.out.println("On ajoute une relation d'agrégation.");
                break;
            case UmlToolbar.INHERITANCE_TOOL:
                System.out.println("On ajoute une relation d'héritage.");
                break;
            default:
                this.select(e.getX(), e.getY());
                break;
        }
        this.repaint();
    }

    @Override
    public void mouseReleased(MouseEvent e) {

    }

    @Override
    public void mouseEntered(MouseEvent e) {

    }

    @Override
    public void mouseExited(MouseEvent e) {

    }

    /**
     * Déplacer un élément en cliquant et déplaçant la souris.
     * @param e Evénement souris
     */
    @Override
    public void mouseDragged(MouseEvent e) {
        //Mise à jour des coordonnées de la souris dans la statusbar
        AppThinker.getWindow().getStatusbar().setPosLabel(e.getX(), e.getY());
        int posX = e.getX();
        int posY = e.getY();
        if(_selected instanceof Class){
            Class a = (Class)_selected;
            int shiftX = 0;
            int shiftY = 0;
            //Si un coin est sélectionné, on redimensionne
            if(cornerSelection != null){
                switch(cornerSelection){
                    case "N":
                        a.resizeUp(posY);
                        break;
                    case "NE":
                        a.resizeUp(posY);
                        a.resizeRight(posX);
                    case "E":
                        a.resizeRight(posX);
                        break;
                    case "SE":
                        a.resizeDown(posY);
                        a.resizeRight(posX);
                    case "S":
                        a.resizeDown(posY);
                        break;
                    case "SW":
                        a.resizeDown(posY);
                        a.resizeLeft(posX);
                        break;
                    case "W":
                        a.resizeLeft(posX);
                        break;
                    case "NW":
                        a.resizeUp(posY);
                        a.resizeLeft(posX);
                        break;
                }
                AppThinker.getWindow().getStatusbar().setSizeLabel(a.getSizeX(), a.getSizeY());
            }
            //Sinon on déplace
            else{
                //On repositionne la classe en prenant en compte le décalage mesuré au clic de la souris
                a.setPosX(posX - _shiftX);
                a.setPosY(posY - _shiftY);
                this.setCursor(new Cursor(Cursor.MOVE_CURSOR));
            }
        }
        this.repaint();
    }

    /**
     * La souris bouge dans la grille.
     * @param e L'événement souris.
     */
    @Override
    public void mouseMoved(MouseEvent e) {
        int getX = e.getX();
        int getY = e.getY();
        int sens = 5;
        //Mise à jour des coordonnées de la souris dans la statusbar
        AppThinker.getWindow().getStatusbar().setPosLabel(e.getX(), e.getY());
        //Si une classe est sélectionnée
        if (_selected instanceof Class) {
            Class a = (Class) _selected;
            AppThinker.getWindow().getStatusbar().setSizeLabel(a.getSizeX(), a.getSizeY());
            int posX = a.getPosX() - (a.getSizeX() / 2);
            int posY = a.getPosY() - (a.getSizeY() / 2);
            //Si la souris est à proximité d'un lien, on affiche le curseur de redimensionnement
            if (getX >= posX - sens && getX <= posX + sens && getY >= posY - sens && getY <= posY + sens) {
                cornerSelection = "NW";
                this.setCursor(new Cursor(Cursor.NW_RESIZE_CURSOR));
            } else if (getX >= posX + a.getSizeX() / 2 - sens && getX <= posX + a.getSizeX() / 2 + sens && getY >= posY - sens && getY <= posY + sens) {
                cornerSelection = "N";
                this.setCursor(new Cursor(Cursor.N_RESIZE_CURSOR));
            } else if (getX >= posX + a.getSizeX() - sens && getX <= posX + a.getSizeX() + sens && getY >= posY - sens && getY <= posY + sens) {
                cornerSelection = "NE";
                this.setCursor(new Cursor(Cursor.NE_RESIZE_CURSOR));
            } else if (getX >= posX + a.getSizeX() - sens && getX <= posX + a.getSizeX() + sens && getY >= posY + a.getSizeY() / 2 - sens && getY <= posY + a.getSizeY() / 2 + sens) {
                cornerSelection = "E";
                this.setCursor(new Cursor(Cursor.E_RESIZE_CURSOR));
            } else if (getX >= posX + a.getSizeX() - sens && getX <= posX + a.getSizeX() + sens && getY >= posY + a.getSizeY() - sens && getY <= posY + a.getSizeY() + sens) {
                cornerSelection = "SE";
                this.setCursor(new Cursor(Cursor.SE_RESIZE_CURSOR));
            } else if (getX >= posX + a.getSizeX() / 2 - sens && getX <= posX + a.getSizeX() / 2 + sens && getY >= posY + a.getSizeY() - sens && getY <= posY + a.getSizeY() + sens) {
                cornerSelection = "S";
                this.setCursor(new Cursor(Cursor.S_RESIZE_CURSOR));
            } else if (getX >= posX - sens && getX <= posX + sens && getY >= posY + a.getSizeY() - sens && getY <= posY + a.getSizeY() + sens) {
                cornerSelection = "SW";
                this.setCursor(new Cursor(Cursor.SW_RESIZE_CURSOR));
            } else if (getX >= posX - sens && getX <= posX + sens && getY >= posY + a.getSizeY() / 2 - sens && getY <= posY + a.getSizeY() / 2 + sens) {
                cornerSelection = "W";
                this.setCursor(new Cursor(Cursor.W_RESIZE_CURSOR));
            } else {
                cornerSelection = null;
                this.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
            }
        }
    }
}
