//
// System.Data.DataView.cs
//
// Author:
//    Daniel Morgan <danmorg@sc.rr.com>
//    Tim Coleman (tim@timcoleman.com)
//    Punit Todi (punits_mailbox@yahoo.com)
//    Atsushi Enomoto <atsushi@ximian.com>
//    Konstantin Triger (kostat@mainsoft.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;
using System.Data.Common;
using System.Globalization;
using Mono.Data.SqlExpressions;
using System.Text;

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 ("Microsoft.VSDesigner.Data.Design.DataSourceEditor, " + Consts.AssemblyMicrosoft_VSDesigner,
		 "System.Drawing.Design.UITypeEditor, " + Consts.AssemblySystem_Drawing)]
	[DefaultEvent ("PositionChanged")]
	[DefaultProperty ("Table")]
	[DesignerAttribute ("Microsoft.VSDesigner.Data.VS.DataViewDesigner, "+ Consts.AssemblyMicrosoft_VSDesigner, "System.ComponentModel.Design.IDesigner")]
#if NET_2_0
	public class DataView : MarshalByValueComponent, IBindingList, IList, ICollection, IEnumerable, ITypedList, ISupportInitialize, IBindingListView, ISupportInitializeNotification
#else
	public class DataView : MarshalByValueComponent, IBindingList, IList, ICollection, IEnumerable, ITypedList, ISupportInitialize
#endif
	{
		internal DataTable dataTable;
		string rowFilter = String.Empty;
		IExpression rowFilterExpr;
		string sort = String.Empty;
		ListSortDirection [] sortOrder;
		PropertyDescriptor sortProperty;
		DataColumn [] sortColumns;
		internal DataViewRowState rowState;
		internal DataRowView[] rowCache = new DataRowView [0];
#if NET_2_0
		private bool dataViewInitialized = true;
#endif

		// BeginInit() support
		bool isInitPhase;
		bool inEndInit;
		DataTable initTable;
		bool initApplyDefaultSort;
		string initSort;
		string initRowFilter;
		DataViewRowState initRowState;

		// FIXME: what are the default values?
		bool allowNew = true; 
		bool allowEdit = true;
		bool allowDelete = true;
		bool applyDefaultSort;
		//bool isSorted = false;

		bool isOpen;

		bool useDefaultSort = true;
		
		Index _index;
		internal DataRow _lastAdded;
		
		private DataViewManager dataViewManager;
		internal static ListChangedEventArgs ListResetEventArgs = new ListChangedEventArgs (ListChangedType.Reset,-1,-1);

		#region Constructors

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

		public DataView (DataTable table)
			: this (table, (DataViewManager) null)
		{
		}

		internal DataView (DataTable table, DataViewManager manager)
		{
			dataTable = table;
			rowState = DataViewRowState.CurrentRows;
			dataViewManager = manager;
			Open ();
		}

		public DataView (DataTable table, string RowFilter,
			string Sort, DataViewRowState RowState)
			: this (table, null, RowFilter, Sort, RowState)
		{
		}

		internal DataView (DataTable table, DataViewManager manager,
			string RowFilter, string Sort, DataViewRowState RowState)
		{
			dataTable = table;
			dataViewManager = manager;
			rowState = DataViewRowState.CurrentRows;
			this.RowFilter = RowFilter;
			this.Sort = Sort;
			rowState = RowState;
			Open ();
		}
		#endregion // Constructors

		#region PublicProperties

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

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

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

		[DataCategory ("Data")]
#if !NET_2_0
		[DataSysDescription ("Indicates whether to use the default sort if the Sort property is not set.")]
#endif
		[DefaultValue (false)]
		[RefreshProperties (RefreshProperties.All)]
		public bool ApplyDefaultSort {
			get { return applyDefaultSort; }
			set {
				if (isInitPhase) {
					initApplyDefaultSort = value;
					return;
				}
				if (applyDefaultSort == value)
					return;

				applyDefaultSort = value;
				if (applyDefaultSort == true &&
					(sort == null || sort == string.Empty))
					PopulateDefaultSort ();
				if (!inEndInit) {
					UpdateIndex (true);
					OnListChanged (new ListChangedEventArgs (ListChangedType.Reset, -1, -1));
				}
			}
		}
		// get the count of rows in the DataView after RowFilter 
		// and RowStateFilter have been applied
		[Browsable (false)]
#if !NET_2_0
		[DataSysDescription ("Returns the number of items currently in this view.")]
#endif
		public int Count {
			get {
				return rowCache.Length;
			}
		}

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

		// Item indexer
		// the compiler creates a DefaultMemeberAttribute from
		// this IndexerNameAttribute
		[System.Runtime.CompilerServices.IndexerName("Item")]
		public DataRowView this[int recordIndex] {
			get {
				if (recordIndex > rowCache.Length)
					throw new IndexOutOfRangeException ("There is no row at " +
									    "position: " + recordIndex + ".");
				return rowCache [recordIndex];
			}
		}

		[DataCategory ("Data")]
#if !NET_2_0
		[DataSysDescription ("Indicates an expression used to filter the data returned by this DataView.")]
#endif
		[DefaultValue ("")]
		public virtual string RowFilter {
			get { return rowFilter; }
			set {
				if (value == null)
					value = String.Empty;
				if (isInitPhase) {
					initRowFilter = value;
					return;
				}

				CultureInfo info = (Table != null) ? Table.Locale : CultureInfo.CurrentCulture;
				if (String.Compare(rowFilter, value, false, info) == 0)
					return;

				if (value.Length == 0)
					rowFilterExpr = null;
				else {
					Parser parser = new Parser ();
					rowFilterExpr = parser.Compile (value);
				}
				rowFilter = value;
				if (!inEndInit)
					UpdateIndex (true);
			}
		}

		[DataCategory ("Data")]
#if !NET_2_0
		[DataSysDescription ("Indicates the versions of data returned by this DataView.")]
#endif
		[DefaultValue (DataViewRowState.CurrentRows)]
		public DataViewRowState RowStateFilter {
			get { return rowState; }
			set {
				if (isInitPhase) {
					initRowState = value;
					return;
				}

				if (value == rowState)
					return;

				rowState = value;
				if (!inEndInit) {
					UpdateIndex (true);
					OnListChanged (new ListChangedEventArgs (ListChangedType.Reset, -1, -1));
				}
			}
		}

		[DataCategory ("Data")]
#if !NET_2_0
		[DataSysDescription ("Indicates the order in which data is returned by this DataView.")]
#endif
		[DefaultValue ("")]
		public string Sort {
			get {
				if (useDefaultSort)
					return String.Empty;
				else
					return sort;
			}
			set {
				if (isInitPhase) {
					initSort = value;
					return;
				}
				if (value == sort)
					return;

				if (value == null || value.Length == 0) {
				/* if given value is null useDefaultSort */
					useDefaultSort = true;
					/* if ApplyDefault sort is true try appling it */
					if (ApplyDefaultSort)
						PopulateDefaultSort ();
				} else {
					/* else donot useDefaultSort. set it as false */
					/* sort is set to value specified */
					useDefaultSort = false;
					sort = value;
					//sortedColumns = SortableColumn.ParseSortString (dataTable, value, true);
				}
				
				if (!inEndInit) {
					UpdateIndex (true);
					OnListChanged (new ListChangedEventArgs (ListChangedType.Reset, -1, -1));
				}
			}
		}

		[TypeConverter (typeof (DataTableTypeConverter))]
		[DataCategory ("Data")]
#if !NET_2_0
		[DataSysDescription ("Indicates the table this DataView uses to get data.")]
#endif
		[DefaultValue (null)]
		[RefreshProperties (RefreshProperties.All)]
		public DataTable Table {
			get { return dataTable; }
			set {
				if (value == dataTable)
					return;

				if (isInitPhase) {
					initTable = value;
					return;
				}

				if (value != null && value.TableName.Equals(string.Empty)) {
					throw new DataException("Cannot bind to DataTable with no name.");
				}

				if (dataTable != null)
					UnregisterEventHandlers ();

				dataTable = value;

				if (dataTable != null) {
					RegisterEventHandlers ();
					OnListChanged (new ListChangedEventArgs (ListChangedType.PropertyDescriptorChanged, 0, 0));
					sort = string.Empty;
					rowFilter = string.Empty;
					if (!inEndInit) {
						UpdateIndex (true);
						OnListChanged (new ListChangedEventArgs (ListChangedType.Reset, -1, -1));
					}
				}
			}
		}

#if NET_2_0
		[Browsable (false)]
		public bool IsInitialized {
			get { return dataViewInitialized;}
		}
#endif
		#endregion // PublicProperties
		
		#region	PublicMethods

		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.");
			
			if (_lastAdded != null)
				// FIXME : finish last added
				CompleteLastAdded (true);

			_lastAdded = dataTable.NewRow ();
			UpdateIndex (true);
			OnListChanged (new ListChangedEventArgs (ListChangedType.ItemAdded, Count - 1, -1));

			return this [Count - 1];
		}

		internal void CompleteLastAdded (bool add)
		{
			DataRow dr = _lastAdded;

			if (add) {
				try {
					dataTable.Rows.Add (_lastAdded);
					//OnListChanged(new ListChangedEventArgs(ListChangedType.ItemAdded, Count - 1, -1));
					_lastAdded = null;
					UpdateIndex ();
				} catch (Exception) {
					_lastAdded = dr;
					throw;
				}
			} else {
				_lastAdded.CancelEdit ();
				_lastAdded = null;
				UpdateIndex ();
				OnListChanged (new ListChangedEventArgs (ListChangedType.ItemDeleted, Count, -1));
			}
		}

		public void BeginInit ()
		{
			initTable = Table;
			initApplyDefaultSort = ApplyDefaultSort;
			initSort = Sort;
			initRowFilter = RowFilter;
			initRowState = RowStateFilter;

			isInitPhase = true;
#if NET_2_0
			dataViewInitialized = false;
#endif
		}

		public void CopyTo (Array array, int index) 
		{
			if (index + rowCache.Length > array.Length)
				throw new IndexOutOfRangeException ();

			int row = 0;
			for (; row < rowCache.Length && row < array.Length; row++)
				array.SetValue (rowCache [row], index + row);
		}

		public void Delete (int index)
		{
			if (!IsOpen)
				throw new DataException ("DataView is not open.");

			if (_lastAdded != null && index == Count) {
				CompleteLastAdded (false);
				return;
			}

			if (!AllowDelete)
				throw new DataException ("Cannot delete on a DataSource where AllowDelete is false.");
			
			if (index > rowCache.Length)
				throw new IndexOutOfRangeException ("There is no row at " +
						"position: " + index + ".");
			DataRowView row = rowCache [index];
			row.Row.Delete ();
		}

#if NET_2_0
		public virtual bool Equals (DataView dv)
		{
			if (this == dv)
				return true;
			if (!(this.Table == dv.Table && this.Sort == dv.Sort &&
				this.RowFilter == dv.RowFilter && 
				this.RowStateFilter == dv.RowStateFilter &&
				this.AllowEdit == dv.AllowEdit && 
				this.AllowNew == dv.AllowNew &&
				this.AllowDelete == dv.AllowDelete &&
				this.Count == dv.Count))
				return false;

			for (int i = 0; i < Count; ++i)
				if (!this [i].Equals (dv [i]))
					return false;
			return true;
		}
#endif

		public void EndInit ()
		{
			isInitPhase = false;

			inEndInit = true;

			Table = initTable;
			ApplyDefaultSort = initApplyDefaultSort;
			Sort = initSort;
			RowFilter = initRowFilter;
			RowStateFilter = initRowState;

			inEndInit = false;

			UpdateIndex (true);

#if NET_2_0
			dataViewInitialized = true;
			DataViewInitialized ();
#endif
		}

		public int Find (object key)
		{
			object [] keys = new object[] { key };
			return Find (keys);
		}

		public int Find (object [] key)
		{
			if (sort == null || sort.Length == 0)
				throw new ArgumentException ("Find finds a row based on a Sort order, and no Sort order is specified");

			if (Index == null)
				UpdateIndex (true);

			int index = -1;
			try {
				index = Index.FindIndex (key);
			} catch (FormatException) {
				// suppress exception
			} catch (InvalidCastException) {
				// suppress exception
			}
			return index;
		}
		
		public DataRowView [] FindRows (object key)
		{
			return FindRows (new object[] {key});
		}

		public DataRowView [] FindRows (object [] key)
		{
			if (sort == null || sort.Length == 0)
				throw new ArgumentException ("Find finds a row based on a Sort order, and no Sort order is specified");

			if (Index == null)
				UpdateIndex (true);

			int [] indexes = Index.FindAllIndexes (key);

			DataRowView[] rowViewArr = new DataRowView [indexes.Length];
			for (int r = 0; r < indexes.Length; r++)
				rowViewArr [r] = rowCache [indexes[r]];
			return rowViewArr;
		}

		public IEnumerator GetEnumerator ()
		{
			DataRowView[] dataRowViews = new DataRowView [Count];
			CopyTo (dataRowViews, 0);
			return dataRowViews.GetEnumerator ();
		}

		#endregion // PublicMethods
		
		[DataCategory ("Data")]
#if !NET_2_0
		[DataSysDescription ("Indicates that the data returned by this DataView has somehow changed.")]
#endif
		public event ListChangedEventHandler ListChanged;

#if NET_2_0
		public event EventHandler Initialized;
#endif

		[Browsable (false)]
#if !NET_2_0
		[DataSysDescription ("Indicates whether the view is open.  ")]
#endif
		protected bool IsOpen {
			get { return isOpen; }
		}

		internal Index Index {
			get {
				return _index;
			}
			set {
				if (_index != null) {
					_index.RemoveRef ();
					Table.DropIndex (_index);
				}

				_index = value;

				if (_index != null)
					_index.AddRef ();
			}
		}

		protected void Close ()
		{
			if (dataTable != null)
				UnregisterEventHandlers ();
			Index = null;
			rowCache = new DataRowView [0];
			isOpen = false;
		}

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

			base.Dispose (disposing);
		}

		protected virtual void IndexListChanged (object sender, ListChangedEventArgs e)
		{
		}

		protected virtual void OnListChanged (ListChangedEventArgs e)
		{
			// Yes, under MS.NET, when it is overriden, the 
			// events are not fired (even if it is essential
			// to internal processing).
			try {
				if (ListChanged != null)
					ListChanged (this, e);
			} catch {
			}
		}

		internal void ChangedList (ListChangedType listChangedType, int newIndex,int oldIndex)
		{
			ListChangedEventArgs e = new ListChangedEventArgs (listChangedType,newIndex,oldIndex);
			OnListChanged (e);
		}

		protected void Open ()
		{
			// I wonder if this comment is still valid, but keep
			// in the meantime.

			// 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.
			//		dataTable.ColumnChanged  += new DataColumnChangeEventHandler(OnColumnChanged);
			
			UpdateIndex (true);
			if (dataTable != null)
				RegisterEventHandlers ();
			isOpen = true;
		}
		
		private void RegisterEventHandlers ()
		{
			//dataTable.ColumnChanging += new DataColumnChangeEventHandler(OnColumnChanging);
			dataTable.ColumnChanged  += new DataColumnChangeEventHandler(OnColumnChanged);
			dataTable.RowChanged     += new DataRowChangeEventHandler(OnRowChanged);
			//dataTable.RowDeleting    += new DataRowChangeEventHandler(OnRowDeleting);
			dataTable.RowDeleted     += new DataRowChangeEventHandler(OnRowDeleted);
			dataTable.Columns.CollectionChanged += new CollectionChangeEventHandler(ColumnCollectionChanged);
			dataTable.Columns.CollectionMetaDataChanged += new CollectionChangeEventHandler(ColumnCollectionChanged);
			dataTable.Constraints.CollectionChanged += new CollectionChangeEventHandler(OnConstraintCollectionChanged);
			dataTable.ChildRelations.CollectionChanged += new CollectionChangeEventHandler(OnRelationCollectionChanged);
			dataTable.ParentRelations.CollectionChanged += new CollectionChangeEventHandler(OnRelationCollectionChanged);

			dataTable.Rows.ListChanged += new ListChangedEventHandler (OnRowCollectionChanged);
		}
		
		private void OnRowCollectionChanged (object sender, ListChangedEventArgs args)
		{
			if (args.ListChangedType == ListChangedType.Reset) {
				rowCache = new DataRowView [0];
				UpdateIndex (true);
				OnListChanged (new ListChangedEventArgs (ListChangedType.Reset, -1, -1 ));
			}
		}

		private void UnregisterEventHandlers ()
		{
//			dataTable.ColumnChanging -= new DataColumnChangeEventHandler(OnColumnChanging);
			dataTable.ColumnChanged  -= new DataColumnChangeEventHandler(OnColumnChanged);
			dataTable.RowChanged     -= new DataRowChangeEventHandler(OnRowChanged);
//			dataTable.RowDeleting    -= new DataRowChangeEventHandler(OnRowDeleting);
			dataTable.RowDeleted     -= new DataRowChangeEventHandler(OnRowDeleted);
			dataTable.Columns.CollectionChanged -= new CollectionChangeEventHandler(ColumnCollectionChanged);
			dataTable.Columns.CollectionMetaDataChanged -= new CollectionChangeEventHandler(ColumnCollectionChanged);
			dataTable.Constraints.CollectionChanged -= new CollectionChangeEventHandler(OnConstraintCollectionChanged);
			dataTable.ChildRelations.CollectionChanged -= new CollectionChangeEventHandler(OnRelationCollectionChanged);
			dataTable.ParentRelations.CollectionChanged -= new CollectionChangeEventHandler(OnRelationCollectionChanged);

			dataTable.Rows.ListChanged -= new ListChangedEventHandler (OnRowCollectionChanged);
		}

		// These index storing and rowView preservation must be done
		// before the actual row value is changed; thus we can't use
		// RowChanging which accepts "already modified" DataRow.

		private void OnColumnChanged (object sender, DataColumnChangeEventArgs args)
		{	/* not used */
			//UpdateIndex(true);
		}
		
		private void OnRowChanged (object sender, DataRowChangeEventArgs args)
		{
			int oldIndex,newIndex;
			oldIndex = newIndex = -1;
			oldIndex = IndexOf (args.Row);
			UpdateIndex (true);
			newIndex = IndexOf (args.Row);

			/* ItemAdded */
			if (args.Action == DataRowAction.Add)
				OnListChanged (new ListChangedEventArgs (ListChangedType.ItemAdded, newIndex, -1));

			/* ItemChanged or ItemMoved */
			if (args.Action == DataRowAction.Change) {
				if (oldIndex == newIndex)
					OnListChanged (new ListChangedEventArgs (ListChangedType.ItemChanged, newIndex, -1));
				else
					OnListChanged (new ListChangedEventArgs (ListChangedType.ItemMoved, newIndex, oldIndex));
			}
		}

		private void OnRowDeleted (object sender, DataRowChangeEventArgs args)
		{
			/* ItemDeleted */
			int newIndex;
			newIndex = IndexOf (args.Row);
			UpdateIndex (true);
			OnListChanged (new ListChangedEventArgs (ListChangedType.ItemDeleted, newIndex, -1));
		}
		
#if NET_2_0
		private void DataViewInitialized ()
		{
			EventArgs e = new EventArgs ();
			OnDataViewInitialized (e);
		}

		private void OnDataViewInitialized (EventArgs e) {
			if (null != Initialized)
				Initialized (this, e);
		}
#endif
		protected virtual void ColumnCollectionChanged (object sender, CollectionChangeEventArgs e)
		{
			// UpdateIndex() is not invoked here (even if the sort
			// column is being removed).

			// PropertyDescriptor Add
			if (e.Action == CollectionChangeAction.Add)
				OnListChanged (new ListChangedEventArgs (ListChangedType.PropertyDescriptorAdded, 0, 0));

			// PropertyDescriptor Removed
			if (e.Action == CollectionChangeAction.Remove)
				OnListChanged (new ListChangedEventArgs (ListChangedType.PropertyDescriptorDeleted, 0, 0));

			// PropertyDescriptor Changed
			if (e.Action == CollectionChangeAction.Refresh)
				OnListChanged (new ListChangedEventArgs (ListChangedType.PropertyDescriptorChanged, 0, 0));
		}

		private void OnConstraintCollectionChanged (object sender, CollectionChangeEventArgs args)
		{
			//	The Sort variable is set to the UniqueConstraint column.
			//  if ApplyDefault Sort is true and Sort is null or is not set Explicitly
			
			// FIXME: The interal cache may change as result of change in Constraint collection
			// one such scenerio is taken care.
			// There may be more. I dont know what else can be done.
			/* useDefaultSort is set to false when Sort is set explicitly */
			if (args.Action == CollectionChangeAction.Add && args.Element is UniqueConstraint) {
				if (ApplyDefaultSort && useDefaultSort)
					PopulateDefaultSort ((UniqueConstraint) args.Element);
			}
			// UpdateIndex() is not invoked here.
		}

		private void OnRelationCollectionChanged (object sender, CollectionChangeEventArgs args)
		{
			/* PropertyDescriptor Add */
			if (args.Action == CollectionChangeAction.Add)
				OnListChanged (new ListChangedEventArgs (ListChangedType.PropertyDescriptorAdded,0,0));

			/* PropertyDescriptor Removed */
			if (args.Action == CollectionChangeAction.Remove)
				OnListChanged (new ListChangedEventArgs (ListChangedType.PropertyDescriptorDeleted,0,0));

			/* FIXME: PropertyDescriptor Changed ???*/
			if (args.Action == CollectionChangeAction.Refresh)
				OnListChanged (new ListChangedEventArgs (ListChangedType.PropertyDescriptorChanged,0,0));
		}

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

#if NET_2_0
		public DataTable ToTable ()
		{
			return this.ToTable (Table.TableName, false, new string[] {});
		}

		public DataTable ToTable (string tableName)
		{
			return this.ToTable (tableName, false, new string[] {});
		}

		public DataTable ToTable (bool isDistinct, params string[] columnNames)
		{
			return this.ToTable (Table.TableName, isDistinct, columnNames);
		}
		
		public DataTable ToTable (string tablename, bool isDistinct, params string[] columnNames)
		{
			if (columnNames == null)
				throw new ArgumentNullException ("columnNames", "'columnNames' argument cannot be null.");
 
			DataTable newTable = new DataTable (tablename);

			DataColumn[] columns;
			ListSortDirection[] sortDirection = null;
			if (columnNames.Length != 0) {
				columns = new DataColumn [columnNames.Length];
				for (int i=0; i < columnNames.Length; ++i)
					columns [i] = Table.Columns [columnNames [i]];

				if (sortColumns != null) {
					sortDirection = new ListSortDirection [columnNames.Length];
					for (int i=0; i < columnNames.Length; ++i) {
						sortDirection [i] = ListSortDirection.Ascending;
						for (int j = 0; j < sortColumns.Length; ++j) {
							if (sortColumns [j] != columns [i])
								continue;
							sortDirection [i] = sortOrder [j];
						}
					}
				}
			} else {
				columns = (DataColumn[]) Table.Columns.ToArray (typeof (DataColumn));
				sortDirection = sortOrder;
			}

			ArrayList expressionCols = new ArrayList ();
			for (int i = 0; i < columns.Length; ++i) {
				DataColumn col = columns [i].Clone ();
				if (col.Expression != String.Empty) { 
					col.Expression = string.Empty;
					expressionCols.Add (col);
				}
				if (col.ReadOnly)
					col.ReadOnly = false;
				newTable.Columns.Add (col);
			}

			DataRow [] rows;
			Index index = new Index (new Key(Table, columns, sortDirection, RowStateFilter, rowFilterExpr));
			if (isDistinct)
				rows = index.GetDistinctRows ();
			else
				rows = index.GetAllRows (); 

			foreach (DataRow row in rows) {
				DataRow newRow = newTable.NewNotInitializedRow ();
				newTable.Rows.AddInternal (newRow);
				newRow.Original = -1;
				if (row.HasVersion (DataRowVersion.Current))
					newRow.Current = newTable.RecordCache.CopyRecord (Table, row.Current, -1);
				else if (row.HasVersion (DataRowVersion.Original))
					newRow.Current = newTable.RecordCache.CopyRecord (Table, row.Original, -1);

				foreach (DataColumn col in expressionCols)
					newRow [col] = row [col.ColumnName];
				newRow.Original = -1;
			}
			return newTable;
		}
#endif

		protected 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)
		{
			if (Table == null)
				// FIXME
				return;

			if (Index == null || force) {
				sortColumns = DataTable.ParseSortString(Table, Sort, out sortOrder, false);
				Index = dataTable.GetIndex(sortColumns,sortOrder,RowStateFilter,FilterExpression,true);
			} else {
				Index.Key.RowStateFilter = RowStateFilter;
				Index.Reset();
			}

			int[] records = Index.GetAll ();

			if (records != null)
				InitDataRowViewArray (records,Index.Size);
			else
				rowCache = new DataRowView [0];
		}

		internal virtual IExpression FilterExpression {
			get {
				return rowFilterExpr;
			}
		}

		private void InitDataRowViewArray (int [] records, int size)
		{
			if (_lastAdded != null)
				rowCache = new DataRowView [size + 1];
			else
				rowCache = new DataRowView [size];

			for (int r = 0; r < size; r++)
				rowCache [r] = new DataRowView (this, Table.RecordCache [records [r]],r);

			if (_lastAdded != null)
				rowCache [size] = new DataRowView (this, _lastAdded, size);
		}

		PropertyDescriptorCollection ITypedList.GetItemProperties (PropertyDescriptor [] listAccessors) 
		{
			if (dataTable == null)
				return new PropertyDescriptorCollection (new PropertyDescriptor [0]);

			// FIXME: use listAccessors somehow
			PropertyDescriptor [] descriptors = 
				new PropertyDescriptor [dataTable.Columns.Count + dataTable.ChildRelations.Count];

			int d = 0;
			for (int col = 0; col < dataTable.Columns.Count; col ++) {
				DataColumn dataColumn = dataTable.Columns[col];
				DataColumnPropertyDescriptor descriptor;

				descriptor = new DataColumnPropertyDescriptor(
					dataColumn.ColumnName, col, null);
				descriptor.SetComponentType (typeof (System.Data.DataRowView));
				descriptor.SetPropertyType (dataColumn.DataType);
				descriptor.SetReadOnly (dataColumn.ReadOnly);
				descriptors [d++] = descriptor;
			}
			for (int rel = 0; rel < dataTable.ChildRelations.Count; rel ++) {
				DataRelation dataRelation = dataTable.ChildRelations [rel];
				DataRelationPropertyDescriptor descriptor;

				descriptor = new DataRelationPropertyDescriptor (dataRelation);
				descriptors [d++] = descriptor;
			}

			return new PropertyDescriptorCollection (descriptors);
		}

		
		string ITypedList.GetListName (PropertyDescriptor [] listAccessors)
		{
			if (dataTable != null)
				return dataTable.TableName;
			return string.Empty;
		}

		bool ICollection.IsSynchronized {
			get {
				return false;
			}
		}

		object ICollection.SyncRoot {
			get {
				return this;
			}
		}

		bool IList.IsFixedSize {
			get {
				return false;
			}
		}
		
		bool IList.IsReadOnly {
			get {
				return false;
			}
		}

		object IList.this [int recordIndex] {
			get {
				return this [recordIndex];
			}
			[MonoTODO]
			set{
				throw new InvalidOperationException ();
			}
		}

		int IList.Add (object value)
		{
			throw new ArgumentException ("Cannot add external objects to this list.");
		}

		void IList.Clear ()
		{
			throw new ArgumentException ("Cannot clear this list.");
		}

		bool IList.Contains (object value)
		{
			DataRowView drv = value as DataRowView;
			if (drv == null)
				return false;

			return drv.DataView == this;
		}

		int IList.IndexOf (object value)
		{
			DataRowView drv = value as DataRowView;
			if (drv != null && drv.DataView == this)
				return drv.Index;
			return -1;
		}

		void IList.Insert (int index,object value)
		{
			throw new ArgumentException ("Cannot insert external objects to this list.");
		}

		void IList.Remove (object value)
		{
			DataRowView drv = value as DataRowView;
			if (drv != null && drv.DataView == this)
				((IList) this).RemoveAt (drv.Index);

			throw new ArgumentException ("Cannot remove external objects to this list.");
		}

		void IList.RemoveAt(int index)
		{
			Delete(index);
		}

		#region IBindingList implementation

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

		object IBindingList.AddNew ()
		{
			return this.AddNew ();
		}

		void IBindingList.ApplySort (PropertyDescriptor property, ListSortDirection direction)
		{
			if (!(property is DataColumnPropertyDescriptor))
				throw new ArgumentException ("Dataview accepts only DataColumnPropertyDescriptors", "property");
			sortProperty = property;
			string sort = String.Format ("[{0}]" , property.Name);
			if (direction == ListSortDirection.Descending)
				sort += " DESC";
			this.Sort = sort;
		}

		int IBindingList.Find (PropertyDescriptor property, object key)
		{
			DataColumn dc = Table.Columns [property.Name];
			Index index = Table.FindIndex (new DataColumn [] {dc}, sortOrder, RowStateFilter, FilterExpression);
			if (index == null)
				index = new Index (new Key (Table, new DataColumn [] {dc}, sortOrder, RowStateFilter, FilterExpression));
			
			return index.FindIndex (new object [] {key});
		}
		
		[MonoTODO]
		void IBindingList.RemoveIndex (PropertyDescriptor property)
		{
			throw new NotImplementedException ();
		}

		void IBindingList.RemoveSort ()
		{
			sortProperty = null;
			this.Sort = String.Empty;
		}
		
		bool IBindingList.AllowEdit {
			get { return AllowEdit; }
		}

		bool IBindingList.AllowNew {
			get { return AllowNew; }
		}

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

		bool IBindingList.IsSorted {
			get { return (Sort != null && Sort.Length != 0); }
		}

		ListSortDirection IBindingList.SortDirection {
			get {
				if (sortOrder != null && sortOrder.Length > 0)
					return sortOrder [0];
				return ListSortDirection.Ascending;
			}
		}

		PropertyDescriptor IBindingList.SortProperty {
			get {
				if (sortProperty == null && sortColumns != null && sortColumns.Length > 0) {
					// return property from Sort String
					PropertyDescriptorCollection properties = ((ITypedList)this).GetItemProperties (null);
					return properties.Find (sortColumns [0].ColumnName, false);
				}
				return sortProperty;
			}
		}

		bool IBindingList.SupportsChangeNotification {
			get { return true; }
		}

		bool IBindingList.SupportsSearching {
			get { return true; }
		}

		bool IBindingList.SupportsSorting {
			get { return true; }
		}
		
		#endregion // IBindingList implementation

#if NET_2_0
		#region IBindingListView implementation
		string IBindingListView.Filter {
			get { return ((DataView) this).RowFilter; }
			set { ((DataView) this).RowFilter = value; }
		}

		ListSortDescriptionCollection IBindingListView.SortDescriptions {
			get {
				ListSortDescriptionCollection col = new ListSortDescriptionCollection ();
				for (int i = 0; i < sortColumns.Length; ++i) {
					ListSortDescription ldesc = new ListSortDescription (
										new DataColumnPropertyDescriptor (sortColumns [i]),
										sortOrder [i]);
					((IList) col).Add (ldesc);
				}
				return col;
			}
		}

		bool IBindingListView.SupportsAdvancedSorting {
			get { return true; }
		}

		bool IBindingListView.SupportsFiltering {
			get { return true; }
		}

		[MonoTODO]
		void IBindingListView.ApplySort (ListSortDescriptionCollection sorts)
		{
			StringBuilder sb = new StringBuilder ();
			foreach (ListSortDescription ldesc in sorts)
				sb.AppendFormat ("[{0}]{1},", ldesc.PropertyDescriptor.Name,
					(ldesc.SortDirection == ListSortDirection.Descending ? " DESC" : string.Empty));
			this.Sort = sb.ToString (0, sb.Length-1);
		}

		void IBindingListView.RemoveFilter ()
		{
			((IBindingListView) this).Filter = string.Empty;
		}
		
		#endregion //IBindingViewList implementation
#endif

		private int IndexOf (DataRow dr)
		{
			for (int i=0; i < rowCache.Length; i++)
				if (dr.Equals (rowCache [i].Row))
				return i;
			return -1;
		}
		
		private void PopulateDefaultSort ()
		{
			sort = string.Empty;
			foreach (Constraint c in dataTable.Constraints) {
				if (c is UniqueConstraint) {
					PopulateDefaultSort ((UniqueConstraint) c);
					break;
				}
			}
		}

		private void PopulateDefaultSort (UniqueConstraint uc)
		{
			if (isInitPhase)
				return;

			DataColumn[] columns = uc.Columns;
			if (columns.Length == 0) {
				sort = String.Empty;
				return;
			}

			StringBuilder builder = new StringBuilder ();
			builder.Append(columns[0].ColumnName);
			for (int i = 1; i<columns.Length; i++) {
				builder.Append(", ");
				builder.Append(columns [i].ColumnName);
			}
			sort = builder.ToString ();
		}
		
		internal DataView CreateChildView (DataRelation relation, int index)
		{
			if (relation == null || relation.ParentTable != Table)
				throw new ArgumentException("The relation is not parented to the table to which this DataView points.");

			int record = GetRecord (index);
			object[] keyValues = new object [relation.ParentColumns.Length];
			for (int i = 0; i < relation.ParentColumns.Length; i++)
				keyValues [i] = relation.ParentColumns [i] [record];

			return new RelatedDataView (relation.ChildColumns, keyValues);
		}

		private int GetRecord (int index)
		{
			if (index < 0 || index >= Count)
				throw new IndexOutOfRangeException(String.Format("There is no row at position {0}.", index));

			return (index == Index.Size) ?
				_lastAdded.IndexFromVersion (DataRowVersion.Default) :
				Index.IndexToRecord (index);
		}

		internal DataRowVersion GetRowVersion (int index)
		{
			int record = GetRecord (index);
			return Table.RecordCache [record].VersionFromIndex (record);
		}
	}
}
