/*
Copyright 2005-2013 Samuel Gesche

This file is part of ArcEnCiel.

ArcEnCiel is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

ArcEnCiel is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with ArcEnCiel.  If not, see <http://www.gnu.org/licenses/>.
*/

package ihm.townto;

import ihm.Charte;

import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Scrollable;
import javax.swing.Timer;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.RenderingHints;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;

import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;

import java.io.File;

import java.net.URI;
import java.net.URISyntaxException;
import java.net.MalformedURLException;

import java.util.Arrays;
import java.util.Comparator;
import java.util.Vector;

/**
 * Cette classe est le composant qui affiche un graphe à l'écran. Il affiche un
 * graphe de profondeur 2 (donc un graphe ayant les noeuds affichés jusqu'à 2
 * connexions à partir du noeud central.<br>
 * Il s'agit avant tout d'une vue locale sur un graphe.<br>
 * Les noeuds sont présentés sous forme de boutons qui génèrent des évènements
 * au clic. Ces évènements sont envoyés à la classe EventHandler.<br>
 * Les liens sont présentés sous forme de lignes de différentes tailles,
 * décorées d'une flèche. La flèche, au passage de la souris, affiche la
 * sémantique rattachée au lien.<br>
 * Cette classe est fortement liée aux xlases Graph et GraphLayout.<br>
 *
 * @author Samuel GESCHE
 * @version 3.0
 * @since 3.0.0
 */
public class GraphPanel extends JPanel implements Scrollable {
    protected Node activeNode;
    protected Node[] crown1;
    protected Node[] crown2;
    protected NumberNode[] numbers;
    protected Link[] links;
    protected LinkFactory.ArrowComponent[] arrows;
    protected NumberLink[] nLinks;

    protected GraphLayout layout = new GraphLayout();
    //protected ForceDirectedLayout layout = new ForceDirectedLayout();

    protected boolean processing = false;
    protected int idG = 0;

    protected ActionListener alMover = new ActionListener(){
        public void actionPerformed(ActionEvent ae){
            doLayout();
        }
    };
    protected Timer mover = new Timer(100,alMover);

    /**
     * Crée un nouveau GraphPanel affichant le Graph spécifié.
     * @param gd Graph le Graph en question
     * @param id int l'identifiant de ce GraphPanel, renvoyé comme offset des
     * évènements
     * @since 3.0.1
     */
    public GraphPanel(Graph gd, int id) {
        setLayout(layout);
        setDoubleBuffered(false);
        this.idG = id;
        setOpaque(false);
        setSize(getPreferredSize());
        setGraph(gd);
        mover.start();
    }

    /**
     * Change le Graph affiché par ce composant.
     * @param gd Graph le nouveau Graph
     * @since 3.0.0
     */
    public void setGraph(Graph gd) {
        createStuff(gd);
        layout.invalidate();
        for (int i = 0; i < 100 / gd.getNumberOfNodes(); i++) {
            doLayout();
        }
    }

    private Node[] allNodesSorted = null;
    /**
     * Renvoie tous les noeuds, triés par ordre de hashcode.
     * @return Node[] ladite première couronne
     * @since 4.1.0
     */
    Node[] getAllNodesSorted() {
        if(allNodesSorted == null){
            Vector res = new Vector();
            res.addElement(activeNode);
            for (int i = 0; i < crown1.length; i++) {
                res.add(crown1[i]);
            }
            for (int i = 0; i < crown2.length; i++) {
                res.add(crown2[i]);
            }
            allNodesSorted = new Node[res.size()];
            res.toArray(allNodesSorted);
            Arrays.sort(allNodesSorted, new ComparateurHashNoeuds());
        }
        return allNodesSorted;
    }

    /**
     * Renvoie la première couronne de noeuds.
     * @return Node[] ladite première couronne
     * @since 3.0.0
     */
    Node[] getFirstCrown() {
        return crown1;
    }

    /**
     * Renvoie la seconde couronne de noeuds.
     * @return Node[] ladite seconde couronne
     * @since 3.0.0
     */
    Node[] getSecondCrown() {
        return crown2;
    }

    /**
     * Renvoie les noeuds contenant des nombres.
     * @return NumberNode[] les noeuds en question
     * @since 3.0.0
     */
    NumberNode[] getNumberNodes() {
        return numbers;
    }

    /**
     * Renvoie le noeud central du graphe.
     * @return Node ledit noeud central
     * @since 3.0.0
     */
    Node getActiveNode() {
        return activeNode;
    }

    /**
     * Renvoie les flèches du graphe.
     * @return LinkFactory.ArrowComponent[] lesdites flèches
     * @since 3.0.0
     */
    LinkFactory.ArrowComponent[] getArrows() {
        return arrows;
    }

    /**
     * Renvoie les noeuds de la seconde couronne reliés au noeud de la première
     * couronne spécifié.
     * @param firstCrownNode Node le noeud en question
     * @return Node[] les noeuds reliés
     * @since 3.0.0
     */
    Node[] getSecondCrown(Node firstCrownNode) {
        int id = firstCrownNode.getId();
        Vector result = new Vector();
        for (int i = 0; i < links.length; i++) {
            if (links[i].getNode1().getId() == id &&
                links[i].getNode2().getId() != activeNode.getId()) {
                result.addElement(links[i].getNode2());
            }
            if (links[i].getNode2().getId() == id &&
                links[i].getNode1().getId() != activeNode.getId()) {
                result.addElement(links[i].getNode1());
            }
        }
        Node[] cmp = new Node[result.size()];
        result.toArray(cmp);
        return cmp;
    }

    protected boolean isActiveNode(Node n){
        return n.getId() == getActiveNode().getId();
    }

    protected boolean isInFirstCrown(Node n){
        Node[] liste = getFirstCrown();
        boolean res = false;
        for(int i=0; i<liste.length; i++){
            if(liste[i].getId() == n.getId()){
                res = true;
                break;
            }
        }
        return res;
    }

    protected boolean isInSecondCrown(Node n){
        Node[] liste = getSecondCrown();
        boolean res = false;
        for(int i=0; i<liste.length; i++){
            if(liste[i].getId() == n.getId()){
                res = true;
                break;
            }
        }
        return res;
    }

    /**
     * Dit si le second noeud est dans la couronne du premier.
     * @param firstNode Node un noeud de la première couronne
     * @param secondNode Node un noeud de la seconde couronne
     * @return boolean le résultat du calcul
     * @since 3.0.0
     */
    boolean isInCrownOf(Node firstNode, Node secondNode) {
        boolean yes = false;
        if (isActiveNode(firstNode) && isInFirstCrown(secondNode) ||
            isActiveNode(firstNode) && isInSecondCrown(secondNode)){
            yes = true;
        }
        if(!yes && isInFirstCrown(firstNode)){
            Node[] liste = getSecondCrown(firstNode);
            for(int i=0; i<liste.length; i++){
                if(liste[i].getId() == secondNode.getId()){
                    yes = true;
                    break;
                }
            }
        }
        /*int id1 = firstNode.getId();
        int id2 = secondNode.getId();
        for (int i = 0; i < links.length; i++) {
            if ((links[i].getNode1().getId() == id1 &&
                 links[i].getNode2().getId() == id2) ||
                (links[i].getNode1().getId() == id2 &&
                 links[i].getNode2().getId() == id1)) {
                yes = true;
                break;
            }
            for (int j = 0; j < links.length; j++) {
                if (links[i].getNode1().getId() == id1 &&
                    links[i].getNode2().getId() == links[j].getNode1().getId() &&
                    links[j].getNode2().getId() == id2 ||
                    links[i].getNode1().getId() == id2 &&
                    links[i].getNode2().getId() == links[j].getNode1().getId() &&
                    links[j].getNode2().getId() == id1 ||
                    links[i].getNode2().getId() == id1 &&
                    links[i].getNode1().getId() == links[j].getNode2().getId() &&
                    links[j].getNode1().getId() == id2 ||
                    links[i].getNode2().getId() == id2 &&
                    links[i].getNode1().getId() == links[j].getNode2().getId() &&
                    links[j].getNode1().getId() == id1) {
                    yes = true;
                    break;
                }
            }
            if (yes) {
                break;
            }
        }*/
        return yes;
    }

    /**
     * Renvoie le premier noeud du lien à l'index spécifié.
     * @param linkIndex int l'index en question
     * @return Node le noeud correspondant
     * @since 3.0.0
     */
    Node getFirstNode(int linkIndex) {
        return links[linkIndex].getNode1();
    }

    /**
     * Renvoie le second noeud du lien à l'index spécifié.
     * @param linkIndex int l'index en question
     * @return Node le noeud correspondant
     * @since 3.0.0
     */
    Node getSecondNode(int linkIndex) {
        return links[linkIndex].getNode2();
    }

    /**
     * Renvoie le NumberNode correspondant au noeud spécifié.
     * @param node Node le noeud en question
     * @return NumberNode son NumberNode
     * @since 3.0.0
     */
    NumberNode getNumberNode(Node node) {
        NumberNode result = null;
        for (int i = 0; i < nLinks.length; i++) {
            if (node.getId() == nLinks[i].getNode().getId()) {
                result = nLinks[i].getNumberNode();
            }
        }
        return result;
    }

    protected void createStuff(Graph gd) {
        processing = true;
        removeAll();

        // Creating Nodes from the Graph.GraphNodes of the Graph
        activeNode = new Node(gd.getActive().getName(),
                              gd.getActive().getDescription(),
                              gd.getActive().getId(),
                              this);
        activeNode.setBorder(Charte.getBorder());
        add(activeNode);

        Graph.GraphNode[] nodes1 = gd.getSortedFirstCrown();
        crown1 = new Node[nodes1.length];
        for (int i = 0; i < crown1.length; i++) {
            crown1[i] = new Node(nodes1[i].getName(),
                                 nodes1[i].getDescription(), nodes1[i].getId(),
                                 this);
            add(crown1[i]);
        }

        Graph.GraphNode[] nodes2 = gd.getSortedSecondCrown();
        crown2 = new Node[nodes2.length];
        for (int i = 0; i < crown2.length; i++) {
            crown2[i] = new Node(nodes2[i].getName(),
                                 nodes2[i].getDescription(),
                                 nodes2[i].getId(), this);
            add(crown2[i]);
        }

        // Creating the number nodes and links
        Vector num = new Vector();
        Vector lin = new Vector();
        for (int i = 0; i < nodes1.length; i++) {
            if (nodes1[i].getRelationsCount() > 0) {
                NumberNode node = new NumberNode(nodes1[i].getId(),
                                                 nodes1[i].getRelationsCount());
                NumberLink link = new NumberLink(crown1[i], node);
                num.addElement(node);
                lin.addElement(link);
                add(node);
                add(link);
            }
        }
        for (int i = 0; i < nodes2.length; i++) {
            if (nodes2[i].getRelationsCount() > 0) {
                NumberNode node = new NumberNode(nodes2[i].getId(),
                                                 nodes2[i].getRelationsCount());
                NumberLink link = new NumberLink(crown2[i], node);
                num.addElement(node);
                lin.addElement(link);
                add(node);
                add(link);
            }
        }
        numbers = new NumberNode[num.size()];
        num.toArray(numbers);
        nLinks = new NumberLink[lin.size()];
        lin.toArray(nLinks);

        // Creating Links from the Graph.GraphLinks of the Graph
        Graph.GraphLink[] llinks = gd.getLinks();
        links = new Link[llinks.length];
        int it = 0;
        for (int i = 0; i < links.length; i++) {
            links[i] = new Link(new Node("", "", 0, this),
                                new Node("", "", 0, this),
                                "",Charte.getMiscellaneous());
            Node node1 = null;
            Node node2 = null;

            int from = llinks[i].getIdConcept1();
            int to = llinks[i].getIdConcept2();

            if (from >= Graph.GraphNode.MIN_ACTIVE &&
                from <= Graph.GraphNode.MAX_ACTIVE) {
                node1 = activeNode;
            }
            if (to >= Graph.GraphNode.MIN_ACTIVE &&
                to <= Graph.GraphNode.MAX_ACTIVE) {
                node2 = activeNode;
            }
            if (from >= Graph.GraphNode.MIN_CROWN1 &&
                from <= Graph.GraphNode.MAX_CROWN1) {
                for (int j = 0; j < crown1.length; j++) {
                    if (crown1[j].getId() == from) {
                        node1 = crown1[j];
                    }
                }
            }
            if (to >= Graph.GraphNode.MIN_CROWN1 &&
                to <= Graph.GraphNode.MAX_CROWN1) {
                for (int j = 0; j < crown1.length; j++) {
                    if (crown1[j].getId() == to) {
                        node2 = crown1[j];
                    }
                }
            }
            if (from >= Graph.GraphNode.MIN_CROWN2 &&
                from <= Graph.GraphNode.MAX_CROWN2) {
                for (int j = 0; j < crown2.length; j++) {
                    if (crown2[j].getId() == from) {
                        node1 = crown2[j];
                    }
                }
            }
            if (to >= Graph.GraphNode.MIN_CROWN2 &&
                to <= Graph.GraphNode.MAX_CROWN2) {
                for (int j = 0; j < crown2.length; j++) {
                    if (crown2[j].getId() == to) {
                        node2 = crown2[j];
                    }
                }
            }
            if (node1 != null && node2 != null) {
                links[i] = new Link(node1, node2, llinks[i].getText(),
                                    llinks[i].getColor());
                add(links[i]);
            }
            scrollRectToVisible(new Rectangle(0, 0, 10, 10));
        }

        // Creating ArrowComponents for the Links
        arrows = new LinkFactory.ArrowComponent[links.length];
        LinkFactory factory = new LinkFactory();
        for (int i = 0; i < arrows.length; i++) {
            int x1 = links[i].getNode1().getLocation().x;
            int y1 = links[i].getNode1().getLocation().y;
            int x2 = links[i].getNode2().getLocation().x;
            int y2 = links[i].getNode2().getLocation().y;
            String text = " " + links[i].getText() + " ";
            arrows[i] = factory.createArrowComponent(
                    text, factory.createArrow(x1, y1, x2, y2),
                    links[i].getColor(), this);
            add(arrows[i]);
        }
        processing = false;
    }

    protected void choose(String name) {
    }

    protected void getInfos(String name) {
    }

    /**
     * Renvoie vrai si les deux noeuds sont liés, faux sinon.
     * @param n1 Node le premier noeud
     * @param n2 Node le second noeud
     * @return boolean le résultat
     * @since 3.0.0
     */
    boolean isLinked(Node n1, Node n2) {
        boolean linked = false;
        for (int i = 0; i < links.length; i++) {
            if ((links[i].getNode1().equals(n1) &&
                 links[i].getNode2().equals(n2)) ||
                (links[i].getNode2().equals(n1) &&
                 links[i].getNode1().equals(n2))) {
                linked = true;
                break;
            }
        }
        return linked;
    }

    /**
     * Applique la disposition au composant.
     * @since 3.0.0
     */
    public void doLayout() {
        if (processing) {
            return;
        }
        super.doLayout();
    }

    /**
     * Lance le redimensionnement des liens.
     * @since 3.0.0
     */
    void resizeLinks(){
        for(int i=0; i<links.length; i++){
            links[i].resize();
        }
        for(int i=0; i<nLinks.length; i++){
            nLinks[i].resize();
        }
    }

    /**
     * Peint le composant.
     * @param g Graphics le Graphics à utiliser
     * @since 3.0.0
     */
    public void paint(Graphics g) {
        super.paint(g);
    }

    /**
     * Renvoie la taille préférée du composant.
     * @return Dimension ladite taille préférée
     * @since 3.0.0
     */
    public Dimension getPreferredSize() {
        return layout.preferredLayoutSize(this);
    }

    /**
     * Renvoie la taille minimale du composant.
     * @return Dimension ladite taille minimale
     * @since 3.0.0
     */
    public Dimension getMinimumSize() {
        return layout.minimumLayoutSize(this);
    }

    /**
     * Renvoie la taille préférée du composant, en tant que navigable.
     * @return Dimension ladite taille préférée
     * @since 3.0.0
     */
    public Dimension getPreferredScrollableViewportSize() {
        return getPreferredSize();
    }

    /**
     * Renvoie le nombre de pixels à faire défiler pour une unité de défilement.
     * @param view Rectangle la partie visible dans le composant de navigation
     * @param ori int soit SwingConstants.VERTICAL, soit
     * SwingConstants.HORIZONTAL
     * @param dir int <0 pour vers le haut/la gauche, >0 pour vers le bas/la
     * droite
     * @return int le nombre recherché
     * @since 3.0.0
     */
    public int getScrollableUnitIncrement(Rectangle view, int ori, int dir) {
        return 100;
    }

    /**
     * Renvoie le nombre de pixels à faire défiler pour un bloc de défilement.
     * @param view Rectangle la partie visible dans le composant de navigation
     * @param ori int soit SwingConstants.VERTICAL, soit
     * SwingConstants.HORIZONTAL
     * @param dir int <0 pour vers le haut/la gauche, >0 pour vers le bas/la
     * droite
     * @return int le nombre recherché
     * @since 3.0.0
     */
    public int getScrollableBlockIncrement(Rectangle view, int ori, int dir) {
        return 1000;
    }

    /**
     * Retourne faux : le composant navigateur ne doit pas forcer sa largeur à
     * celle du composant
     * @return boolean faux, on vous dit
     * @since 3.0.0
     */
    public boolean getScrollableTracksViewportWidth() {
        return false;
    }

    /**
     * Retourne faux : le composant navigateur ne doit pas forcer sa hauteur à
     * celle du composant
     * @return boolean faux, on vous dit
     * @since 3.0.0
     */
    public boolean getScrollableTracksViewportHeight() {
        return false;
    }

    /**
     * L'implémentation interne des noeuds, en tant que composants graphiques.
     * @since 3.0.0
     */
    class Node extends JPanel {
        private GraphPanel parent;
        private int id;
        private String name;
        private JLabel contenu;
        private Image image;
        private String son = "";

        /**
         * Crée un Node avec les attributs spécifiés.
         * @param name String le texte à afficher
         * @param description String la description affichée au passage de la
         * souris
         * @param id int l'identifiant
         * @param parent GraphPanel le conteneur
         * @since 3.0.0
         */
        public Node(String name, String description, int id, GraphPanel parent) {
            super();
            contenu = new JLabel("", JLabel.CENTER);
            this.id = id;
            this.parent = parent;
            this.name = name;
            setLayout(new FlowLayout());
            Charte.formate(this);

            if(name.endsWith("jpg") || name.endsWith("gif") ||
               name.endsWith("png")){
                try {
                    URI uri = new URI(name).normalize();
                    if (uri.getScheme().startsWith("file")) {
                        image = new ImageIcon(
                                new File(uri).getAbsolutePath()).getImage();
                        if (image.getWidth(this) > 100 || image.getHeight(this) > 50) {
                            image = image.getScaledInstance(
                                    100, 50, Image.SCALE_SMOOTH);
                        }
                    } else if (uri.getScheme().startsWith("http")) {
                        image = new ImageIcon(uri.toURL()).getImage();
                        if (image.getWidth(this) > 100 || image.getHeight(this) > 50) {
                            image = image.getScaledInstance(
                                    100, 50, Image.SCALE_SMOOTH);
                        }
                    } else {
                        image = null;
                    }
                } catch (NullPointerException npe) { // pas de Scheme comme file:, http:
                    image = null;
                } catch (URISyntaxException use) { // URI incorrecte
                    image = null;
                } catch (MalformedURLException mue) { // Trensformation en URL ratée
                    image = null;
                }
            } else if(name.endsWith("au") || name.endsWith("mid") ||
                      name.endsWith("wav")){
                try {
                    URI uri = new URI(name).normalize();
                    if (uri.getScheme().startsWith("file")) {
                        son = new File(uri).getAbsolutePath();
                    } else if (uri.getScheme().startsWith("http")) {
                        son = uri.toURL().toString();
                    } else {
                        son = "";
                    }
                } catch (NullPointerException npe) { // pas de Scheme comme file:, http:
                    son = "";
                } catch (URISyntaxException use) { // URI incorrecte
                    son = "";
                } catch (MalformedURLException mue) { // Trensformation en URL ratée
                    son = "";
                }
            }
            if(image != null){
                contenu.setIcon(new ImageIcon(image));
                contenu.setToolTipText("<html><img src="+name+"></img>");
            } else if(!(son.equals(""))){
                contenu.setIcon(Charte.getIconeSon());
            } else {
                contenu.setText(name);
                String[] desc = description.split("\n");
                String ss = "<html>";
                for (int i = 0; i < desc.length; i++) {
                    String s = "";
                    if (desc[i].length() > 100) {
                        String[] s2 = desc[i].split("\\s");
                        String s3 = "";
                        for (int j = 0; j < s2.length; j++) {
                            if (s3.length() + s2[j].length() < 100) {
                                s3 += s2[j] + " ";
                            } else {
                                s += s3 + "<br>";
                                s3 = s2[j] + " ";
                            }
                        }
                        s += s3;
                    } else {
                        s = desc[i];
                    }
                    ss += s + "<br>";
                }
                contenu.setToolTipText(ss);
            }
            contenu.setCursor(new Cursor(Cursor.HAND_CURSOR));
            contenu.addMouseListener(new MouseAdapter() {
                public void mouseClicked(MouseEvent me) {
                    switch (me.getButton()) {
                    case MouseEvent.BUTTON1:
                        chosen();
                        break;
                    default:
                        infos();
                        break;
                    }
                }
                public void mousePressed(MouseEvent me){
                    layout.setActive(false);
                }
                public void mouseReleased(MouseEvent me){
                    layout.setActive(true);
                }
            });
            contenu.addMouseMotionListener(new MouseMotionAdapter() {
                public void mouseDragged(MouseEvent me) {
                    translate(me.getX() - getSize().width / 2,
                              me.getY() - getSize().height / 2);
                }
            });
            add(contenu);
        }

        /**
         * Renvoie le nombre de lignes du composant (1, pour être précis)
         * @return int le nombre un
         * @since 3.0.3
         */
        public int getLineCount(){
            return 1;
        }

        protected void translate(int px, int py) {
            int x1 = getLocation().x + px;
            int y1 = getLocation().y + py;
            setLocation(x1, y1);
            for(int i=0; i<crown1.length; i++){
                if(isInCrownOf(this, crown1[i])){
                    crown1[i].translate2(px, py);
                }
            }
            for(int i=0; i<crown2.length; i++){
                if(isInCrownOf(this, crown2[i])){
                    crown2[i].translate2(px, py);
                }
            }
        }

        protected void translate2(int px, int py) {
            int x1 = getLocation().x + px;
            int y1 = getLocation().y + py;
            setLocation(x1, y1);
        }


        /**
         * Change la taille de police de ce composant.
         * @param fontSize int la nouvelle taille
         * @since 3.0.0
         */
        public void setFontSize(int fontSize) {
            contenu.setFont(getFont().deriveFont((float)fontSize));
        }

        /**
         * Bascule l'affichage du texte de ce composant en gras.
         * @since 3.0.0
         */
        public void setBold() {
            contenu.setFont(getFont().deriveFont(Font.BOLD));
        }

        /**
         * Renvoie l'identifiant de ce composant.
         * @return l'identifiant en question
         * @since 3.0.0
         */
        public int getId() {
            return id;
        }

        protected void chosen() {
            EventHandler.notifyEvent(EventHandler.TERM_CHOSEN + idG, name);
        }

        protected void infos() {
            parent.getInfos(contenu.getText());
        }
    }

    /**
     * L'implémentation interne des noeuds nombres, en tant que composants
     * graphiques.
     * @since 3.0.0
     */
    class NumberNode extends JLabel {
        private int id;

        /**
         * Crée un composant avec les attributs spécifiés.
         * @param id int u identifiant pour ce composant
         * @param number int un nombre à afficher
         * @since 3.0.0
         */
        public NumberNode(int id, int number) {
            super("" + number, JLabel.CENTER);
            setOpaque(false);
            this.id = id;
            setOpaque(false);
        }

        /**
         * Peint le composant
         * @param g Graphics le Graphics à utiliser
         * @since 3.0.0
         */
        public void paint(Graphics g) {
            super.paint(g);
        }

        /**
         * Renvoie l'identifiant de ce composant.
         * @return int l'identifiant en question
         * @since 3.0.0
         */
        public int getId() {
            return id;
        }
    }

    protected class Link extends JLabel {

        private Node node1;
        private Node node2;
        private String linkText;
        private Color color;

        public Link(Node node1, Node node2, String text, Color color) {
            this.node1 = node1;
            this.node2 = node2;
            this.linkText = text;
            this.color = color;
        }

        public Node getNode1() {
            return node1;
        }

        public Node getNode2() {
            return node2;
        }

        public String getText() {
            return linkText;
        }

        public Color getColor() {
            return color;
        }

        public void resize(){
            setBounds(node1.getBounds().union(node2.getBounds()));
        }


        public void paint(Graphics g){
            g.setColor(Charte.getMiscellaneous());
            Area a = new Area(new Rectangle(0, 0, getWidth(), getHeight()));
            Rectangle r1 = node1.getBounds();
            Rectangle r2 = node2.getBounds();
            r1.translate(0-getX(), 0-getY());
            r2.translate(0-getX(), 0-getY());
            a.subtract(new Area(r1));
            a.subtract(new Area(r2));
            ((Graphics2D)g).clip(a);
            ((Graphics2D)g).setStroke(new BasicStroke(0.5f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
            ((Graphics2D)g).setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
            ((Graphics2D)g).drawLine(node1.getLocation().x + node1.getSize().width / 2 - getX(),
                                     node1.getLocation().y + node1.getSize().height / 2 - getY(),
                                     node2.getLocation().x + node2.getSize().width / 2 - getX(),
                                     node2.getLocation().y + node2.getSize().height / 2 - getY());
        }
    }

    protected class NumberLink extends JLabel {
        private Node node1;
        private NumberNode node2;

        public NumberLink(Node node1, NumberNode node2) {
            this.node1 = node1;
            this.node2 = node2;
        }

        public Node getNode() {
            return node1;
        }

        public NumberNode getNumberNode() {
            return node2;
        }

        public void resize(){
            setBounds(node1.getBounds().union(node2.getBounds()));
        }

        public void paint(Graphics g){
            g.setColor(Charte.getMiscellaneous());

            Area a = new Area(new Rectangle(0, 0, getWidth(), getHeight()));
            Rectangle r1 = node1.getBounds();
            r1.translate(0-getX(), 0-getY());
            a.subtract(new Area(r1));
            Ellipse2D e = new Ellipse2D.Double(node2.getX() - getX(),
                          node2.getY() - getY(),
                          node2.getSize().width, node2.getSize().height);
            a.subtract(new Area(e));
            ((Graphics2D)g).clip(a);

            float[] dash = {2.0f, 2.0f};
            ((Graphics2D)g).setStroke(new BasicStroke(0.5f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 10.0f, dash, 0));
            ((Graphics2D)g).setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
            ((Graphics2D)g).drawLine(node1.getLocation().x + node1.getSize().width / 2 - getX(),
                                     node1.getLocation().y + node1.getSize().height / 2 - getY(),
                                     node2.getLocation().x + node2.getSize().width / 2 - getX(),
                                     node2.getLocation().y + node2.getSize().height / 2 - getY());
        }

    }


    private class ComparateurHashNoeuds implements Comparator {
        public int compare(Object o1, Object o2) {
            return o1.hashCode() - o2.hashCode();
        }
    }
}
