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


#ifndef INCLUDE_ECXX_PLATFORM_GENERIC_HUB75_TPP
#error "this file cannot be included directly"
#endif
#undef INCLUDE_ECXX_PLATFORM_GENERIC_HUB75_TPP


#ifndef ECXX_PLATFORM_GENERIC_HUB75_DRIVER_TPP
#define ECXX_PLATFORM_GENERIC_HUB75_DRIVER_TPP


//
// Define some convenience macros
//
#define GH75_TSPEC                                                                                       \
    typename                                                                        ColorFX2D_T,         \
    template <typename, HUB75Config::RowScanning, HUB75Config::RowAddressing> class PixelAddressMapperT, \
    template <typename, typename, typename, typename, uint16_t              > class HUB75ExtProcedureT,  \
    uint32_t                                                                        ClockFrequencyN,     \
    HUB75Config::DataPin                                                            DataPinE,            \
    HUB75Config::RowScanning                                                        RowScanningE,        \
    HUB75Config::RowAddressing                                                      RowAddressingE,      \
    uint8_t...                                                                      Pins


#define GH75_TDECL       \
    ColorFX2D_T,         \
    PixelAddressMapperT, \
    HUB75ExtProcedureT,  \
    ClockFrequencyN,     \
    DataPinE,            \
    RowScanningE,        \
    RowAddressingE,      \
    Pins...


//
// A simple generic HUB75 LED matrix driver
//
template <GH75_TSPEC>
inline GenericHUB75Driver<GH75_TDECL>::GenericHUB75Driver()
: _rawByteBuffer(nullptr)
{}


template <GH75_TSPEC>
inline GenericHUB75Driver<GH75_TDECL>::~GenericHUB75Driver()
{ if(_rawByteBuffer) end(); }


template <GH75_TSPEC>
inline constexpr uint32_t GenericHUB75Driver<GH75_TDECL>::_bcmTimerFrequency_mult(uint32_t tr, uint32_t mf) noexcept
{ return ( (tr * mf) >= MinimumBCMTimerFrequency) ? (tr * mf) : _bcmTimerFrequency_mult(tr, mf + 1); }


template <GH75_TSPEC>
inline constexpr uint32_t GenericHUB75Driver<GH75_TDECL>::getBCMTimerFrequency(uint8_t frameRate) noexcept
{
    return _bcmTimerFrequency_mult(
        eCxx::HUB75Config::BCMStepCount * static_cast<uint16_t>(RowScanningMode) * frameRate
    );
}


template <GH75_TSPEC>
inline bool GenericHUB75Driver<GH75_TDECL>::begin(const uint8_t* rawByteBuffer)
{
    // Check if already initialized
    if(_rawByteBuffer) return false;

    // Save the raw byte buffer pointer
    _rawByteBuffer = rawByteBuffer;

    // Initialize variables
    _rowCounter = 0;
    _bcmPhase   = HUB75Config::BCMPhaseEnd >> 1;
    _bcmCounter = 0;

    memset(_rowBuffer, 0, RowBufferByteSize);

    // Initialize the pins
    _ctlPin .begin();
    _dataPin.begin();
    _addrPin.begin();

    // Execute the extended initialization procedure
    ExtProcedureType::initialize(_ctlPin, _dataPin, _addrPin);

    // Done
    return true;
}


template <GH75_TSPEC>
inline bool GenericHUB75Driver<GH75_TDECL>::begin(const ColorFX2DType& colorFX2D)
{ return begin( colorFX2D.colorBufferData() ); }


template <GH75_TSPEC>
inline void GenericHUB75Driver<GH75_TDECL>::end()
{
    // Check if it is not yet initialized
    if(!_rawByteBuffer) return;

    // Clear the raw byte buffer pointer
    _rawByteBuffer = nullptr;

    // Uninitialize the pins
    _ctlPin .end();
    _dataPin.end();
    _addrPin.end();
}


template <GH75_TSPEC>
inline void GenericHUB75Driver<GH75_TDECL>::setRawByteBuffer(const uint8_t* rawByteBuffer)
{
    // Check if it is not yet initialized
    if(!_rawByteBuffer) return;

    // Check if the new raw byte buffer is nullptr
    if(!rawByteBuffer) return;

    // Save the new raw byte buffer pointer
    _rawByteBuffer = rawByteBuffer;
}


template <GH75_TSPEC> template <bool Packed>
inline void GenericHUB75Driver<GH75_TDECL>::_update() noexcept
{
    // Check if it is not yet initialized
    if(!_rawByteBuffer) return;

    // If the BCM counter is not zero yet, decrement it and return
    if(_bcmCounter) {
        --_bcmCounter;
        return;
    }

    // Turn off the LEDs
    _ctlPin.oe.digitalWrite(ControlPinType::OutEnL);

    // Select the next BCM phase
    _bcmPhase <<= 1;

    // Check if the current BCM cycle has completed
    if(_bcmPhase == HUB75Config::BCMPhaseEnd) {
        // Reset the BCM phase to the beginning
        _bcmPhase = HUB75Config::BCMPhaseStart;
        // Increment the row counter
        ++_rowCounter;
        if( _rowCounter > ( static_cast<uint8_t>(RowScanningMode) - 1 ) ) _rowCounter = 0;
        // Copy the pixel data to the row buffer - packed update
        __if_constexpr(Packed) {
            // Prepare the source and destination pointers
            const uint8_t* srcPtr = _rawByteBuffer + static_cast<uint16_t>(_rowCounter) * RowPixelCount * 2;
                  uint8_t* dstPtr = _rowBuffer;
            // Loop as many as the number of pixels in the row
            for(uint16_t i = 0; i < RowPixelCount; ++i) {
                // Get the source values
                const uint8_t v0 = *srcPtr++;
                const uint8_t v1 = *srcPtr++;
                // Unpack the values
                // Byte  0        1
                // Bit   76543210 76543210
                // Value GGGRRRRR XGGBBBBB
                const uint8_t r = (  v0       & 0b00011111 );
                const uint8_t g = ( (v0 >> 3) & 0b00011100 ) | ( (v1 >> 5) & 0b00000011 );
                const uint8_t b = (  v1       & 0b00011111 );
                // Store the values
                *dstPtr++ = r;
                *dstPtr++ = g;
                *dstPtr++ = b;
            }
        }
        // Copy the pixel data to the row buffer - normal update
        else {
            uint8_t* dstPtr = _rowBuffer;
            for(uint8_t div = 0; div < PixelAddressMapperType::rowDivisonCount(); ++div) {
                // Get the source pointer and step
                const     uint8_t* srcPtr = PixelAddressMapperType::getStartDivAddress(_rawByteBuffer, _rowCounter, div);
                constexpr int8_t   srcStp = PixelAddressMapperType::divAddressStep();
                // Loop as many as the number of pixels in the division
                for(uint16_t idx = 0; idx < PixelAddressMapperType::divPixelCount(); ++idx) {
                    // Get the values
                    const uint8_t r = *srcPtr >> HUB75Config::BCM_CCShiftRightAmount; srcPtr += srcStp;
                    const uint8_t g = *srcPtr >> HUB75Config::BCM_CCShiftRightAmount; srcPtr += srcStp;
                    const uint8_t b = *srcPtr >> HUB75Config::BCM_CCShiftRightAmount; srcPtr += srcStp;
                    // Perform color correction and store the values
                    *dstPtr++ = HUB75Config::BCM_CCLUT_MSB3_O5[r];
                    *dstPtr++ = HUB75Config::BCM_CCLUT_MSB3_O5[g];
                    *dstPtr++ = HUB75Config::BCM_CCLUT_MSB3_O5[b];
                }
            }
        }
    }

    // Reset the BCM counter
    _bcmCounter = _bcmPhase;

    // Output the address
    _addrPin.writeAddress(_rowCounter);

    // Output the bit values
    SendDataType::template writeDataBits< !!ExtProcedureType::LatchCommandEndPulseCount >(
        _rowBuffer, _bcmPhase, _ctlPin, _dataPin, ExtProcedureType::LatchCommandEndPulseCount
    );

    // Assert the latch pin as needed
    __if_constexpr( !ExtProcedureType::LatchCommandEndPulseCount ) {
                                                         asmNOP<ClockDelay0a>();
        _ctlPin.le.digitalWrite(ControlPinType::LatchH); asmNOP<ClockDelay1 >();
    }

    // Deassert the latch pin
    _ctlPin.le.digitalWrite(ControlPinType::LatchL); asmNOP<ClockDelay0b>();

    // Turn on the LEDs
    _ctlPin.oe.digitalWrite(ControlPinType::OutEnH);
}


template <GH75_TSPEC>
inline void GenericHUB75Driver<GH75_TDECL>::update() noexcept
{ _update<false>(); }


template <GH75_TSPEC>
inline void GenericHUB75Driver<GH75_TDECL>::packedUpdate() noexcept
{ _update<true>(); }


//
// Undefine the convenience macros
//
#undef GH75_TSPEC
#undef GH75_TDECL


#endif
