// NodeStore.cs - Tree store implementation for TreeView.
//
// Author: Mike Kestner  <mkestner@ximian.com>
//
// Copyright (c) 2003 Novell, Inc.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of version 2 of the Lesser GNU General 
// Public License as published by the Free Software Foundation.
//
// This program 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this program; if not, write to the
// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
// Boston, MA 02111-1307, USA.


namespace Gtk {

	using System;
	using System.Collections;
	using System.Reflection;
	using System.Runtime.InteropServices;

	public class NodeStore : GLib.Object {

        	class IDHashtable : Hashtable {
                	class IDComparer : IComparer {
                        	public int Compare (object x, object y)
                        	{
                                	if ((int) x == (int) y)
                                        	return 0;
                                	else
                                        	return 1;
                        	}
                	}

                	class IDHashCodeProvider : IHashCodeProvider {
				public int GetHashCode (object o)
				{
					return (int) o;
				}
			}

                	public IDHashtable () : base (new IDHashCodeProvider (), new IDComparer ()) {}
        	}

		delegate int GetFlagsDelegate ();
		delegate int GetNColumnsDelegate ();
		delegate IntPtr GetColumnTypeDelegate (int col);
		delegate bool GetNodeDelegate (out int node_idx, IntPtr path);
		delegate IntPtr GetPathDelegate (int node_idx);
		delegate void GetValueDelegate (int node_idx, int col, ref GLib.Value val);
		delegate bool NextDelegate (ref int node_idx);
		delegate bool ChildrenDelegate (out int child, int parent);
		delegate bool HasChildDelegate (int node_idx);
		delegate int NChildrenDelegate (int node_idx);
		delegate bool NthChildDelegate (out int child, int parent, int n);
		delegate bool ParentDelegate (out int parent, int child);

		[StructLayout(LayoutKind.Sequential)]
		struct TreeModelIfaceDelegates  {
			public GetFlagsDelegate get_flags;
			public GetNColumnsDelegate get_n_columns;
			public GetColumnTypeDelegate get_column_type;
			public GetNodeDelegate get_node;
			public GetPathDelegate get_path;
			public GetValueDelegate get_value;
			public NextDelegate next;
			public ChildrenDelegate children;
			public HasChildDelegate has_child;
			public NChildrenDelegate n_children;
			public NthChildDelegate nth_child;
			public ParentDelegate parent;
		}

		int stamp;
		Hashtable node_hash = new IDHashtable ();
 		GLib.GType[] ctypes;
		PropertyInfo[] getters;
		int n_cols = 1;
		ArrayList nodes = new ArrayList ();
		TreeModelIfaceDelegates tree_model_iface;

		int get_flags_cb ()
		{
			return (int) TreeModelFlags.ItersPersist;
		}

		int get_n_columns_cb ()
		{
			return n_cols;
		}

		IntPtr get_column_type_cb (int col)
		{
			return ctypes [col].Val;
		}

		bool get_node_cb (out int node_idx, IntPtr path)
		{
			if (path == IntPtr.Zero)
				throw new ArgumentNullException ("path");

			TreePath treepath = new TreePath (path);
			node_idx = -1;

			ITreeNode node = GetNodeAtPath (treepath);
			if (node == null)
				return false;

			node_idx = node.ID;
			node_hash [node.ID] = node;
			return true;
		}

		IntPtr get_path_cb (int node_idx)
		{
			TreePath path = new TreePath ();
			int idx;

			ITreeNode node = node_hash [node_idx] as ITreeNode;
			if (node == null) throw new Exception ("Invalid Node ID");

			while (node.Parent != null) {
				idx = node.Parent.IndexOf (node);
				if (idx < 0) throw new Exception ("Badly formed tree");
				path.PrependIndex (idx);
				node = node.Parent;
			}
			idx = Nodes.IndexOf (node);
			if (idx < 0) throw new Exception ("Node not found in Nodes list");
			path.PrependIndex (idx);
			return path.Handle;
		}

		[DllImport("libgobject-2.0-0.dll")]
		static extern void g_value_init (ref GLib.Value val, IntPtr type);

		void get_value_cb (int node_idx, int col, ref GLib.Value val)
		{
			ITreeNode node = node_hash [node_idx] as ITreeNode;
			if (node == null)
				return;
			g_value_init (ref val, ctypes [col].Val);
			object col_val = getters[col].GetValue (node, null);
			val.Val = col_val;
		}

		bool next_cb (ref int node_idx)
		{
			ITreeNode node = node_hash [node_idx] as ITreeNode;
			if (node == null)
				return false;

			int idx;
			if (node.Parent == null)
				idx = Nodes.IndexOf (node);
			else
				idx = node.Parent.IndexOf (node);
			
			if (idx < 0) throw new Exception ("Node not found in Nodes list");

			if (node.Parent == null) {
				if (++idx >= Nodes.Count)
					return false;
				node = Nodes [idx] as ITreeNode;
			} else {
				if (++idx >= node.Parent.ChildCount)
					return false;
				node = node.Parent [idx];
			}
			node_hash [node.ID] = node;
			node_idx = node.ID;
			return true;
		}

		bool children_cb (out int child_idx, int parent)
		{
			child_idx = -1;
			ITreeNode node;

			if (parent == -1) {
				if (Nodes.Count <= 0)
					return false;
				node = Nodes [0] as ITreeNode;
				child_idx = node.ID;
				node_hash [node.ID] = node;
				return true;
			}
				
			node = node_hash [parent] as ITreeNode;
			if (node == null || node.ChildCount <= 0)
				return false;

			ITreeNode child = node [0];
			node_hash [child.ID] = child;
			child_idx = child.ID;
			return true;
		}

		bool has_child_cb (int node_idx)
		{
			ITreeNode node = node_hash [node_idx] as ITreeNode;
			if (node == null || node.ChildCount <= 0)
				return false;

			return true;
		}

		int n_children_cb (int node_idx)
		{
			if (node_idx == -1)
				return Nodes.Count;
				
			ITreeNode node = node_hash [node_idx] as ITreeNode;
			if (node == null || node.ChildCount <= 0)
				return 0;

			return node.ChildCount;
		}

		bool nth_child_cb (out int child_idx, int parent, int n)
		{
			child_idx = -1;
			ITreeNode node;

			if (parent == -1) {
				if (Nodes.Count <= n)
					return false;
				node = Nodes [n] as ITreeNode;
				child_idx = node.ID;
				node_hash [node.ID] = node;
				return true;
			}
				
			node = node_hash [parent] as ITreeNode;
			if (node == null || node.ChildCount <= n)
				return false;

			ITreeNode child = node [n];
			node_hash [child.ID] = child;
			child_idx = child.ID;
			return true;
		}

		bool parent_cb (out int parent_idx, int child)
		{
			parent_idx = -1;
			ITreeNode node = node_hash [child] as ITreeNode;
			if (node == null || node.Parent == null)
				return false;

			node_hash [node.Parent.ID] = node.Parent;
			parent_idx = node.Parent.ID;
			return true;
		}

		[DllImport("gtksharpglue-2.0")]
		static extern void gtksharp_node_store_set_tree_model_callbacks (IntPtr raw, ref TreeModelIfaceDelegates cbs);

		private void BuildTreeModelIface ()
		{
			tree_model_iface.get_flags = new GetFlagsDelegate (get_flags_cb);
			tree_model_iface.get_n_columns = new GetNColumnsDelegate (get_n_columns_cb);
			tree_model_iface.get_column_type = new GetColumnTypeDelegate (get_column_type_cb);
			tree_model_iface.get_node = new GetNodeDelegate (get_node_cb);
			tree_model_iface.get_path = new GetPathDelegate (get_path_cb);
			tree_model_iface.get_value = new GetValueDelegate (get_value_cb);
			tree_model_iface.next = new NextDelegate (next_cb);
			tree_model_iface.children = new ChildrenDelegate (children_cb);
			tree_model_iface.has_child = new HasChildDelegate (has_child_cb);
			tree_model_iface.n_children = new NChildrenDelegate (n_children_cb);
			tree_model_iface.nth_child = new NthChildDelegate (nth_child_cb);
			tree_model_iface.parent = new ParentDelegate (parent_cb);

			gtksharp_node_store_set_tree_model_callbacks (Handle, ref tree_model_iface);
		}

		[DllImport("gtksharpglue-2.0")]
		static extern IntPtr gtksharp_node_store_new ();

		public NodeStore (Type node_type) : base (IntPtr.Zero)
		{
			Raw = gtksharp_node_store_new ();
			ScanType (node_type);
			BuildTreeModelIface ();
		}

		void ScanType (Type type)
		{
			object[] attrs = type.GetCustomAttributes (false);
			foreach (object attr in attrs) {
				switch (attr.ToString ()) {
				case "Gtk.TreeNodeAttribute":
					TreeNodeAttribute tna = attr as TreeNodeAttribute;
					n_cols = tna.ColumnCount;
					break;
				default:
					Console.WriteLine ("Unknown attr: " + attr);
					break;
				}
			}

 			ctypes = new GLib.GType [n_cols];
 			getters = new PropertyInfo [n_cols];

			MemberInfo[] info = type.GetMembers ();
			foreach (MemberInfo mi in info) {
				PropertyInfo pi;
				object[] attr_info = mi.GetCustomAttributes (false);
				foreach (object attr in attr_info) {
					switch (attr.ToString ()) {
					case "Gtk.TreeNodeValueAttribute":
						TreeNodeValueAttribute tnva = attr as TreeNodeValueAttribute;
						int col = tnva.Column;
						pi = mi as PropertyInfo;
						getters [col] = pi;
						GLib.GType ctype = GLib.TypeConverter.LookupType (pi.PropertyType);
                                		if (ctype == GLib.GType.Invalid)
                                        		throw new Exception ("Unknown type");
                                        	ctypes[col] = ctype;
						break;
					default:
						Console.WriteLine ("Unknown custom attr: " + attr);
						break;
					}
				}
			}
		}
							
		private IList Nodes {
			get {
				return nodes as IList;
			}
		}							

		[DllImport("gtksharpglue-2.0")]
		static extern void gtksharp_node_store_emit_row_changed (IntPtr handle, IntPtr path, int node_idx);

		private void changed_cb (object o, EventArgs args)
		{
			ITreeNode node = o as ITreeNode;
			node_hash [node.ID] = node;

			gtksharp_node_store_emit_row_changed (Handle, get_path_cb (node.ID), node.ID);
		}

		[DllImport("gtksharpglue-2.0")]
		static extern void gtksharp_node_store_emit_row_inserted (IntPtr handle, IntPtr path, int node_idx);

		private void child_added_cb (object o, ITreeNode child)
		{
			node_hash [child.ID] = child;

			gtksharp_node_store_emit_row_inserted (Handle, get_path_cb (child.ID), child.ID);
		}

		[DllImport("gtksharpglue-2.0")]
		static extern void gtksharp_node_store_emit_row_deleted (IntPtr handle, IntPtr path);

		[DllImport("gtksharpglue-2.0")]
		static extern void gtksharp_node_store_emit_row_has_child_toggled (IntPtr handle, IntPtr path, int node_idx);

		private void child_deleted_cb (object o, int idx)
		{
			ITreeNode node = o as ITreeNode;
			
			TreePath path = new TreePath (get_path_cb (node.ID));
			TreePath child_path = path.Copy ();
			child_path.AppendIndex (idx);

			gtksharp_node_store_emit_row_deleted (Handle, child_path.Handle);

			if (node.ChildCount <= 0) {
				node_hash [node.ID] = node;
				gtksharp_node_store_emit_row_has_child_toggled (Handle, path.Handle, node.ID);
			}
		}

		private void ConnectNode (ITreeNode node)
		{
			node.Changed += new EventHandler (changed_cb);
			node.ChildAdded += new TreeNodeAddedHandler (child_added_cb);
			node.ChildRemoved += new TreeNodeRemovedHandler (child_deleted_cb);
		}

		public void AddNode (ITreeNode node)
		{
			nodes.Add (node);
			node_hash [node.ID] = node;
			ConnectNode (node);
			for (int i = 0; i < node.ChildCount; i++)
				ConnectNode (node [i]);

			gtksharp_node_store_emit_row_inserted (Handle, get_path_cb (node.ID), node.ID);
		}

		public void RemoveNode (ITreeNode node)
		{
			int idx = nodes.IndexOf (node);
			if (idx < 0)
				return;
			nodes.Remove (node);

			TreePath path = new TreePath ();
			path.AppendIndex (idx);

			gtksharp_node_store_emit_row_deleted (Handle, path.Handle);
		}

		private ITreeNode GetNodeAtPath (TreePath path)
		{
			int[] indices = path.Indices;

			if (indices[0] >= Nodes.Count)
				return null;

			ITreeNode node = Nodes [indices [0]] as ITreeNode;
			int i;
			for (i = 1; i < path.Depth; i++) {
				if (indices [i] >= node.ChildCount)
					return null;

				node = node [indices [i]];
			}

			return node;
		}

		public ITreeNode GetNode (TreePath path) {
			if (path == null)
				throw new ArgumentNullException ();

			return GetNodeAtPath (path);
		}

		[DllImport("gtksharpglue-2.0")]
		static extern IntPtr gtksharp_node_store_get_type ();
		
		public static new GLib.GType GType {
			get {
				return new GLib.GType (gtksharp_node_store_get_type ());
			}
		}
	}
}
