/*
 * Copyright (C) 2020-2021 Aloysius Indrayanto
 *
 * This file is part of the eCxx library, see LICENSE file for the license details.
 */


#include <eCxx/eCxx_ColorAnimator2D.h>
#include <eCxx/eCxx_LEDDriver.h>

#include "AVR_Common.h"
#include "HUB75PinNumber.h"


#ifndef DISABLE_TEST_APPLICATION_BUILD


//
// LED panel configuration
//

// Comment this to disable double buffering
#define USE_DOUBLE_BUFFERING

// Uncomment this to use an outdoor LED panel with 1/4 scanning and block address mapper
// instead of an indoor LED panel with 1/8 scanning with default (straight) address mapper
// #define USE_OUTDOOR_S_1_4_BLOCK

// Uncomment this to use starlight animation instead of fire animation
// #define USE_STARLIGHT_ANIMATION

static constexpr eCxx::HUB75Config::DataPin       DataPinMode       = eCxx::HUB75Config::DataPin      ::R0_R1_G0_G1_B0_B1;
#ifndef USE_OUTDOOR_S_1_4_BLOCK
static constexpr eCxx::HUB75Config::RowScanning   RowScanningMode   = eCxx::HUB75Config::RowScanning  ::S_1_8;
#else
static constexpr eCxx::HUB75Config::RowScanning   RowScanningMode   = eCxx::HUB75Config::RowScanning  ::S_1_4;
#endif
static constexpr eCxx::HUB75Config::RowAddressing RowAddressingMode = eCxx::HUB75Config::RowAddressing::Direct;

static constexpr uint32_t ClockFrequency = 2000000UL;

static constexpr uint8_t  PixelX         = 32;
static constexpr uint8_t  PixelY         = 16;


//
// Instantiate the color effect buffer, animator, and procedure
//
using ColorType          = eCxx::RGBColor;
using ColorFX2DType      = eCxx::ColorFX2D<
                               ColorType,
                               PixelX,
                               PixelY,
#ifndef USE_OUTDOOR_S_1_4_BLOCK
                               eCxx::CBP::InterRowMajor,
#else
                               eCxx::CBP::RowMajor,
#endif
                               eCxx::CBP::RawBuffer,
                               0,                                     // Block       X size (ignored for non CBP::Block variants)
                               0,                                     // Block       Y size (ignored for non CBP::Block variants)
                               static_cast<uint8_t>(RowScanningMode), // Interleaved Y size (for CBP::InterRowMajor             )
#ifdef USE_DOUBLE_BUFFERING
                               true                                   // Use double buffering
#else
                               false                                  // Use single buffering
#endif
                           >;
using ColorAnim2DType    = eCxx::ColorAnimator2D<1, ColorFX2DType>;
using Anim2DFireProcType = eCxx::Animation2DProc_Fire<
                               ColorAnim2DType,
                               eCxx::Fire2DConfig::Flow::BottomToTop,
                               eCxx::Fire2DConfig::Dist::Uniform,
                               false
                           >;
using Anim2DStarProcType = eCxx::Animation2DProc_RandomStarlightTwoColors<
                               ColorAnim2DType,
                               16
                           >;

static ColorFX2DType      colorFX2D;
static ColorAnim2DType    anim2D( colorFX2D );
#ifndef USE_STARLIGHT_ANIMATION
static Anim2DFireProcType fire2D( 16, eCxx::Fire2DConfig::redRGBFireLUT_16() );
#else
static Anim2DStarProcType star2D( eCxx::Ext::RGB::black, eCxx::Ext::RGB::white );
#endif


//
// Instantiate the HUB75 driver
//
using HUB75DriverType = eCxx::GenericHUB75Driver<
                            ColorFX2DType,
#ifndef USE_OUTDOOR_S_1_4_BLOCK
                            eCxx::HUB75PixelAddressMapper_DefaultIndoor,
#else
                            eCxx::HUB75PixelAddressMapper_Block8,
#endif
                            eCxx::HUB75ExtProcedure_NOP,
                            ClockFrequency,
                            DataPinMode,
                            RowScanningMode,
                            RowAddressingMode,
                            PIN_CLK, PIN_LE, PIN_OE,
                            PIN_R0,  PIN_R1, PIN_G0, PIN_G1, PIN_B0, PIN_B1,
                            PIN_A,   PIN_B,  PIN_C,  PIN_D
                        >;

static constexpr uint8_t  FrameRate = 10;
static constexpr uint32_t TimerFreq = HUB75DriverType::getBCMTimerFrequency(FrameRate);

static HUB75DriverType hub75Driver;


#endif // DISABLE_TEST_APPLICATION_BUILD


//
// The init function
//
void eCxx_user_init()
{
    // Initialize application
    initBasicAVRApp();

#ifndef DISABLE_TEST_APPLICATION_BUILD

    // Initialize the animator
#ifndef USE_STARLIGHT_ANIMATION
    anim2D.initSegment(0, fire2D);
#else
    anim2D.initSegment(0, star2D);
#endif
    anim2D.reset();

    // Initialize the driver
#ifdef USE_DOUBLE_BUFFERING
    hub75Driver.begin( colorFX2D.colorFrontBufferData() );
#else
    hub75Driver.begin( colorFX2D.colorBufferData() );
#endif

    // Initialize periodic interrupt for the HUB75 driver
#ifdef __AVR_ARCH_X__

    using TCB = eCxx::TimerCounterB;

    auto& tcb = *App::tcb1();

    tcb.setClock(TCB::ClockSource::Peripheral1);
    tcb.setMode(TCB::Mode::PeriodicInterrupt);
    tcb.setPeriod( (F_CPU + TimerFreq / 2) / TimerFreq );
    tcb.setPMOutput(TCB::PWMOutput::Disabled);
    tcb.attachInterrupt( +[]() {
        hub75Driver.update();
    } );
    tcb.start();

#else

    using TC1 = eCxx::TimerCounter1;

    // Initialize Timer1
    TC1::start(TC1::ClockPrescaler::_1);

    TC1::setPeriod( (F_CPU + TimerFreq / 2) / TimerFreq );
    TC1::attachPeriodicInterrupt( +[]() {
        hub75Driver.update();
    } );

#endif

#endif // DISABLE_TEST_APPLICATION_BUILD
}


//
// The task function
//
void eCxx_user_task()
{
#ifndef DISABLE_TEST_APPLICATION_BUILD

    // Process event and delay
    static eCxx::uint32_t prvMillis = 0;
           eCxx::uint32_t curMillis = App::processEvent();
    if( curMillis - prvMillis < ( (1000 + FrameRate / 2) / FrameRate) ) return;
    else                                                                prvMillis = curMillis;

#if 1

    // Update the animation
#ifdef USE_DOUBLE_BUFFERING
    colorFX2D.clear(); // Without double buffering this will cause blinking display
#endif
    anim2D.update();

#else

    #ifndef USE_DOUBLE_BUFFERING
    #error "Tracing test needs double buffering to be enabled!"
    #endif

    // Tracing test
    static uint8_t x = PixelX - 1;
    static uint8_t y = PixelY - 1;
    ++x;
    if(x >= PixelX) {
        x = 0;
        ++y;
        if(y >= PixelY) y = 0;
    }
    colorFX2D.clear();
    colorFX2D.setXY(x, y, eCxx::Ext::RGB::darkGray);

#endif

#ifdef USE_DOUBLE_BUFFERING
    // Swap the front buffer and back buffer
    colorFX2D.flipBuffer();
    hub75Driver.setRawByteBuffer( colorFX2D.colorFrontBufferData() );
#endif

#else // DISABLE_TEST_APPLICATION_BUILD

    // Process event
    eCxx::Application::processEvent();

#endif // DISABLE_TEST_APPLICATION_BUILD
}
