#include "tween.h"

typedef struct tween_param_s
{
    struct tween_param_s *previos;
    struct tween_param_s *next;
    float                 value;
    float                 value_start;
    float                 value_finish;
    bool                  run;
    bool                  pause;
    unsigned int          time;
    unsigned int          time_count;
    ease_t                ease;
} tween_param_t;

static tween_param_t *list_tween_param_first = NULL;
static tween_param_t *list_tween_param_last  = NULL;
static unsigned int   tween_last_time        = 0;

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

static float _Tween_EaseOutCubic(float t, float b, float c, float d)
{
    float  result = 0;
    float _t      = t;

    _t     = _t / d - 1;
    result = c * (_t * _t * _t + 1) + b;

    return result;
}

void Tween_Init(void)
{
    tween_last_time = 0;
}

tween_param_t* Tween_ParamCreate(unsigned int time, float value_start, float value_finish, ease_t ease)
{
    tween_param_t *param = (tween_param_t*) malloc(sizeof(tween_param_t));

    param->previos      = list_tween_param_last;
    param->next         = NULL;
    param->value        = value_start;
    param->value_start  = value_start;
    param->value_finish = value_finish;
    param->run          = false;
    param->pause        = false;
    param->time         = time;
    param->time_count   = 0;
    param->ease         = ease;

    if (list_tween_param_first == NULL)
        list_tween_param_first = param;

    if (list_tween_param_last != NULL)
        list_tween_param_last->next = param;

    list_tween_param_last = param;

    return param;
}

void Tween_ParamDelete(tween_param_t* param)
{
    if (param == NULL)
        return;

    tween_param_t *param_previos = param->previos;
    tween_param_t *param_next    = param->next;

    if (param == list_tween_param_last)
        list_tween_param_last = param_previos;

    if (param == list_tween_param_first)
        list_tween_param_first = param_next;

    if (param_previos != NULL)
        param_previos->next = param_next;

    if (param_next != NULL)
        param_next->previos = param_previos;

    free(param);
    param = NULL;
}

void Tween_ParamResetValues(tween_param_t* param, float value_start, float value_finish)
{
    if (param == NULL)
        return;

    param->value        = value_start;
    param->value_start  = value_start;
    param->value_finish = value_finish;
    param->run          = false;
    param->pause        = false;
    param->time_count   = 0;
}

void Tween_ParamRun(tween_param_t* param)
{
    if (param == NULL)
        return;

    if (!param->pause)
    {
        param->time_count = 0;
        param->value      = param->value_start;
    }

    param->run   = true;
    param->pause = false;
}

void Tween_ParamPause(tween_param_t* param)
{
    if (param == NULL)
        return;

    param->run   = false;
	param->pause = true;
}

void Tween_ParamStop(tween_param_t* param, bool set_value_finish)
{
    if (param == NULL)
        return;

    param->run   = false;
	param->pause = false;

    if (set_value_finish)
        param->value = param->value_finish;
}

float Tween_ParamGetValue(tween_param_t* param)
{
    if (param == NULL)
        return 0;

    return param->value;
}

bool Tween_ParamIsFinish(tween_param_t* param)
{
    bool result = false;

    if (param == NULL)
        return result;

    if (param->value == param->value_finish)
        result = true;

    return result;
}

void Tween_Update(void)
{
    tween_param_t *param = list_tween_param_first;

    unsigned int millisecond = SDL_GetTicks();

    if (tween_last_time == 0)
        tween_last_time = millisecond;

    int dealy = (millisecond - tween_last_time);

    if (param != NULL)
    {
        while(param != NULL)
        {
            if (param->run)
            {
                param->time_count += dealy;

                if (param->time_count < param->time)
                {
                    switch(param->ease)
					{
                        case EASE_LINEAR         : param->value = _Tween_EaseLinear      (param->time_count, param->value_start, (param->value_finish - param->value_start), param->time); break;
                        /*
                        case EASE_IN_SINE        : param->value = _Tween_EaseInSine      (param->time_count, param->value_start, (param->value_finish - param->value_start), param->time); break;
                        case EASE_OUT_SINE       : param->value = _Tween_EaseOutSine     (param->time_count, param->value_start, (param->value_finish - param->value_start), param->time); break;
                        case EASE_IN_OUT_SINE    : param->value = _Tween_EaseInOutSine   (param->time_count, param->value_start, (param->value_finish - param->value_start), param->time); break;
                        case EASE_IN_QUAD        : param->value = _Tween_EaseInQuad      (param->time_count, param->value_start, (param->value_finish - param->value_start), param->time); break;
                        case EASE_OUT_QUAD       : param->value = _Tween_EaseOutQuad     (param->time_count, param->value_start, (param->value_finish - param->value_start), param->time); break;
                        case EASE_IN_OUT_QUAD    : param->value = _Tween_EaseInOutQuad   (param->time_count, param->value_start, (param->value_finish - param->value_start), param->time); break;
                        case EASE_IN_CUBIC       : param->value = _Tween_EaseInCubic     (param->time_count, param->value_start, (param->value_finish - param->value_start), param->time); break;
                        */
                        case EASE_OUT_CUBIC      : param->value = _Tween_EaseOutCubic    (param->time_count, param->value_start, (param->value_finish - param->value_start), param->time); break;
                        /*
                        case EASE_IN_OUT_CUBIC   : param->value = _Tween_EaseInOutCubic  (param->time_count, param->value_start, (param->value_finish - param->value_start), param->time); break;
                        case EASE_IN_QUART       : param->value = _Tween_EaseInQuart     (param->time_count, param->value_start, (param->value_finish - param->value_start), param->time); break;
                        case EASE_OUT_QUART      : param->value = _Tween_EaseOutQuart    (param->time_count, param->value_start, (param->value_finish - param->value_start), param->time); break;
                        case EASE_IN_OUT_QUART   : param->value = _Tween_EaseInOutQuart  (param->time_count, param->value_start, (param->value_finish - param->value_start), param->time); break;
                        case EASE_IN_QUINT       : param->value = _Tween_EaseInQuint     (param->time_count, param->value_start, (param->value_finish - param->value_start), param->time); break;
                        case EASE_OUT_QUINT      : param->value = _Tween_EaseOutQuint    (param->time_count, param->value_start, (param->value_finish - param->value_start), param->time); break;
                        case EASE_IN_OUT_QUINT   : param->value = _Tween_EaseInOutQuint  (param->time_count, param->value_start, (param->value_finish - param->value_start), param->time); break;
                        case EASE_IN_EXPO        : param->value = _Tween_EaseInExpo      (param->time_count, param->value_start, (param->value_finish - param->value_start), param->time); break;
                        case EASE_OUT_EXPO       : param->value = _Tween_EaseOutExpo     (param->time_count, param->value_start, (param->value_finish - param->value_start), param->time); break;
                        case EASE_IN_OUT_EXPO    : param->value = _Tween_EaseInOutExpo   (param->time_count, param->value_start, (param->value_finish - param->value_start), param->time); break;
                        case EASE_IN_CIRC        : param->value = _Tween_EaseInCirc      (param->time_count, param->value_start, (param->value_finish - param->value_start), param->time); break;
                        case EASE_OUT_CIRC       : param->value = _Tween_EaseOutCirc     (param->time_count, param->value_start, (param->value_finish - param->value_start), param->time); break;
                        case EASE_IN_OUT_CIRC    : param->value = _Tween_EaseInOutCirc   (param->time_count, param->value_start, (param->value_finish - param->value_start), param->time); break;
                        case EASE_IN_BACK        : param->value = _Tween_EaseInBack      (param->time_count, param->value_start, (param->value_finish - param->value_start), param->time); break;
                        case EASE_OUT_BACK       : param->value = _Tween_EaseOutBack     (param->time_count, param->value_start, (param->value_finish - param->value_start), param->time); break;
                        case EASE_IN_OUT_BACK    : param->value = _Tween_EaseInOutBack   (param->time_count, param->value_start, (param->value_finish - param->value_start), param->time); break;
                        case EASE_IN_ELASTIC     : param->value = _Tween_EaseInElastic   (param->time_count, param->value_start, (param->value_finish - param->value_start), param->time); break;
                        case EASE_OUT_ELASTIC    : param->value = _Tween_EaseOutElastic  (param->time_count, param->value_start, (param->value_finish - param->value_start), param->time); break;
                        case EASE_IN_OUT_ELASTIC : param->value = _Tween_EaseInOutElastic(param->time_count, param->value_start, (param->value_finish - param->value_start), param->time); break;
                        case EASE_IN_BOUNCE      : param->value = _Tween_EaseInBounce    (param->time_count, param->value_start, (param->value_finish - param->value_start), param->time); break;
                        case EASE_OUT_BOUNCE     : param->value = _Tween_EaseOutBounce   (param->time_count, param->value_start, (param->value_finish - param->value_start), param->time); break;
                        case EASE_IN_OUT_BOUNCE  : param->value = _Tween_EaseInOutBounce (param->time_count, param->value_start, (param->value_finish - param->value_start), param->time); break;
                        */
                    }
                }
                else Tween_ParamStop(param, true);
            }

            param = param->next;
        }
    }

    if (tween_last_time != 0)
        tween_last_time = millisecond;
}

void Tween_Quit(void)
{
    tween_param_t *param = list_tween_param_first;

    if (param != NULL)
    {
        while(param != NULL)
        {
            tween_param_t *param_next = param->next;

            free(param);

            param = param_next;
        }
    }

    list_tween_param_first = NULL;
    list_tween_param_last  = NULL;
    tween_last_time        = 0;
}
