
using System;
using System.ComponentModel;
using System.ComponentModel.Design.Serialization;
using System.Collections.Generic;

using NUnit.Framework;

namespace MonoTests.System.ComponentModel
{
	[TestFixture]
	public class BindingListTest
	{
		[Test]
		public void BindingListDefaults ()
		{
			BindingList<string> l = new BindingList<string> ();
			IBindingList ibl = (IBindingList)l;
				
			Assert.IsTrue (l.AllowEdit, "1");
			Assert.IsFalse (l.AllowNew, "2");
			Assert.IsTrue (l.AllowRemove, "3");
			Assert.IsTrue (l.RaiseListChangedEvents, "4");

			Assert.IsFalse (ibl.IsSorted, "5");
			Assert.AreEqual (ibl.SortDirection, ListSortDirection.Ascending, "6");
			Assert.IsTrue (ibl.SupportsChangeNotification, "7");
			Assert.IsFalse (ibl.SupportsSearching, "8");
			Assert.IsFalse (ibl.SupportsSorting, "9");
			Assert.IsFalse (((IRaiseItemChangedEvents)l).RaisesItemChangedEvents, "10");
		}

		[Test]
		public void BindingListDefaults_FixedSizeList ()
		{
			string[] arr = new string[10];
			BindingList<string> l = new BindingList<string> (arr);
			IBindingList ibl = (IBindingList)l;

			Assert.IsTrue (l.AllowEdit, "1");
			Assert.IsFalse (l.AllowNew, "2");
			Assert.IsTrue (l.AllowRemove, "3");
			Assert.IsTrue (l.RaiseListChangedEvents, "4");

			Assert.IsFalse (ibl.IsSorted, "5");
			Assert.AreEqual (ibl.SortDirection, ListSortDirection.Ascending, "6");
			Assert.IsTrue (ibl.SupportsChangeNotification, "7");
			Assert.IsFalse (ibl.SupportsSearching, "8");
			Assert.IsFalse (ibl.SupportsSorting, "9");
			Assert.IsFalse (((IRaiseItemChangedEvents)l).RaisesItemChangedEvents, "10");
		}

		[Test]
		public void BindingListDefaults_NonFixedSizeList ()
		{
			List<string> list = new List<string> ();
			BindingList<string> l = new BindingList<string> (list);
			IBindingList ibl = (IBindingList)l;

			Assert.IsTrue (l.AllowEdit, "1");
			Assert.IsFalse (l.AllowNew, "2");
			Assert.IsTrue (l.AllowRemove, "3");
			Assert.IsTrue (l.RaiseListChangedEvents, "4");

			Assert.IsFalse (ibl.IsSorted, "5");
			Assert.AreEqual (ibl.SortDirection, ListSortDirection.Ascending, "6");
			Assert.IsTrue (ibl.SupportsChangeNotification, "7");
			Assert.IsFalse (ibl.SupportsSearching, "8");
			Assert.IsFalse (ibl.SupportsSorting, "9");
			Assert.IsFalse (((IRaiseItemChangedEvents)l).RaisesItemChangedEvents, "10");
		}

		[Test]
		public void BindingListDefaults_ReadOnlyList ()
		{
			List<string> list = new List<string> ();
			BindingList<string> l = new BindingList<string> (list);
			IBindingList ibl = (IBindingList)l;

			Assert.IsTrue (l.AllowEdit, "1");
			Assert.IsFalse (l.AllowNew, "2");
			Assert.IsTrue (l.AllowRemove, "3");
			Assert.IsTrue (l.RaiseListChangedEvents, "4");

			Assert.IsFalse (ibl.IsSorted, "5");
			Assert.AreEqual (ibl.SortDirection, ListSortDirection.Ascending, "6");
			Assert.IsTrue (ibl.SupportsChangeNotification, "7");
			Assert.IsFalse (ibl.SupportsSearching, "8");
			Assert.IsFalse (ibl.SupportsSorting, "9");
			Assert.IsFalse (((IRaiseItemChangedEvents)l).RaisesItemChangedEvents, "10");
		}

		[Test]
		public void TestAllowNew ()
		{
			/* Object has a default ctor */
			BindingList<object> l1 = new BindingList<object> ();
			Assert.IsTrue (l1.AllowNew, "1");

			/* string has no default ctor */
			BindingList<string> l2 = new BindingList<string> ();
			Assert.IsFalse (l2.AllowNew, "2");

			/* adding a delegate to AddingNew fixes that */
			l2.AddingNew += delegate (object sender, AddingNewEventArgs e) { };
			Assert.IsTrue (l2.AllowNew, "3");

			l1 = new BindingList<object> ();

			bool list_changed = false;
			bool expected = false;

			l1.ListChanged += delegate (object sender, ListChangedEventArgs e) {
				list_changed = true;
				Assert.AreEqual (-1, e.NewIndex, "4");
				Assert.AreEqual (ListChangedType.Reset, e.ListChangedType, "5");
				Assert.AreEqual (expected, l1.AllowNew, "6");
			};

			expected = false;
			l1.AllowNew = false;

			Assert.IsTrue (list_changed, "7");

			/* the default for T=object is true, so check
			   if we enter the block for raising the event
			   if we explicitly set it to the value it
			   currently has. */
			l1 = new BindingList<object> ();

			list_changed = false;
			
			l1.ListChanged += delegate (object sender, ListChangedEventArgs e) {
				list_changed = true;
				Assert.AreEqual (-1, e.NewIndex, "8");
				Assert.AreEqual (ListChangedType.Reset, e.ListChangedType, "9");
				Assert.AreEqual (expected, l1.AllowNew, "10");
			};

			expected = true;
			l1.AllowNew = true;

			/* turns out it doesn't raise the event, so the check must only be for "allow_new == value" */
			Assert.IsFalse (list_changed, "11");
		}

		[Test]
		public void TestResetBindings ()
		{
			BindingList<object> l = new BindingList<object> ();

			bool list_changed = false;

			l.ListChanged += delegate (object sender, ListChangedEventArgs e) {
				list_changed = true;
				Assert.AreEqual (-1, e.NewIndex, "1");
				Assert.AreEqual (ListChangedType.Reset, e.ListChangedType, "2");
			};

			l.ResetBindings ();

			Assert.IsTrue (list_changed, "3");
		}

		[Test]
		public void TestResetItem ()
		{
			List<object> list = new List<object>();
			list.Add (new object());

			BindingList<object> l = new BindingList<object> (list);

			bool item_changed = false;

			l.ListChanged += delegate (object sender, ListChangedEventArgs e) {
				item_changed = true;
				Assert.AreEqual (0, e.NewIndex, "1");
				Assert.AreEqual (ListChangedType.ItemChanged, e.ListChangedType, "2");
			};

			l.ResetItem (0);

			Assert.IsTrue (item_changed, "3");
		}

		[Test]
		public void TestRemoveItem ()
		{
			List<object> list = new List<object>();
			list.Add (new object());

			BindingList<object> l = new BindingList<object> (list);

			bool item_deleted = false;

			l.ListChanged += delegate (object sender, ListChangedEventArgs e) {
				item_deleted = true;
				Assert.AreEqual (0, e.NewIndex, "1");
				Assert.AreEqual (ListChangedType.ItemDeleted, e.ListChangedType, "2");
				Assert.AreEqual (0, l.Count, "3"); // to show the event is raised after the removal
			};

			l.RemoveAt (0);

			Assert.IsTrue (item_deleted, "4");
		}

		[Test]
		[ExpectedException (typeof (NotSupportedException))]
		public void TestRemoveItem_AllowRemoveFalse ()
		{
			List<object> list = new List<object>();
			list.Add (new object());

			BindingList<object> l = new BindingList<object> (list);

			l.AllowRemove = false;

			l.RemoveAt (0);
		}

		[Test]
		public void TestAllowEditEvent ()
		{
			BindingList<object> l = new BindingList<object>();

			bool event_raised = false;
			bool expected = false;

			l.ListChanged += delegate (object sender, ListChangedEventArgs e) {
				event_raised = true;
				Assert.AreEqual (-1, e.NewIndex, "1");
				Assert.AreEqual (ListChangedType.Reset, e.ListChangedType, "2");
				Assert.AreEqual (expected, l.AllowEdit, "3");
			};

			expected = false;
			l.AllowEdit = false;

			Assert.IsTrue (event_raised, "4");

			// check to see if RaiseListChangedEvents affects AllowEdit's event.
			l.RaiseListChangedEvents = false;

			event_raised = false;
			expected = true;
			l.AllowEdit = true;

			Assert.IsFalse (event_raised, "5");
		}

		[Test]
		public void TestAllowRemove ()
		{
			BindingList<object> l = new BindingList<object>();

			bool event_raised = false;
			bool expected = false;

			l.ListChanged += delegate (object sender, ListChangedEventArgs e) {
				event_raised = true;
				Assert.AreEqual (-1, e.NewIndex, "1");
				Assert.AreEqual (ListChangedType.Reset, e.ListChangedType, "2");
				Assert.AreEqual (expected, l.AllowRemove, "3");
			};

			expected = false;
			l.AllowRemove = false;

			Assert.IsTrue (event_raised, "4");

			// check to see if RaiseListChangedEvents affects AllowRemove's event.
			l.RaiseListChangedEvents = false;

			event_raised = false;
			expected = true;
			l.AllowRemove = true;

			Assert.IsFalse (event_raised, "5");
		}

		[Test]
		public void TestAddNew_SettingArgsNewObject ()
		{
			BindingList<object> l = new BindingList<object>();

			bool adding_event_raised = false;
			object o = new object ();

			l.AddingNew += delegate (object sender, AddingNewEventArgs e) {
				adding_event_raised = true;
				Assert.IsNull (e.NewObject, "1");
				e.NewObject = o;
			};

			object rv = l.AddNew ();
			Assert.IsTrue (adding_event_raised, "2");
			Assert.AreSame (o, rv, "3");
		}

		[Test]
		public void TestAddNew ()
		{
			BindingList<object> l = new BindingList<object>();

			bool adding_event_raised = false;
			object o = new object ();

			l.AddingNew += delegate (object sender, AddingNewEventArgs e) {
				adding_event_raised = true;
				Assert.IsNull (e.NewObject, "1");
			};

			object rv = l.AddNew ();
			Assert.IsTrue (adding_event_raised, "2");
			Assert.IsNotNull (rv, "3");
		}

		[Test]
		public void TestAddNew_Cancel ()
		{
			BindingList<object> l = new BindingList<object>();

			bool adding_event_raised = false;
			object o = new object ();

			bool list_changed = false;
			ListChangedType change_type = ListChangedType.Reset;
			int list_changed_index = -1;

			l.AddingNew += delegate (object sender, AddingNewEventArgs e) {
				adding_event_raised = true;
				Assert.IsNull (e.NewObject, "1");
			};

			l.ListChanged += delegate (object sender, ListChangedEventArgs e) {
				list_changed = true;
				change_type = e.ListChangedType;
				list_changed_index = e.NewIndex;
			};

			object rv = l.AddNew ();
			Assert.IsTrue (adding_event_raised, "2");
			Assert.IsNotNull (rv, "3");

			Assert.AreEqual (1, l.Count, "4");
			Assert.AreEqual (0, l.IndexOf (rv), "5");
			Assert.IsTrue (list_changed, "6");
			Assert.AreEqual (ListChangedType.ItemAdded, change_type, "7");
			Assert.AreEqual (0, list_changed_index, "8");

			list_changed = false;

			l.CancelNew (0);

			Assert.AreEqual (0, l.Count, "9");
			Assert.IsTrue (list_changed, "10");
			Assert.AreEqual (ListChangedType.ItemDeleted, change_type, "11");
			Assert.AreEqual (0, list_changed_index, "12");
		}

		[Test]
		public void TestAddNew_CancelDifferentIndex ()
		{
			List<object> list = new List<object>();

			list.Add (new object ());
			list.Add (new object ());

			BindingList<object> l = new BindingList<object>(list);

			bool adding_event_raised = false;
			object o = new object ();

			bool list_changed = false;
			ListChangedType change_type = ListChangedType.Reset;
			int list_changed_index = -1;

			l.AddingNew += delegate (object sender, AddingNewEventArgs e) {
				adding_event_raised = true;
				Assert.IsNull (e.NewObject, "1");
			};

			l.ListChanged += delegate (object sender, ListChangedEventArgs e) {
				list_changed = true;
				change_type = e.ListChangedType;
				list_changed_index = e.NewIndex;
			};

			object rv = l.AddNew ();
			Assert.IsTrue (adding_event_raised, "2");
			Assert.IsNotNull (rv, "3");

			Assert.AreEqual (3, l.Count, "4");
			Assert.AreEqual (2, l.IndexOf (rv), "5");
			Assert.IsTrue (list_changed, "6");
			Assert.AreEqual (ListChangedType.ItemAdded, change_type, "7");
			Assert.AreEqual (2, list_changed_index, "8");

			list_changed = false;

			l.CancelNew (0);

			Assert.IsFalse (list_changed, "9");
			Assert.AreEqual (3, l.Count, "10");

			l.CancelNew (2);

			Assert.IsTrue (list_changed, "11");
			Assert.AreEqual (ListChangedType.ItemDeleted, change_type, "12");
			Assert.AreEqual (2, list_changed_index, "13");
			Assert.AreEqual (2, l.Count, "14");
		}

		[Test]
		public void TestAddNew_End ()
		{
			BindingList<object> l = new BindingList<object>();

			bool adding_event_raised = false;
			object o = new object ();

			bool list_changed = false;
			ListChangedType change_type = ListChangedType.Reset;
			int list_changed_index = -1;

			l.AddingNew += delegate (object sender, AddingNewEventArgs e) {
				adding_event_raised = true;
				Assert.IsNull (e.NewObject, "1");
			};

			l.ListChanged += delegate (object sender, ListChangedEventArgs e) {
				list_changed = true;
				change_type = e.ListChangedType;
				list_changed_index = e.NewIndex;
			};

			object rv = l.AddNew ();
			Assert.IsTrue (adding_event_raised, "2");
			Assert.IsNotNull (rv, "3");

			Assert.AreEqual (1, l.Count, "4");
			Assert.AreEqual (0, l.IndexOf (rv), "5");
			Assert.IsTrue (list_changed, "6");
			Assert.AreEqual (ListChangedType.ItemAdded, change_type, "7");
			Assert.AreEqual (0, list_changed_index, "8");

			list_changed = false;

			l.EndNew (0);

			Assert.AreEqual (1, l.Count, "9");
			Assert.IsFalse (list_changed, "10");
		}

		[Test]
		public void TestAddNew_CancelDifferentIndexThenEnd ()
		{
			BindingList<object> l = new BindingList<object>();

			bool adding_event_raised = false;
			object o = new object ();

			bool list_changed = false;
			ListChangedType change_type = ListChangedType.Reset;
			int list_changed_index = -1;

			l.AddingNew += delegate (object sender, AddingNewEventArgs e) {
				adding_event_raised = true;
				Assert.IsNull (e.NewObject, "1");
			};

			l.ListChanged += delegate (object sender, ListChangedEventArgs e) {
				list_changed = true;
				change_type = e.ListChangedType;
				list_changed_index = e.NewIndex;
			};

			object rv = l.AddNew ();
			Assert.IsTrue (adding_event_raised, "2");
			Assert.IsNotNull (rv, "3");

			Assert.AreEqual (1, l.Count, "4");
			Assert.AreEqual (0, l.IndexOf (rv), "5");
			Assert.IsTrue (list_changed, "6");
			Assert.AreEqual (ListChangedType.ItemAdded, change_type, "7");
			Assert.AreEqual (0, list_changed_index, "8");

			list_changed = false;

			l.CancelNew (2);

			Assert.AreEqual (1, l.Count, "9");
			Assert.IsFalse (list_changed, "10");

			l.EndNew (0);

			Assert.AreEqual (1, l.Count, "11");
			Assert.IsFalse (list_changed, "12");
		}

		[Test]
		public void TestAddNew_EndDifferentIndexThenCancel ()
		{
			BindingList<object> l = new BindingList<object>();

			bool adding_event_raised = false;
			object o = new object ();

			bool list_changed = false;
			ListChangedType change_type = ListChangedType.Reset;
			int list_changed_index = -1;

			l.AddingNew += delegate (object sender, AddingNewEventArgs e) {
				adding_event_raised = true;
				Assert.IsNull (e.NewObject, "1");
			};

			l.ListChanged += delegate (object sender, ListChangedEventArgs e) {
				list_changed = true;
				change_type = e.ListChangedType;
				list_changed_index = e.NewIndex;
			};

			object rv = l.AddNew ();
			Assert.IsTrue (adding_event_raised, "2");
			Assert.IsNotNull (rv, "3");

			Assert.AreEqual (1, l.Count, "4");
			Assert.AreEqual (0, l.IndexOf (rv), "5");
			Assert.IsTrue (list_changed, "6");
			Assert.AreEqual (ListChangedType.ItemAdded, change_type, "7");
			Assert.AreEqual (0, list_changed_index, "8");

			list_changed = false;

			l.EndNew (2);

			Assert.AreEqual (1, l.Count, "9");
			Assert.IsFalse (list_changed, "10");

			l.CancelNew (0);

			Assert.IsTrue (list_changed, "11");
			Assert.AreEqual (ListChangedType.ItemDeleted, change_type, "12");
			Assert.AreEqual (0, list_changed_index, "13");
		}

		class BindingListPoker : BindingList<object>
		{
			public object DoAddNewCore()
			{
				return base.AddNewCore ();
			}
		}

		// test to make sure that the events are raised in AddNewCore and not in AddNew
		[Test]
		public void TestAddNewCore_Insert ()
		{
			BindingListPoker poker = new BindingListPoker ();

			bool adding_event_raised = false;

			bool list_changed = false;
			ListChangedType change_type = ListChangedType.Reset;
			int list_changed_index = -1;

			poker.AddingNew += delegate (object sender, AddingNewEventArgs e) {
				adding_event_raised = true;
			};

			poker.ListChanged += delegate (object sender, ListChangedEventArgs e) {
				list_changed = true;
				change_type = e.ListChangedType;
				list_changed_index = e.NewIndex;
			};

			object o = poker.DoAddNewCore ();

			Assert.IsTrue (adding_event_raised, "1");
			Assert.IsTrue (list_changed, "2");
			Assert.AreEqual (ListChangedType.ItemAdded, change_type, "3");
			Assert.AreEqual (0, list_changed_index, "4");
			Assert.AreEqual (1, poker.Count, "5");
		}

		private class Item : INotifyPropertyChanged {

			public event PropertyChangedEventHandler PropertyChanged;

			string _name;

			public string Name {
				get { return _name; }
				set {
					if (_name != value) {
						_name = value;
						OnPropertyChanged ("Name");
					}
				}
			}

			void OnPropertyChanged (string name)
			{
				var fn = PropertyChanged;
				if (fn != null)
					fn (this, new PropertyChangedEventArgs (name));
			}
		}

		[Test] // https://bugzilla.xamarin.com/show_bug.cgi?id=8366
		public void Bug8366 ()
		{
			bool added = false;
			bool changed = false;
			var list = new BindingList<Item> ();
			list.ListChanged += (object sender, ListChangedEventArgs e) => {
				added |= e.ListChangedType == ListChangedType.ItemAdded;
				changed |= e.ListChangedType == ListChangedType.ItemChanged;
			};

			var item = new Item () { Name = "1" };
			list.Add (item);

			item.Name = "2";

			Assert.IsTrue (added, "ItemAdded");
			Assert.IsTrue (changed, "ItemChanged");
		}
		
		[Test] // https://bugzilla.xamarin.com/show_bug.cgi?id=16902
		public void Bug16902 ()
		{
			var list = new BindingList<Item> ();
			list.Insert (0, null);
			var count = list.Count;
			
			Assert.AreEqual (1, count, "1");
		}

		private class Person : INotifyPropertyChanged
		{
			private string _lastName;
			private string _firstName;

			public string FirstName {
				get { return _firstName; }
				set {
					_firstName = value;
					OnPropertyChanged ("FirstName"); // string matches property name
				}
			}

			public string LastName {
				get { return _lastName; }
				set {
					_lastName = value;
					OnPropertyChanged ("Apepe"); // string doesn't match property name
				}
			}

			public event PropertyChangedEventHandler PropertyChanged;

			protected virtual void OnPropertyChanged (string propertyName = null)
			{
				PropertyChangedEventHandler handler = PropertyChanged;
				if (handler != null)
					handler (this, new PropertyChangedEventArgs (propertyName));
			}
		}

		[Test] // https://bugzilla.xamarin.com/show_bug.cgi?id=20672
		public void Bug20672 ()
		{
			string changedPropertyName = string.Empty;
			bool isEventRaised = false;
			bool? hasPropertyDescriptor = false;

			var persons = new BindingList<Person>();
			persons.Add (new Person() { FirstName = "Stefaan", LastName = "de Vogelaere" });
			persons.Add (new Person() { FirstName = "Christophe", LastName = "De Langhe" });
			persons.ListChanged += (object sender, ListChangedEventArgs e) => {
			    isEventRaised = true;
			    hasPropertyDescriptor = e.PropertyDescriptor != null;
			};

			//if the OnPropertyChanged string matches a valid property name, PropertyDescriptor should be generated
			persons[0].FirstName = "Stefan";
			Assert.IsTrue (isEventRaised);
			Assert.IsTrue ((bool) hasPropertyDescriptor, "#1");

			//if the OnPropertyChanged string doesn't match a valid property name, no PropertyDescriptor should be generated
			persons[0].LastName = "de le Vulu";
			Assert.IsFalse ((bool) hasPropertyDescriptor, "#2");
		}
	}
}

