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


#ifndef ECXX_PLATFORM_GENERIC_HUB75_DRIVER_H
#define ECXX_PLATFORM_GENERIC_HUB75_DRIVER_H


#include <eCxx/CTL/GFX/ColorTypes.h>
#include <eCxx/CTL/GFX/ColorBufferPolicy.h>
#include <eCxx/Platform/Common/PinDDX.h>

#include "HUB75Config.h"
#include "HUB75ExtProcedure.h"
#include "HUB75PinDDX.h"
#include "HUB75PixelAddressMapper.h"
#include "HUB75SendData.h"


namespace eCxx {


//
// A simple generic HUB75 LED matrix driver
//
/*
 * # In all platforms this driver is implemented using bit banging method.
 *
 * # The clock frequency (ClockFrequencyN) must be between 1 MHz and 25 MHz (1000000UL to 25000000UL);
 *   a clock frequency of 2 MHz (2000000UL) seems safe when used together with ~470R series resistors
 *   on the lines.
 *
 * # The ColorFX2D_T must be in RGB variant format.
 *
 * # The raw byte buffer must match the ColorFX2D_T format.
 *
 * # The Pins must be arranged in [ CLK LE OE R0 R1 G0 G1 B0 B1 A B C D E ] sequence; the actual data
 *   and address pins will depend on the data pin mode, row scanning mode, row addressing mode, and
 *   the number of rows).
 *
 * # There is no need to call Application::setPinMode() manually because this driver does it automatically.
 *
 * # This driver automatically apply color correction using CIE-1931 lightness from 3-bit per color channel
 *   to 5-bit per BCM channel.
 */
template <
    typename                                                                        ColorFX2D_T,
    template <typename, HUB75Config::RowScanning, HUB75Config::RowAddressing> class HUB75PixelAddressMapperT,
    template <typename, typename, typename, typename, uint16_t              > class HUB75ExtProcedureT,
    uint32_t                                                                        ClockFrequencyN,
    HUB75Config::DataPin                                                            DataPinE,
    HUB75Config::RowScanning                                                        RowScanningE,
    HUB75Config::RowAddressing                                                      RowAddressingE,
    uint8_t...                                                                      Pins
>
class GenericHUB75Driver : public Uncopyable {
    static_assert(
        eCxx::isBaseOf<
            BasicRGBColor<typename ColorFX2D_T::BufferColorType>,
                          typename ColorFX2D_T::BufferColorType
        >::Value,
        "template argument substituting ColorFX2D_T::BufferColorType is not an RGB color variant"
    );

    static_assert(
        (ColorFX2D_T::XElementCount >= 8) && (ColorFX2D_T::XElementCount <= 64),
        "template argument substituting ColorFX2D_T::XElementCount is not between 8 and 64"
    );

    static_assert(
        (ColorFX2D_T::YElementCount >= 8) && (ColorFX2D_T::YElementCount <= 64),
        "template argument substituting ColorFX2D_T::YElementCount is not between 8 and 64"
    );

    static_assert(
        ClockFrequencyN >= 1000000UL,
        "template argument substituting ClockFrequencyN is smaller than 1000000"
    );

    static_assert(
        ClockFrequencyN <= 25000000UL,
        "template argument substituting ClockFrequencyN is larger than 25000000"
    );

    public:
        using ColorFX2DType   = ColorFX2D_T;
        using BufferColorType = typename ColorFX2D_T::BufferColorType;

        static constexpr uint32_t                   ClockFrequency    = ClockFrequencyN;

        static constexpr HUB75Config::DataPin       DataPinMode       = DataPinE;
        static constexpr HUB75Config::RowScanning   RowScanningMode   = RowScanningE;
        static constexpr HUB75Config::RowAddressing RowAddressingMode = RowAddressingE;

        static constexpr uint8_t                    XPixelCount       = ColorFX2D_T::XElementCount;
        static constexpr uint8_t                    YPixelCount       = ColorFX2D_T::YElementCount;

        static constexpr uint8_t                    AddressLineCount  = HUB75EffectiveAddressLineCount<RowScanningE>::Value;

    private:
        static constexpr uint32_t MinimumBCMTimerFrequency = 50000;

        static constexpr uint8_t  ClockTPeriod             = (Application::getCPUFrequency() + ClockFrequency / 2) / ClockFrequency;
        static constexpr uint8_t  ClockLPeriodHalf         = (ClockTPeriod + 2) / 4;
        static constexpr uint8_t  ClockHPeriod             = (ClockTPeriod + 1) / 2;

        static constexpr uint8_t  ClockDelay0bAdj          = 5 * HUB75DataPin< DataPinMode, eCxx::integerSequence<uint8_t, 0, 0, 0, 0, 0, 0>, 0 >::PinCount;

        static constexpr uint8_t  ClockDelay0a             = (ClockLPeriodHalf > 1              ) ? (ClockLPeriodHalf - 1              ) : 0;
        static constexpr uint8_t  ClockDelay0b             = (ClockLPeriodHalf > ClockDelay0bAdj) ? (ClockLPeriodHalf - ClockDelay0bAdj) : 0;
        static constexpr uint8_t  ClockDelay1              = (ClockHPeriod     > 1              ) ? (ClockHPeriod     - 1              ) : 0;

    private:
        static constexpr uint8_t  ActiveRowCount    = ColorFX2D_T::YElementCount / static_cast<uint8_t>(RowScanningMode);
        static constexpr uint16_t RowPixelCount     = static_cast<uint16_t>(XPixelCount) * static_cast<uint16_t>(ActiveRowCount);
        static constexpr uint16_t RowBufferByteSize = RowPixelCount * ColorFX2DType::ColorByteSize;

        static_assert(
            (ActiveRowCount >= 2) && (ActiveRowCount <= 8),
             "ActiveRowCount is not between 2 and 8"
        );

    private:
        using PIN_LIST               = eCxx::integerSequence<uint8_t, Pins...>;

        using ControlPinType         = HUB75ControlPin<false, false, true,                  ClockDelay0a, ClockDelay0b, ClockDelay1, PIN_LIST, 0                                               >;
        using DataPinType            = HUB75DataPin   <DataPinMode,                                                                  PIN_LIST, ControlPinType::PinCount                        >;
        using AddressPinType         = HUB75AddressPin<RowAddressingMode, AddressLineCount, ClockDelay0a, ClockDelay0b, ClockDelay1, PIN_LIST, ControlPinType::PinCount + DataPinType::PinCount>;

        using SendDataType           = HUB75SendData           <DataPinMode, ControlPinType, ClockDelay0a, ClockDelay0b, ClockDelay1, DataPinType, RowPixelCount, ColorFX2DType::ColorByteSize>;
        using ExtProcedureType       = HUB75ExtProcedureT      <ControlPinType, DataPinType, AddressPinType, SendDataType, RowPixelCount>;
        using PixelAddressMapperType = HUB75PixelAddressMapperT<ColorFX2DType, RowScanningMode, RowAddressingMode>;

        static_assert(
            PixelAddressMapperType::rowPixelCount() == RowPixelCount,
             "RowPixelCount is not the same as PixelAddressMapperType::rowPixelCount()"
        );

    public:
        inline GenericHUB75Driver();
        inline ~GenericHUB75Driver();

        static constexpr uint32_t getBCMTimerFrequency(uint8_t frameRate) noexcept;

        inline bool begin(const uint8_t* rawByteBuffer);
        inline bool begin(const ColorFX2DType& colorFX2D);
        inline void end();

        // Replace the raw byte buffer (it is needed when using double or custom buffering)
        // NOTE : The 'rawByteBuffer' parameter cannot be a nullptr
        inline void setRawByteBuffer(const uint8_t* rawByteBuffer);

        // NOTE : # This function must be called periodically at a frequency multiple of
        //          (HUB75Config::BCMStepCount * RowScanningMode * <frame_rate>) Hz. However,
        //          for a relatively flicker-free visual, the frequency should be at least ~50 kHz.
        //        # Using the value returned by GenericHUB75Driver::getBCMTimerFrequency()
        //          is recommended.
        //        # Using timer/counter interrupt is highly recommended for stability.
        inline void ICACHE_RAM_ATTR update() noexcept;

        // NOTE : # It has the same requirements as update().
        //        # However, the data is simply unpacked from the raw byte buffer without pixel
        //          address mapping and color correction; therefore:
        //              + The pixel flow of the pixel data in the raw byte buffer must match the
        //                pixel flow of the LED panel.
        //              + The pixel data must be interleaved between plane #0 (R0, G0, B0) data
        //                and plane #1 (R1, G1, B1) data.
        //              + The pixel must be in two bytes per pixel format; the bits must be in
        //                in this arrangement:
        //                    Byte  (N)      (N + 1)
        //                    Bit   76543210 76543210
        //                    Value GGGRRRRR XGGBBBBB   (G4 G3 G2 R4 R3 R2 R1 R0   XX G1 G0 B4 B3 B2 B1 B0)
        //                Note that the X bit is ignored.
        inline void ICACHE_RAM_ATTR packedUpdate() noexcept;

    private:
        const ControlPinType _ctlPin;
        const DataPinType    _dataPin;
        const AddressPinType _addrPin;

        const uint8_t*       _rawByteBuffer;
        uint8_t              _rowBuffer[RowBufferByteSize];
        uint8_t              _rowCounter;
        uint8_t              _bcmPhase;
        uint8_t              _bcmCounter;

        static constexpr eCxx::uint32_t _bcmTimerFrequency_mult(eCxx::uint32_t tr, eCxx::uint32_t mf = 1) noexcept;

        template <bool Packed>
        inline void ICACHE_RAM_ATTR _update() noexcept;
};


//
// Include the implementation file
//
#define INCLUDE_ECXX_PLATFORM_GENERIC_HUB75_TPP
#include "GenericHUB75Driver.tpp"


} // namespace eCxx


#endif
