using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.ComponentModel;
using System.Windows.Forms;
using DevComponents.DotNetBar;
using System.Drawing;
namespace DevComponents.DotNetBar.Animation
{
    internal abstract class Animation : Component
    {
        #region Events
        /// 
        /// Occurs after animation has completed.
        /// 
        public event EventHandler AnimationCompleted;
        /// 
        /// Raises AnimationCompleted event.
        /// 
        /// Provides event arguments.
        protected virtual void OnAnimationCompleted(EventArgs e)
        {
            EventHandler handler = AnimationCompleted;
            if (handler != null)
                handler(this, e);
        }
        #endregion
        #region Constructor
        private BackgroundWorker _Worker = null;
        private AnimationEasing _EasingFunction = AnimationEasing.EaseOutQuad;
        private double _Duration = 300;
        private EasingFunctionDelegate[] _AnimationFunctions;
        private List _AnimationList = new List();
        /// 
        /// Initializes a new instance of the Animation class.
        /// 
        /// Target object for animation
        /// Target property name for animation
        public Animation(AnimationEasing animationEasing, int animationDuration)
            :
            this(new AnimationRequest[0], animationEasing, animationDuration)
        {
        }
        /// 
        /// Initializes a new instance of the Animation class.
        /// 
        /// Target object for animation
        /// Target property name for animation
        public Animation(AnimationRequest animationRequest, AnimationEasing animationEasing, int animationDuration)
            :
            this(new AnimationRequest[] { animationRequest }, animationEasing, animationDuration)
        {
        }
        /// 
        /// Initializes a new instance of the Animation class.
        /// 
        /// Target object for animation
        /// Target property name for animation
        public Animation(AnimationRequest[] animationRequests, AnimationEasing animationEasing, int animationDuration)
        {
            if (animationRequests != null && animationRequests.Length > 0)
                _AnimationList.AddRange(animationRequests);
            _EasingFunction = animationEasing;
            _Duration = (int)animationDuration;
            InitializeAnimationFunctions();
        }
        private void InitializeAnimationFunctions()
        {
            _AnimationFunctions = new EasingFunctionDelegate[28];
            _AnimationFunctions[(int)AnimationEasing.EaseInBounce] = new EasingFunctionDelegate(EaseInBounce);
            _AnimationFunctions[(int)AnimationEasing.EaseInCirc] = new EasingFunctionDelegate(EaseInCirc);
            _AnimationFunctions[(int)AnimationEasing.EaseInCubic] = new EasingFunctionDelegate(EaseInCubic);
            _AnimationFunctions[(int)AnimationEasing.EaseInElastic] = new EasingFunctionDelegate(EaseInElastic);
            _AnimationFunctions[(int)AnimationEasing.EaseInExpo] = new EasingFunctionDelegate(EaseInExpo);
            _AnimationFunctions[(int)AnimationEasing.EaseInOutBounce] = new EasingFunctionDelegate(EaseInOutBounce);
            _AnimationFunctions[(int)AnimationEasing.EaseInOutCirc] = new EasingFunctionDelegate(EaseInOutCirc);
            _AnimationFunctions[(int)AnimationEasing.EaseInOutCubic] = new EasingFunctionDelegate(EaseInOutCubic);
            _AnimationFunctions[(int)AnimationEasing.EaseInOutElastic] = new EasingFunctionDelegate(EaseInOutElastic);
            _AnimationFunctions[(int)AnimationEasing.EaseInOutExpo] = new EasingFunctionDelegate(EaseInOutExpo);
            _AnimationFunctions[(int)AnimationEasing.EaseInOutQuad] = new EasingFunctionDelegate(EaseInOutQuad);
            _AnimationFunctions[(int)AnimationEasing.EaseInOutQuart] = new EasingFunctionDelegate(EaseInOutQuart);
            _AnimationFunctions[(int)AnimationEasing.EaseInOutQuint] = new EasingFunctionDelegate(EaseInOutQuint);
            _AnimationFunctions[(int)AnimationEasing.EaseInOutSine] = new EasingFunctionDelegate(EaseInOutSine);
            _AnimationFunctions[(int)AnimationEasing.EaseInQuad] = new EasingFunctionDelegate(EaseInQuad);
            _AnimationFunctions[(int)AnimationEasing.EaseInQuart] = new EasingFunctionDelegate(EaseInQuart);
            _AnimationFunctions[(int)AnimationEasing.EaseInQuint] = new EasingFunctionDelegate(EaseInQuint);
            _AnimationFunctions[(int)AnimationEasing.EaseInSine] = new EasingFunctionDelegate(EaseInSine);
            _AnimationFunctions[(int)AnimationEasing.EaseOutBounce] = new EasingFunctionDelegate(EaseOutBounce);
            _AnimationFunctions[(int)AnimationEasing.EaseOutCirc] = new EasingFunctionDelegate(EaseOutCirc);
            _AnimationFunctions[(int)AnimationEasing.EaseOutCubic] = new EasingFunctionDelegate(EaseOutCubic);
            _AnimationFunctions[(int)AnimationEasing.EaseOutElastic] = new EasingFunctionDelegate(EaseOutElastic);
            _AnimationFunctions[(int)AnimationEasing.EaseOutExpo] = new EasingFunctionDelegate(EaseOutExpo);
            _AnimationFunctions[(int)AnimationEasing.EaseOutQuad] = new EasingFunctionDelegate(EaseOutQuad);
            _AnimationFunctions[(int)AnimationEasing.EaseOutQuart] = new EasingFunctionDelegate(EaseOutQuart);
            _AnimationFunctions[(int)AnimationEasing.EaseOutQuint] = new EasingFunctionDelegate(EaseOutQuint);
            _AnimationFunctions[(int)AnimationEasing.EaseOutSine] = new EasingFunctionDelegate(EaseOutSine);
            _AnimationFunctions[(int)AnimationEasing.Linear] = new EasingFunctionDelegate(Linear);
        }
        #endregion
        #region Implementation
        private bool _AutoDispose = false;
        /// 
        /// Gets or sets whether animation is auto-disposed once its completed. Default value is false.
        /// 
        public bool AutoDispose
        {
            get { return _AutoDispose; }
            set
            {
                _AutoDispose = value;
            }
        }
        public List Animations
        {
            get
            {
                return _AnimationList;
            }
        }
        protected override void Dispose(bool disposing)
        {
            //Console.WriteLine("{0} Animation DISPOSED", DateTime.Now);
            Stop();
            _IsDisposed = true;
            base.Dispose(disposing);
        }
        private bool _IsDisposed = false;
        public bool IsDisposed
        {
            get { return _IsDisposed; }
            internal set
            {
                _IsDisposed = value;
            }
        }
        /// 
        /// Stops animation if one is currently running.
        /// 
        public void Stop()
        {
            BackgroundWorker worker = _Worker;
            if (worker != null)
            {
                worker.DoWork -= new DoWorkEventHandler(WorkerDoWork);
                worker.RunWorkerCompleted -= new RunWorkerCompletedEventHandler(RunWorkerCompleted);
                worker.CancelAsync();
                worker.Dispose();
                _Worker = null;
            }
        }
        public void Start()
        {
            Start(_AnimationList.ToArray());
        }
        protected void Start(AnimationRequest[] requests)
        {
            if (_Worker != null)
                throw new InvalidOperationException("Animation is already running animations");
            //Console.WriteLine("{0} Animation Started", DateTime.Now);
            _IsCompleted = false;
            _Worker = new BackgroundWorker();
            _Worker.WorkerSupportsCancellation = true;
            _Worker.DoWork += new DoWorkEventHandler(WorkerDoWork);
            _Worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(RunWorkerCompleted);
            _Worker.RunWorkerAsync(requests);
        }
        private void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            //Console.WriteLine(string.Format("{0}  RunWorkerCompleted", DateTime.Now));
            BackgroundWorker worker = _Worker;
            _Worker = null;
            if (worker != null)
            {
                worker.DoWork -= new DoWorkEventHandler(WorkerDoWork);
                worker.RunWorkerCompleted -= new RunWorkerCompletedEventHandler(RunWorkerCompleted);
                worker.Dispose();
            }
            _IsCompleted = true;
            OnAnimationCompleted(EventArgs.Empty);
            if (_AutoDispose)
                this.Dispose();
        }
        protected virtual void SetTargetPropertyValue(object target, PropertyInfo property, object value)
        {
            Control c = target as Control;
            if (c != null)
            {
                c.Invoke(new MethodInvoker(delegate
                {
                    property.SetValue(target, value, null);
                    if (c.Parent != null)
                        c.Parent.Update();
                    else
                        c.Update();
                }));
            }
            else if (target is BaseItem)
            {
                if (_AnimationUpdateControl != null)
                    _AnimationUpdateControl.Invoke(new MethodInvoker(delegate { property.SetValue(target, value, null); if (value is Rectangle) _AnimationUpdateControl.Invalidate(); }));
                else
                {
                    if (target is DevComponents.DotNetBar.Metro.MetroTileItem && property.Name == "CurrentFrameOffset")
                        ((DevComponents.DotNetBar.Metro.MetroTileItem)target).CurrentFrameOffset = (int)value;
                    else
                        property.SetValue(target, value, null);
                }
            }
            else
            {
                property.SetValue(target, value, null);
            }
        }
        protected abstract object GetPropertyValueCorrectType(double value);
        protected virtual void WorkerDoWork(object sender, DoWorkEventArgs e)
        {
            AnimationRequest[] requests = (AnimationRequest[])e.Argument;
            double elapsedTime = 0;
            DateTime startTime = DateTime.UtcNow;
            double duration = _Duration;
            bool firstPass = true;
            if (_FixedStepCount <= 0)
            {
                while (elapsedTime <= duration)
                {
                    try
                    {
                        foreach (AnimationRequest request in requests)
                        {
                            double toValue = request.GetToValue();
                            double fromValue = request.GetFromValue();
                            double change = toValue - fromValue;
                            if (firstPass)
                                SetTargetPropertyValue(request.Target, request.Property, request.From);
                            double r = _AnimationFunctions[(int)_EasingFunction](elapsedTime, fromValue, change, duration);
                            if (change < 0 && r < toValue || change > 0 && r > toValue) { r = toValue; e.Cancel = true; break; }
                            if (change < 0 && r > fromValue || change > 0 && r < fromValue) r = fromValue;
                            SetTargetPropertyValue(request.Target, request.Property, GetPropertyValueCorrectType(r));
                            elapsedTime = DateTime.UtcNow.Subtract(startTime).TotalMilliseconds;
                            if (e.Cancel) break;
                        }
                        if (e.Cancel) break;
                        ExecuteStepUpdateMethod();
                        if (_AnimationUpdateControl != null)
                        {
                            if (_AnimationUpdateControl.InvokeRequired)
                                _AnimationUpdateControl.BeginInvoke(new MethodInvoker(delegate { _AnimationUpdateControl.Update(); }));
                            else
                                _AnimationUpdateControl.Update();
                        }
                        if (e.Cancel) break;
                        firstPass = false;
                    }
                    catch (TargetInvocationException exc)
                    {
                        if (exc.InnerException is ObjectDisposedException) // Stop work if target has been disposed
                            return;
                        throw;
                    }
                }
            }
            else
            {
                try
                {
                    for (int i = 0; i < _FixedStepCount; i++)
                    {
                        foreach (AnimationRequest request in requests)
                        {
                            double toValue = request.GetToValue();
                            double fromValue = request.GetFromValue();
                            double change = toValue - fromValue;
                            double step = change / _FixedStepCount;
                            if (firstPass)
                                SetTargetPropertyValue(request.Target, request.Property, request.From);
                            double r = fromValue + step * (i + 1);
                            if (change < 0 && r < toValue || change > 0 && r > toValue) { r = toValue; e.Cancel = true; break; }
                            if (change < 0 && r > fromValue || change > 0 && r < fromValue) r = fromValue;
                            SetTargetPropertyValue(request.Target, request.Property, GetPropertyValueCorrectType(r));
                            if (e.Cancel) break;
                            if (_Duration > 0)
                            {
                                try
                                {
                                    using (
                                        System.Threading.ManualResetEvent wait =
                                            new System.Threading.ManualResetEvent(false))
                                        wait.WaitOne((int) _Duration);
                                    //System.Threading.Thread.Sleep((int)_Duration);
                                }
                                catch
                                {
                                    break;
                                }
                            }
                        }
                        if (e.Cancel) break;
                        ExecuteStepUpdateMethod();
                        if (_AnimationUpdateControl != null)
                            _AnimationUpdateControl.Update();
                        if (e.Cancel) break;
                        firstPass = false;
                    }
                }
                catch (TargetInvocationException exc)
                {
                    if (exc.InnerException is ObjectDisposedException) // Stop work if target has been disposed
                        return;
                    throw;
                }
            }
            // Make sure final to value is assigned
            foreach (AnimationRequest request in requests)
            {
                try
                {
                    SetTargetPropertyValue(request.Target, request.Property, request.To);
                }
                catch (TargetInvocationException exc)
                {
                    if (exc.InnerException is ObjectDisposedException) // Stop work if target has been disposed
                        continue;
                    throw;
                }
            }
            //System.Diagnostics.Debug.WriteLine(string.Format("{0}  WorkerDoWork DONE", DateTime.Now));
        }
        private bool _IsCompleted;
        /// 
        /// Gets whether animation run is complete.
        /// 
        public bool IsCompleted
        {
            get { return _IsCompleted; }
        }
        /// 
        /// Gets the animation duration in milliseconds.
        /// 
        public double Duration
        {
            get { return _Duration; }
            internal set
            {
                _Duration = value;
            }
        }
        /// 
        /// Gets the animation easing function.
        /// 
        public AnimationEasing EasingFunction
        {
            get
            {
                return _EasingFunction;
            }
        }
        protected EasingFunctionDelegate[] AnimationFunctions
        {
            get
            {
                return _AnimationFunctions;
            }
        }
        protected virtual void ExecuteStepUpdateMethod()
        {
            if (_StepUpdateMethod != null)
                _StepUpdateMethod.DynamicInvoke(null);
        }
        private Delegate _StepUpdateMethod = null;
        /// 
        /// Sets the method which is called each time value on target object property is set. This method may execute the visual updates on animation client.
        /// 
        /// Method to call
        public void SetStepUpdateMethod(Delegate method)
        {
            _StepUpdateMethod = method;
        }
        protected delegate double EasingFunctionDelegate(double t, double b, double c, double d);
        private Control _AnimationUpdateControl;
        public Control AnimationUpdateControl
        {
            get { return _AnimationUpdateControl; }
            set { _AnimationUpdateControl = value; }
        }
        private int _FixedStepCount = 0;
        /// 
        /// Gets or sets the number of fixed steps animation will perform from star to finish instead of using the easing function in time.
        /// Stepped animation executes specified number of steps always with Duration specifying delays between each step.
        /// 
        public int FixedStepCount
        {
            get { return _FixedStepCount; }
            set
            {
                _FixedStepCount = value;
            }
        }
        #endregion
        #region Easing Functions
        private double EaseInOutQuad(double t, double b, double c, double d)
        {
            if ((t /= d / 2) < 1) return c / 2 * t * t + b;
            return -c / 2 * ((--t) * (t - 2) - 1) + b;
        }
        private double EaseInQuad(double t, double b, double c, double d)
        {
            return c * (t /= d) * t + b;
        }
        private double EaseOutQuad(double t, double b, double c, double d)
        {
            return -c * (t /= d) * (t - 2) + b;
        }
        private double EaseInCubic(double t, double b, double c, double d)
        {
            return c * (t /= d) * t * t + b;
        }
        private double EaseOutCubic(double t, double b, double c, double d)
        {
            return c * ((t = t / d - 1) * t * t + 1) + b;
        }
        private double EaseInOutCubic(double t, double b, double c, double d)
        {
            if ((t /= d / 2) < 1) return c / 2 * t * t * t + b;
            return c / 2 * ((t -= 2) * t * t + 2) + b;
        }
        private double EaseInQuart(double t, double b, double c, double d)
        {
            return c * (t /= d) * t * t * t + b;
        }
        private double EaseOutQuart(double t, double b, double c, double d)
        {
            return -c * ((t = t / d - 1) * t * t * t - 1) + b;
        }
        private double EaseInOutQuart(double t, double b, double c, double d)
        {
            if ((t /= d / 2) < 1) return c / 2 * t * t * t * t + b;
            return -c / 2 * ((t -= 2) * t * t * t - 2) + b;
        }
        private double EaseInQuint(double t, double b, double c, double d)
        {
            return c * (t /= d) * t * t * t * t + b;
        }
        private double EaseOutQuint(double t, double b, double c, double d)
        {
            return c * ((t = t / d - 1) * t * t * t * t + 1) + b;
        }
        private double EaseInOutQuint(double t, double b, double c, double d)
        {
            if ((t /= d / 2) < 1) return c / 2 * t * t * t * t * t + b;
            return c / 2 * ((t -= 2) * t * t * t * t + 2) + b;
        }
        private double EaseInSine(double t, double b, double c, double d)
        {
            return -c * Math.Cos(t / d * (Math.PI / 2)) + c + b;
        }
        private double EaseOutSine(double t, double b, double c, double d)
        {
            return c * Math.Sin(t / d * (Math.PI / 2)) + b;
        }
        private double EaseInOutSine(double t, double b, double c, double d)
        {
            return -c / 2 * (Math.Cos(Math.PI * t / d) - 1) + b;
        }
        private double EaseInExpo(double t, double b, double c, double d)
        {
            return (t == 0) ? b : c * Math.Pow(2, 10 * (t / d - 1)) + b;
        }
        private double EaseOutExpo(double t, double b, double c, double d)
        {
            return (t == d) ? b + c : c * (-Math.Pow(2, -10 * t / d) + 1) + b;
        }
        private double EaseInOutExpo(double t, double b, double c, double d)
        {
            if (t == 0) return b;
            if (t == d) return b + c;
            if ((t /= d / 2) < 1) return c / 2 * Math.Pow(2, 10 * (t - 1)) + b;
            return c / 2 * (-Math.Pow(2, -10 * --t) + 2) + b;
        }
        private double EaseInCirc(double t, double b, double c, double d)
        {
            return -c * (Math.Sqrt(1 - (t /= d) * t) - 1) + b;
        }
        private double EaseOutCirc(double t, double b, double c, double d)
        {
            return c * Math.Sqrt(1 - (t = t / d - 1) * t) + b;
        }
        private double EaseInOutCirc(double t, double b, double c, double d)
        {
            if ((t /= d / 2) < 1) return -c / 2 * (Math.Sqrt(1 - t * t) - 1) + b;
            return c / 2 * (Math.Sqrt(1 - (t -= 2) * t) + 1) + b;
        }
        private double EaseInElastic(double t, double b, double c, double d)
        {
            double s = 1.70158;
            double p = 0;
            double a = c;
            if (t == 0)
                return b;
            if ((t /= d) == 1)
                return b + c;
            if (p == 0)
                p = d * .3;
            if (a < Math.Abs(c))
            {
                a = c;
                s = p / 4;
            }
            else
                s = p / (2 * Math.PI) * Math.Asin(c / a);
            return -(a * Math.Pow(2, 10 * (t -= 1)) * Math.Sin((t * d - s) * (2 * Math.PI) / p)) + b;
        }
        private double EaseOutElastic(double t, double b, double c, double d)
        {
            double s = 1.70158;
            double p = 0;
            double a = c;
            if (t == 0)
                return b;
            if ((t /= d) == 1)
                return b + c;
            if (p == 0) p = d * .3;
            if (a < Math.Abs(c))
            {
                a = c;
                s = p / 4;
            }
            else
                s = p / (2 * Math.PI) * Math.Asin(c / a);
            return a * Math.Pow(2, -10 * t) * Math.Sin((t * d - s) * (2 * Math.PI) / p) + c + b;
        }
        private double EaseInOutElastic(double t, double b, double c, double d)
        {
            double s = 1.70158;
            double p = 0;
            double a = c;
            if (t == 0)
                return b;
            if ((t /= d / 2) == 2)
                return b + c;
            if (p == 0)
                p = d * (.3 * 1.5);
            if (a < Math.Abs(c))
            {
                a = c;
                s = p / 4;
            }
            else
                s = p / (2 * Math.PI) * Math.Asin(c / a);
            if (t < 1)
                return -.5 * (a * Math.Pow(2, 10 * (t -= 1)) * Math.Sin((t * d - s) * (2 * Math.PI) / p)) + b;
            return a * Math.Pow(2, -10 * (t -= 1)) * Math.Sin((t * d - s) * (2 * Math.PI) / p) * .5 + c + b;
        }
        private double EaseInBounce(double t, double b, double c, double d)
        {
            return c - EaseOutBounce(d - t, 0, c, d) + b;
        }
        private double EaseOutBounce(double t, double b, double c, double d)
        {
            if ((t /= d) < (1 / 2.75))
            {
                return c * (7.5625 * t * t) + b;
            }
            else if (t < (2 / 2.75))
            {
                return c * (7.5625 * (t -= (1.5 / 2.75)) * t + .75) + b;
            }
            else if (t < (2.5 / 2.75))
            {
                return c * (7.5625 * (t -= (2.25 / 2.75)) * t + .9375) + b;
            }
            else
            {
                return c * (7.5625 * (t -= (2.625 / 2.75)) * t + .984375) + b;
            }
        }
        private double EaseInOutBounce(double t, double b, double c, double d)
        {
            if (t < d / 2) return EaseInBounce(t * 2, 0, c, d) * .5 + b;
            return EaseOutBounce(t * 2 - d, 0, c, d) * .5 + c * .5 + b;
        }
        private double Linear(double t, double b, double c, double d)
        {
            return (t / d) * c + b;
        }
        #endregion
    }
    internal class AnimationRequest
    {
        private PropertyInfo _TargetProperty = null;
        /// 
        /// Initializes a new instance of the AnimationRequest class.
        /// 
        /// Target object for animation.
        /// Target property name for animation.
        /// From value.
        /// To value.
        public AnimationRequest(object target, string targetPropertyName, object to)
        {
            _TargetProperty = target.GetType().GetProperty(targetPropertyName);
            _Target = target;
            From = _TargetProperty.GetValue(target, null);
            To = to;
        }
        /// 
        /// Initializes a new instance of the AnimationRequest class.
        /// 
        /// Target object for animation.
        /// Target property name for animation.
        /// From value.
        /// To value.
        public AnimationRequest(object target, string targetPropertyName, object from, object to)
        {
            _TargetProperty = target.GetType().GetProperty(targetPropertyName);
            _Target = target;
            From = from;
            To = to;
        }
        private object _Target;
        /// 
        /// Target object for animation.
        /// 
        public object Target
        {
            get { return _Target; }
            set { _Target = value; }
        }
        private object _From;
        /// 
        /// Animation from value.
        /// 
        public object From
        {
            get { return _From; }
            set
            {
                _From = value;
                _FromValue = GetDoubleValue(value);
            }
        }
        private object _To;
        /// 
        /// Animation to value.
        /// 
        public object To
        {
            get { return _To; }
            set
            {
                _To = value;
                _ToValue = GetDoubleValue(value);
            }
        }
        private static double GetDoubleValue(object value)
        {
            if (value is int)
                return (double)(int)value;
            else if (value is double)
                return (double)value;
            else if (value is long)
                return (double)(long)value;
            else if (value is float)
                return (double)(float)value;
            return double.NaN;
        }
        internal PropertyInfo Property
        {
            get
            {
                return _TargetProperty;
            }
        }
        private double _ToValue = double.NaN;
        internal double GetToValue()
        {
            return _ToValue;
        }
        private double _FromValue = double.NaN;
        internal double GetFromValue()
        {
            return _FromValue;
        }
    }
    /// 
    /// Specifies the animation easing function
    /// 
    public enum AnimationEasing : int
    {
        EaseInOutQuad = 0,
        EaseInQuad = 1,
        EaseOutQuad = 2,
        EaseInCubic = 3,
        EaseOutCubic = 4,
        EaseInOutCubic = 5,
        EaseInQuart = 6,
        EaseOutQuart = 7,
        EaseInOutQuart = 8,
        EaseInQuint = 9,
        EaseOutQuint = 10,
        EaseInOutQuint = 11,
        EaseInSine = 12,
        EaseOutSine = 13,
        EaseInOutSine = 14,
        EaseInExpo = 15,
        EaseOutExpo = 16,
        EaseInOutExpo = 17,
        EaseInCirc = 18,
        EaseOutCirc = 19,
        EaseInOutCirc = 20,
        EaseInElastic = 21,
        EaseOutElastic = 22,
        EaseInOutElastic = 23,
        EaseInBounce = 24,
        EaseOutBounce = 25,
        EaseInOutBounce = 26,
        Linear = 27
    }
}