//
// File: foldhaus_assembly.cpp
// Author: Peter Slattery
// Creation Date: 2020-01-01
//
#ifndef FOLDHAUS_ASSEMBLY_CPP

///////////////////////////
//
//  Assembly Array
//
///////////////////////////

internal assembly_array
AssemblyArray_Create(u32 CountMax, gs_memory_arena* Storage)
{
    assembly_array Result = {0};
    Result.CountMax = CountMax;
    Result.Values = PushArray(Storage, assembly, Result.CountMax);
    return Result;
}

internal u32
AssemblyArray_Push(assembly_array* Array, assembly Assembly)
{
    Assert(Array->Count < Array->CountMax);
    u32 Index = Array->Count++;
    Array->Values[Index] = Assembly;
    return Index;
}

internal assembly*
AssemblyArray_Take(assembly_array* Array)
{
    u32 Index = AssemblyArray_Push(Array, {});
    assembly* Result = Array->Values + Index;
    return Result;
}

internal void
AssemblyArray_RemoveAt(assembly_array* Array, u32 Index)
{
    u32 LastAssemblyIndex = --Array->Count;
    Array->Values[Index] = Array->Values[LastAssemblyIndex];
}

typedef bool assembly_array_filter_proc(assembly A);
bool AssemblyFilter_OutputsViaSACN(assembly A) { return A.OutputMode == NetworkProtocol_SACN; }
bool AssemblyFilter_OutputsViaUART(assembly A) { return A.OutputMode == NetworkProtocol_UART; }

internal assembly_array
AssemblyArray_Filter(assembly_array Array, assembly_array_filter_proc* Filter, gs_memory_arena* Storage)
{
    assembly_array Result = AssemblyArray_Create(Array.Count, Storage);
    
    for (u32 i = 0; i < Array.Count; i++)
    {
        assembly At = Array.Values[i];
        if (Filter(At))
        {
            AssemblyArray_Push(&Result, At);
        }
    }
    
    return Result;
}

///////////////////////////
//
//  LedSystem
//
///////////////////////////

internal led_system
LedSystemInitialize(gs_allocator PlatformMemory, u32 BuffersMax)
{
    led_system Result = {};
    Result.PlatformMemory = PlatformMemory;
    // TODO(Peter): Since we have access to PlatformMemory, just realloc Buffers when we fill it up
    Result.BuffersCountMax = BuffersMax;
    Result.Buffers = AllocatorAllocArray(PlatformMemory, led_buffer, Result.BuffersCountMax);
    return Result;
}

internal u32
LedSystemTakeFreeBuffer(led_system* System, u32 LedCount)
{
    s32 Result = -1;
    
    if (System->BuffersCount < System->BuffersCountMax)
    {
        Result = System->BuffersCount++;
    }
    else
    {
        // NOTE(Peter): Look for a buffer that's flagged as empty
        for (u32 i = 0; i < System->BuffersCount; i++)
        {
            if (System->Buffers[i].LedCount == 0
                && System->Buffers[i].Colors == 0
                && System->Buffers[i].Positions == 0)
            {
                Result = i;
                break;
            }
        }
        Assert(Result >= 0); // NOTE(Peter): We ran out of room for led buffers
    }
    
    led_buffer* Buffer = &System->Buffers[Result];
    Buffer->LedCount = LedCount;
    Buffer->Colors = AllocatorAllocArray(System->PlatformMemory, pixel, Buffer->LedCount);
    Buffer->Positions = AllocatorAllocArray(System->PlatformMemory, v4, Buffer->LedCount);
    
    System->LedsCountTotal += LedCount;
    
    return (u32)Result;
}

internal void
LedSystemFreeBuffer(led_system* System, u32 BufferIndex)
{
    Assert(BufferIndex < System->BuffersCountMax);
    led_buffer* Buffer = &System->Buffers[BufferIndex];
    AllocatorFreeArray(System->PlatformMemory, Buffer->Colors, pixel, Buffer->LedCount);
    AllocatorFreeArray(System->PlatformMemory, Buffer->Positions, v4, Buffer->LedCount);
    System->LedsCountTotal -= Buffer->LedCount;
    *Buffer = {};
}

internal void
LedBufferSetLed(led_buffer* Buffer, u32 Led, v4 Position)
{
    Assert(Led < Buffer->LedCount);
    Buffer->Positions[Led] = Position;
}

internal u32
Assembly_ConstructStrip(assembly* Assembly, led_buffer* LedBuffer, v2_strip* StripAt, strip_gen_data GenData, v4 RootPosition, u32 LedStartIndex)
{
    u32 LedsAdded = 0;
    
    switch (GenData.Method)
    {
        case StripGeneration_InterpolatePoints:
        {
            strip_gen_interpolate_points InterpPoints = GenData.InterpolatePoints;
            v4 WS_StripStart = RootPosition + ToV4Point(InterpPoints.StartPosition * Assembly->Scale);
            v4 WS_StripEnd = RootPosition + ToV4Point(InterpPoints.EndPosition * Assembly->Scale);
            
            v4 SingleStep = (WS_StripEnd - WS_StripStart) / (r32)InterpPoints.LedCount;
            for (u32 Step = 0; Step < InterpPoints.LedCount; Step++)
            {
                s32 LedIndex = LedStartIndex + LedsAdded++;
                v4 LedPosition = WS_StripStart + (SingleStep * Step);
                LedBufferSetLed(LedBuffer, LedIndex, LedPosition);
                StripAt->LedLUT[Step] = LedIndex;
            }
        }break;
        
        case StripGeneration_Sequence:
        {
            strip_gen_sequence Sequence = GenData.Sequence;
            for (u32 i = 0; i < Sequence.ElementsCount; i++)
            {
                strip_gen_data SegmentGenData = Sequence.Elements[i];
                LedsAdded += Assembly_ConstructStrip(Assembly, LedBuffer, StripAt, SegmentGenData, RootPosition, LedStartIndex + LedsAdded);
            }
        }break;
        
        InvalidDefaultCase;
    }
    
    return LedsAdded;
}

internal void
ConstructAssemblyFromDefinition (assembly* Assembly, led_system* LedSystem)
{
    Assembly->LedBufferIndex = LedSystemTakeFreeBuffer(LedSystem, Assembly->LedCountTotal);
    led_buffer* LedBuffer = LedSystemGetBuffer(LedSystem, Assembly->LedBufferIndex);
    
    v4 RootPosition = ToV4Vec(Assembly->Center);
    
    // Add Leds
    u32 LedsAdded = 0;
    for (u32 StripIdx = 0; StripIdx < Assembly->StripCount; StripIdx++)
    {
        v2_strip* StripAt = &Assembly->Strips[StripIdx];
        StripAt->LedLUT = PushArray(&Assembly->Arena, u32, StripAt->LedCount);
        
        strip_gen_data GenData = StripAt->GenerationData;
        LedsAdded += Assembly_ConstructStrip(Assembly, LedBuffer, StripAt, GenData, RootPosition, LedsAdded);
    }
}

internal void
LoadAssembly (assembly_array* Assemblies, led_system* LedSystem, gs_memory_arena* Scratch, context Context, gs_const_string Path, event_log* GlobalLog)
{
    gs_file AssemblyFile = ReadEntireFile(Context.ThreadContext.FileHandler, Path);
    if (FileNoError(AssemblyFile))
    {
        gs_string AssemblyFileText = MakeString((char*)AssemblyFile.Memory);
        
        s32 IndexOfLastSlash = FindLast(Path, '\\');
        gs_const_string FileName = Substring(Path, IndexOfLastSlash + 1, Path.Length);
        
        assembly* NewAssembly = AssemblyArray_Take(Assemblies);
        NewAssembly->Arena = CreateMemoryArena(Context.ThreadContext.Allocator);
        
        if (ParseAssemblyFile(NewAssembly, FileName, AssemblyFileText, Scratch))
        {
            ConstructAssemblyFromDefinition(NewAssembly, LedSystem);
        }
        else
        {
            FreeMemoryArena(&NewAssembly->Arena);
            Assemblies->Count -= 1;
        }
    }
    else
    {
        LogError(GlobalLog, "Unable to load assembly file");
    }
}

internal void
UnloadAssembly (u32 AssemblyIndex, app_state* State, context Context)
{
    Assert(AssemblyIndex < State->Assemblies.Count);
    assembly* Assembly = &State->Assemblies.Values[AssemblyIndex];
    LedSystemFreeBuffer(&State->LedSystem, Assembly->LedBufferIndex);
    FreeMemoryArena(&Assembly->Arena);
    AssemblyArray_RemoveAt(&State->Assemblies, AssemblyIndex);
}

// Querying Assemblies

internal led_strip_list
AssemblyStripsGetWithTagValue(assembly Assembly, gs_const_string TagName, gs_const_string TagValue, gs_memory_arena* Storage)
{
    led_strip_list Result = {0};
    // TODO(pjs): @Optimization
    // We can probably come back here and do this allocation procedurally, or in buckets, or with
    // a linked list. But for now, I just want to get this up and running
    Result.CountMax = Assembly.StripCount;
    Result.StripIndices = PushArray(Storage, u32, Result.CountMax);
    
    u64 NameHash = HashDJB2ToU32(StringExpand(TagName));
    u64 ValueHash = 0;
    if (TagValue.Length > 0)
    {
        ValueHash = HashDJB2ToU32(StringExpand(TagValue));
    }
    
    for (u32 StripIndex = 0; StripIndex < Assembly.StripCount; StripIndex++)
    {
        v2_strip StripAt = Assembly.Strips[StripIndex];
        for (u32 j = 0; j < StripAt.TagsCount; j++)
        {
            v2_tag TagAt = StripAt.Tags[j];
            if (TagAt.NameHash == NameHash)
            {
                // NOTE(pjs): We can pass an empty string to the Value parameter,
                // and it will match all values of Tag
                if (ValueHash == 0 || ValueHash == TagAt.ValueHash)
                {
                    Result.StripIndices[Result.Count++] = StripIndex;
                }
            }
        }
    }
    
    return Result;
}

#define FOLDHAUS_ASSEMBLY_CPP
#endif // FOLDHAUS_ASSEMBLY_CPP