using System.Globalization;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Animation;
using HandyControl.Tools;

// ReSharper disable PossibleInvalidOperationException
namespace HandyControl.Media.Animation
{
    public class GeometryAnimation : GeometryAnimationBase
    {
        private string[] _strings;

        private double[] _numbersFrom;

        private double[] _numbersTo;

        private double[] _numbersAccumulator;

        public GeometryAnimation()
        {

        }

        public GeometryAnimation(string fromValue, string toValue) : this()
        {
            From = Geometry.Parse(fromValue);
            To = Geometry.Parse(toValue);
        }

        private void UpdateValue()
        {
            if (_numbersFrom == null || _numbersTo == null || _numbersFrom.Length != _numbersTo.Length) return;

            _numbersAccumulator = new double[_numbersFrom.Length];
            for (var i = 0; i < _numbersFrom.Length; i++)
            {
                _numbersAccumulator[i] = _numbersTo[i] - _numbersFrom[i];
            }
        }

        public GeometryAnimation(Geometry fromValue, Geometry toValue) : this()
        {
            From = fromValue;
            To = toValue;
        }

        public GeometryAnimation(Geometry fromValue, Geometry toValue, Duration duration) : this()
        {
            From = fromValue;
            To = toValue;
            Duration = duration;
        }

        public GeometryAnimation(Geometry fromValue, Geometry toValue, Duration duration, FillBehavior fillBehavior) : this()
        {
            From = fromValue;
            To = toValue;
            Duration = duration;
            FillBehavior = fillBehavior;
        }

        public new GeometryAnimation Clone() => (GeometryAnimation) base.Clone();

        protected override Freezable CreateInstanceCore() => new GeometryAnimation();

        protected override Geometry GetCurrentValueCore(Geometry defaultOriginValue, Geometry defaultDestinationValue, AnimationClock animationClock)
        {
            if (_numbersAccumulator == null)
            {
                if (_numbersFrom == null)
                {
                    var geometryStr = defaultOriginValue.ToString(CultureInfo.InvariantCulture);
                    AnimationHelper.DecomposeGeometryStr(geometryStr, out _numbersFrom);
                }

                if (_numbersTo == null)
                {
                    var geometryStr = defaultDestinationValue.ToString(CultureInfo.InvariantCulture);
                    AnimationHelper.DecomposeGeometryStr(geometryStr, out _numbersTo);
                    _strings = Regex.Split(geometryStr, RegexPatterns.DigitsPattern);
                }

                UpdateValue();
            }

            if (_numbersAccumulator == null) return defaultOriginValue;

            var progress = animationClock.CurrentProgress.Value;

            var easingFunction = EasingFunction;
            if (easingFunction != null)
            {
                progress = easingFunction.Ease(progress);
            }

            var accumulated = new double[_numbersAccumulator.Length];

            if (IsCumulative)
            {
                var currentRepeat = (double) (animationClock.CurrentIteration - 1);

                if (currentRepeat > 0.0)
                {
                    accumulated = new double[_numbersAccumulator.Length];
                    for (var i = 0; i < _numbersAccumulator.Length; i++)
                    {
                        accumulated[i] = _numbersAccumulator[i] * currentRepeat;
                    }
                }
            }

            var numbers = new double[_numbersAccumulator.Length];

            for (var i = 0; i < _numbersAccumulator.Length; i++)
            {
                numbers[i] = accumulated[i] + _numbersFrom[i] + _numbersAccumulator[i] * progress;
            }

            return AnimationHelper.ComposeGeometry(_strings, numbers);
        }

        public static readonly DependencyProperty FromProperty = DependencyProperty.Register(
            "From", typeof(Geometry), typeof(GeometryAnimation), new PropertyMetadata(default(Geometry), OnFromChanged));

        private static void OnFromChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var obj = (GeometryAnimation) d;
            if (e.NewValue is Geometry geometry)
            {
                AnimationHelper.DecomposeGeometryStr(geometry.ToString(CultureInfo.InvariantCulture), out obj._numbersFrom);
                obj.UpdateValue();
            }
        }

        public Geometry From
        {
            get => (Geometry) GetValue(FromProperty);
            set => SetValue(FromProperty, value);
        }

        public static readonly DependencyProperty ToProperty = DependencyProperty.Register(
            "To", typeof(Geometry), typeof(GeometryAnimation), new PropertyMetadata(default(Geometry), OnToChanged));

        private static void OnToChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var obj = (GeometryAnimation) d;
            if (e.NewValue is Geometry geometry)
            {
                var geometryStr = geometry.ToString(CultureInfo.InvariantCulture);
                AnimationHelper.DecomposeGeometryStr(geometryStr, out obj._numbersTo);
                obj._strings = Regex.Split(geometryStr, RegexPatterns.DigitsPattern);
                obj.UpdateValue();
            }
        }

        public Geometry To
        {
            get => (Geometry) GetValue(ToProperty);
            set => SetValue(ToProperty, value);
        }

        public static readonly DependencyProperty EasingFunctionProperty = DependencyProperty.Register(
            "EasingFunction", typeof(IEasingFunction), typeof(GeometryAnimation), new PropertyMetadata(default(IEasingFunction)));

        public IEasingFunction EasingFunction
        {
            get => (IEasingFunction) GetValue(EasingFunctionProperty);
            set => SetValue(EasingFunctionProperty, value);
        }

        public bool IsCumulative
        {
            get => (bool) GetValue(IsCumulativeProperty);
            set => SetValue(IsCumulativeProperty, value);
        }
    }
}
