/*
 * Java-Gnome Bindings Library
 *
 * Copyright 1998-2004 the Java-Gnome Team, all rights reserved.
 *
 * The Java-Gnome bindings library is free software distributed under
 * the terms of the GNU Library General Public License version 2.
 */

package org.gnu.gtk;

import java.util.Vector;

import org.gnu.gdk.Pixbuf;
import org.gnu.glib.EventMap;
import org.gnu.glib.EventType;
import org.gnu.glib.GObject;
import org.gnu.glib.Type;
import org.gnu.glib.Value;
import org.gnu.gtk.event.TreeModelEvent;
import org.gnu.gtk.event.TreeModelListener;
import org.gnu.glib.Handle;

/**
 * A generic tree interface for use with {@link TreeView} widgets. Internally,
 * the {@link TreeStore} and {@link ListStore} objects are constructed on top of
 * TreeModels. If you were using the C version of gtk, you would be able to
 * construct other objects like those (although I find no reason for doing so).
 * This object provides a number of useful methods which can be used with either
 * the TreeStore or ListStore.
 * <p>
 * For a full overview of the tree, list and table widgets, please see the
 * {@link TreeView} description.
 */
public class TreeModel extends GObject {

    /**
     */
    protected TreeModel(Handle handle) {
        super(handle);
    }

    protected static TreeModel getTreeModel(Handle handle) {
        if (handle == null)
            return null;

        TreeModel obj = (TreeModel) getGObjectFromHandle(handle);
        if (obj == null)
            obj = new TreeModel(handle);

        return obj;
    }

    /*
     * TODO: Foreach function.
     */

    /**
     * Returns the number of data blocks supported by the model
     * 
     * @deprecated use <code>getColumnCount</code> instead.
     */
    public int getDataBlockCount() {
        return gtk_tree_model_get_n_columns(getHandle());
    }

    /**
     * Returns the number of data blocks supported by the model
     */
    public int getColumnCount() {
        return gtk_tree_model_get_n_columns(getHandle());
    }

    /**
     * Returns the type of the data block.
     * 
     * @param index
     *            The index of the data block
     */
    public Type getType(int index) {
        return new org.gnu.glib.Type(gtk_tree_model_get_column_type(
                getHandle(), index));
    }

    /**
     * Returns a valid iterator pointing to <code>path</code>, or
     * <code>null</code>/
     */
    public TreeIter getIter(TreePath path) {
        Handle hndl = gtk_tree_model_get_iter(getHandle(), path.getHandle());
        return iterFromHandle(hndl);
    }

    /**
     * Returns a valid iterator pointing to the path represented by
     * <code>pathString</code>, or returns <code>null</code> if the path is
     * invalid.
     */
    public TreeIter getIter(String pathString) {
        Handle hndl = gtk_tree_model_get_iter_from_string(getHandle(),
                pathString);
        return iterFromHandle(hndl);
    }

    /**
     * Returns the iterator at the start of the model (the one at the path "0"),
     * or returns <code>null</code> if the tree is empty.
     */
    public TreeIter getFirstIter() {
        Handle hndl = gtk_tree_model_get_iter_first(getHandle());
        return iterFromHandle(hndl);
    }

    /**
     * Returns the Value at the given iter in the specified data block.
     */
    public String getValue(TreeIter iter, DataColumnString dataBlock) {
        Handle value = gtk_tree_model_get_value(getHandle(), iter.getHandle(),
                dataBlock.getColumn());
        Value val = new Value(value);
        return val.getString();
    }

    /**
     * Returns the Value at the given iter in the specified data block.
     */
    public int getValue(TreeIter iter, DataColumnInt dataBlock) {
        Handle value = gtk_tree_model_get_value(getHandle(), iter.getHandle(),
                dataBlock.getColumn());
        Value val = new Value(value);
        return val.getInt();
    }

    /**
     * Returns the <code>long</code> at the row specified by iter and column
     * specified data block.
     * 
     * @since 2.8.5
     */
    public long getValue(TreeIter iter, DataColumnLong dataBlock) {
        Handle value = gtk_tree_model_get_value(getHandle(), iter.getHandle(),
                dataBlock.getColumn());
        Value val = new Value(value);
        return val.getLong();
    }

    /**
     * Returns the Value at the given iter in the specified data block.
     */
    public boolean getValue(TreeIter iter, DataColumnBoolean dataBlock) {
        Handle value = gtk_tree_model_get_value(getHandle(), iter.getHandle(),
                dataBlock.getColumn());
        Value val = new Value(value);
        return val.getBoolean();
    }

    /**
     * Returns the Value at the given iter in the specified data block.
     */
    public double getValue(TreeIter iter, DataColumnDouble dataBlock) {
        Handle value = gtk_tree_model_get_value(getHandle(), iter.getHandle(),
                dataBlock.getColumn());
        Value val = new Value(value);
        return val.getDouble();
    }

    /**
     * Returns the Value at the given iter in the specified data block.
     */
    public Object getValue(TreeIter iter, DataColumnObject dataBlock) {
        Handle value = gtk_tree_model_get_value(getHandle(), iter.getHandle(),
                dataBlock.getColumn());
        Value val = new Value(value);
        return val.getJavaObject();
    }

    /**
     * Returns the Value at the given iter in the specified data block.
     */
    public Pixbuf getValue(TreeIter iter, DataColumnPixbuf dataBlock) {
        Handle value = gtk_tree_model_get_value(getHandle(), iter.getHandle(),
                dataBlock.getColumn());
        Value val = new Value(value);
        return val.getPixbuf();
    }

    /**
     * Returns the Value at the given iter in the specified data block.
     */
    public int getValue(TreeIter iter, DataColumnIconSize dataBlock) {
        Handle value = gtk_tree_model_get_value(getHandle(), iter.getHandle(),
                dataBlock.getColumn());
        Value val = new Value(value);
        return val.getInt();
    }

    /**
     * Returns the Value at the given iter in the specified data block.
     */
    public String getValue(TreeIter iter, DataColumnStockItem dataBlock) {
        Handle value = gtk_tree_model_get_value(getHandle(), iter.getHandle(),
                dataBlock.getColumn());
        Value val = new Value(value);
        return val.getString();
    }

    private TreeIter iterFromHandle(Handle hndl) {
        return TreeIter.getTreeIter(hndl, this);
    }

    /***************************************************************************
     * Event Handler Related code
     **************************************************************************/

    /**
     * Listeners for handling toggle events
     */
    private Vector listeners = null;

    /**
     * Register an object to handle button events.
     * 
     * @see org.gnu.gtk.event.TreeModelListener
     */
    public void addListener(TreeModelListener listener) {
        // Don't add the listener a second time if it is in the Vector.
        int i = findListener(listeners, listener);
        if (i == -1) {
            if (null == listeners) {
                evtMap.initialize(this, TreeModelEvent.Type.ROW_CHANGED);
                evtMap.initialize(this, TreeModelEvent.Type.ROW_DELETED);
                evtMap.initialize(this,
                        TreeModelEvent.Type.ROW_HAS_CHILD_TOGGLED);
                evtMap.initialize(this, TreeModelEvent.Type.ROW_INSERTED);
                evtMap.initialize(this, TreeModelEvent.Type.ROWS_REORDERED);
                listeners = new Vector();
            }
            listeners.addElement(listener);
        }
    }

    /**
     * Removes a listener
     * 
     * @see #addListener(TreeModelListener)
     */
    public void removeListener(TreeModelListener listener) {
        int i = findListener(listeners, listener);
        if (i > -1) {
            listeners.remove(i);
        }
        if (0 == listeners.size()) {
            evtMap.uninitialize(this, TreeModelEvent.Type.ROW_CHANGED);
            evtMap.uninitialize(this, TreeModelEvent.Type.ROW_DELETED);
            evtMap
                    .uninitialize(this,
                            TreeModelEvent.Type.ROW_HAS_CHILD_TOGGLED);
            evtMap.uninitialize(this, TreeModelEvent.Type.ROW_INSERTED);
            evtMap.uninitialize(this, TreeModelEvent.Type.ROWS_REORDERED);
            listeners = null;
        }
    }

    /**
     * Give us a way to locate a specific listener in a Vector.
     * 
     * @param list
     *            The Vector of listeners to search.
     * @param listener
     *            The object that is to be located in the Vector.
     * @return Returns the index of the listener in the Vector, or -1 if the
     *         listener is not contained in the Vector.
     */
    protected static int findListener(Vector list, Object listener) {
        if (null == list || null == listener)
            return -1;
        return list.indexOf(listener);
    }

    protected void fireTreeModelEvent(TreeModelEvent event) {
        if (null == listeners) {
            return;
        }
        int size = listeners.size();
        int i = 0;
        while (i < size) {
            TreeModelListener tl = (TreeModelListener) listeners.elementAt(i);
            tl.treeModelEvent(event);
            i++;
        }
    }

    private void handleRowChanged(Handle path, Handle iter) {
        TreeModelEvent event = new TreeModelEvent(this,
                TreeModelEvent.Type.ROW_CHANGED);
        event.setTreePath(TreePath.getTreePath(path));
        event.setTreeIter(TreeIter.getTreeIter(iter, this));
        fireTreeModelEvent(event);
    }

    private void handleRowInserted(Handle path, Handle iter) {
        TreeModelEvent event = new TreeModelEvent(this,
                TreeModelEvent.Type.ROW_INSERTED);
        event.setTreePath(TreePath.getTreePath(path));
        event.setTreeIter(TreeIter.getTreeIter(iter, this));
        fireTreeModelEvent(event);
    }

    private void handleRowHasChildToggled(Handle path, Handle iter) {
        TreeModelEvent event = new TreeModelEvent(this,
                TreeModelEvent.Type.ROW_HAS_CHILD_TOGGLED);
        event.setTreePath(TreePath.getTreePath(path));
        event.setTreeIter(TreeIter.getTreeIter(iter, this));
        fireTreeModelEvent(event);
    }

    private void handleRowDeleted(Handle path) {
        TreeModelEvent event = new TreeModelEvent(this,
                TreeModelEvent.Type.ROW_DELETED);
        event.setTreePath(TreePath.getTreePath(path));
        fireTreeModelEvent(event);
    }

    private void handleRowsReordered(Handle path, Handle iter, Handle newOrder) {
        TreeModelEvent event = new TreeModelEvent(this,
                TreeModelEvent.Type.ROWS_REORDERED);
        event.setTreePath(TreePath.getTreePath(path));
        event.setTreeIter(TreeIter.getTreeIter(iter, this));
        event.setNewOrder(new int[0]);
        fireTreeModelEvent(event);
    }

    public Class getEventListenerClass(String signal) {
        Class cls = evtMap.getEventListenerClass(signal);
        if (cls == null)
            cls = super.getEventListenerClass(signal);
        return cls;
    }

    public EventType getEventType(String signal) {
        EventType et = evtMap.getEventType(signal);
        if (et == null)
            et = super.getEventType(signal);
        return et;
    }

    private static EventMap evtMap = new EventMap();
    static {
        addEvents(evtMap);
    }

    /**
     * Implementation method to build an EventMap for this widget class. Not
     * useful (or supported) for application use.
     */
    private static void addEvents(EventMap anEvtMap) {
        anEvtMap.addEvent("rows_reordered", "handleRowsReordered",
                TreeModelEvent.Type.ROWS_REORDERED, TreeModelListener.class);
        anEvtMap.addEvent("row_changed", "handleRowChanged",
                TreeModelEvent.Type.ROW_CHANGED, TreeModelListener.class);
        anEvtMap.addEvent("row_inserted", "handleRowInserted",
                TreeModelEvent.Type.ROW_INSERTED, TreeModelListener.class);
        anEvtMap.addEvent("row_has_child_toggled", "handleRowHasChildToggled",
                TreeModelEvent.Type.ROW_HAS_CHILD_TOGGLED,
                TreeModelListener.class);
        anEvtMap.addEvent("row_deleted", "handleRowDeleted",
                TreeModelEvent.Type.ROW_DELETED, TreeModelListener.class);
    }

    native static final protected int gtk_tree_model_get_n_columns(
            Handle treeModel);

    native static final protected int gtk_tree_model_get_column_type(
            Handle treeModel, int index);

    native static final protected Handle gtk_tree_model_get_iter(
            Handle treeModel, Handle path);

    native static final protected Handle gtk_tree_model_get_iter_from_string(
            Handle treeModel, String pathString);

    native static final protected String gtk_tree_model_get_string_from_iter(
            Handle treeModel, Handle iter);

    native static final protected Handle gtk_tree_model_get_iter_first(
            Handle treeModel);

    native static final protected Handle gtk_tree_model_get_path(
            Handle treeModel, Handle iter);

    native static final protected Handle gtk_tree_model_get_value(
            Handle treeModel, Handle iter, int column);

    native static final protected Handle gtk_tree_model_iter_next(
            Handle treeModel, Handle iter);

    native static final protected Handle gtk_tree_model_iter_children(
            Handle treeModel, Handle parent);

    native static final protected boolean gtk_tree_model_iter_has_child(
            Handle treeModel, Handle iter);

    native static final protected int gtk_tree_model_iter_n_children(
            Handle treeModel, Handle iter);

    native static final protected Handle gtk_tree_model_iter_nth_child(
            Handle treeModel, Handle parent, int n);

    native static final protected Handle gtk_tree_model_iter_parent(
            Handle treeModel, Handle child);
    // native static final protected void gtk_tree_model_row_changed(int
    // treeModel, int path, int iter);
    // native static final protected void gtk_tree_model_row_inserted(int
    // treeModel, int path, int iter);
    // native static final protected void
    // gtk_tree_model_row_has_child_toggled(int treeModel, int path, int iter);
    // native static final protected void gtk_tree_model_row_deleted(int
    // treeModel, int path);
    // native static final protected void gtk_tree_model_rows_reordered(int
    // treeModel, int path, int iter, int[] newOrder);
}
