//
// File: foldhaus_animation_renderer.cpp
// Author: Peter Slattery
// Creation Date: 2020-11-14
//
#ifndef FOLDHAUS_ANIMATION_RENDERER_CPP

internal pixel
LedBlend_Overwrite(pixel PixelA, pixel PixelB, u8* UserData)
{
    return PixelB;
}

internal pixel
LedBlend_Lerp(pixel PixelA, pixel PixelB, u8* UserData)
{
    r32 BOpacity = *(r32*)UserData;
    
    pixel Result = {};
    
    r32 AOpacity = 1.0f - BOpacity;
    Result.R = (u8)((PixelA.R * AOpacity) + (PixelB.R * BOpacity));
    Result.G = (u8)((PixelA.G * AOpacity) + (PixelB.G * BOpacity));
    Result.B = (u8)((PixelA.B * AOpacity) + (PixelB.B * BOpacity));
    return Result;
}

internal pixel
LedBlend_Add(pixel PixelA, pixel PixelB, u8* UserData)
{
    pixel Result = {};
    
    u32 R = (u32)PixelA.R + (u32)PixelB.R;
    u32 G = (u32)PixelA.G + (u32)PixelB.G;
    u32 B = (u32)PixelA.B + (u32)PixelB.B;
    
    Result.R = (u8)Min(R, (u32)255);
    Result.G = (u8)Min(G, (u32)255);
    Result.B = (u8)Min(B, (u32)255);
    
    return Result;
}

internal pixel
LedBlend_Multiply(pixel PixelA, pixel PixelB, u8* UserData)
{
    pixel Result = {};
    
    r32 DR = (r32)PixelA.R / 255.f;
    r32 DG = (r32)PixelA.G / 255.f;
    r32 DB = (r32)PixelA.B / 255.f;
    
    r32 SR = (r32)PixelB.R / 255.f;
    r32 SG = (r32)PixelB.G / 255.f;
    r32 SB = (r32)PixelB.B / 255.f;
    
    Result.R = (u8)((DR * SR) * 255.f);
    Result.G = (u8)((DG * SG) * 255.f);
    Result.B = (u8)((DB * SB) * 255.f);
    
    return Result;
}

internal pixel
LedBlend_Overlay(pixel PixelA, pixel PixelB, u8* UserData)
{
    pixel Result = {};
    return Result;
}

internal led_blend_proc*
LedBlend_GetProc(blend_mode BlendMode)
{
    led_blend_proc* Result = 0;
    switch (BlendMode)
    {
        case BlendMode_Overwrite: { Result = LedBlend_Overwrite; }break;
        case BlendMode_Add:       { Result = LedBlend_Add;       }break;
        case BlendMode_Multiply:  { Result = LedBlend_Multiply;  }break;
        InvalidDefaultCase;
    }
    return Result;
}

struct pattern_args
{
    assembly Assembly;
    gs_memory_arena* Transient;
    u8* UserData;
};

internal void
AnimationSystem_RenderBlockToLedBuffer(animation_system* System, animation_block Block, led_buffer* Buffer, animation_pattern_array Patterns, pattern_args PatternArgs)
{
    u32 FramesIntoBlock = System->CurrentFrame - Block.Range.Min;
    r32 SecondsIntoBlock = FramesIntoBlock * System->SecondsPerFrame;
    
    animation_pattern Pattern = Patterns_GetPattern(Patterns, Block.AnimationProcHandle);
    Pattern.Proc(Buffer, PatternArgs.Assembly, SecondsIntoBlock, PatternArgs.Transient, PatternArgs.UserData);
}

// NOTE(pjs): This mirrors animation_layer_frame to account
// for overlapping
struct layer_led_buffer
{
    led_buffer HotBuffer;
    led_buffer NextHotBuffer;
};

internal led_buffer
RenderAnimationToLedBuffer (animation_system* System,
                            pattern_args PatternArgs,
                            animation_frame CurrFrame,
                            layer_led_buffer* LayerBuffers,
                            led_buffer* AssemblyLedBuffer,
                            animation_pattern_array Patterns,
                            gs_memory_arena* Transient)
{
    led_buffer AccBuffer = LedBuffer_CreateCopyCleared(*AssemblyLedBuffer, Transient);
    
    // Create the LayerLEDBuffers
    for (u32 Layer = 0; Layer < CurrFrame.LayersCount; Layer++)
    {
        layer_led_buffer TempBuffer = {};
        
        if (CurrFrame.Layers[Layer].HasHot)
        {
            TempBuffer.HotBuffer = LedBuffer_CreateCopyCleared(*AssemblyLedBuffer, Transient);
            
            if (CurrFrame.Layers[Layer].HasNextHot)
            {
                TempBuffer.NextHotBuffer = LedBuffer_CreateCopyCleared(*AssemblyLedBuffer, Transient);
            }
        }
        
        LayerBuffers[Layer] = TempBuffer;
    }
    
    // Render Each layer's block to the appropriate temp buffer
    for (u32 Layer = 0; Layer < CurrFrame.LayersCount; Layer++)
    {
        animation_layer_frame LayerFrame = CurrFrame.Layers[Layer];
        if (LayerFrame.HasHot)
        {
            led_buffer TempBuffer = LayerBuffers[Layer].HotBuffer;
            animation_block Block = LayerFrame.Hot;
            AnimationSystem_RenderBlockToLedBuffer(System, Block, &TempBuffer,  Patterns, PatternArgs);
        }
        
        if (LayerFrame.HasNextHot)
        {
            led_buffer TempBuffer = LayerBuffers[Layer].NextHotBuffer;
            animation_block Block = LayerFrame.NextHot;
            AnimationSystem_RenderBlockToLedBuffer(System, Block, &TempBuffer,  Patterns, PatternArgs);
        }
    }
    
    // Blend together any layers that have a hot and next hot buffer
    for (u32 Layer = 0; Layer < CurrFrame.LayersCount; Layer++)
    {
        animation_layer_frame LayerFrame = CurrFrame.Layers[Layer];
        layer_led_buffer LayerBuffer = LayerBuffers[Layer];
        if (LayerFrame.HasNextHot)
        {
            LedBuffer_Blend(LayerBuffer.HotBuffer,
                            LayerBuffer.NextHotBuffer,
                            &LayerBuffer.HotBuffer,
                            LedBlend_Lerp,
                            (u8*)&LayerFrame.NextHotOpacity);
        }
    }
    
    // Consolidate Temp Buffers back into AssemblyLedBuffer
    // We do this in reverse order so that they go from top to bottom
    for (u32 Layer = 0; Layer < CurrFrame.LayersCount; Layer++)
    {
        if (CurrFrame.Layers[Layer].HasHot)
        {
            led_blend_proc* Blend = LedBlend_GetProc(CurrFrame.Layers[Layer].BlendMode);
            LedBuffer_Blend(AccBuffer,
                            LayerBuffers[Layer].HotBuffer,
                            &AccBuffer,
                            Blend,
                            0);
        }
    }
    
    return AccBuffer;
}

internal void
AnimationSystem_RenderToLedBuffers(animation_system* System, assembly_array Assemblies,
                                   led_system* LedSystem,
                                   animation_pattern_array Patterns,
                                   gs_memory_arena* Transient,
                                   u8* UserData)
{
    s32 CurrentFrame = System->CurrentFrame;
    r32 FrameTime = CurrentFrame * System->SecondsPerFrame;
    
#if 1
    animation_array Animations = System->Animations;
    animation_fade_group FadeGroup = System->ActiveFadeGroup;
    
    animation* FromAnim = AnimationArray_Get(Animations, FadeGroup.From);
    animation_frame FromFrame = AnimationSystem_CalculateAnimationFrame(System, FromAnim, Transient);
    layer_led_buffer* FromLayerBuffers = PushArray(Transient, layer_led_buffer, FromFrame.LayersCount);
    
    animation* ToAnim = AnimationArray_Get(Animations, FadeGroup.To);
    animation_frame ToFrame = {0};
    layer_led_buffer* ToLayerBuffers = 0;
    if (ToAnim)
    {
        ToFrame = AnimationSystem_CalculateAnimationFrame(System, ToAnim, Transient);
        ToLayerBuffers = PushArray(Transient, layer_led_buffer, ToFrame.LayersCount);
    }
    
    for (u32 AssemblyIndex = 0; AssemblyIndex < Assemblies.Count; AssemblyIndex++)
    {
        assembly Assembly = Assemblies.Values[AssemblyIndex];
        led_buffer* AssemblyLedBuffer = LedSystemGetBuffer(LedSystem, Assembly.LedBufferIndex);
        
        pattern_args PatternArgs = {};
        PatternArgs.Assembly = Assembly;
        PatternArgs.Transient = Transient;
        PatternArgs.UserData = UserData;
        
        led_buffer FromBuffer = RenderAnimationToLedBuffer(System,
                                                           PatternArgs,
                                                           FromFrame,
                                                           FromLayerBuffers,
                                                           AssemblyLedBuffer,
                                                           Patterns,
                                                           Transient);
        led_buffer ConsolidatedBuffer = FromBuffer;
        
        if (ToAnim) {
            led_buffer ToBuffer = RenderAnimationToLedBuffer(System,
                                                             PatternArgs,
                                                             ToFrame,
                                                             ToLayerBuffers,
                                                             AssemblyLedBuffer,
                                                             Patterns,
                                                             Transient);
            
            r32 BlendPercent = FadeGroup.FadeElapsed / FadeGroup.FadeDuration;
            LedBuffer_Blend(FromBuffer, ToBuffer, &ConsolidatedBuffer, LedBlend_Lerp, (u8*)&BlendPercent);
        }
        
        LedBuffer_Copy(ConsolidatedBuffer, AssemblyLedBuffer);
    }
    
#else
    
    animation* ActiveAnim = AnimationSystem_GetActiveAnimation(System);
    animation_frame CurrFrame = AnimationSystem_CalculateAnimationFrame(System, ActiveAnim, Transient);
    
    for (u32 AssemblyIndex = 0; AssemblyIndex < Assemblies.Count; AssemblyIndex++)
    {
        assembly Assembly = Assemblies.Values[AssemblyIndex];
        led_buffer* AssemblyLedBuffer = LedSystemGetBuffer(LedSystem, Assembly.LedBufferIndex);
        
        pattern_args PatternArgs = {};
        PatternArgs.Assembly = Assembly;
        PatternArgs.Transient = Transient;
        PatternArgs.UserData = UserData;
        
        led_buffer AccBuffer = RenderAnimationToLedBuffer(System,
                                                          PatternArgs,
                                                          CurrFrame,
                                                          LayerBuffers,
                                                          AssemblyLedBuffer,
                                                          Patterns,
                                                          Transient);
        LedBuffer_Copy(AccBuffer, AssemblyLedBuffer);
    }
    
#endif
    
    System->LastUpdatedFrame = System->CurrentFrame;
}

#define FOLDHAUS_ANIMATION_RENDERER_CPP
#endif // FOLDHAUS_ANIMATION_RENDERER_CPP