/*
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 java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.LayoutManager;
import javax.swing.JComponent;

/**
 * Cette classe est un LayoutManager utilisant la métaphore électromécanique
 * pour arranger automatiquement un graphe. <br>
 *
 * @author Samuel GESCHE
 * @version 4.1
 * @since 4.1.0
 */
public class ForceDirectedLayout implements LayoutManager {
    private boolean termine = false;
    private boolean commence = false;
    // constante multiplicative de répulsion
    private final static int K = 1;
    // charge d'un composant
    private final static int Q = 200;
    // constante de raideur d'un lien
    private final static int S = 1;
    // longueur au repos d'un lien
    private final static int D0 = 50;


    /**
     * Crée un gestionnaire de disposition de classe ForceDirectedLayout.
     * @since 4.1.0
     */
    public ForceDirectedLayout() {

    }

    /**
     * Déclare cet agencement invalide (l'agencement reprendra donc par
     * itérations).
     * @since 4.1.0
     */
    public void invalidate() {
        termine = false;
    }

    /**
     * Définit si la disposition doit être recalculée.
     * @since 4.1.0
     */
    public void setActive(boolean b) {
        termine = b;
    }


    /**
     * Ne fait strictement rien.
     * @param name String
     * @param comp Component
     * @since 4.1.0
     */
    public void addLayoutComponent(String name, Component comp) {

    }

    /**
     * Ne fait strictement rien.
     * @param comp Component
     * @since 4.1.0
     */
    public void removeLayoutComponent(Component comp) {

    }

    /**
     * Calcule la taille préférée du conteneur spécifié, au vu des composants
     * qu'il contient.
     * @param parent Container le conteneur en question
     * @return Dimension la taille préférée de ce conteneur
     * @since 4.1.0
     */
    public Dimension preferredLayoutSize(Container parent) {
        int x = 0;
        int y = 0;
        for (int i = 0; i < parent.getComponentCount(); i++) {
            int nx = parent.getComponent(i).getX() +
                     parent.getComponent(i).getWidth();
            int ny = parent.getComponent(i).getY() +
                     parent.getComponent(i).getHeight();
            if (nx > x) x = nx;
            if (ny > y) y = ny;
        }
        return new Dimension(x, y);
    }

    /**
     * Calcule la taille minimale du conteneur spécifié, au vu des composants
     * qu'il contient.
     * @param parent Container le conteneur en question
     * @return Dimension la taille minimale de ce conteneur
     * @since 4.1.0
     */
    public Dimension minimumLayoutSize(Container parent) {
        return new Dimension(0, 0);
    }

    /**
     * Agence les composants du conteneur spécifié. En réalité, effectue une
     * itération de cet agencement, donc des appels multiples sont nécessaires
     * pour animer le graphe.
     * @param parent Container le conteneur en question
     * @since 4.1.0
     */
    public void layoutContainer(Container parent) {
        if(!termine){
            if(commence){
                iteration((GraphPanel)parent);
            } else {
                demarrage((GraphPanel)parent);
            }
        }
    }

    /* Crée une disposition initiale déterministe */
    private void demarrage(GraphPanel component){
        JComponent[] liste = component.getAllNodesSorted();
        for(int i=0; i<liste.length; i++){
            liste[i].setLocation(component.WIDTH/2+i, component.HEIGHT/2+i);
        }
        commence = true;
    }

    private void iteration(GraphPanel component) {
        JComponent[] liste = component.getAllNodesSorted();
        Dimension[] repulsion = new Dimension[liste.length];
        for (int i = 0; i < liste.length; i++) {
            repulsion[i] = new Dimension(0,0);
            for (int j = 0; j < liste.length; j++) {
                Dimension rep = getRepulsion(liste[i], liste[j]);
                repulsion[i].setSize(repulsion[i].getWidth() + rep.getWidth(),
                                     repulsion[i].getHeight() + rep.getHeight());
            }
        }
        /*for (int i = 0; i < liste.length; i++) {
            liste[i].setLocation(liste[i].getX() + repulsion[i].width,
                                 liste[i].getY() + repulsion[i].height);
        }*/
        int energie = 0;
        for(int i=0; i<repulsion.length; i++){
            energie += Math.sqrt(repulsion[i].width * repulsion[i].width +
                                 repulsion[i].height * repulsion[i].height);
        }
        if(energie < 2*liste.length){
            termine = true;
        }
    }

    private Dimension getRepulsion(JComponent c1, JComponent c2){
        double distanceX = c2.getX() - c1.getX();
        double distanceY = c2.getY() - c1.getY();
        double distance = Math.sqrt(distanceX * distanceX + distanceY * distanceY);
        double force = K * Q * Q / (distance * distance);
        int forceX = (int)(force * (distanceX / distance));
        int forceY = (int)(force * (distanceY / distance));
        return new Dimension(forceX, forceY);
    }

    private Dimension getAttraction(JComponent c1, JComponent c2){
        double distanceX = c2.getX() - c1.getX();
        double distanceY = c2.getY() - c1.getY();
        double distance = Math.sqrt(distanceX * distanceX + distanceY * distanceY);
        double force = -S*(distance-D0);
        int forceX = (int)(force * (distanceX / distance));
        int forceY = (int)(force * (distanceY / distance));
        return new Dimension(forceX, forceY);
    }

}
