using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using tinydatabinding.Helpers;
using tinydatabinding.Interfaces;

namespace tinydatabinding.Core.Base
{
    public abstract class BindingNodeBase : IBinding
    {
        #region Properties

        /// <summary>
        /// Gets or sets target object.
        /// </summary>
        public object Target { get; set; }

        /// <summary>
        /// Gets or sets node parent.
        /// </summary>
        public BindingNodeBase Parent { get; set; }

        /// <summary>
        /// Defines a list with node children.
        /// </summary>
        public IList<BindingNodeBase> Children { get; set; }

        #endregion

        #region Methods

        public virtual void Init()
        {
            DebugHelper.CountCall(this, "BindingNodeBase.Init");

            Subscribe(Target);
        }

        public virtual void Destroy()
        {
            DebugHelper.CountCall(this, "BindingNodeBase.Destroy");

            if (Children != null)
            {
                foreach (var childNode in Children)
                {
                    childNode.Destroy();
                }

                Children.Clear();
            }

            Unsubscribe(Target);

            Children = null;
            Parent = null;
            Target = null;
        }

        public virtual void SetValue(object value)
        {
            DebugHelper.CountCall(this, "BindingNodeBase.SetValue");

            if (Children != null)
            {
                foreach (var childNode in Children)
                {
                    childNode.SetValue(value);
                }
            }
        }

        protected virtual void OnValueChanged()
        {
            DebugHelper.CountCall(this, "BindingNodeBase.OnValueChanged");
            
            Parent.OnValueChanged();
        }

        protected virtual void NccTargetOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
        {
            DebugHelper.CountCall(this, "BindingNodeBase.NccTargetOnCollectionChanged");

            OnValueChanged();
        }

        protected virtual void NpcTargetOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
        {
            DebugHelper.CountCall(this, "BindingNodeBase.NpcTargetOnPropertyChanged");

            if (Children != null &&
                Children.Any(
                    x =>
                        x is MemberBindingNodeBase &&
                        ((MemberBindingNodeBase) x).GetMemberName() == propertyChangedEventArgs.PropertyName))
            {
                OnValueChanged();
            }
        }

        /// <summary>
        /// Unsubscribes specified object.
        ///  </summary>
        /// <param name="target"></param>
        protected virtual void Unsubscribe(object target)
        {
            DebugHelper.CountCall(this, "BindingNodeBase.Unsubscribe");

            var npc = target as INotifyPropertyChanged;
            if (npc != null)
            {
                npc.PropertyChanged -= NpcTargetOnPropertyChanged;
            }
            var ncc = target as INotifyCollectionChanged;
            if (ncc != null)
            {
                ncc.CollectionChanged -= NccTargetOnCollectionChanged;
            }
        }

        /// <summary>
        /// Subscribes specified object.
        ///  </summary>
        /// <param name="target"></param>
        protected virtual void Subscribe(object target)
        {
            DebugHelper.CountCall(this, "BindingNodeBase.Subscribe");

            var npc = target as INotifyPropertyChanged;
            if (npc != null)
            {
                npc.PropertyChanged += NpcTargetOnPropertyChanged;
            }
            var ncc = target as INotifyCollectionChanged;
            if (ncc != null)
            {
                ncc.CollectionChanged += NccTargetOnCollectionChanged;
            }
        }

        #endregion
    }
}