using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Animation;
using HandyControl.Tools;

// ReSharper disable PossibleInvalidOperationException

namespace HandyControl.Media.Animation
{
    [ContentProperty("KeyFrames")]
    public class GeometryAnimationUsingKeyFrames : GeometryAnimationBase, IKeyFrameAnimation, IAddChild
    {
        private string[] _strings;

        public string[] Strings
        {
            get
            {
                if (_keyFrames == null || _keyFrames.Count == 0)
                {
                    return null;
                }

                return _strings ??= Regex.Split(_keyFrames[0].Value.ToString(), RegularPatterns.DigitsPattern, RegexOptions.CultureInvariant);
            }
        }

        private GeometryKeyFrameCollection _keyFrames;
        
        private ResolvedKeyFrameEntry[] _sortedResolvedKeyFrames;
        
        private bool _areKeyTimesValid;

        public GeometryAnimationUsingKeyFrames() => _areKeyTimesValid = true;

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

        public new GeometryAnimationUsingKeyFrames CloneCurrentValue() => (GeometryAnimationUsingKeyFrames)base.CloneCurrentValue();

        protected override bool FreezeCore(bool isChecking)
        {
            var canFreeze = base.FreezeCore(isChecking);

            canFreeze &= Freeze(_keyFrames, isChecking);

            if (canFreeze & !_areKeyTimesValid)
            {
                ResolveKeyTimes();
            }

            return canFreeze;
        }

        protected override void OnChanged()
        {
            _areKeyTimesValid = false;

            base.OnChanged();
        }

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

        protected override void CloneCore(Freezable sourceFreezable)
        {
            var sourceAnimation = (GeometryAnimationUsingKeyFrames)sourceFreezable;
            base.CloneCore(sourceFreezable);

            CopyCommon(sourceAnimation, false);
        }

        protected override void CloneCurrentValueCore(Freezable sourceFreezable)
        {
            var sourceAnimation = (GeometryAnimationUsingKeyFrames)sourceFreezable;
            base.CloneCurrentValueCore(sourceFreezable);

            CopyCommon(sourceAnimation, true);
        }

        protected override void GetAsFrozenCore(Freezable source)
        {
            var sourceAnimation = (GeometryAnimationUsingKeyFrames)source;
            base.GetAsFrozenCore(source);

            CopyCommon(sourceAnimation, false);
        }

        protected override void GetCurrentValueAsFrozenCore(Freezable source)
        {
            var sourceAnimation = (GeometryAnimationUsingKeyFrames)source;
            base.GetCurrentValueAsFrozenCore(source);

            CopyCommon(sourceAnimation, true);
        }

        private void CopyCommon(GeometryAnimationUsingKeyFrames sourceAnimation, bool isCurrentValueClone)
        {
            _areKeyTimesValid = sourceAnimation._areKeyTimesValid;

            if (_areKeyTimesValid && sourceAnimation._sortedResolvedKeyFrames != null)
            {
                _sortedResolvedKeyFrames = (ResolvedKeyFrameEntry[])sourceAnimation._sortedResolvedKeyFrames.Clone();
            }

            if (sourceAnimation._keyFrames != null)
            {
                if (isCurrentValueClone)
                {
                    _keyFrames = (GeometryKeyFrameCollection)sourceAnimation._keyFrames.CloneCurrentValue();
                }
                else
                {
                    _keyFrames = sourceAnimation._keyFrames.Clone();
                }

                OnFreezablePropertyChanged(null, _keyFrames);
            }
        }

        void IAddChild.AddChild(object child)
        {
            WritePreamble();

            if (child == null)
            {
                throw new ArgumentNullException(nameof(child));
            }

            AddChild(child);

            WritePostscript();
        }

        [EditorBrowsable(EditorBrowsableState.Advanced)]
        protected virtual void AddChild(object child)
        {
            if (child is GeometryKeyFrame keyFrame)
            {
                KeyFrames.Add(keyFrame);
            }
            else
            {
                throw new ArgumentException("Animation_ChildMustBeKeyFrame", nameof(child));
            }
        }

        void IAddChild.AddText(string childText)
        {
            if (childText == null)
            {
                throw new ArgumentNullException(nameof(childText));
            }

            AddText(childText);
        }

        [EditorBrowsable(EditorBrowsableState.Advanced)]
        protected virtual void AddText(string childText) => throw new InvalidOperationException("Animation_NoTextChildren");

        protected override Geometry GetCurrentValueCore(Geometry defaultOriginValue, Geometry defaultDestinationValue, AnimationClock animationClock)
        {
            if (_keyFrames == null)
            {
                return defaultDestinationValue;
            }

            if (!_areKeyTimesValid)
            {
                ResolveKeyTimes();
            }

            if (_sortedResolvedKeyFrames == null)
            {
                return defaultDestinationValue;
            }

            var currentTime = animationClock.CurrentTime.Value;
            var keyFrameCount = _sortedResolvedKeyFrames.Length;
            var maxKeyFrameIndex = keyFrameCount - 1;

            double[] currentIterationValue;

            var currentResolvedKeyFrameIndex = 0;

            while (currentResolvedKeyFrameIndex < keyFrameCount && currentTime > _sortedResolvedKeyFrames[currentResolvedKeyFrameIndex]._resolvedKeyTime)
            {
                currentResolvedKeyFrameIndex++;
            }

            while (currentResolvedKeyFrameIndex < maxKeyFrameIndex && currentTime == _sortedResolvedKeyFrames[currentResolvedKeyFrameIndex + 1]._resolvedKeyTime)
            {
                currentResolvedKeyFrameIndex++;
            }

            if (currentResolvedKeyFrameIndex == keyFrameCount)
            {
                currentIterationValue = GetResolvedKeyFrameValue(maxKeyFrameIndex);
            }
            else if (currentTime == _sortedResolvedKeyFrames[currentResolvedKeyFrameIndex]._resolvedKeyTime)
            {
                currentIterationValue = GetResolvedKeyFrameValue(currentResolvedKeyFrameIndex);
            }
            else
            {
                double currentSegmentProgress;
                double[] fromValue;

                if (currentResolvedKeyFrameIndex == 0)
                {
                    AnimationHelper.DecomposeGeometryStr(defaultOriginValue.ToString(), out fromValue);

                    currentSegmentProgress = currentTime.TotalMilliseconds / _sortedResolvedKeyFrames[0]._resolvedKeyTime.TotalMilliseconds;
                }
                else
                {
                    var previousResolvedKeyFrameIndex = currentResolvedKeyFrameIndex - 1;
                    var previousResolvedKeyTime = _sortedResolvedKeyFrames[previousResolvedKeyFrameIndex]._resolvedKeyTime;

                    fromValue = GetResolvedKeyFrameValue(previousResolvedKeyFrameIndex);

                    var segmentCurrentTime = currentTime - previousResolvedKeyTime;
                    var segmentDuration = _sortedResolvedKeyFrames[currentResolvedKeyFrameIndex]._resolvedKeyTime - previousResolvedKeyTime;

                    currentSegmentProgress = segmentCurrentTime.TotalMilliseconds / segmentDuration.TotalMilliseconds;
                }

                currentIterationValue = GetResolvedKeyFrame(currentResolvedKeyFrameIndex).InterpolateValue(fromValue, currentSegmentProgress);
            }
            
            return AnimationHelper.ComposeGeometry(Strings, currentIterationValue);
        }

        protected sealed override Duration GetNaturalDurationCore(Clock clock) => new Duration(LargestTimeSpanKeyTime);

        IList IKeyFrameAnimation.KeyFrames
        {
            get => KeyFrames;
            set => KeyFrames = (GeometryKeyFrameCollection)value;
        }

        public GeometryKeyFrameCollection KeyFrames
        {
            get
            {
                ReadPreamble();

                if (_keyFrames == null)
                {
                    if (IsFrozen)
                    {
                        _keyFrames = GeometryKeyFrameCollection.Empty;
                    }
                    else
                    {
                        WritePreamble();

                        _keyFrames = new GeometryKeyFrameCollection();

                        OnFreezablePropertyChanged(null, _keyFrames);

                        WritePostscript();
                    }
                }

                return _keyFrames;
            }
            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException(nameof(value));
                }

                WritePreamble();

                if (value != _keyFrames)
                {
                    OnFreezablePropertyChanged(_keyFrames, value);
                    _keyFrames = value;

                    WritePostscript();
                }
            }
        }

        [EditorBrowsable(EditorBrowsableState.Never)]
        public bool ShouldSerializeKeyFrames()
        {
            ReadPreamble();

            return _keyFrames != null && _keyFrames.Count > 0;
        }

        private struct KeyTimeBlock
        {
            public int BeginIndex;
            public int EndIndex;
        }

        private double[] GetResolvedKeyFrameValue(int resolvedKeyFrameIndex) =>
            GetResolvedKeyFrame(resolvedKeyFrameIndex).Numbers;

        private GeometryKeyFrame GetResolvedKeyFrame(int resolvedKeyFrameIndex) =>
            _keyFrames[_sortedResolvedKeyFrames[resolvedKeyFrameIndex]._originalKeyFrameIndex];

        private TimeSpan LargestTimeSpanKeyTime
        {
            get
            {
                var hasTimeSpanKeyTime = false;
                var largestTimeSpanKeyTime = TimeSpan.Zero;

                if (_keyFrames != null)
                {
                    var keyFrameCount = _keyFrames.Count;

                    for (var index = 0; index < keyFrameCount; index++)
                    {
                        var keyTime = _keyFrames[index].KeyTime;

                        if (keyTime.Type == KeyTimeType.TimeSpan)
                        {
                            hasTimeSpanKeyTime = true;

                            if (keyTime.TimeSpan > largestTimeSpanKeyTime)
                            {
                                largestTimeSpanKeyTime = keyTime.TimeSpan;
                            }
                        }
                    }
                }

                return hasTimeSpanKeyTime ? largestTimeSpanKeyTime : TimeSpan.FromSeconds(1.0);
            }
        }

        private void ResolveKeyTimes()
        {
            var keyFrameCount = 0;

            if (_keyFrames != null)
            {
                keyFrameCount = _keyFrames.Count;
            }

            if (keyFrameCount == 0)
            {
                _sortedResolvedKeyFrames = null;
                _areKeyTimesValid = true;
                return;
            }

            _sortedResolvedKeyFrames = new ResolvedKeyFrameEntry[keyFrameCount];

            var index = 0;

            for (; index < keyFrameCount; index++)
            {
                _sortedResolvedKeyFrames[index]._originalKeyFrameIndex = index;
            }

            var duration = Duration;
            var calculationDuration = duration.HasTimeSpan ? duration.TimeSpan : LargestTimeSpanKeyTime;
            var maxKeyFrameIndex = keyFrameCount - 1;
            var unspecifiedBlocks = new List<KeyTimeBlock>();
            var hasPacedKeyTimes = false;

            index = 0;
            while (index < keyFrameCount)
            {
                // ReSharper disable once PossibleNullReferenceException
                var keyTime = _keyFrames[index].KeyTime;

                switch (keyTime.Type)
                {
                    case KeyTimeType.Percent:

                        _sortedResolvedKeyFrames[index]._resolvedKeyTime = TimeSpan.FromMilliseconds(keyTime.Percent * calculationDuration.TotalMilliseconds);
                        index++;
                        break;

                    case KeyTimeType.TimeSpan:

                        _sortedResolvedKeyFrames[index]._resolvedKeyTime = keyTime.TimeSpan;

                        index++;
                        break;

                    case KeyTimeType.Paced:
                    case KeyTimeType.Uniform:

                        if (index == maxKeyFrameIndex)
                        {
                            _sortedResolvedKeyFrames[index]._resolvedKeyTime = calculationDuration;
                            index++;
                        }
                        else if (index == 0 && keyTime.Type == KeyTimeType.Paced)
                        {
                            _sortedResolvedKeyFrames[index]._resolvedKeyTime = TimeSpan.Zero;
                            index++;
                        }
                        else
                        {
                            if (keyTime.Type == KeyTimeType.Paced)
                            {
                                hasPacedKeyTimes = true;
                            }

                            var block = new KeyTimeBlock
                            {
                                BeginIndex = index
                            };

                            while (++index < maxKeyFrameIndex)
                            {
                                var type = _keyFrames[index].KeyTime.Type;

                                if (type == KeyTimeType.Percent || type == KeyTimeType.TimeSpan)
                                {
                                    break;
                                }

                                if (type == KeyTimeType.Paced)
                                {
                                    hasPacedKeyTimes = true;
                                }
                            }

                            block.EndIndex = index;
                            unspecifiedBlocks.Add(block);
                        }

                        break;
                }
            }

            foreach (var block in unspecifiedBlocks)
            {
                var blockBeginTime = TimeSpan.Zero;

                if (block.BeginIndex > 0)
                {
                    blockBeginTime = _sortedResolvedKeyFrames[block.BeginIndex - 1]._resolvedKeyTime;
                }

                long segmentCount = block.EndIndex - block.BeginIndex + 1;
                var uniformTimeStep = TimeSpan.FromTicks((_sortedResolvedKeyFrames[block.EndIndex]._resolvedKeyTime - blockBeginTime).Ticks / segmentCount);

                index = block.BeginIndex;
                var resolvedTime = blockBeginTime + uniformTimeStep;

                while (index < block.EndIndex)
                {
                    _sortedResolvedKeyFrames[index]._resolvedKeyTime = resolvedTime;

                    resolvedTime += uniformTimeStep;
                    index++;
                }
            }

            if (hasPacedKeyTimes)
            {
                ResolvePacedKeyTimes();
            }

            Array.Sort(_sortedResolvedKeyFrames);

            _areKeyTimesValid = true;
        }

        private void ResolvePacedKeyTimes()
        {
            var index = 1;
            var maxKeyFrameIndex = _sortedResolvedKeyFrames.Length - 1;

            do
            {
                if (_keyFrames[index].KeyTime.Type == KeyTimeType.Paced)
                {
                    var firstPacedBlockKeyFrameIndex = index;

                    var segmentLengths = new List<double>();
                    var prePacedBlockKeyTime = _sortedResolvedKeyFrames[index - 1]._resolvedKeyTime;
                    var totalLength = 0.0;
                    var prevKeyValue = _keyFrames[index - 1].Numbers;

                    do
                    {
                        var currentKeyValue = _keyFrames[index].Numbers;
                        totalLength += Math.Abs(currentKeyValue[0] - prevKeyValue[0]);
                        segmentLengths.Add(totalLength);
                        prevKeyValue = currentKeyValue;
                        index++;
                    }
                    while (index < maxKeyFrameIndex && _keyFrames[index].KeyTime.Type == KeyTimeType.Paced);
                    totalLength += Math.Abs(_keyFrames[index].Numbers[0] - prevKeyValue[0]);

                    var pacedBlockDuration = _sortedResolvedKeyFrames[index]._resolvedKeyTime - prePacedBlockKeyTime;

                    for (int i = 0, currentKeyFrameIndex = firstPacedBlockKeyFrameIndex; i < segmentLengths.Count; i++, currentKeyFrameIndex++)
                    {
                        _sortedResolvedKeyFrames[currentKeyFrameIndex]._resolvedKeyTime = prePacedBlockKeyTime + TimeSpan.FromMilliseconds(segmentLengths[i] / totalLength * pacedBlockDuration.TotalMilliseconds);
                    }
                }
                else
                {
                    index++;
                }
            }
            while (index < maxKeyFrameIndex);
        }
    }
}