/*
 * Copyright 2010-2015 Institut Pasteur.
 * 
 * This file is part of Icy.
 * 
 * Icy 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.
 * 
 * Icy 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 Icy. If not, see <http://www.gnu.org/licenses/>.
 */
package icy.gui.component.model;

import icy.util.StringUtil;

import java.util.ArrayList;
import java.util.List;

import javax.swing.event.EventListenerList;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * @author Stephane
 */
public class XMLTreeModel implements TreeModel
{
    public static class XMLAdapterNode
    {
        public Node node;

        /**
         * Creates a new instance of the XMLAdapterNode class
         */
        public XMLAdapterNode(Node node)
        {
            super();

            this.node = node;
        }

        /**
         * Return all children
         */
        public List<Node> getChildren()
        {
            final List<Node> result = new ArrayList<Node>();

            if (node.hasAttributes())
            {
                final NamedNodeMap attributes = node.getAttributes();
                final int count = attributes.getLength();

                for (int i = 0; i < count; i++)
                    result.add(attributes.item(i));
            }

            final NodeList nodes = node.getChildNodes();
            final int count = nodes.getLength();

            for (int i = 0; i < count; i++)
            {
                final Node node = nodes.item(i);

                if (node instanceof Element)
                    result.add(node);
            }

            return result;
        }

        /**
         * Return index of child in this node.
         * 
         * @param child
         *        The child to look for
         * @return index of child, -1 if not present (error)
         */
        public int index(XMLAdapterNode child)
        {
            int result = 0;

            for (Node node : getChildren())
            {
                if (child.node == node)
                    return result;

                result++;
            }

            return -1; // Should never get here.
        }

        /**
         * Returns an adapter node given a valid index found through
         * the method: public int index(XMLAdapterNode child)
         * 
         * @param index
         *        find this by calling index(XMLAdapterNode)
         * @return the desired child
         */
        public XMLAdapterNode child(int index)
        {
            final Node n = getChildren().get(index);

            if (n == null)
                return null;

            return new XMLAdapterNode(n);
        }

        /**
         * Return the number of element children for this element/node
         * 
         * @return int number of element children
         */
        public int childCount()
        {
            return getChildren().size();
        }

        /**
         * Return the value of this node from its sub text nodes
         */
        protected String getValue()
        {
            final NodeList nodes = node.getChildNodes();
            final int count = nodes.getLength();
            String result = "";

            for (int i = 0; i < count; i++)
            {
                final Node node = nodes.item(i);

                // text node
                if (!(node instanceof Element))
                {
                    final String value = node.getNodeValue();

                    if ((value != null) && !StringUtil.equals(value, "null"))
                        result += value + " ";
                }
            }

            return result.trim();
        }

        @Override
        public String toString()
        {
            final String nodeName = node.getNodeName();
            final String nodeValue = node.getNodeValue();

            if (!StringUtil.isEmpty(nodeValue) && !StringUtil.equals(nodeValue, "null"))
                return nodeName + " = " + nodeValue;

            return nodeName;
        }
    }

    protected Document document;

    /**
     * listeners
     */
    protected EventListenerList listeners = new EventListenerList();

    public XMLTreeModel(Document doc)
    {
        super();

        if (doc == null)
            throw new NullPointerException();

        document = doc;
    }

    @Override
    public Object getRoot()
    {
        if (document.getDocumentElement() == null)
            return null;

        return new XMLAdapterNode(document.getDocumentElement());
    }

    @Override
    public Object getChild(Object parent, int index)
    {
        return ((XMLAdapterNode) parent).child(index);
    }

    @Override
    public int getIndexOfChild(Object parent, Object child)
    {
        return ((XMLAdapterNode) parent).index((XMLAdapterNode) child);
    }

    @Override
    public int getChildCount(Object parent)
    {
        return ((XMLAdapterNode) parent).childCount();
    }

    @Override
    public boolean isLeaf(Object node)
    {
        return ((XMLAdapterNode) node).childCount() == 0;
    }

    @Override
    public void valueForPathChanged(TreePath path, Object newValue)
    {
        // ignore here
    }

    /*
     * Use these methods to add and remove event listeners.
     * (Needed to satisfy TreeModel interface, but not used.)
     */

    /**
     * Adds a listener for the TreeModelEvent posted after the tree changes.
     * 
     * @see #removeTreeModelListener
     * @param l
     *        the listener to add
     */
    @Override
    public void addTreeModelListener(TreeModelListener l)
    {
        listeners.add(TreeModelListener.class, l);
    }

    /**
     * Removes a listener previously added with <B>addTreeModelListener()</B>.
     * 
     * @see #addTreeModelListener
     * @param l
     *        the listener to remove
     */
    @Override
    public void removeTreeModelListener(TreeModelListener l)
    {
        listeners.remove(TreeModelListener.class, l);
    }

    public void fireTreeNodesChanged(TreeModelEvent e)
    {
        for (TreeModelListener listener : listeners.getListeners(TreeModelListener.class))
            listener.treeNodesChanged(e);
    }

    public void fireTreeNodesInserted(TreeModelEvent e)
    {
        for (TreeModelListener listener : listeners.getListeners(TreeModelListener.class))
            listener.treeNodesInserted(e);
    }

    public void fireTreeNodesRemoved(TreeModelEvent e)
    {
        for (TreeModelListener listener : listeners.getListeners(TreeModelListener.class))
            listener.treeNodesRemoved(e);
    }

    public void fireTreeStructureChanged(TreeModelEvent e)
    {
        for (TreeModelListener listener : listeners.getListeners(TreeModelListener.class))
            listener.treeStructureChanged(e);
    }
}