namespace DBus 
{
  
  using System;
  using System.Runtime.InteropServices;
  using System.Diagnostics;
  using System.Reflection;
  using System.IO;
  using System.Collections;
  
  public delegate int DBusHandleMessageFunction (IntPtr rawConnection,
						 IntPtr rawMessage,
						 IntPtr userData);
  internal delegate void DBusObjectPathUnregisterFunction(IntPtr rawConnection,
							  IntPtr userData);
  internal delegate int DBusObjectPathMessageFunction(IntPtr rawConnection,
						      IntPtr rawMessage,
						      IntPtr userData);
  [StructLayout (LayoutKind.Sequential)]
  internal struct DBusObjectPathVTable
  {
    public DBusObjectPathUnregisterFunction unregisterFunction;
    public DBusObjectPathMessageFunction messageFunction;
    public IntPtr padding1;
    public IntPtr padding2;
    public IntPtr padding3;
    public IntPtr padding4;
    
    public DBusObjectPathVTable(DBusObjectPathUnregisterFunction unregisterFunction,
				DBusObjectPathMessageFunction messageFunction) 
    {
      this.unregisterFunction = unregisterFunction;
      this.messageFunction = messageFunction;
      this.padding1 = IntPtr.Zero;
      this.padding2 = IntPtr.Zero;
      this.padding3 = IntPtr.Zero;
      this.padding4 = IntPtr.Zero;
    }
  }
  public class Connection : IDisposable
  {
    /// 
    /// A pointer to the underlying Connection structure
    /// 
    private IntPtr rawConnection;
    
    /// 
    /// The current slot number
    /// 
    private static int slot = -1;
    
    private int timeout = -1;
    private ArrayList filters = new ArrayList ();      // of DBusHandleMessageFunction
    private ArrayList matches = new ArrayList ();      // of string
    private Hashtable object_paths = new Hashtable (); // key: string  value: DBusObjectPathVTable
    internal Connection(IntPtr rawConnection)
    {
      RawConnection = rawConnection;
    }
    
    public Connection(string address)
    {
      // the assignment bumps the refcount
      Error error = new Error();
      error.Init();
      RawConnection = dbus_connection_open(address, ref error);
      if (RawConnection != IntPtr.Zero) {
	dbus_connection_unref(RawConnection);
      } else {
	throw new DBusException(error);
      }
      SetupWithMain();
    }
    public void Dispose() 
    {
      Dispose(true);
      GC.SuppressFinalize(this);
    }
    
    public void Dispose (bool disposing) 
    {
      if (disposing && RawConnection != IntPtr.Zero) 
	{
	  dbus_connection_disconnect(rawConnection);
	  RawConnection = IntPtr.Zero; // free the native object
	}
    }
    public void Flush()
    {
      dbus_connection_flush(RawConnection);
    }
    public void SetupWithMain() 
    {      
      dbus_connection_setup_with_g_main(RawConnection, IntPtr.Zero);
    }
    
    ~Connection () 
    {
      Dispose (false);
    }
    
    internal static Connection Wrap(IntPtr rawConnection) 
    {
      if (slot > -1) {
	// Maybe we already have a Connection object associated with
	// this rawConnection then return it
	IntPtr rawThis = dbus_connection_get_data (rawConnection, slot);
	if (rawThis != IntPtr.Zero && ((GCHandle)rawThis).Target == typeof(DBus.Connection)) {
	  return (DBus.Connection) ((GCHandle)rawThis).Target;
	}
      }
      
      // If it doesn't exist then create a new connection around it
      return new Connection(rawConnection);
    }
    public void AddFilter (DBusHandleMessageFunction func)
    {
      if (!dbus_connection_add_filter (RawConnection,
				       func,
				       IntPtr.Zero,
				       IntPtr.Zero))
        throw new OutOfMemoryException ();
      this.filters.Add (func);
    }
    public void RemoveFilter (DBusHandleMessageFunction func)
    {
      dbus_connection_remove_filter (RawConnection, func, IntPtr.Zero);
      this.filters.Remove (func);
    }
    public void AddMatch (string match_rule)
    {
      dbus_bus_add_match (RawConnection, match_rule, IntPtr.Zero);
      this.matches.Add (match_rule);
    }
    public void RemoveMatch (string match_rule)
    {
      dbus_bus_remove_match (RawConnection, match_rule, IntPtr.Zero);
      this.matches.Remove (match_rule);
    }
    internal void RegisterObjectPath (string path, DBusObjectPathVTable vtable)
    {
      if (!dbus_connection_register_object_path (RawConnection, path, ref vtable, IntPtr.Zero))
        throw new OutOfMemoryException ();
 
      this.object_paths[path] = vtable;
    }
 
    internal void UnregisterObjectPath (string path)
    {
      dbus_connection_unregister_object_path (RawConnection, path);
 
      this.object_paths.Remove (path);
    }
    public string UniqueName
    {
      get
	{
	  return Marshal.PtrToStringAnsi (dbus_bus_get_unique_name (RawConnection));
	}
    }
    public int Timeout
    {
      get
	{
	  return this.timeout;
	}
      set
	{
	  this.timeout = value;
	}
    }
    
    private int Slot
    {
      get 
	{
	  if (slot == -1) 
	    {
	      // We need to initialize the slot
	      if (!dbus_connection_allocate_data_slot (ref slot))
		throw new OutOfMemoryException ();
	      
	      Debug.Assert (slot >= 0);
	    }
	  
	  return slot;
	}
    }
    
    internal IntPtr RawConnection 
    {
      get 
	{
	  return rawConnection;
	}
      set 
	{
	  if (value == rawConnection)
	    return;
	  
	  if (rawConnection != IntPtr.Zero) 
	    {
              // Remove our callbacks from this connection
              foreach (DBusHandleMessageFunction func in this.filters)
                dbus_connection_remove_filter (rawConnection, func, IntPtr.Zero);
              foreach (string match_rule in this.matches)
                dbus_bus_remove_match (rawConnection, match_rule, IntPtr.Zero);
              foreach (string path in this.object_paths.Keys)
                dbus_connection_unregister_object_path (rawConnection, path);
	      // Get the reference to this
	      IntPtr rawThis = dbus_connection_get_data (rawConnection, Slot);
	      Debug.Assert (rawThis != IntPtr.Zero);
	      
	      // Blank over the reference
	      dbus_connection_set_data (rawConnection, Slot, IntPtr.Zero, IntPtr.Zero);
	      
	      // Free the reference
	      ((GCHandle) rawThis).Free();
	      
	      // Unref the connection
	      dbus_connection_unref(rawConnection);
	    }
	  
	  this.rawConnection = value;
	  
	  if (rawConnection != IntPtr.Zero) 
	    {
	      GCHandle rawThis;
	      
	      dbus_connection_ref (rawConnection);
	      
	      // We store a weak reference to the C# object on the C object
	      rawThis = GCHandle.Alloc (this, GCHandleType.WeakTrackResurrection);
	      
	      dbus_connection_set_data(rawConnection, Slot, (IntPtr) rawThis, IntPtr.Zero);
              // Add the callbacks to this new connection
              foreach (DBusHandleMessageFunction func in this.filters)
                dbus_connection_add_filter (rawConnection, func, IntPtr.Zero, IntPtr.Zero);
              foreach (string match_rule in this.matches)
                dbus_bus_add_match (rawConnection, match_rule, IntPtr.Zero);
              foreach (string path in this.object_paths.Keys) {
                DBusObjectPathVTable vtable = (DBusObjectPathVTable) this.object_paths[path];
                dbus_connection_register_object_path (rawConnection, path, ref vtable, IntPtr.Zero);
	      }
	    }
	  else
	    {
	      this.filters.Clear ();
              this.matches.Clear ();
	      this.object_paths.Clear ();
	    }
	}
    }
    [DllImport("dbus-glib-1")]
    private extern static void dbus_connection_setup_with_g_main(IntPtr rawConnection,
							     IntPtr rawContext);
    
    [DllImport ("dbus-1")]
    private extern static IntPtr dbus_connection_open (string address, ref Error error);
    
    [DllImport ("dbus-1")]
    private extern static void dbus_connection_unref (IntPtr ptr);
    
    [DllImport ("dbus-1")]
    private extern static void dbus_connection_ref (IntPtr ptr);
    
    [DllImport ("dbus-1")]
    private extern static bool dbus_connection_allocate_data_slot (ref int slot);
    
    [DllImport ("dbus-1")]
    private extern static void dbus_connection_free_data_slot (ref int slot);
    
    [DllImport ("dbus-1")]
    private extern static bool dbus_connection_set_data (IntPtr ptr,
							 int    slot,
							 IntPtr data,
							 IntPtr free_data_func);
    
    [DllImport ("dbus-1")]
    private extern static void dbus_connection_flush (IntPtr  ptr);
    
    [DllImport ("dbus-1")]
    private extern static IntPtr dbus_connection_get_data (IntPtr ptr,
							   int    slot);
    
    [DllImport ("dbus-1")]
    private extern static void dbus_connection_disconnect (IntPtr ptr);
    [DllImport ("dbus-1")]
    private extern static IntPtr dbus_bus_get_unique_name (IntPtr ptr);
    [DllImport("dbus-1")]
    private extern static bool dbus_connection_add_filter(IntPtr rawConnection,
							  DBusHandleMessageFunction filter,
							  IntPtr userData,
							  IntPtr freeData);
    [DllImport("dbus-1")]
    private extern static void dbus_connection_remove_filter(IntPtr rawConnection,
							     DBusHandleMessageFunction filter,
							     IntPtr userData);
    [DllImport("dbus-1")]
    private extern static void dbus_bus_add_match(IntPtr rawConnection,
						  string rule,
						  IntPtr erro);
    [DllImport("dbus-1")]
    private extern static void dbus_bus_remove_match(IntPtr rawConnection,
						     string rule,
						     IntPtr erro);
    [DllImport ("dbus-1")]
    private extern static bool dbus_connection_register_object_path (IntPtr rawConnection,
								     string path,
								     ref DBusObjectPathVTable vTable,
								     IntPtr userData);
    [DllImport ("dbus-1")]
    private extern static void dbus_connection_unregister_object_path (IntPtr rawConnection,
								       string path);
  }
}