namespace GConf
{
	using System;
	using System.Collections;
	using System.Runtime.InteropServices;

	internal enum ValueType
	{
		Invalid,
		String,
		Int,
		Float,
		Bool,
		Schema,
		List,
		Pair
	};

	internal struct NativeValue
	{
		public ValueType type;
	}

	internal class InvalidValueTypeException : Exception
	{
	}
	
	internal class Value : IDisposable  
	{
		IntPtr Raw = IntPtr.Zero;
		ValueType val_type = ValueType.Invalid;
		bool managed = true;
		
		[DllImport("gconf-2")]
		static extern void gconf_value_set_string (IntPtr value, string data);
		[DllImport("gconf-2")]
		static extern void gconf_value_set_int (IntPtr value, int data);
		[DllImport("gconf-2")]
		static extern void gconf_value_set_float (IntPtr value, double data);
		[DllImport("gconf-2")]
		static extern void gconf_value_set_bool (IntPtr value, bool data);

		ValueType LookupType (object data)
		{
			if (data is string) {
				return ValueType.String;
			} else if (data is int) {
				return ValueType.Int;
			} else if (data is double) {
				return ValueType.Float;
			} else if (data is bool) {
				return ValueType.Bool;
			} else if (data is ICollection) {
				return ValueType.List;
			} else {
				return ValueType.Invalid;
			}
		}

		public void Set (object data)
		{
			if (data == null)
				throw new NullReferenceException ();
			
			ValueType type = LookupType (data);
			Set (data, type);
		}

		[DllImport("gconf-2")]
		static extern IntPtr gconf_value_set_list_nocopy (IntPtr value, IntPtr list);
		
		[DllImport("gconf-2")]
		static extern IntPtr gconf_value_set_list_type (IntPtr value, ValueType vtype);
		
		void Set (object data, ValueType type)
		{
			if (data == null)
				throw new NullReferenceException ();

			switch (type)
			{
				case ValueType.String:
					gconf_value_set_string (Raw, (string) data);
					break;
				case ValueType.Int:
					gconf_value_set_int (Raw, (int) data);
					break;
				case ValueType.Float:
					gconf_value_set_float (Raw, (double) data);
					break;
				case ValueType.Bool:
					gconf_value_set_bool (Raw, (bool) data);
					break;
				case ValueType.List:
					ValueType listType;
					GLib.SList list = GetListFromCollection ((ICollection) data, out listType);
					gconf_value_set_list_type (Raw, listType);
					gconf_value_set_list_nocopy (Raw, list.Handle);
					break;
				default:
					throw new InvalidValueTypeException ();
			}
		}
		
		GLib.SList GetListFromCollection (ICollection data, out ValueType listType)
		{
			object [] arr = (object []) Array.CreateInstance (typeof (object), data.Count);
			data.CopyTo (arr, 0);

			listType = ValueType.Invalid;
			GLib.SList list = new GLib.SList (IntPtr.Zero);
			GC.SuppressFinalize (list);

			foreach (object o in arr) {
				ValueType type = LookupType (o);
				if (listType == ValueType.Invalid)
					listType = type;

				if (listType == ValueType.Invalid || type != listType)
					throw new InvalidValueTypeException ();

				Value v = new Value (o);
				GC.SuppressFinalize (v);
				list.Append (v.Raw);
			}
			
			return list;
		}

		[DllImport("gconf-2")]
		static extern IntPtr gconf_value_get_string (IntPtr value);
		
		[DllImport("gconf-2")]
		static extern int gconf_value_get_int (IntPtr value);
		
		[DllImport("gconf-2")]
		static extern double gconf_value_get_float (IntPtr value);
		
		[DllImport("gconf-2")]
		static extern bool gconf_value_get_bool (IntPtr value);
		
		[DllImport("gconf-2")]
		static extern IntPtr gconf_value_get_list (IntPtr value);
		
		public object Get ()
		{
			switch (val_type)
			{
				case ValueType.String:
					return Marshal.PtrToStringAnsi (gconf_value_get_string (Raw));
				case ValueType.Int:
					return gconf_value_get_int (Raw);
				case ValueType.Float:
					return gconf_value_get_float (Raw);
				case ValueType.Bool:
					return gconf_value_get_bool (Raw);
				case ValueType.List:
					GLib.SList list = new GLib.SList (gconf_value_get_list (Raw), typeof (Value));
					Array result = Array.CreateInstance (GetListType (), list.Count);
					int i = 0;
					foreach (Value v in list) {
						((IList) result) [i] =  v.Get ();
						v.managed = false; // This is the trick to prevent a crash
						i++;
					}

					return result;
				default:
					throw new InvalidValueTypeException ();
			}
		}

		[DllImport("gconf-2")]
		static extern ValueType gconf_value_get_list_type (IntPtr value);

		Type GetListType ()
		{
			ValueType vt = gconf_value_get_list_type (Raw);
			switch (vt) {
			case ValueType.String:
				return typeof (string);
			case ValueType.Int:
				return typeof (int);
			case ValueType.Float:
				return typeof (float);
			case ValueType.Bool:
				return typeof (bool);
			case ValueType.List:
				return typeof (GLib.SList);
			default:
				throw new InvalidValueTypeException ();
			}
		}

		[DllImport("gconf-2")]
		static extern IntPtr gconf_value_new (ValueType type);
		
		public Value (ValueType type)
		{
			Raw = gconf_value_new (type);
		}

		void Initialize (object val, ValueType type)
		{
			Raw = gconf_value_new (type);
			val_type = type;
			Set (val, type);	
		}

		public Value (IntPtr raw)
		{
			Raw = raw;
			NativeValue val = (NativeValue) Marshal.PtrToStructure (raw, typeof (NativeValue));
			val_type = val.type;
		}
/*
		public Value (string val)
		{
			Initialize (val, ValueType.String);
		}

		public Value (int val)
		{
			Initialize (val, ValueType.Int);
		}
		
		public Value (double val)
		{
			Initialize (val, ValueType.Float);
		}

		public Value (bool val)
		{
			Initialize (val, ValueType.Bool);
		}
*/
		public Value (object val)
		{
			Initialize (val, LookupType (val));
		}
		public bool Managed
		{
			get { return managed; }
			set { managed = value; }
		}

		[DllImport("gconf-2")]
		static extern void gconf_value_free (IntPtr value);
		
		~Value ()
		{
			Dispose (false);
		}
		
		public void Dispose ()
		{
			Dispose (true);
			GC.SuppressFinalize (this);
		}

		protected virtual void Dispose (bool disposing)
		{
			if (managed && Raw != IntPtr.Zero)
			{
				gconf_value_free (Raw);
				Raw = IntPtr.Zero;
			}
		}

		public IntPtr Handle
		{
			get {
				return Raw;
			}
		}
	}
}