/*
 * 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_SPI_LED_DRIVER_TPP
#error "this file cannot be included directly"
#endif
#undef INCLUDE_ECXX_PLATFORM_GENERIC_SPI_LED_DRIVER_TPP


#ifndef ECXX_PLATFORM_GENERIC_SPI_LED_DRIVER_TPP
#define ECXX_PLATFORM_GENERIC_SPI_LED_DRIVER_TPP


//
// A simple generic, SPI-like, addressable digital LED driver
//
template <uint16_t LEDCountN, uint8_t PinDataN, uint8_t PinClockN, uint32_t ClockFrequencyN, typename ColorFX_T, template<uint16_t> class FrameFormatT>
inline GenericSPILEDDriver<LEDCountN, PinDataN, PinClockN, ClockFrequencyN, ColorFX_T, FrameFormatT>
::GenericSPILEDDriver()
: _rawByteBuffer(nullptr)
{}


template <uint16_t LEDCountN, uint8_t PinDataN, uint8_t PinClockN, uint32_t ClockFrequencyN, typename ColorFX_T, template<uint16_t> class FrameFormatT>
inline GenericSPILEDDriver<LEDCountN, PinDataN, PinClockN, ClockFrequencyN, ColorFX_T, FrameFormatT>
::~GenericSPILEDDriver()
{ if(_rawByteBuffer) end(); }


template <uint16_t LEDCountN, uint8_t PinDataN, uint8_t PinClockN, uint32_t ClockFrequencyN, typename ColorFX_T, template<uint16_t> class FrameFormatT>
inline bool GenericSPILEDDriver<LEDCountN, PinDataN, PinClockN, ClockFrequencyN, ColorFX_T, FrameFormatT>
::begin(const uint8_t* rawByteBuffer)
{
    // Check if already initialized
    if(_rawByteBuffer) return false;

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

    // Set pin modes to output and set them to low
    _pinData .pinModeOutput(); _pinData .digitalWrite(false);
    _pinClock.pinModeOutput(); _pinClock.digitalWrite(false);

    // Done
    return true;
}


template <uint16_t LEDCountN, uint8_t PinDataN, uint8_t PinClockN, uint32_t ClockFrequencyN, typename ColorFX_T, template<uint16_t> class FrameFormatT>
inline bool GenericSPILEDDriver<LEDCountN, PinDataN, PinClockN, ClockFrequencyN, ColorFX_T, FrameFormatT>
::begin(const ColorFXType& colorFX)
{ return begin( colorFX.colorBufferData() ); }


template <uint16_t LEDCountN, uint8_t PinDataN, uint8_t PinClockN, uint32_t ClockFrequencyN, typename ColorFX_T, template<uint16_t> class FrameFormatT>
inline void GenericSPILEDDriver<LEDCountN, PinDataN, PinClockN, ClockFrequencyN, ColorFX_T, FrameFormatT>
::end()
{
    // Check if it is not yet initialized
    if(!_rawByteBuffer) return;

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

    // Set pin modes to input without pull-up
    _pinData .pinModeInput(false);
    _pinClock.pinModeInput(false);
}


template <uint16_t LEDCountN, uint8_t PinDataN, uint8_t PinClockN, uint32_t ClockFrequencyN, typename ColorFX_T, template<uint16_t> class FrameFormatT>
inline void GenericSPILEDDriver<LEDCountN, PinDataN, PinClockN, ClockFrequencyN, ColorFX_T, FrameFormatT>
::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 <uint16_t LEDCountN, uint8_t PinDataN, uint8_t PinClockN, uint32_t ClockFrequencyN, typename ColorFX_T, template<uint16_t> class FrameFormatT>
inline void GenericSPILEDDriver<LEDCountN, PinDataN, PinClockN, ClockFrequencyN, ColorFX_T, FrameFormatT>
::_sendBit(bool value) const
{
    _pinData .digitalWrite(value); asmNOP<ClockDelay0a>();
    _pinClock.digitalWrite(true ); asmNOP<ClockDelay1 >();
    _pinClock.digitalWrite(false); asmNOP<ClockDelay0b>();
}


template <uint16_t LEDCountN, uint8_t PinDataN, uint8_t PinClockN, uint32_t ClockFrequencyN, typename ColorFX_T, template<uint16_t> class FrameFormatT>
inline void GenericSPILEDDriver<LEDCountN, PinDataN, PinClockN, ClockFrequencyN, ColorFX_T, FrameFormatT>
::_sendByte(uint8_t value) const
{
    _sendBit(value & 0b10000000);
    _sendBit(value & 0b01000000);
    _sendBit(value & 0b00100000);
    _sendBit(value & 0b00010000);
    _sendBit(value & 0b00001000);
    _sendBit(value & 0b00000100);
    _sendBit(value & 0b00000010);
    _sendBit(value & 0b00000001);
}


template <uint16_t LEDCountN, uint8_t PinDataN, uint8_t PinClockN, uint32_t ClockFrequencyN, typename ColorFX_T, template<uint16_t> class FrameFormatT>
inline void GenericSPILEDDriver<LEDCountN, PinDataN, PinClockN, ClockFrequencyN, ColorFX_T, FrameFormatT>
::update()
{
    // Check if it is not yet initialized
    if(!_rawByteBuffer) return;

    // A helper functor for sending a byte
    struct SendByte {
        const GenericSPILEDDriver* const _driver;

        inline __ECXX_ATTR_ALWAYS_INLINE SendByte(const GenericSPILEDDriver* const driver)
        : _driver(driver)
        {}

        inline __ECXX_ATTR_ALWAYS_INLINE void operator() (uint8_t value) const
        {
            // Send a high start bit as needed
            __if_constexpr(DisplayFrameFormatType::ChannelStartBit1) _driver->_sendBit(true);
            // Send the byte
            _driver->_sendByte(value);
        }
    };

    // Yield immediately for safety
    Application::yield(true);

    // Perform pre-frame wait delay as needed
    __if_constexpr(DisplayFrameFormatType::PreWait_uS) {
        Application::delayUS(DisplayFrameFormatType::PreWait_uS);
    }

    // Send the start frame data as needed
    __if_constexpr(DisplayFrameFormatType::StartFrameCount) {
        for(uint8_t i = 0; i < DisplayFrameFormatType::StartFrameCount; ++i) {
            _sendByte(DisplayFrameFormatType::StartFrameValue);
        }
    }

    // Send the frame data - with pixel transformation
    __if_constexpr(DisplayFrameFormatType::TransformPixel) {
        // Instantiate the functor for sending a byte
        const SendByte sendByte(this);
        // Copy the buffer pointer for iteration
        const uint8_t* ptr = _rawByteBuffer;
        // Prepare a buffer for transformation
        uint8_t buf[DisplayFrameFormatType::PixelTransformedByteSize];
        // Send the bytes
        for(uint16_t i = 0; i < LEDCountN; ++i) {
            // Transform the bytes
            DisplayFrameFormatType::transformPixel(buf, ptr);
            ptr += DisplayFrameFormatType::PixelByteSize;
            // Send the transformed bytes
            eCxx::processArray<DisplayFrameFormatType::PixelTransformedByteSize>(buf, sendByte);
        }
    }

    // Send the frame data - with channel transformation
    else __if_constexpr(DisplayFrameFormatType::TransformChannel) {
        // Copy the buffer pointer for iteration
        const uint8_t* ptr = _rawByteBuffer;
        // Send the bytes
        for(uint16_t i = 0; i < LEDCountN * static_cast<uint16_t>(DisplayFrameFormatType::PixelByteSize); ++i) {
            // Send a high start bit as needed
            __if_constexpr(DisplayFrameFormatType::ChannelStartBit1) _sendBit(true);
            // Send the transformed byte
            _sendByte( DisplayFrameFormatType::transformChannel(*ptr++) );
        }
    }
    // Send the frame data - without transformation
    else {
        // Copy the buffer pointer for iteration
        const uint8_t* ptr = _rawByteBuffer;
        // Send the bytes
        for(uint16_t i = 0; i < LEDCountN * static_cast<uint16_t>(DisplayFrameFormatType::PixelByteSize); ++i) {
            // Send a high start bit as needed
            __if_constexpr(DisplayFrameFormatType::ChannelStartBit1) _sendBit(true);
            // Send the byte
            _sendByte(*ptr++);
        }
    }

    // Send the end frame data as needed
    __if_constexpr(DisplayFrameFormatType::EndFrameCount) {
        for(uint8_t i = 0; i < DisplayFrameFormatType::EndFrameCount; ++i) {
            _sendByte(DisplayFrameFormatType::EndFrameValue);
        }
    }

    // Send the extra end frame data as needed
    __if_constexpr(DisplayFrameFormatType::EndFrameExtraCount) {
        for(uint8_t i = 0; i < DisplayFrameFormatType::EndFrameExtraCount; ++i) {
            _sendByte(DisplayFrameFormatType::EndFrameExtraValue);
        }
    }

    // Send the extra end frame low bits as needed
    __if_constexpr(DisplayFrameFormatType::EndFrameExtraBit0Count) {
        for(uint8_t i = 0; i < DisplayFrameFormatType::EndFrameExtraBit0Count; ++i) {
            _sendBit(false);
        }
    }
}


#endif
