//
// System.Data.DataView.cs
//
// Author:
//    Daniel Morgan <danmorg@sc.rr.com>
//    Tim Coleman (tim@timcoleman.com)
//
// Copyright (C) Daniel Morgan, 2002, 2003
// (C) Ximian, Inc 2002
// Copyright (C) Tim Coleman, 2002-2003
//

using System;
using System.Collections;
using System.ComponentModel;
using System.Reflection;

namespace System.Data 
{
	/// <summary>
	/// A DataView is used in the binding of data between
	/// a DataTable and Windows Forms or Web Forms allowing
	/// a view of a DataTable for editing, filtering,
	/// navigation, searching, and sorting.
	/// </summary>
	//[Designer]
	[Editor]
	[DefaultEvent ("PositionChanged")]
	[DefaultProperty ("Table")]
	public class DataView : MarshalByValueComponent, IBindingList, IList, ICollection, IEnumerable, ITypedList, ISupportInitialize
	{
		DataTable dataTable = null;
		string rowFilter = "";
		string sort = "";
		DataViewRowState rowState;
		DataRowView[] rowCache = null;
		
		// FIXME: what are the default values?
		bool allowNew = true; 
		bool allowEdit = true;
		bool allowDelete = true;
		bool applyDefaultSort = false;
		bool isSorted = false;

		bool isOpen = false;

		bool bInit = false;
		
		internal DataViewManager dataViewManager = null;

		public DataView () 
		{
			dataTable = new DataTable ();
			rowState = DataViewRowState.CurrentRows;
			Open ();
		}

		public DataView (DataTable table) 
		{
			dataTable = table;
			rowState = DataViewRowState.CurrentRows;
			Open ();
		}

		public DataView (DataTable table, string RowFilter,
				string Sort, DataViewRowState RowState) 
		{
			dataTable = table;
			rowState = DataViewRowState.CurrentRows;
			rowFilter = RowFilter;
			sort = Sort;
			rowState = RowState;
			Open();
		}

		[DataCategory ("Data")]
		[DataSysDescription ("Indicates whether this DataView and the user interface associated with it allows deletes.")]
		[DefaultValue (true)]
		public bool AllowDelete {
			[MonoTODO]
			get {
				return allowDelete;
			}
			
			[MonoTODO]
			set {
				allowDelete = value;
			}
		}

		[DataCategory ("Data")]
		[DataSysDescription ("Indicates whether this DataView and the user interface associated with it allows edits.")]
		[DefaultValue (true)]
		public bool AllowEdit {
			[MonoTODO]
			get {
				return allowEdit;
			}
			
			[MonoTODO]
			set {
				allowEdit = value;
			}
		}

		[DataCategory ("Data")]
		[DataSysDescription ("Indicates whether this DataView and the user interface associated with it allows new rows to be added.")]
		[DefaultValue (true)]
		public bool AllowNew {
			[MonoTODO]
			get {
				return allowNew;
			}
			
			[MonoTODO]
			set {
				allowNew = value;
			}
		}

		[DataCategory ("Data")]
		[DataSysDescription ("Indicates whether to use the default sort if the Sort property is not set.")]
		[DefaultValue (false)]
		[RefreshProperties (RefreshProperties.All)]
		public bool ApplyDefaultSort {
			[MonoTODO]
			get {				
				return applyDefaultSort;
			}
			
			[MonoTODO]
			set {
				applyDefaultSort = value;
				// FIXME: update the index cache to the DataTable, and 
				//        only refresh the index when the DataTable
				//        has changes via column, row, or constraint
				//        changed events
				UpdateIndex ();
			}
		}

		// get the count of rows in the DataView after RowFilter 
		// and RowStateFilter have been applied
		[Browsable (false)]
		[DataSysDescription ("Returns the number of items currently in this view.")]
		public int Count {
			[MonoTODO]
			get {
				// FIXME: remove this line once collection change
				//        events from the DataTable are handled
				UpdateIndex ();

				return rowCache.Length;;
			}
		}

		[Browsable (false)]
		[DataSysDescription ("This returns a pointer to back to the DataViewManager that owns this DataSet (if any).")]
		public DataViewManager DataViewManager {
			[MonoTODO]
			get {
				return dataViewManager;
			}
		}

		// Item indexer
		// the compiler creates a DefaultMemeberAttribute from
		// this IndexerNameAttribute
		[System.Runtime.CompilerServices.IndexerName("Item")]
		public DataRowView this[int recordIndex] {
			[MonoTODO]
			get {
				// FIXME: use index cache to the DataTable, and 
				//        only refresh the index when the DataTable
				//        has changes via column, row, or constraint
				//        changed events
				// Remove this line once changed events are handled
				UpdateIndex ();
				
				return rowCache[recordIndex];
			}
		}

		[DataCategory ("Data")]
		[DataSysDescription ("Indicates an expression used to filter the data returned by this DataView.")]
		[DefaultValue ("")]
		public virtual string RowFilter {
			[MonoTODO]
			get {
				return rowFilter;
			}
			
			[MonoTODO]
			set {
				rowFilter = value;
				// FIXME: update the index cache to the DataTable, and 
				//        only refresh the index when the DataTable
				//        has changes via column, row, or constraint
				//        changed events
				UpdateIndex ();
			}
		}

		[DataCategory ("Data")]
		[DataSysDescription ("Indicates the versions of data returned by this DataView.")]
		[DefaultValue (DataViewRowState.CurrentRows)]
		public DataViewRowState RowStateFilter {
			[MonoTODO]
			get {
				return rowState;
			}
			
			[MonoTODO]
			set {
				rowState = value;
				// FIXME: update the index cache to the DataTable, and 
				//        only refresh the index when the DataTable
				//        has changes via column, row, or constraint
				//        changed events
				UpdateIndex ();
			}
		}

		[DataCategory ("Data")]
		[DataSysDescription ("Indicates the order in which data is returned by this DataView.")]
		[DefaultValue ("")]
		public string Sort {
			[MonoTODO]
			get {
				return sort;
			}
			
			[MonoTODO]
			set {
				sort = value;
				// FIXME: update the index cache to the DataTable, and 
				//        only refresh the index when the DataTable
				//        has changes via column, row, or constraint
				//        changed events
				UpdateIndex ();
			}
		}

		[DataCategory ("Data")]
		[DataSysDescription ("Indicates the table this DataView uses to get data.")]
		[DefaultValue (null)]
		[RefreshProperties (RefreshProperties.All)]
		public DataTable Table {
			[MonoTODO]
			get {
				return dataTable;
			}
			
			[MonoTODO]
			set {
				dataTable = value;
				// FIXME: update the index cache to the DataTable, and 
				//        only refresh the index when the DataTable
				//        has changes via column, row, or constraint
				//        changed events
				UpdateIndex ();
			}
		}

		[MonoTODO]
		public virtual DataRowView AddNew() 
		{
			if (!IsOpen)
				throw new DataException ("DataView is not open.");
			if (!AllowNew)
				throw new DataException ("Cannot call AddNew on a DataView where AllowNew is false.");
			
			DataRow row = dataTable.NewRow ();
			DataRowView view = new DataRowView (this, row);
			
			dataTable.Rows.Add (row);

			UpdateIndex ();
			OnListChanged (new ListChangedEventArgs (ListChangedType.ItemAdded, rowCache.Length));
			return view;
		}

		[MonoTODO]
		public void BeginInit() 
		{
			bInit = true; 
			// FIXME:
		}

		[MonoTODO]
		public void CopyTo (Array array, int index) 
		{
			// FIXME: use index cache to the DataTable, and 
			//        only refresh the index when the DataTable
			//        has changes via column, row, or constraint
			//        changed events
			UpdateIndex ();
			
			int row = 0;
			for (; row < rowCache.Length && row < array.Length; row++) {
				array.SetValue (rowCache[row], index + row);
			}
			if (row < array.Length) {
				for (int r = 0; r < array.Length; r++) {
					array.SetValue (null, index + r);
				}
			}
		}

		public void Delete(int index) 
		{
			if (!IsOpen)
				throw new DataException ("DataView is not open.");
			if (!AllowDelete)
				throw new DataException ("Cannot delete on a DataSource where AllowDelete is false.");
			
			UpdateIndex ();

			if (index > rowCache.Length)
				throw new IndexOutOfRangeException ("There is no row at " +
						"position: " + index + ".");
			
			DataRowView row = rowCache [index];
			row.Row.Delete ();

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

#if NET_1_2
		[MonoTODO]
		public virtual bool Equals (DataView dv)
		{
			throw new NotImplementedException ();
		}
#endif

		[MonoTODO]
		public void EndInit() 
		{
			bInit = false;
			// FIXME:
		}

		[MonoTODO]
		public int Find(object key) 
		{
			// FIXME: use index cache to the DataTable, and 
			//        only refresh the index when the DataTable
			//        has changes via column, row, or constraint
			//        changed events

			throw new NotImplementedException ();
		}

		[MonoTODO]
		public int Find(object[] key) 
		{
			// FIXME: use an index cache to the DataTable, and 
			//        only refresh the index when the DataTable
			//        has changes via column, row, or constraint
			//        changed events

			throw new NotImplementedException ();
		}

		[MonoTODO]
		public DataRowView[] FindRows(object key) 
		{
			// FIXME: use an index cache to the DataTable, and 
			//        only refresh the index when the DataTable
			//        has changes via column, row, or constraint
			//        changed events

			throw new NotImplementedException ();
		}

		[MonoTODO]
		public DataRowView[] FindRows(object[] key) 
		{
			// FIXME: use an index cache to the DataTable, and 
			//        only refresh the index when the DataTable
			//        has changes via column, row, or constraint
			//        changed events
			
			throw new NotImplementedException ();
		}

		[MonoTODO]
		public IEnumerator GetEnumerator() 
		{
			// FIXME: use an index cache to the DataTable, and 
			//        only refresh the index when the DataTable
			//        has changes via column, row, or constraint
			//        changed events
			UpdateIndex ();					

			return new DataViewEnumerator (rowCache);
		}
		
		[MonoTODO]
		[DataCategory ("Data")]
		[DataSysDescription ("Indicates the data returned by this DataView has somehow changed.")]
		public event ListChangedEventHandler ListChanged;

		protected bool IsOpen {
			[MonoTODO]
			get {
				return isOpen;
			}
		}

		[MonoTODO]
		protected void Close() 
		{
			// FIXME:
			isOpen = false;
		}

		[MonoTODO]
		protected virtual void ColumnCollectionChanged (object sender, 
							CollectionChangeEventArgs e) 
		{
			throw new NotImplementedException ();
		}

		protected override void Dispose (bool disposing) 
		{
			if (disposing)
				Close ();

			base.Dispose (disposing);
		}

		[MonoTODO]
		protected virtual void IndexListChanged(object sender, ListChangedEventArgs e) 
		{
			throw new NotImplementedException ();
		}

		[MonoTODO]
		protected virtual void OnListChanged(ListChangedEventArgs e) 
		{
			try {
				if (ListChanged != null)
					ListChanged (this, e);
			} catch {
			}
		}

		[MonoTODO]
		protected void Open() 
		{
			// FIXME: create the initial index cache to the DataTable, and 
			//        only refresh the index when the DataTable
			//        has changes via column, row, or constraint
			//        changed events. the index cache is generally
			//        a DataViewRow array that points to the actual
			//        DataRows in the this DataTable's DataRowCollection;
			//        this index is really a cache that gets 
			//        created during Open(), gets Updated 
			//        when various properties of this view
			//        changes, gets Updated when this DataTable's 
			//        row, column, or constraint collections have changed.
			//        I'm not sure what else.
			//        The data view will know one of the DataTable's
			//        collections have changed via one of 
			//        its changed events.
			//        Otherwise, if getting a/the DataRowView(s),
			//        Count, or other properties, then just use the
			//        index cache.
			UpdateIndex (true);
			isOpen = true;
		}

		// internal use by Mono
		protected void Reset() 
		{
			// TODO: what really happens?
			Close ();
			rowCache = null;
			Open ();
			OnListChanged (new ListChangedEventArgs (ListChangedType.Reset, -1));
		}

#if NET_1_2
		[MonoTODO]
		public DataTable ToTable ()
		{
			throw new NotImplementedException ();
		}

		[MonoTODO]
		public DataTable ToTable (bool isDistinct, string[] columnNames)
		{
			throw new NotImplementedException ();
		}
#endif

		// internal use by Mono
		protected virtual void UpdateIndex () 
		{
			UpdateIndex (false);
		}

		// This is method is internal to 
		// the Mono implementation of DataView; it
		// is not to be used from your code.
		//
		// Update the DataRowView array which is an index cache
		// into the DataTable's DataRowCollection.
		//
		// I assume this is what UpdateIndex is used for
		protected virtual void UpdateIndex(bool force) 
		{
			DataRowView[] newRowCache = null;
			DataRow[] rows = null;
			
			rows = dataTable.Select (RowFilter, Sort, RowStateFilter);

			newRowCache = new DataRowView[rows.Length];
			for (int r = 0; r < rows.Length; r++) {
				newRowCache[r] = new DataRowView (this, rows[r]);
			}
			rowCache = newRowCache;
		}

		[MonoTODO]
		PropertyDescriptorCollection ITypedList.GetItemProperties (PropertyDescriptor[] listAccessors) 
		{
			// FIXME: use listAccessors somehow

			DataColumnPropertyDescriptor[] descriptors = 
				new DataColumnPropertyDescriptor[dataTable.Columns.Count];

			DataColumnPropertyDescriptor descriptor;
			DataColumn dataColumn;
			for (int col = 0; col < dataTable.Columns.Count; col ++)
			{
				dataColumn = dataTable.Columns[col];
				
				descriptor = new DataColumnPropertyDescriptor(
					dataColumn.ColumnName, col, null);
				descriptor.SetComponentType (typeof (System.Data.DataRowView));
				descriptor.SetPropertyType (dataColumn.DataType);
				descriptor.SetReadOnly (dataColumn.ReadOnly);

				descriptors[col] = descriptor;
			}

			return new PropertyDescriptorCollection (descriptors);
		}

		[MonoTODO]
		string ITypedList.GetListName (PropertyDescriptor[] listAccessors) 
		{
			return "";
		}

		//int ICollection.Count { 
		//	get {
		//		return Count;
		//	} 
		//}

		bool ICollection.IsSynchronized { 
			[MonoTODO]
			get {
				return false;
			} 
		}

		object ICollection.SyncRoot { 
			[MonoTODO]
			get {
				// FIXME:
				return this;
			}
		}

		//void ICollection.CopyTo (Array array, int index) 
		//{
		//	CopyTo (array, index);
		//}

		bool IList.IsFixedSize {
			[MonoTODO]
			get {
				return false;
			}
		}
		
		bool IList.IsReadOnly {
			[MonoTODO]
			get {
				return false;
			}
		}

		object IList.this[int recordIndex] {
			[MonoTODO]
			get {
				return this[recordIndex];
			}

			[MonoTODO]
			set{
				throw new InvalidOperationException();
			}
		}

		[MonoTODO]
		int IList.Add (object value) 
		{
			throw new NotImplementedException ();
		}

		[MonoTODO]
		void IList.Clear () 
		{
			throw new NotImplementedException ();
		}

		[MonoTODO]
		bool IList.Contains (object value) 
		{
			throw new NotImplementedException ();
		}

		[MonoTODO]
		int IList.IndexOf (object value) 
		{
			throw new NotImplementedException ();
		}
			
		[MonoTODO]
		void IList.Insert(int index,object value) 
		{
			throw new NotImplementedException ();
		}

		[MonoTODO]
		void IList.Remove(object value) 
		{
			throw new NotImplementedException ();
		}

		[MonoTODO]
		void IList.RemoveAt(int index) 
		{
			throw new NotImplementedException ();
		}

		#region IBindingList implementation

		[MonoTODO]
		void IBindingList.AddIndex (PropertyDescriptor property) 
		{
			throw new NotImplementedException ();
		}

		[MonoTODO]
		object IBindingList.AddNew () 
		{
			throw new NotImplementedException ();
		}

		[MonoTODO]
		void IBindingList.ApplySort (PropertyDescriptor property, ListSortDirection direction) 
		{
			throw new NotImplementedException ();
		}

		[MonoTODO]
		int IBindingList.Find (PropertyDescriptor property, object key) 
		{
			throw new NotImplementedException ();
		}

		[MonoTODO]
		void IBindingList.RemoveIndex (PropertyDescriptor property) 
		{
			throw new NotImplementedException ();
		}

		[MonoTODO]
		void IBindingList.RemoveSort () 
		{
			throw new NotImplementedException ();
		}
		
		bool IBindingList.AllowEdit {
			[MonoTODO]
			get {
				return AllowEdit;
			}
		}

		bool IBindingList.AllowNew {
			[MonoTODO]
			get {
				return AllowNew;
			}
		}

		bool IBindingList.AllowRemove {
			[MonoTODO]
			get {
				return AllowDelete;
			}
		}

		bool IBindingList.IsSorted {
			[MonoTODO]
			get {
				return isSorted;
			}
		}

		ListSortDirection IBindingList.SortDirection {
			[MonoTODO]
			get {
				// FIXME: 
				return ListSortDirection.Ascending;
			}
		}

		PropertyDescriptor IBindingList.SortProperty {
			[MonoTODO]
			get {
				// FIXME:
				return null;
			}
		}

		bool IBindingList.SupportsChangeNotification {
			[MonoTODO]
			get {
				return false;
			}
		}

		bool IBindingList.SupportsSearching {
			[MonoTODO]
			get {
				return false;
			}
		}

		bool IBindingList.SupportsSorting {
			[MonoTODO]
			get {
				return false;
			}
		}

		#endregion // IBindingList implementation

		private class DataViewEnumerator : IEnumerator 
		{
			private DataRowView[] rows;
			int on = -1;

			internal DataViewEnumerator (DataRowView[] dataRowViews) 
			{
				rows = dataRowViews;
			}

			public object Current {
				get {
					if (on == -1 || on >= rows.Length)
						throw new InvalidOperationException ();
					return rows[on];
				}
			}

			public bool MoveNext () 
			{
				// TODO: how do you determine
				// if a collection has been
				// changed?
				if (on < rows.Length - 1) {
					on++;
					return true;
				}

				return false; // EOF
			}

			public void Reset () {
				on = -1;
			}
		}
	}
}

