using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;

namespace miew.Binding
{
	public interface IListTransactor<T>
	{
		void Insert(int index, T item);
		int Count { get; }
		int IndexOf(T item);
		void RemoveAt(int index);
		void Clear();
		T Get(int index);
		void Set(int index, T t);
		T New();
	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	///
	/// grouping of interfaces that enables maximum functionality of a bound datagrid
	///
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public interface IListItemBind<T> : IList<T>, IList, IBindingList, ICancelAddNew, IRaiseItemChangedEvents
	{
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	///
	/// BindingListHelper(T) : support rich in-situ editing of data sources by intermediating on-the-fly between a 
	/// ListTransactor(T) and the several interfaces which consumers may use for editing.
	///
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public class ListItemBindHelper<T> : IListItemBind<T>
	{
		readonly IListTransactor<T> m_lt;
		readonly Object sync_root = new Object();
		readonly bool f_allow_new;
		readonly bool f_fixed_size = false;

		/// a singleton pending item
		T adding = default(T);

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		/// Constructors
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

		public ListItemBindHelper(IList<T> lt, Func<T> create_new)
			: this(new ListAdapter(lt, create_new))
		{
			this.f_allow_new = (create_new != null);
			this.f_fixed_size = lt is T[] || lt is System.Array;
		}

		public ListItemBindHelper(IEnumerable<T> lt)
			: this(lt.ToArray(), null)
		{
		}

		public ListItemBindHelper(IListTransactor<T> lt)
		{
			this.m_lt = lt;
			this.f_allow_new = true;

			if (typeof(INotifyPropertyChanged).IsAssignableFrom(typeof(T)))
			{
				pceh = new PropertyChangedEventHandler(ItemPropertyChanged);
				pdc = TypeDescriptor.GetProperties(typeof(T));

				foreach (T t in this)
					AttachPropertyChanged(t);
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		/// Item change notification
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

		PropertyChangedEventHandler pceh = null;
		PropertyDescriptorCollection pdc = null;
		int ix_mru = -1;

		void AttachPropertyChanged(T item)
		{
			if (pceh != null)
			{
				INotifyPropertyChanged inpc = item as INotifyPropertyChanged;
				if (inpc != null)
					inpc.PropertyChanged += pceh;
			}
		}

		void DetachPropertyChanged(T item)
		{
			if (pceh != null)
			{
				INotifyPropertyChanged inpc = (item as INotifyPropertyChanged);
				if (inpc != null)
					inpc.PropertyChanged -= pceh;
			}
		}

		void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			if (sender == null || e == null || string.IsNullOrEmpty(e.PropertyName) || !(sender is T))
			{
				_raise_reset_event();
				return;
			}

			T item = (T)sender;

			if (ix_mru < 0 || ix_mru >= m_lt.Count || !Object.Equals(m_lt.Get(ix_mru), item))
				ix_mru = m_lt.IndexOf(item);

			if (ix_mru == -1)
			{
				DetachPropertyChanged(item);
				_raise_reset_event();
				return;
			}

			PropertyDescriptor pd = pdc.Find(e.PropertyName, true);
			_raise_changed_event(new ListChangedEventArgs(ListChangedType.ItemChanged, ix_mru, pd));
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		/// internal
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

		void _insert(int index, T item)
		{
			EndNew(0);

			if (f_fixed_size)
				return;

			m_lt.Insert(index, item);

			AttachPropertyChanged(item);

			_raise_changed_event(new ListChangedEventArgs(ListChangedType.ItemAdded, index));
		}

		int _index_of(T item)
		{
#if true
			return m_lt.IndexOf(item);
#else
			int ix = m_lt.IndexOf(item);
			if (ix == -1 && !Object.Equals(adding, default(T)) && Object.Equals(adding, item))
				ix = m_lt.Count;
			return ix;
#endif
		}

		void _remove_at(int index)
		{
			EndNew(0);

			if (f_fixed_size)
				return;

			if (pceh != null)
				DetachPropertyChanged(_get(index));

			m_lt.RemoveAt(index);

			_raise_changed_event(new ListChangedEventArgs(ListChangedType.ItemDeleted, index));
		}

		T _get(int index)
		{
			return m_lt.Get(index);
		}

		void _set(int index, T value)
		{
			// EndNew(0) ???
			if (pceh != null)
				DetachPropertyChanged(_get(index));

			m_lt.Set(index, value);

			AttachPropertyChanged(value);

			_raise_changed_event(new ListChangedEventArgs(ListChangedType.ItemChanged, index));
		}

		void _clear()
		{
			EndNew(0);

			if (f_fixed_size)
				return;

			foreach (T t in this)
				DetachPropertyChanged(t);

			m_lt.Clear();
			_raise_reset_event();
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		/// IList(T)
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

		public void Add(T item)
		{
			_insert(m_lt.Count, item);
		}

		public void Insert(int index, T item)
		{
			_insert(index, item);
		}

		public T this[int index]
		{
			get { return _get(index); }
			set { _set(index, (T)value); }
		}

		public bool Contains(T item)
		{
			return _index_of(item) != -1;
		}

		public bool Remove(T item)
		{
			int ix = m_lt.IndexOf(item);
			if (ix == -1 && !Object.Equals(adding, default(T)) && Object.Equals(adding, item))
				EndNew(0);
			else
				_remove_at(ix);
			return true;
		}

		public void Clear()
		{
			_clear();
		}

		public int IndexOf(T item)
		{
			return _index_of(item);
		}

		public void RemoveAt(int index)
		{
			_remove_at(index);
		}

		public int Count
		{
			get
			{
#if true
				return m_lt.Count;
#else
				int c = m_lt.Count;
				if (!Object.Equals(adding,default(T)))
					c++;
				return c;
#endif
			}
		}

		public bool IsReadOnly { get { return false; } }

		public void CopyTo(T[] array, int index)
		{
			foreach (T t in this)
				array[index++] = t;
		}

		public IEnumerator<T> GetEnumerator()
		{
			int c = m_lt.Count;
			for (int i = 0; i < c; i++)
				yield return _get(i);
		}

		IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		/// IList
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

		public int Add(object value)
		{
			if (!(value is T))
				return -1;
			int c = m_lt.Count;
			_insert(c, (T)value);
			return c;
		}

		object IList.this[int index]
		{
			get { return _get(index); }
			set { _set(index, (T)value); }
		}

		public bool Contains(object value)
		{
			return value is T ? _index_of((T)value) != -1 : false;
		}

		public int IndexOf(object value)
		{
			return value is T ? _index_of((T)value) : -1;
		}

		public void Insert(int index, object value)
		{
			if (value is T)
				_insert(index, (T)value);
		}

		public void Remove(object value)
		{
			int ix = m_lt.IndexOf((T)value);
			if (ix == -1 && !Object.Equals(adding, default(T)) && Object.Equals(adding, value))
				EndNew(0);
			else
				_remove_at(ix);
		}

		public bool IsFixedSize { get { return f_fixed_size; } }

		public void CopyTo(System.Array array, int index)
		{
			Object[] rgo = (Object[])array;
			foreach (T t in this)
				rgo[index++] = t;
		}

		public bool IsSynchronized { get { return false; } }

		public object SyncRoot { get { return sync_root; } }

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		/// IRaiseItemChangedEvents
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

		public bool RaisesItemChangedEvents { get { return this.pceh != null; } }

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		/// IBindingList event
		///
		/// Reset  Much of the list has changed. Any listening controls should refresh all their data from the list.  
		/// ItemAdded  An item added to the list. NewIndex contains the index of the item that was added.  
		/// ItemDeleted  An item deleted from the list. NewIndex contains the index of the item that was deleted.  
		/// ItemMoved  An item moved within the list.
		/// ItemChanged  An item changed in the list. NewIndex contains the index of the item that was changed.  
		/// PropertyDescriptorAdded  A PropertyDescriptor was added, which changed the schema.  
		/// PropertyDescriptorDeleted  A PropertyDescriptor was deleted, which changed the schema.  
		/// PropertyDescriptorChanged  A PropertyDescriptor was changed, which changed the schema.  
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

		public event ListChangedEventHandler ListChanged;

		void _raise_changed_event(ListChangedEventArgs e)
		{
			ListChangedEventHandler h = ListChanged;
			if (h != null)
				h.Invoke(this, e);
		}

		void _raise_reset_event()
		{
			_raise_changed_event(new ListChangedEventArgs(ListChangedType.Reset, -1));
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		/// IBindingList members
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

		public object AddNew()
		{
			if (!f_allow_new)
				throw new InvalidOperationException("Cannot add a new item with this binding because it was constructed using an IList");

			EndNew(0);
			return adding = _raise_add_new();
		}

		public bool AllowEdit { get { return true; } }

		public bool AllowNew { get { return f_allow_new; } }

		public bool AllowRemove { get { return true; } }

		public bool SupportsChangeNotification { get { return true; } }

		public bool SupportsSorting { get { return true; } }

		public bool SupportsSearching { get { return true; } }

		public void AddIndex(PropertyDescriptor property)
		{
			throw new NotImplementedException();
		}

		public void RemoveIndex(PropertyDescriptor property)
		{
			throw new NotImplementedException();
		}

		public int Find(PropertyDescriptor property, object key)
		{
			throw new NotImplementedException();
		}

		public void ApplySort(PropertyDescriptor property, ListSortDirection direction)
		{
			throw new NotImplementedException();
		}

		public void RemoveSort()
		{
			throw new NotImplementedException();
		}

		public bool IsSorted
		{
			get { throw new NotImplementedException(); }
		}

		public ListSortDirection SortDirection
		{
			get { throw new NotImplementedException(); }
		}

		public PropertyDescriptor SortProperty
		{
			get { throw new NotImplementedException(); }
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		/// AddingNew event : not from an interface
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

		public event AddingNewEventHandler AddingNew;

		T _raise_add_new()
		{
			AddingNewEventHandler h = AddingNew;
			if (h != null)
			{
				AddingNewEventArgs e = new AddingNewEventArgs();
				h.Invoke(this, e);
				return (T)e.NewObject;
			}

			T t = m_lt.New();
			if (Object.Equals(t, default(T)))
				throw new InvalidOperationException();
			return t;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		/// ICancelAddNew
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

		public void CancelNew(int itemIndex)
		{
			adding = default(T);
		}

		public void EndNew(int itemIndex)
		{
			if (!Object.Equals(adding, default(T)))
			{
				T tmp = adding;
				adding = default(T);
				_insert(m_lt.Count, tmp);
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		/// IList-to-IListTransactor adapter
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

		class ListAdapter : IListTransactor<T>
		{
			readonly IList<T> list;
			readonly Func<T> create_new;
			public ListAdapter(IList<T> list, Func<T> create_new)
			{
				this.list = list;
				this.create_new = create_new;
			}

			public void Insert(int index, T item) { list.Insert(index, item); }
			public int Count { get { return list.Count; } }
			public int IndexOf(T item) { return list.IndexOf(item); }
			public void RemoveAt(int index) { list.RemoveAt(index); }
			public void Clear() { list.Clear(); }
			public T Get(int index) { return list[index]; }
			public void Set(int index, T t) { list[index] = t; }
			public T New() { return create_new(); }
		};

	};
}