/*
Jaxe - Editeur XML en Java

Copyright (C) 2010 Observatoire de Paris-Meudon

Ce programme est un logiciel libre ; vous pouvez le redistribuer et/ou le modifier conformément aux dispositions de la Licence Publique Générale GNU, telle que publiée par la Free Software Foundation ; version 2 de la licence, ou encore (à votre choix) toute version ultérieure.

Ce programme est distribué dans l'espoir qu'il sera utile, mais SANS AUCUNE GARANTIE ; sans même la garantie implicite de COMMERCIALISATION ou D'ADAPTATION A UN OBJET PARTICULIER. Pour plus de détail, voir la Licence Publique Générale GNU .

Vous devez avoir reçu un exemplaire de la Licence Publique Générale GNU en même temps que ce programme ; si ce n'est pas le cas, écrivez à la Free Software Foundation Inc., 675 Mass Ave, Cambridge, MA 02139, Etats-Unis.
*/

package xpages.jeimage;

import jaxe.JaxeDocument;

import org.apache.log4j.Logger;

import org.w3c.dom.Element; 
import org.w3c.dom.Document;

import java.util.*;

import java.net.URL;
import java.net.MalformedURLException;

import java.awt.Toolkit;
import java.awt.Image;
import java.awt.MediaTracker;

import javax.swing.JPanel;
import javax.swing.JTree;
import javax.swing.JLabel;
import javax.swing.ImageIcon;
import javax.swing.tree.TreePath;
import javax.swing.event.*;


/**
 * Affiche un document DOM dans un JTree
 */
public class DomJTree extends JPanel {
    /**
     * Logger for this class
     */
    private static final Logger LOG = Logger.getLogger(DomJTree.class);
    
    private static final int ELEMENT_TYPE = 1;    
    private static int taillemax = 300;    
    private static String cheminFichierXML = "";
    private JaxeDocument doc;
    private Document document;     
    private URL urlimg = null;
    
    JTree tree;
    TreePath arbreSelection;
    AdaptateurNoeud adpNoeud;

    /**
     * construit un JTree à partir d'un adaptateur de model
     * @param doc Document Jaxe
     * @param docImages Document de fichiers d'images
     * @param cheminIMG Chemin de l'image
     * @param deplier Si true, on déplie le JTree
     * @param label Label pour afficher les aperçus d'images
     */
    public DomJTree(final JaxeDocument doc, Document docImages, String cheminIMG, boolean deplier, final JLabel label) {
        this.doc = doc;
        document = docImages;
        cheminFichierXML = doc.furl.getFile();

        // définir l'arbre
        tree = new JTree(new AdaptateurModele());
        tree.setShowsRootHandles(true);
        tree.setRootVisible(true);

        // affiche une image dans un label lorsqu'on sélectionne un noeud (si le contenu est de type image)
        tree.addTreeSelectionListener( new TreeSelectionListener() {
            public void valueChanged(TreeSelectionEvent e) {
                arbreSelection = e.getNewLeadSelectionPath();
                if (arbreSelection != null) {
                    adpNoeud = (AdaptateurNoeud) arbreSelection.getLastPathComponent();
                    if (!adpNoeud.estUnDossier()) {
                        try { 
                            urlimg = new URL(doc.furl,cheminNoeud(arbreSelection)); 
                        } catch (final MalformedURLException ex) { 
                            LOG.error("urlimg", ex); 
                        }
                        Image img = Toolkit.getDefaultToolkit().createImage(urlimg);
                        if (img != null && chargerImage(img))
                            img = reduireImage(img);
                        ImageIcon icon = new ImageIcon(img);
                        if (label != null)
                            label.setIcon(icon);
                    }
                    else 
                        label.setIcon(null);
                }// if
            }
        }
        );
        
        
        // affiche l'arborescence de l'image selectionnée dans le dialogue
        if (deplier == true) {
            if (cheminIMG != "") {
                String[] elIMG = cheminIMG.split("\\" + "/");
                
                AdaptateurNoeud[] p = new AdaptateurNoeud[elIMG.length+1];
                
                p[0] = (AdaptateurNoeud) tree.getModel().getRoot();
                boolean trouve = false;
                for (int i=0; i < elIMG.length; i++) {
                    if (p[i] == null) break;
                    for (int j=0; j < p[i].nombreEnfant(); j++) {
                        if (elIMG[i].equals(p[i].enfant(j).toString())) {
                            p[i+1] = p[i].enfant(j);
                            if (i == elIMG.length-1) trouve = true;
                            break;
                        }
                    }
                }
                
                if (trouve) {
                    TreePath arbreDeplier = new TreePath(p);
                    tree.setExpandsSelectedPaths(true);
                    if (arbreDeplier != null)
                        tree.setSelectionPath(arbreDeplier);
                }
            }
        }
        
    } //constructeur DomJTree
    
            
    /**
     * retourne le chemin (format String) d'un noeud à partir du TreePath sélectionné
     * @param arbreS un TreePath sélectionné
     * @return le chemin en forme de String d'un noeud
     */
    public static String cheminNoeud(TreePath arbreS) {
        String cheminFImg = "";
        if (arbreS != null) {
            cheminFImg = arbreS.toString();
            cheminFImg = cheminFImg.replace(", ","/");
            cheminFImg = cheminFImg.substring(nomContribution().length()+2,cheminFImg.length()-1);            
        }
        
        return cheminFImg;
    }
    
    
    /**
     * renvoie le nom de la contribution
     */
    private static String nomContribution() {
        String[] fictmp = cheminFichierXML.split("\\" + "/");
        String nomFichierXML = fictmp[fictmp.length-1];
        String[] chtmp = nomFichierXML.split("\\.xml");
        String Contrib = chtmp[0];
        return Contrib;
    }
    
    
    /**
     * charge l'image img
     * @param img de la classe Image
     */
    private boolean chargerImage(final Image img) {
        if (img == null)
            return false;
        final MediaTracker tracker = new MediaTracker(doc.jframe);
        tracker.addImage(img, 0);
        try {
            tracker.waitForAll();
        } catch (final InterruptedException e) {
            return false;
        }
        return !tracker.isErrorAny();
    }
    
    
    /**
     * reduit la taille de l'image img
     * @param img de la classe Image
     */
    private static Image reduireImage(Image img) {
        if (img == null)
            return null;
        int width = img.getWidth(null);
        int height = img.getHeight(null);
        if (width == -1 || height == -1) {
            return null;
        } else if (width > taillemax || height > taillemax) {
            if (width > height) {
                final double scale = taillemax*1.0 / width;
                width = taillemax;
                height = (int)(height*scale);
            } else {
                final double scale = taillemax*1.0 / height;
                height = taillemax;
                width = (int)(width*scale);
            }
            final Image img2 = img.getScaledInstance(width, height, Image.SCALE_FAST);
            img.flush();
            return img2;
        }
        return img;
    }



   /**
    * cette classe est un adaptateur de noeud DOM (Node), elle retourne:
    * - le texte que l'on veut voir dans le JTree,                     
    * - l'index d'un noeud enfant,                                      
    * - un enfant en position i,                                       
    * - le nombre d'enfants    
    */
    public class AdaptateurNoeud {
        org.w3c.dom.Node domNode;
        AdaptateurNoeud[] enfants;
        
        /**
         * construit un AdaptateurNoeud correspondant au noeud DOM
         * @param node Un noeud du DOM
         */
        public AdaptateurNoeud(org.w3c.dom.Node node) {
            domNode = node;
            ArrayList<AdaptateurNoeud> tabAdp = new ArrayList<AdaptateurNoeud>();
            
            for (int i=0; i<domNode.getChildNodes().getLength(); i++) {
                node = domNode.getChildNodes().item(i);
                if (node.getNodeType() == ELEMENT_TYPE ) {
                    tabAdp.add(new AdaptateurNoeud(node));
                }
            }
            if (tabAdp.size() == 0)
                enfants = null;
            else {
                enfants = new AdaptateurNoeud[tabAdp.size()];
                tabAdp.toArray(enfants);
            }
        } //constructeur AdaptateurNoeud
        
        
        /**
         * retourne le texte qui identifie le noeud dans l'arbre
         */
        public String toString() {
            if (domNode instanceof Element)
                return ((Element)domNode).getAttribute("nom");
            
        return "";
        }
        
        
        /**
         * retourne l'index d'un noeud enfant
         */
        public int index(AdaptateurNoeud child) {
            if (enfants == null)
                return -1;
                
            for (int i=0; i<enfants.length; i++) {
                if (child == enfants[i]) return i;
            }
        return -1;
        }
        
        
        /**
         * trouve un noeud enfant à l'index spécifié
         * @return un AdaptateurNoeud
         */ 
        public AdaptateurNoeud enfant(int index) {
            if (enfants == null)
                return null;
            
            return enfants[index];
        }
        
        
        /**
         * retourne le nombre d'enfants de ce noeud
         */
        public int nombreEnfant() {
            if (enfants == null)
                return 0;
                
            return enfants.length;
        }
        
        
        /**
         * retourne true si l'élément est un dossier 
         */
        public boolean estUnDossier() {
            return ("DOSSIER".equals(domNode.getNodeName()));
        }
      
    } //fin class AdaptateurNoeud
    



    /**
     * Convertit le document courant DOM en JTree model
     */
    public class AdaptateurModele implements javax.swing.tree.TreeModel {
        AdaptateurNoeud racine;
        
        /**
         * construit la racine dun JTree 
         */
        public AdaptateurModele() {
            racine = new AdaptateurNoeud(document.getDocumentElement());
        }// constructeur AdaptateurModele
        
        
        /**
         * retourne le noeud racine du DOM
         * @return un AdaptateurNoeud (dérivant de la classe Object)
         */
        public Object getRoot() {
            return racine;
        } 
        
        
        /**
         * retourne true si le noeud est une feuille (sans enfants)
         * @param aNoeud
         */
        public boolean isLeaf(Object aNoeud) {
            AdaptateurNoeud adnode = (AdaptateurNoeud) aNoeud;
            if (adnode.estUnDossier())
                return false;
                
            return true;
        }
        
        
        /**
         * retourne le nombre d'enfants du noeud parent
         * @param parent
         */
        public int getChildCount(Object parent) {
            AdaptateurNoeud node = (AdaptateurNoeud) parent;
            return node.nombreEnfant();
        }
        
        
        /**
         * retourne le noeud enfant de 'parent' à l'index spécifié
         * @param parent Le noeud parent
         * @param index
         * @return un AdaptateurNoeud
         */
        public Object getChild(Object parent, int index) {
            AdaptateurNoeud node = (AdaptateurNoeud) parent;
            return node.enfant(index);
        }
        
        
        /**
         * retourne l'index du noeud enfant child
         * @param parent Le noeud parent
         * @param child Le noeud enfant
         */
        public int getIndexOfChild(Object parent, Object child) {
            AdaptateurNoeud node = (AdaptateurNoeud) parent;
            return node.index((AdaptateurNoeud) child);
        }
        
                
        /**
         * définit l'objet utilisateur du TreeNode identifié par 'path' et affiche un noeud modifié
         */
        public void valueForPathChanged(TreePath path, Object newValue) { /* pas de changement à ce GUI */ }


        private Vector<TreeModelListener> listenerList = new Vector<TreeModelListener>();
        
        
        /**
         * ajoute un écouteur
         */
        public void addTreeModelListener( TreeModelListener listener ) {
            if ( listener != null && ! listenerList.contains( listener ) ) {
                listenerList.addElement( listener );
            }
        }
         
         
        /**
         * supprime un écouteur
         */
        public void removeTreeModelListener( TreeModelListener listener ) {
            if ( listener != null ) {
                listenerList.removeElement( listener );
            }
        }
        
                
        /**
         * informe les écouteurs des changements
         */
        public void fireTreeNodesChanged( TreeModelEvent e ) {
            Enumeration<TreeModelListener> listeners = listenerList.elements();
            while ( listeners.hasMoreElements() ) {
                TreeModelListener listener = listeners.nextElement();
                listener.treeNodesChanged( e );
            }
        } 
        
        
        /**
         * informe les écouteurs des changements
         */
        public void fireTreeNodesInserted( TreeModelEvent e ) {
            Enumeration<TreeModelListener> listeners = listenerList.elements();
            while ( listeners.hasMoreElements() ) {
                TreeModelListener listener = listeners.nextElement();
                listener.treeNodesInserted( e );
            }
        }
        
        
        /**
         * informe les écouteurs des changements
         */
        public void fireTreeNodesRemoved( TreeModelEvent e ) {
            Enumeration<TreeModelListener> listeners = listenerList.elements();
            while ( listeners.hasMoreElements() ) {
                TreeModelListener listener = listeners.nextElement();
                listener.treeNodesRemoved( e );
            }
        }
        
        
        /**
         * informe les écouteurs des changements
         */
        public void fireTreeStructureChanged( TreeModelEvent e ) {
            Enumeration<TreeModelListener> listeners = listenerList.elements();
            while ( listeners.hasMoreElements() ) {
                TreeModelListener listener = listeners.nextElement();
                listener.treeStructureChanged( e );
            }
        }
        
    } //fin class AdaptateurModele
    
    
} //fin class DomJTree
