global animation_curve_state *Global_AnimationCurveState = 0;

////////////////////////////////
//~ sixten: Animation Curve Functions

inline animation_curve_state *AC_GetState(void)
{
    return(Global_AnimationCurveState);
}

inline void AC_SetState(animation_curve_state *State)
{
    Global_AnimationCurveState = State;
}

static void AC_Init(animation_curve_state *State)
{
    State->Arena = ArenaAllocate(Gigabytes(1));
}

inline animation_curve_key AC_GenerateKeyFromString(string String)
{
    animation_curve_key Key;
    Key.Value = HashString(String);
    
    return(Key);
}

static animation_curve_entry *AC_GetEntryByKey(animation_curve_key Key, r32 Initial)
{
    animation_curve_state *State = AC_GetState();
    
    u64 Hash = Key.Value;
    u64 Slot = Hash % ArrayCount(State->Buckets);
    
    animation_curve_bucket *Bucket = State->Buckets + Slot;
    
    animation_curve_entry *Result = 0;
    for(animation_curve_entry *Entry = Bucket->First;
        Entry != 0;
        Entry = Entry->Next)
    {
        if(AreEqual(Entry->Key, Key))
        {
            Result = Entry;
            break;
        }
    }
    
    if(!Result)
    {
        if(DLLIsEmpty(State->FirstFreeEntry))
        {
            Result = PushStruct(State->Arena, animation_curve_entry);
        }
        else
        {
            Result = State->FirstFreeEntry;
            DLLRemove(State->FirstFreeEntry, State->LastFreeEntry, Result);
        }
        
        DLLInsertLast(Bucket->First, Bucket->Last, Result);
        
        Result->Value = Initial;
    }
    
    Result->Key = Key;
    Result->LastFrameTouched = State->CurrentFrame;
    
    return(Result);
}

inline r32 AC_GetValue(string Name, r32 Initial)
{
    animation_curve_key Key = AC_GenerateKeyFromString(Name);
    animation_curve_entry *Entry = AC_GetEntryByKey(Key, Initial);
    
    r32 Result = Entry->Value;
    return(Result);
}

inline void AC_SetValue(string Name, r32 Value)
{
    animation_curve_key Key = AC_GenerateKeyFromString(Name);
    animation_curve_entry *Entry = AC_GetEntryByKey(Key, Value);
    
    Entry->Value = Value;
}

inline r32 AC_AnimateValueDirect(r32 Target, r32 Duration, r32 *Value)
{
    animation_curve_state *State = AC_GetState();
    
    r32 Result = *Value;
    
    r32 Rate = 1.0 - Pow(2, -(10.0 / Duration * State->dtForFrame));
    *Value += (Target - *Value) * Rate;
    
    return(Result);
}

inline r32 AC_AnimateValue(r32 Target, r32 Initial, r32 Duration, string Name)
{
    animation_curve_key Key = AC_GenerateKeyFromString(Name);
    animation_curve_entry *Entry = AC_GetEntryByKey(Key, Initial);
    
    r32 Result = AC_AnimateValueDirect(Target, Duration, &Entry->Value);
    return(Result);
}

inline r32 AC_AnimateValueF(r32 Target, r32 Initial, r32 Duration, char *Format, ...)
{
    temporary_memory Scratch = GetScratch(0, 0);
    
    va_list Arguments;
    va_start(Arguments, Format);
    string String = PushFormatVariadic(Scratch.Arena, Format, Arguments);
    va_end(Arguments);
    
    r32 Result = AC_AnimateValue(Target, Initial, Duration, String);
    
    ReleaseScratch(Scratch);
    
    return(Result);
}

static void AC_NewFrame(animation_curve_state *State, r32 dtForFrame)
{
    AC_SetState(State);
    State->dtForFrame = dtForFrame;
    
    // sixten: Prune untouched entries.
    for(s32 BucketIndex = 0;
        BucketIndex < ArrayCount(State->Buckets);
        ++BucketIndex)
    {
        animation_curve_bucket *Bucket = State->Buckets + BucketIndex;
        
        animation_curve_entry *Entry = Bucket->First;
        while(Entry != 0)
        {
            if(Entry->LastFrameTouched != State->CurrentFrame)
            {
                animation_curve_entry *ToRemove = Entry;
                Entry = Entry->Next;
                
                DLLRemove(Bucket->First, Bucket->Last, ToRemove);
                DLLInsertLast(State->FirstFreeEntry, State->LastFreeEntry, ToRemove);
            }
            else
            {
                Entry = Entry->Next;
            }
        }
    }
    
    ++State->CurrentFrame;
}