using Microsoft.Xna.Framework;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace MonoGame.FourEngine.Components
{
    public partial class AnimationComponent
    {
        public abstract class Animation : IEnumerable<Point>
        {
            public virtual void Update(UpdateEventArgs args)
            { }

            public abstract IEnumerator<Point> GetEnumerator();

            IEnumerator IEnumerable.GetEnumerator()
                => GetEnumerator();
        }

        public class FramedAnimation : Animation
        {
            public bool IsLooped { get; }

            private FrameAnimator _enumerator;
            
            public FramedAnimation(IEnumerable<AnimationFrame> animationFrames, bool isLooped)
            {
                IsLooped = IsLooped;
                _enumerator = new FrameAnimator(animationFrames, isLooped);
            }

            public static FramedAnimation GenerateRange(Range rangeX, int y, int t, bool isLooped)
            {
                var frames = rangeX.Enumerable
                    .Select(x => new AnimationFrame(x, y, t))
                    .ToList();
                return new FramedAnimation(frames, isLooped);
            }

            public static FramedAnimation GenerateRange(int x, Range rangeY, int t, bool isLooped)
            {
                var frames = rangeY.Enumerable
                    .Select(y => new AnimationFrame(x, y, t))
                    .ToList();
                return new FramedAnimation(frames, isLooped);
            }

            public override void Update(UpdateEventArgs args)
                => _enumerator.Update(args);

            public override IEnumerator<Point> GetEnumerator()
                => _enumerator;

            class FrameAnimator : IEnumerator<Point>
            {
                public Point Current { get; private set; }

                object IEnumerator.Current => Current;

                private IEnumerator<AnimationFrame> _animationEnumerator;

                private double _msLeft;
                private bool _isLooped;

                public FrameAnimator(IEnumerable<AnimationFrame> animationFrames, bool isLooped)
                {
                    _animationEnumerator = animationFrames.GetEnumerator();
                    _isLooped = isLooped;
                    Reset();
                }

                public void Update(UpdateEventArgs args)
                {
                    _msLeft -= args.DeltaTime.TotalMilliseconds;
                }

                public bool MoveNext()
                {
                    if (_msLeft <= 0)
                    {
                        if (!_animationEnumerator.MoveNext())
                        {
                            if (!_isLooped) return false;
                            _animationEnumerator.Reset();
                            _animationEnumerator.MoveNext();
                        }
                        _msLeft = _animationEnumerator.Current.Delay.TotalMilliseconds;
                    }
                    Current = _animationEnumerator.Current.SpriteFrame;
                    return true;
                }

                public void Reset()
                {
                    _animationEnumerator.Reset();
                    _animationEnumerator.MoveNext();
                    Current = _animationEnumerator.Current.SpriteFrame;
                    _msLeft = default;
                }

                public void Dispose()
                {
                    _animationEnumerator.Dispose();
                }
            }
        }

        public class RuleAnimation : Animation
        {
            public override IEnumerator<Point> GetEnumerator()
                => _enumerator;

            private RuleAnimator _enumerator;

            public RuleAnimation(Point initialFrame, Func<Point, TimeSpan, Point?> func)
            {
                _enumerator = new RuleAnimator(initialFrame, func);
            }

            public override void Update(UpdateEventArgs args)
                => _enumerator.Update(args);

            class RuleAnimator : IEnumerator<Point>
            {
                public Point Current { get; private set; }

                object IEnumerator.Current => Current;

                private Point _initialFrame;
                private Func<Point, TimeSpan, Point?> _func;

                private TimeSpan _timePassed;

                public RuleAnimator(Point initialFrame, Func<Point, TimeSpan, Point?> func)
                {
                    _initialFrame = initialFrame;
                    _func = func;
                    Reset();
                }

                public bool MoveNext()
                {
                    var newFrame = _func(Current, _timePassed);
                    if (newFrame.HasValue)
                    {
                        if (Current != newFrame)
                            _timePassed = default;
                        Current = newFrame.Value;
                    }
                    return true;
                }
                
                public void Update(UpdateEventArgs args)
                {
                    _timePassed += args.DeltaTime;
                }

                public void Reset()
                {
                    Current = _initialFrame;
                    _timePassed = default;
                }

                public void Dispose() => Reset();
            }
        }
    }
}