using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Text;
using System.Xml.Serialization;


namespace Wpf_Demo.ViewModels {
        /// <summary>
        /// This is the base class from which most business objects will be derived. 
        /// To create a business object, inherit from this class.
        /// </summary>
        /// <typeparam name="TType">The type of the derived class.</typeparam>
        /// <typeparam name="TKey">The type of the Id property.</typeparam>
        public class BusinessBase<TType, TKey> : INotifyPropertyChanged, IChangeTracking, IDisposable
            where TType : BusinessBase<TType, TKey>, new() {

                public BusinessBase()
                {
                }

                #region Properties

                [XmlElement("Id")]
                public TKey Id { get; set; }


                #endregion

                #region IsNew, IsDeleted, IsChanged

                /// <summary>
                /// </summary>
                private bool isNew = true;
                /// <summary>
                /// Gets if this is a new object, False if it is a pre-existing object.
                /// </summary>
                /// <value></value>
                public bool IsNew
                {
                    get { return isNew; }
                    set { isNew = value; }
                }

                /// <summary>
                /// </summary>
                private bool isDeleted;
                /// <summary>
                /// Gets if this object is marked for deletion.
                /// </summary>
                /// <value></value>
                [XmlIgnore]
                public bool IsDeleted
                {
                        get { return isDeleted; }
                }

                /// <summary>
                /// </summary>
                private bool isChanged = true;
                /// <summary>
                /// Gets if this object's data has been changed.
                /// </summary>
                /// <value></value>
                public virtual bool IsChanged
                {
                    get { return isChanged; }
                    set { isChanged = value;}
                }

                /// <summary>
                /// Marks the object for deletion. It will then be 
                /// deleted when the object's Save() method is called.
                /// </summary>
                public virtual void Delete()
                {
                        isDeleted = true;
                        isChanged = true;
                }

                /// <summary>
                /// </summary>
                private readonly List<string> changedProperties = new List<string>();
                /// <summary>
                /// A collection of the properties that have 
                /// been marked as being dirty.
                /// </summary>
                /// <value></value>
                public virtual List<string> ChangedProperties
                {
                        get { return changedProperties; }
                }

                /// <summary>
                /// Marks an object as being dirty, or changed.
                /// </summary>
                /// <param name="propertyName">The name of the property to mark dirty.</param>
                protected virtual void MarkChanged(string propertyName)
                {
                        isChanged = true;
                        if (!changedProperties.Contains(propertyName)) {
                                changedProperties.Add(propertyName);
                        }

                        OnPropertyChanged(propertyName);
                }

                /// <summary>
                /// Marks the object as being an clean, 
                /// which means not dirty.
                /// </summary>
                public virtual void MarkOld()
                {
                    isChanged = false;
                    isNew = false;
                    changedProperties.Clear();
                }

                #endregion

                #region Validation

                /// <summary>
                /// </summary>
                [XmlIgnore]
                private readonly Dictionary<string, string> brokenRules = new Dictionary<string, string>();

                /// <summary>
                /// Add or remove a broken rule.
                /// </summary>
                /// <param name="propertyName">The name of the property.</param>
                /// <param name="errorMessage">The description of the error</param>
                /// <param name="isBroken">True if the validation rule is broken.</param>
                protected virtual void AddRule(string propertyName, string errorMessage, bool isBroken)
                {
                        if (isBroken)
                                brokenRules[propertyName] = errorMessage;
                        else {
                                if (brokenRules.ContainsKey(propertyName))
                                        brokenRules.Remove(propertyName);
                        }
                }

                /// <summary>
                /// Reinforces the business rules by adding additional rules to the 
                /// broken rules collection.
                /// </summary>
                protected virtual void ValidateRules()
                {
                }

                /// <summary>
                /// Gets whether the object is valid or not.
                /// </summary>
                /// <value></value>
                [XmlIgnore]
                public bool IsValid
                {
                        get
                        {
                                ValidateRules();
                                return brokenRules.Count == 0;
                        }
                }

                /// <summary>
                /// If the object has broken business rules, use this property to get access
                /// to the different validation messages.
                /// </summary>
                /// <value></value>
                /// /// 
                [XmlIgnore]
                public virtual string ValidationMessage
                {
                        get
                        {
                                if (!IsValid) {
                                        StringBuilder sb = new StringBuilder();
                                        foreach (string messages in brokenRules.Values) {
                                                sb.AppendLine(messages);
                                        }

                                        return sb.ToString();
                                }

                                return string.Empty;
                        }
                }

                #endregion

                #region Methods

                /// <summary>
                /// Loads an instance of the object based on the Id.
                /// </summary>
                /// <param name="id">The unique identifier of the object</param>
                /// <returns></returns>
                public static TType Load(TKey id)
                {
                        TType instance = new TType();
                        instance = instance.DataSelect(id);
                        instance.Id = id;

                        if (instance != null) {
                                instance.MarkOld();
                                return instance;
                        }

                        return null;
                }



                /// <summary>
                /// Saves the object to the data store (inserts, updates or deletes).
                /// </summary>
                /// <returns></returns>
                virtual public SaveAction Save()
                {
                        if (!IsValid && !IsDeleted)
                                throw new InvalidOperationException(ValidationMessage);

                        if (IsDisposed && !IsDeleted)
                                throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "You cannot save a disposed {0}", GetType().Name));

                        if (IsChanged)
                                return Update();

                        return SaveAction.None;
                }

                /// <summary>
                /// Is called by the save method when the object is old and dirty.
                /// </summary>
                /// <returns></returns>
                private SaveAction Update()
                {
                        SaveAction action = SaveAction.None;

                        if (IsDeleted) {
                                if (!IsNew) {
                                        action = SaveAction.Delete;
                                        OnSaving(this, action);
                                        DataDelete();
                                }
                        } else {
                                if (IsNew) {
                                        action = SaveAction.Insert;
                                        OnSaving(this, action);
                                        DataInsert();
                                } else {
                                        action = SaveAction.Update;
                                        OnSaving(this, action);
                                        DataUpdate();
                                }

                                MarkOld();
                        }

                        OnSaved(this, action);
                        return action;
                }

                #endregion

                #region Data Access

                /// <summary>
                /// Retrieves the object from the data store and populates it.
                /// </summary>
                /// <param name="id">The unique identifier of the object.</param>
                /// <returns>True if the object exists and is being populated successfully</returns>
                protected virtual TType DataSelect(TKey id)
                {
                    return null;
                }

                /// <summary>
                /// Updates the object in its data store.
                /// </summary>
                protected virtual void DataUpdate()
                {
                }

                /// <summary>
                /// Inserts a new object to the data store.
                /// </summary>
                protected virtual void DataInsert()
                {
                }

                /// <summary>
                /// Deletes the object from the data store.
                /// </summary>
                protected virtual void DataDelete()
                {
                }

            #endregion

                #region Equality overrides

                /// <summary>
                /// A uniquely key to identify this particular instance of the class
                /// </summary>
                /// <returns>A unique integer value</returns>
                public override int GetHashCode()
                {
                        return Id.GetHashCode();
                }

                /// <summary>
                /// Compares this object with another
                /// </summary>
                /// <param name="obj">The object to compare</param>
                /// <returns>True if the two objects as equal</returns>
                public override bool Equals(object obj)
                {
                        if (obj == null) {
                                return false;
                        }

                        if (obj.GetType() == GetType()) {
                                return obj.GetHashCode() == GetHashCode();
                        }

                        return false;
                }

                /// <summary>
                /// Checks to see if two business objects are the same.
                /// </summary>
                /// <param name="first"></param>
                /// <param name="second"></param>
                /// <returns></returns>
                public static bool operator ==(BusinessBase<TType, TKey> first, BusinessBase<TType, TKey> second)
                {
                        if (ReferenceEquals(first, second)) {
                                return true;
                        }

                        if ((object)first == null || (object)second == null) {
                                return false;
                        }

                        return first.GetHashCode() == second.GetHashCode();
                }

                /// <summary>
                /// Checks to see if two business objects are different.
                /// </summary>
                /// <param name="first"></param>
                /// <param name="second"></param>
                /// <returns></returns>
                public static bool operator !=(BusinessBase<TType, TKey> first, BusinessBase<TType, TKey> second)
                {
                        return !(first == second);
                }

                #endregion

                #region Events

                /// <summary>
                /// Occurs when the class is Saved
                /// </summary>
                public static event EventHandler<SavedEventArgs> Saved;
                /// <summary>
                /// Raises the Saved event.
                /// </summary>
                /// <param name="businessObject"></param>
                /// <param name="action"></param>
                protected static void OnSaved(BusinessBase<TType, TKey> businessObject, SaveAction action)
                {
                        if (Saved != null) {
                                Saved(businessObject, new SavedEventArgs(action));
                        }
                }

                /// <summary>
                /// Occurs when the class is Saved
                /// </summary>
                public static event EventHandler<SavedEventArgs> Saving;
                /// <summary>
                /// Raises the Saving event
                /// </summary>
                /// <param name="businessObject"></param>
                /// <param name="action"></param>
                protected static void OnSaving(BusinessBase<TType, TKey> businessObject, SaveAction action)
                {
                        if (Saving != null) {
                                Saving(businessObject, new SavedEventArgs(action));
                        }
                }

                /// <summary>
                /// Occurs when this instance is marked dirty. 
                /// It means the instance has been changed but not saved.
                /// </summary>
                public event PropertyChangedEventHandler PropertyChanged;
                /// <summary>
                /// Raises the <see cref="PropertyChanged"/> event safely.
                /// </summary>
                /// <param name="propertyName"></param>
                protected virtual void OnPropertyChanged(string propertyName)
                {
                        if (PropertyChanged != null) {
                                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                        }
                }

                #endregion

                #region IDisposable

                /// <summary>
                /// </summary>
                private bool isDisposed;
                /// <summary>
                /// Gets or sets if the object has been disposed.
                /// <remarks>
                /// If the objects is disposed, it must not be disposed a second
                /// time. The IsDisposed property is set the first time the object
                /// is disposed. If the IsDisposed property is true, then the Dispose()
                /// method will not dispose again. This help not to prolong the object's
                /// life if the Garbage Collector.
                /// </remarks>
                /// </summary>
                /// <value></value>
                [XmlIgnore]
                protected bool IsDisposed
                {
                        get { return isDisposed; }
                }

                /// <summary>
                /// Disposes the object and frees resources for the Garbage Collector.
                /// </summary>
                /// <param name="disposing">If true, the object gets disposed.</param>
                protected virtual void Dispose(bool disposing)
                {
                        if (IsDisposed)
                                return;

                        if (disposing) {
                                changedProperties.Clear();
                                brokenRules.Clear();
                                isDisposed = true;
                        }
                }

                /// <summary>
                /// Disposes the object and frees resources for the Garbage Collector.
                /// </summary>
                public void Dispose()
                {
                        Dispose(true);
                        GC.SuppressFinalize(this);
                }

                #endregion

                #region IDataErrorInfo Members

                /// <summary>
                /// Gets an error message indicating what is wrong with this object.
                /// </summary>
                /// <returns>An error message indicating what is wrong with this object. The default is an empty string ("").</returns>
                /// <value></value>
                [XmlIgnore]
                public string Error
                {
                        get { return ValidationMessage; }
                }

                /// <summary>
                /// Gets the <see cref="System.String"/> with the specified column name.
                /// </summary>
                /// <param name="columnName"></param>
                /// <value></value>
                public string this[string columnName]
                {
                        get
                        {
                                if (brokenRules.ContainsKey(columnName))
                                        return brokenRules[columnName];

                                return string.Empty;
                        }
                }

                #endregion

                #region IChangeTracking Members

                /// <summary>
                /// Resets the object’s state to unchanged by accepting the modifications.
                /// </summary>
                void IChangeTracking.AcceptChanges()
                {
                        Save();
                }

                #endregion
        }
}