using System;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using ReactiveUI;

namespace Valg.EVA.Skanning.JobManagement.Startup.Data
{
    public static class ReactiveModelExtensions
    {
        public static void Bubble<TModel, TFromProperty>(this
            TModel model,
            Expression<Func<TModel, TFromProperty>> fromSelector,
            params Action[] actions)
            where TModel : INotifyPropertyChanged
        {
            model.PropertyChanged += (sender, args) =>
            {
                
                if (args.PropertyName == fromSelector.PropertyName())
                    Array.ForEach(actions, a => a.Invoke());
            };
        }

        public static void Bubble<TModel, TFromProperty>(this
            TModel model,
            Expression<Func<TModel, TFromProperty>> fromSelector,
            params Expression<Func<TModel, object>>[] toSelectors)
            where TModel : ReactiveObject
        {
            PropertyChangedEventHandler handler = (sender, args) => NotifyTargets(model, fromSelector, toSelectors, args);
            model.PropertyChanged -= handler;
            model.PropertyChanged += handler;
        }

        private static void NotifyTargets<TModel, TFromProperty>(TModel model, Expression<Func<TModel, TFromProperty>> fromSelector, Expression<Func<TModel, object>>[] toSelectors, PropertyChangedEventArgs args) where TModel : ReactiveObject
        {
            if (args.PropertyName != fromSelector.PropertyName())
                return;
            
            var toPs = string.Join(",", toSelectors.Select(s => s.PropertyName()));
            

            Array.ForEach(
                toSelectors,
                toSelector => model.RaisePropertyChanged(toSelector.PropertyName()));
        }

        public static void Bubble<TFromModel, TToModel, TFromProperty>(this
            TFromModel fromModel,
            Expression<Func<TFromModel, TFromProperty>> fromSelector,
            TToModel toModel,
            params Expression<Func<TToModel, object>>[] toSelectors)
            where TFromModel : INotifyPropertyChanged
            where TToModel : ReactiveObject
        {
            PropertyChangedEventHandler handler = (sender, args) => NotifyTargets(fromModel, fromSelector, toModel, toSelectors, args);

            fromModel.PropertyChanged -= handler;
            fromModel.PropertyChanged += handler;
        }

        private static void NotifyTargets<TFromModel, TToModel, TFromProperty>(TFromModel fromModel, Expression<Func<TFromModel, TFromProperty>> fromSelector, TToModel toModel, Expression<Func<TToModel, object>>[] toSelectors, PropertyChangedEventArgs args)
            where TFromModel : INotifyPropertyChanged where TToModel : ReactiveObject
        {
            if (args.PropertyName == fromSelector.PropertyName())
            {
                var toPs = string.Join(",", toSelectors.Select(s => s.PropertyName()));
                
                Array.ForEach(
                    toSelectors,
                    toSelector => toModel.RaisePropertyChanged(toSelector.PropertyName()));
            }
        }

        public static void Bubble<TFromModel, TToModel>(this
            TFromModel fromModel,
            TToModel toModel,
            params Expression<Func<TToModel, object>>[] toSelectors)
            where TFromModel : INotifyPropertyChanged
            where TToModel : ReactiveObject
        {
            fromModel.PropertyChanged += (sender, args) =>
            {
                var toPs = string.Join(",", toSelectors.Select(s => s.PropertyName()));
                
                Array.ForEach(
                    toSelectors,
                    toSelector => toModel.RaisePropertyChanged(toSelector.PropertyName()));
            };
        }

        public static void BubbleCollection<TFromModel, TToModel>(this
            TFromModel fromModel,
            TToModel toModel,
            params Expression<Func<TToModel, object>>[] toSelectors)
            where TFromModel : INotifyCollectionChanged
            where TToModel : ReactiveObject
        {
            NotifyCollectionChangedEventHandler handler = (sender, args) => NotifyTargets(fromModel, toModel, toSelectors);
            
            fromModel.CollectionChanged -= handler;
            fromModel.CollectionChanged += handler;
        }

        private static void NotifyTargets<TFromModel, TToModel>(TFromModel fromModel, TToModel toModel, Expression<Func<TToModel, object>>[] toSelectors) where TFromModel : INotifyCollectionChanged
            where TToModel : ReactiveObject
        {
            var toPs = string.Join(",", toSelectors.Select(s => s.PropertyName()));
            
            Array.ForEach(
                toSelectors,
                toSelector => toModel.RaisePropertyChanged(toSelector.PropertyName()));
        }

        public static void BubbleCollection<TFromModel>(this
            TFromModel fromModel,
            params Action<TFromModel>[] actions)
            where TFromModel : INotifyCollectionChanged
        {
            NotifyCollectionChangedEventHandler handler = (sender, args) => NotifyTargets(fromModel, actions);
            
            fromModel.CollectionChanged -= handler;
            fromModel.CollectionChanged += handler;
        }

        private static void NotifyTargets<TFromModel>(TFromModel fromModel, Action<TFromModel>[] actions)
            where TFromModel : INotifyCollectionChanged
        {
            
            Array.ForEach(
                actions,
                action => action.Invoke(fromModel));
        }
    }
}
