using System;
using System.Collections.Generic;
using UnityEngine;

public delegate void SimpleTweenUpdate(float cVal);
public delegate void SimpleTweenFinish();

public class SimpleTween
{
    public delegate float TweenFunction(float t, float b, float c, float d);

    protected TweenFunction thisTween;

    float t; //current time
    float c; //delta
    float b; //start value
    float d; //duration

    public float tweenValue;
    bool toInt = false;

    bool instant = false;

    public float MaxTime = -1;

    protected int InstanceID;
    protected static int NextID = 0;

    private bool active = false;
    public bool Active { get { return active; } }

    public SimpleTweenFinish OnFinish = null;
    public SimpleTweenUpdate OnUpdate = null;
    public bool Loop = false;

    private bool ignoreFirstUpdate = true;
    float timeScale = 1;
    public float TimeScale { get { return timeScale; } set { timeScale = value; } }


    public enum TweenType { EaseLinear, QuadEaseIn, QuadEaseOut, QuadEaseInOut, StrongEaseIn, StrongEaseOut, StrongEaseInOut, SineWaveIn };


    protected int paused = 0;
    public void Pause()
    {
        paused++;
    }
    public void Resume()
    {
        if (paused > 0)
        {
            paused--;
        }
    }
    public bool Paused { get { return paused > 0; } }

    private EnterFrameManager efm;
    public SimpleTween()
    {
        efm = GameObject.FindObjectOfType<EnterFrameManager>();
        InstanceID = NextID++;
        active = false;
    }

    public SimpleTween(SimpleTweenUpdate onUpdate, TweenType tType, float start, float end, float time, bool toInt = false)
    {
        efm = GameObject.FindObjectOfType<EnterFrameManager>();
        InstanceID = NextID++;
        TweenFunction func = GetTweenFunction(tType);
        Init(onUpdate, func, start, end, time, toInt);
    }

    public SimpleTween(SimpleTweenUpdate onUpdate, TweenFunction tweenFunc, float start, float end, float time, bool toInt = false)
    {
        efm = GameObject.FindObjectOfType<EnterFrameManager>();
        InstanceID = NextID++;
        Init(onUpdate, tweenFunc, start, end, time, toInt);
    }

    public void Stop()
    {
        active = false;
        OnFinish = null;
        OnUpdate = null;
        efm.Remove(this);
    }

    public void Init(SimpleTweenUpdate onUpdate, TweenFunction tweenFunc, float start, float end, float time, bool toInt = false)
    {
        if (Active) Stop();
        this.OnUpdate = onUpdate;
        this.toInt = toInt;

        tweenValue = start;

        this.thisTween = tweenFunc;


        t = 0;
        c = end - start;
        b = start;
        d = time;

        instant = (d == 0);
        efm.Add(this, delegate (float timeElapsed) { Update(timeElapsed); });
        active = true;
    }

    public void Update(float timeElapsed)
    {
        if (ignoreFirstUpdate)
        {
            ignoreFirstUpdate = false;
            return;
        }

        if (MaxTime != -1) timeElapsed = Math.Min(timeElapsed, MaxTime);

        if (paused > 0) return;
        if (!Active) return;

        t += (timeElapsed * timeScale);

        if (t >= d) t = d;

        float val = 0;

        val = thisTween(instant ? 1.0f : t, b, c, instant ? 1.0f : d);
        /*
        switch(this.tType)
        {
            case TweenType.EaseLinear : val = SimpleTween.EaseLinear(instant ? 1.0f : t, b, c, instant ? 1.0f : d); break;
            case TweenType.QuadEaseIn : val = SimpleTween.QuadEaseIn(instant ? 1.0f : t, b, c, instant ? 1.0f : d); break;
            case TweenType.QuadEaseInOut : val = SimpleTween.QuadEaseInOut(instant ? 1.0f : t, b, c, instant ? 1.0f : d); break;
            case TweenType.QuadEaseOut : val = SimpleTween.QuadEaseOut(instant ? 1.0f : t, b, c, instant ? 1.0f : d); break;
            case TweenType.StrongEaseIn : val = SimpleTween.StrongEaseIn(instant ? 1.0f : t, b, c, instant ? 1.0f : d); break;
            case TweenType.StrongEaseInOut : val = SimpleTween.StrongEaseInOut(instant ? 1.0f : t, b, c, instant ? 1.0f : d); break;
            case TweenType.StrongEaseOut : val = SimpleTween.StrongEaseOut(instant ? 1.0f : t, b, c, instant ? 1.0f : d); break;
        }
        */

        int intVal = (int)val;
        if (toInt) val = intVal;

        tweenValue = val;

        if (OnUpdate != null) OnUpdate(tweenValue);

        if (t == d)
        {
            if (Loop)
            {
                t = 0;
            }
            else
            {
                active = false;
                efm.Remove(this);
            }
            if (OnFinish != null) OnFinish();

        }

    }

    public void ForceUpdateCall()
    {
        if (active)
        {
            if (OnUpdate != null) OnUpdate(tweenValue);
        }
    }

    private static Dictionary<TweenType, TweenFunction> typesToFunctions = new Dictionary<TweenType, TweenFunction>()
    {
        { TweenType.EaseLinear, EaseLinear },
        { TweenType.QuadEaseIn, QuadEaseIn },
        { TweenType.QuadEaseInOut, QuadEaseInOut },
        { TweenType.QuadEaseOut, QuadEaseOut },
        { TweenType.StrongEaseIn, StrongEaseIn },
        { TweenType.StrongEaseInOut, StrongEaseInOut },
        { TweenType.StrongEaseOut, StrongEaseOut },
        { TweenType.SineWaveIn, SineWaveIn },
    };
    public static TweenFunction GetTweenFunction(TweenType tweenType)
    {
        if (typesToFunctions.ContainsKey(tweenType)) return typesToFunctions[tweenType];
        return EaseLinear;
    }

    public static float SineWaveIn(float t, float b, float c, float d)
    {
        float param = (t / d);
        float s = (-( (float)Math.Pow(((param-0.774f)*1.4f), 2) )) + 1.1f;
        return c * s + b;
    }

    public static float EaseLinear(float t, float b, float c, float d)
    {
        return c * t / d + b;
    }

    public static float QuadEaseIn(float t, float b, float c, float d)
    {
        return c * (t /= d) * t + b;
    }

    public static float QuadEaseOut(float t, float b, float c, float d)
    {
        return -c * (t /= d) * (t - 2) + b;
    }

    public static float QuadEaseInOut(float t, float b, float c, float d)
    {
        if ((t /= d * 0.5f) < 1) return c * 0.5f * t * t + b;
        return -c * 0.5f * ((--t) * (t - 2) - 1) + b;
    }

    public static float StrongEaseIn(float t, float b, float c, float d)
    {
        return c * (t /= d) * t * t * t * t + b;
    }

    public static float StrongEaseOut(float t, float b, float c, float d)
    {
        return c * ((t = t / d - 1) * t * t * t * t + 1) + b;
    }

    public static float StrongEaseInOut(float t, float b, float c, float d)
    {
        if ((t /= d * 0.5f) < 1) return c * 0.5f * t * t * t * t * t + b;
        return c * 0.5f * ((t -= 2) * t * t * t * t + 2) + b;
    }

}