using System;
using System.Collections;
using System.ComponentModel;
using System.Data.SqlTypes;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Xml;
using System.Xml.Serialization;

using BLToolkit.Common;
using BLToolkit.ComponentModel;
using BLToolkit.Mapping;
using BLToolkit.Reflection;
using BLToolkit.TypeBuilder;
using BLToolkit.Validation;

namespace BLToolkit.EditableObjects
{
	#region Instance Types
	[GlobalInstanceType(typeof(byte),     typeof(EditableValue<byte>))]
	[GlobalInstanceType(typeof(char),     typeof(EditableValue<char>))]
	[GlobalInstanceType(typeof(ushort),   typeof(EditableValue<ushort>))]
	[GlobalInstanceType(typeof(uint),     typeof(EditableValue<uint>))]
	[GlobalInstanceType(typeof(ulong),    typeof(EditableValue<ulong>))]
	[GlobalInstanceType(typeof(bool),     typeof(EditableValue<bool>))]
	[GlobalInstanceType(typeof(sbyte),    typeof(EditableValue<sbyte>))]
	[GlobalInstanceType(typeof(short),    typeof(EditableValue<short>))]
	[GlobalInstanceType(typeof(int),      typeof(EditableValue<int>))]
	[GlobalInstanceType(typeof(long),     typeof(EditableValue<long>))]
	[GlobalInstanceType(typeof(float),    typeof(EditableValue<float>))]
	[GlobalInstanceType(typeof(double),   typeof(EditableValue<double>))]
	[GlobalInstanceType(typeof(string),   typeof(EditableValue<string>), "")]
	[GlobalInstanceType(typeof(DateTime), typeof(EditableValue<DateTime>))]
	[GlobalInstanceType(typeof(decimal),  typeof(EditableValue<decimal>))]
	[GlobalInstanceType(typeof(Guid),     typeof(EditableValue<Guid>))]

	[GlobalInstanceType(typeof(byte?),     typeof(EditableValue<byte?>))]
	[GlobalInstanceType(typeof(char?),     typeof(EditableValue<char?>))]
	[GlobalInstanceType(typeof(ushort?),   typeof(EditableValue<ushort?>))]
	[GlobalInstanceType(typeof(uint?),     typeof(EditableValue<uint?>))]
	[GlobalInstanceType(typeof(ulong?),    typeof(EditableValue<ulong?>))]
	[GlobalInstanceType(typeof(bool?),     typeof(EditableValue<bool?>))]
	[GlobalInstanceType(typeof(sbyte?),    typeof(EditableValue<sbyte?>))]
	[GlobalInstanceType(typeof(short?),    typeof(EditableValue<short?>))]
	[GlobalInstanceType(typeof(int?),      typeof(EditableValue<int?>))]
	[GlobalInstanceType(typeof(long?),     typeof(EditableValue<long?>))]
	[GlobalInstanceType(typeof(float?),    typeof(EditableValue<float?>))]
	[GlobalInstanceType(typeof(double?),   typeof(EditableValue<double?>))]
	[GlobalInstanceType(typeof(DateTime?), typeof(EditableValue<DateTime?>))]
	[GlobalInstanceType(typeof(decimal?),  typeof(EditableValue<decimal?>))]
	[GlobalInstanceType(typeof(Guid?),     typeof(EditableValue<Guid?>))]

	[GlobalInstanceType(typeof(SqlBoolean),  typeof(EditableValue<SqlBoolean>))]
	[GlobalInstanceType(typeof(SqlByte),     typeof(EditableValue<SqlByte>))]
	[GlobalInstanceType(typeof(SqlDateTime), typeof(EditableValue<SqlDateTime>))]
	[GlobalInstanceType(typeof(SqlDecimal),  typeof(EditableValue<SqlDecimal>))]
	[GlobalInstanceType(typeof(SqlDouble),   typeof(EditableValue<SqlDouble>))]
	[GlobalInstanceType(typeof(SqlGuid),     typeof(EditableValue<SqlGuid>))]
	[GlobalInstanceType(typeof(SqlInt16),    typeof(EditableValue<SqlInt16>))]
	[GlobalInstanceType(typeof(SqlInt32),    typeof(EditableValue<SqlInt32>))]
	[GlobalInstanceType(typeof(SqlInt64),    typeof(EditableValue<SqlInt64>))]
	[GlobalInstanceType(typeof(SqlMoney),    typeof(EditableValue<SqlMoney>))]
	[GlobalInstanceType(typeof(SqlSingle),   typeof(EditableValue<SqlSingle>))]
	[GlobalInstanceType(typeof(SqlString),   typeof(EditableValue<SqlString>), "")]

	[GlobalInstanceType(typeof(XmlDocument),    typeof(EditableXmlDocument))]
	[GlobalInstanceType(typeof(EditableObject), typeof(EditableObjectHolder), IsObjectHolder=true)]
	#endregion
	[ImplementInterface(typeof(IEditable))]
	[ImplementInterface(typeof(IMemberwiseEditable))]
	[ImplementInterface(typeof(IPrintDebugState))]
	[ImplementInterface(typeof(ISetParent))]
	[ComVisible(true)]
	[Serializable]
	public abstract class EditableObject : EntityBase,
		ICloneable, IEditableObject, INotifyPropertyChanged,
		ISupportMapping, IValidatable, IPropertyChanged, INotifyObjectEdit
	{
		#region Constructor

		protected EditableObject()
		{
			ISetParent setParent = this as ISetParent;

			if (setParent != null)
				setParent.SetParent(this, null);
		}

		#endregion

		#region IEditable

		public virtual void AcceptChanges()
		{
			if (this is IEditable)
				((IEditable)this).AcceptChanges();
		}

		public virtual void RejectChanges()
		{
			var dirtyMembers = GetDirtyMembers();

			if (this is IEditable)
				((IEditable)this).RejectChanges();

			foreach (PropertyInfo dirtyMember in dirtyMembers)
				OnPropertyChanged(dirtyMember.Name);
		}

		[MapIgnore, Bindable(false)]
		public virtual bool IsDirty
		{
			get { return this is IEditable? ((IEditable)this).IsDirty: false; }
		}

		public virtual void AcceptMemberChanges(string memberName)
		{
			if (this is IMemberwiseEditable)
				((IMemberwiseEditable)this).AcceptMemberChanges(null, memberName);
		}

		public virtual void RejectMemberChanges(string memberName)
		{
			bool notifyChange = IsDirtyMember(memberName);

			if (this is IMemberwiseEditable)
				((IMemberwiseEditable)this).RejectMemberChanges(null, memberName);

			if (notifyChange)
				OnPropertyChanged(memberName);
		}

		public virtual bool IsDirtyMember(string memberName)
		{
			bool isDirty = false;

			if (this is IMemberwiseEditable)
				((IMemberwiseEditable)this).IsDirtyMember(null, memberName, ref isDirty);

			return isDirty;
		}

		public virtual PropertyInfo[] GetDirtyMembers()
		{
			ArrayList list = new ArrayList();

			if (this is IMemberwiseEditable)
				((IMemberwiseEditable)this).GetDirtyMembers(null, list);

			return (PropertyInfo[])list.ToArray(typeof(PropertyInfo));
		}

		[MapIgnore, Bindable(false)]
		public virtual string PrintDebugState
		{
			get
			{
#if DEBUG
				if (this is IPrintDebugState)
				{
					string s = string.Format(
						"====== {0} ======\r\nIsDirty: {1}\r\n" +
						"Property       IsDirty Original                                 Current\r\n" +
						"==================== = ======================================== ========================================\r\n",
						GetType().Name, IsDirty);

					((IPrintDebugState)this).PrintDebugState(null, ref s);

					return s + "\r\n";
				}
#endif
				return "";
			}
		}

		#endregion

		#region ISupportMapping Members

		private bool _isInMapping;
		[MapIgnore, Bindable(false)]
		public  bool  IsInMapping
		{
			get { return _isInMapping; }
		}

		protected void SetIsInMapping(bool isInMapping)
		{
			_isInMapping = isInMapping;
		}

		public virtual void BeginMapping(InitContext initContext)
		{
			_isInMapping = true;
		}

		public virtual void EndMapping(InitContext initContext)
		{
			if (initContext.IsDestination)
				AcceptChanges();

			_isInMapping = false;

			if (initContext.IsDestination)
				OnPropertyChanged("");
		}

		#endregion

		#region Notify Changes

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

		private int _notNotifyChangesCount = 0;
		[MapIgnore, Bindable(false), XmlIgnore]
		public  bool  NotifyChanges
		{
			get { return _notNotifyChangesCount == 0;   }
			set { _notNotifyChangesCount = value? 0: 1; }
		}

		public void LockNotifyChanges()
		{
			_notNotifyChangesCount++;
		}

		public void UnlockNotifyChanges()
		{
			_notNotifyChangesCount--;

			if (_notNotifyChangesCount < 0)
				throw new InvalidOperationException();
		}

		#endregion

		#region IPropertyChanged Members

		void IPropertyChanged.OnPropertyChanged(PropertyInfo propertyInfo)
		{
			if (_isInMapping == false)
				OnPropertyChanged(propertyInfo.Name);
		}

		#endregion

		#region INotifyPropertyChanged Members

		[field : NonSerialized]
		public virtual event PropertyChangedEventHandler PropertyChanged;

		#endregion

		#region IEditableObject Members

		public virtual void BeginEdit()
		{
			if (ObjectEdit != null)
				ObjectEdit(this, new ObjectEditEventArgs(ObjectEditType.Begin));
		}

		public virtual void CancelEdit()
		{
			if (ObjectEdit != null)
				ObjectEdit(this, new ObjectEditEventArgs(ObjectEditType.Cancel));
		}

		public virtual void EndEdit()
		{
			if (ObjectEdit != null)
				ObjectEdit(this, new ObjectEditEventArgs(ObjectEditType.End));
		}

		#endregion

		#region INotifyObjectEdit Members

		public event ObjectEditEventHandler ObjectEdit;

		#endregion

		#region ICloneable Members

		///<summary>
		///Creates a new object that is a copy of the current instance.
		///</summary>
		///<returns>
		///A new object that is a copy of this instance.
		///</returns>
		object ICloneable.Clone()
		{
			return TypeAccessor.Copy(this);
		}

		#endregion
	}
}
