using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Media;
using System.Windows.Threading;

namespace HandyControl.Expression.Media
{
    [TypeConverter(typeof(GeometryEffectConverter))]
    public abstract class GeometryEffect : Freezable
    {
        // ReSharper disable once InconsistentNaming
        private static GeometryEffect defaultGeometryEffect;

        public static readonly DependencyProperty GeometryEffectProperty =
            DependencyProperty.RegisterAttached("GeometryEffect", typeof(GeometryEffect),
                typeof(GeometryEffect),
                new DrawingPropertyMetadata(DefaultGeometryEffect,
                    DrawingPropertyMetadataOptions.AffectsRender, OnGeometryEffectChanged));

        protected Geometry CachedGeometry;

        private bool _effectInvalidated;

        static GeometryEffect()
        {
            DrawingPropertyMetadata.DrawingPropertyChanged +=
                delegate (object sender, DrawingPropertyChangedEventArgs args)
                {
                    if (sender is GeometryEffect effect && args.Metadata.AffectsRender)
                        effect.InvalidateGeometry(InvalidateGeometryReasons.PropertyChanged);
                };
        }

        public static GeometryEffect DefaultGeometryEffect =>
            defaultGeometryEffect ?? (defaultGeometryEffect = new NoGeometryEffect());

        public Geometry OutputGeometry =>
            CachedGeometry;

        protected internal DependencyObject Parent { get; private set; }

        protected internal virtual void Attach(DependencyObject obj)
        {
            if (Parent != null) Detach();
            _effectInvalidated = true;
            CachedGeometry = null;
            if (InvalidateParent(obj)) Parent = obj;
        }

        public new GeometryEffect CloneCurrentValue()
        {
            return (GeometryEffect) base.CloneCurrentValue();
        }

        protected override Freezable CreateInstanceCore()
        {
            return (Freezable) Activator.CreateInstance(GetType());
        }

        protected abstract GeometryEffect DeepCopy();

        protected internal virtual void Detach()
        {
            _effectInvalidated = true;
            CachedGeometry = null;
            if (Parent != null)
            {
                InvalidateParent(Parent);
                Parent = null;
            }
        }

        public abstract bool Equals(GeometryEffect geometryEffect);

        public static GeometryEffect GetGeometryEffect(DependencyObject obj)
        {
            return (GeometryEffect) obj.GetValue(GeometryEffectProperty);
        }

        public bool InvalidateGeometry(InvalidateGeometryReasons reasons)
        {
            if (_effectInvalidated) return false;
            _effectInvalidated = true;
            if (reasons != InvalidateGeometryReasons.ParentInvalidated) InvalidateParent(Parent);
            return true;
        }

        private static bool InvalidateParent(DependencyObject parent)
        {
            if (parent is IShape shape)
            {
                shape.InvalidateGeometry(InvalidateGeometryReasons.ChildInvalidated);
                return true;
            }

            if (parent is GeometryEffect effect)
            {
                effect.InvalidateGeometry(InvalidateGeometryReasons.ChildInvalidated);
                return true;
            }

            return false;
        }

        private static void OnGeometryEffectChanged(DependencyObject obj,
            DependencyPropertyChangedEventArgs e)
        {
            var oldValue = e.OldValue as GeometryEffect;
            var newValue = e.NewValue as GeometryEffect;
            if (!Equals(oldValue, newValue))
            {
                if (oldValue != null && obj.Equals(oldValue.Parent)) oldValue.Detach();
                if (newValue != null)
                {
                    if (newValue.Parent != null)
                    {
                        obj.Dispatcher.BeginInvoke(new Action(() =>
                        {
                            var effect = newValue.CloneCurrentValue();
                            obj.SetValue(GeometryEffectProperty, effect);
                        }), DispatcherPriority.Send, null);
                    }
                    else
                    {
                        newValue.Attach(obj);
                    }
                }
            }
        }

        public bool ProcessGeometry(Geometry input)
        {
            var flag = false;
            if (_effectInvalidated)
            {
                flag |= UpdateCachedGeometry(input);
                _effectInvalidated = false;
            }

            return flag;
        }

        public static void SetGeometryEffect(DependencyObject obj, GeometryEffect value)
        {
            obj.SetValue(GeometryEffectProperty, value);
        }

        protected abstract bool UpdateCachedGeometry(Geometry input);


        private class NoGeometryEffect : GeometryEffect
        {
            protected override GeometryEffect DeepCopy()
            {
                return new NoGeometryEffect();
            }

            public override bool Equals(GeometryEffect geometryEffect)
            {
                if (geometryEffect != null) return geometryEffect is NoGeometryEffect;
                return true;
            }

            protected override bool UpdateCachedGeometry(Geometry input)
            {
                CachedGeometry = input;
                return false;
            }
        }
    }
}
