// ValueArray.cs - ValueArray wrapper implementation
//
// Authors: 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 GLib {

	using System;
	using System.Collections;
	using System.Collections.Generic;
	using System.Runtime.InteropServices;

	public class ValueArray : IDisposable, ICollection, ICloneable, IWrapper {

		private IntPtr handle = IntPtr.Zero;

		static private IList<IntPtr> PendingFrees = new List<IntPtr> ();
		static private bool idle_queued = false;

		[DllImport (Global.GObjectNativeDll, CallingConvention = CallingConvention.Cdecl)]
		static extern IntPtr g_value_array_new (uint n_preallocs);

		public ValueArray (uint n_preallocs)
		{
			handle = g_value_array_new (n_preallocs);
		}

		public ValueArray (IntPtr raw)
		{
			handle = raw;
		}
		
		~ValueArray ()
		{
			Dispose (false);
		}
		
		// IDisposable
		public void Dispose ()
		{
			Dispose (true);
			GC.SuppressFinalize (this);
		}

		[DllImport (Global.GObjectNativeDll, CallingConvention = CallingConvention.Cdecl)]
		static extern void g_value_array_free (IntPtr raw);

		void Dispose (bool disposing)
		{
			if (Handle == IntPtr.Zero)
				return;

			lock (PendingFrees) {
				PendingFrees.Add (handle);

				if (! idle_queued) {
					Timeout.Add (50, new TimeoutHandler (PerformFrees));
					idle_queued = true;
				}
			}

			handle = IntPtr.Zero;
		}

		static bool PerformFrees ()
		{
			IntPtr[] handles;

			lock (PendingFrees) {
				idle_queued = false;

				handles = new IntPtr [PendingFrees.Count];
				PendingFrees.CopyTo (handles, 0);
				PendingFrees.Clear ();
			}

			foreach (IntPtr h in handles)
				g_value_array_free (h);

			return false;
		}
		
		public IntPtr Handle {
			get {
				return handle;
			}
		}

		struct NativeStruct {
			public uint n_values;
			public IntPtr values;
			public uint n_prealloced;
		}

		NativeStruct Native {
			get { return (NativeStruct) Marshal.PtrToStructure (Handle, typeof(NativeStruct)); }
		}

		public IntPtr ArrayPtr {
			get { return Native.values; }
		}

		[DllImport (Global.GObjectNativeDll, CallingConvention = CallingConvention.Cdecl)]
		static extern void g_value_array_append (IntPtr raw, ref GLib.Value val);

		public void Append (GLib.Value val)
		{
			g_value_array_append (Handle, ref val);
		}

		[DllImport (Global.GObjectNativeDll, CallingConvention = CallingConvention.Cdecl)]
		static extern void g_value_array_insert (IntPtr raw, uint idx, ref GLib.Value val);

		public void Insert (uint idx, GLib.Value val)
		{
			g_value_array_insert (Handle, idx, ref val);
		}

		[DllImport (Global.GObjectNativeDll, CallingConvention = CallingConvention.Cdecl)]
		static extern void g_value_array_prepend (IntPtr raw, ref GLib.Value val);

		public void Prepend (GLib.Value val)
		{
			g_value_array_prepend (Handle, ref val);
		}

		[DllImport (Global.GObjectNativeDll, CallingConvention = CallingConvention.Cdecl)]
		static extern void g_value_array_remove (IntPtr raw, uint idx);

		public void Remove (uint idx)
		{
			g_value_array_remove (Handle, idx);
		}

		// ICollection
		public int Count {
			get { return (int) Native.n_values; }
		}

		[DllImport (Global.GObjectNativeDll, CallingConvention = CallingConvention.Cdecl)]
		static extern IntPtr g_value_array_get_nth (IntPtr raw, uint idx);

		public object this [int index] { 
			get { 
				IntPtr raw_val = g_value_array_get_nth (Handle, (uint) index);
				return Marshal.PtrToStructure (raw_val, typeof (GLib.Value));
			}
		}

		// Synchronization could be tricky here. Hmm.
		public bool IsSynchronized {
			get { return false; }
		}

		public object SyncRoot {
			get { return null; }
		}

		public void CopyTo (Array array, int index)
		{
			if (array == null)
				throw new ArgumentNullException ("Array can't be null.");

			if (index < 0)
				throw new ArgumentOutOfRangeException ("Index must be greater than 0.");

			if (index + Count < array.Length)
				throw new ArgumentException ("Array not large enough to copy into starting at index.");
			
			for (int i = 0; i < Count; i++)
				((IList) array) [index + i] = this [i];
		}

		private class ListEnumerator : IEnumerator
		{
			private int current = -1;
			private ValueArray vals;

			public ListEnumerator (ValueArray vals)
			{
				this.vals = vals;
			}

			public object Current {
				get {
					if (current == -1)
						return null;
					return vals [current];
				}
			}

			public bool MoveNext ()
			{
				if (++current >= vals.Count) {
					current = -1;
					return false;
				}

				return true;
			}

			public void Reset ()
			{
				current = -1;
			}
		}
		
		// IEnumerable
		public IEnumerator GetEnumerator ()
		{
			return new ListEnumerator (this);
		}

		[DllImport (Global.GObjectNativeDll, CallingConvention = CallingConvention.Cdecl)]
		static extern IntPtr g_value_array_copy (IntPtr raw);

		// ICloneable
		public object Clone ()
		{
			return new ValueArray (g_value_array_copy (Handle));
		}

		[DllImport (Global.GObjectNativeDll, CallingConvention = CallingConvention.Cdecl)]
		static extern IntPtr g_value_array_get_type ();

		public static GLib.GType GType {
			get {
				return new GLib.GType (g_value_array_get_type ());
			}
		}
	}
}
