/*
 * 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 "NodeMCU_Common.h"


//
// Pin numbers
//
#ifdef ECXX_ESP01
#warning "this firmware is NOT meant for ESP-01"
#endif

/*
 *   12    14    2     0           5           4
 * ┌─────┬─────┬─────┮═════╤═════┭─────┬─────┬─────┐
 * │ OE  │ CLK │ C   │ A   │ B1  │ R1  │ B0  │ R0  │
 * ├─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┤
 * │ GND │ LE  │ D   │ B   │ E   │ G1  │ GND │ G0  │
 * └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
 *         13    15    3     1
 *
 * On the LED panel, connect R0-out to G0-in, G0-out to B0-in,
 *                           R1-out to G1-in, G1-out to B1-in
 */

static constexpr eCxx::uint8_t PIN_CLK = 14;
static constexpr eCxx::uint8_t PIN_LE  = 13;
static constexpr eCxx::uint8_t PIN_OE  = 12;

static constexpr eCxx::uint8_t PIN_R0  =  4;
static constexpr eCxx::uint8_t PIN_R1  =  5;

static constexpr eCxx::uint8_t PIN_A   =  0;
static constexpr eCxx::uint8_t PIN_B   =  3;
static constexpr eCxx::uint8_t PIN_C   =  2;
static constexpr eCxx::uint8_t PIN_D   = 15;
static constexpr eCxx::uint8_t PIN_E   =  1;


//
// LED panel configuration
//
static constexpr eCxx::HUB75Config::DataPin       DataPinMode       = eCxx::HUB75Config::DataPin      ::R0G0B0_R1G1B1;
static constexpr eCxx::HUB75Config::RowScanning   RowScanningMode   = eCxx::HUB75Config::RowScanning  ::S_1_8;
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,
                               eCxx::CBP::InterRowMajor,
                               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             )
                               true                                   // Use double buffering
                           >;
using ColorAnim2DType    = eCxx::ColorAnimator2D<1, ColorFX2DType>;
using Anim2DFireProcType = eCxx::Animation2DProc_Fire<
                               ColorAnim2DType,
                               eCxx::Fire2DConfig::Flow::BottomToTop,
                               eCxx::Fire2DConfig::Dist::Uniform,
                               false
                           >;

static ColorFX2DType      colorFX2D;
static ColorAnim2DType    anim2D( colorFX2D );
static Anim2DFireProcType fire2D( 16, eCxx::Fire2DConfig::redRGBFireLUT_16() );


//
// Instantiate the HUB75 driver
//
using HUB75DriverType = eCxx::GenericHUB75Driver<
                            ColorFX2DType,
                            eCxx::HUB75PixelAddressMapper_DefaultIndoor,
                            eCxx::HUB75ExtProcedure_NOP,
                            ClockFrequency,
                            DataPinMode,
                            RowScanningMode,
                            RowAddressingMode,
                            PIN_CLK, PIN_LE, PIN_OE,
                            PIN_R0,  PIN_R1,
                            PIN_A,   PIN_B,  PIN_C,  PIN_D,  PIN_E
                        >;

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

static HUB75DriverType hub75Driver;


//
// ISR for updating HUB75
//
static void ICACHE_RAM_ATTR hub75ISR()
{ hub75Driver.update(); }


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

    // Enable the 74LVC541 buffers on the eCxx NodeMCU development board because
    // the pins defined above are buffered
    App::setPinMode  ( eCxx::NodeMCU::D0, eCxx::Application::PinMode::Output ); // GPIO16
    App::digitalWrite( eCxx::NodeMCU::D0, false                              ); // The buffers are active low

    // Initialize the animator
    anim2D.initSegment(0, fire2D);
    anim2D.reset();

    // Initialize the driver
    hub75Driver.begin( colorFX2D.colorFrontBufferData() );

    // Initialize periodic interrupt for the HUB75 driver
    // NOTE : Using lambda callback seems does not work because GenericHUB75Driver<T> is a template class
    App::attachPeriodicInterrupt(hub75ISR, TimerFreq);
}


//
// The task function
//
void eCxx_user_task()
{
    // Process event and delay
    // (do not blink the activity indicator LED because the same pin is used by the HUB75 driver)
    static eCxx::uint32_t prvMillis = 0;
           eCxx::uint32_t curMillis = App::processEvent(false);
    if( curMillis - prvMillis < ( (1000 + FrameRate / 2) / FrameRate) ) return;
    else                                                                prvMillis = curMillis;

    // Update the animation
    colorFX2D.clear();
    anim2D.update();

    // Swap the front buffer and back buffer
    colorFX2D.flipBuffer();
    hub75Driver.setRawByteBuffer( colorFX2D.colorFrontBufferData() );
}
