/*
 * 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_SPI_LED_DRIVER_H
#define ECXX_PLATFORM_GENERIC_SPI_LED_DRIVER_H


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

#include "GenericSPILEDDriverDFF.h"


namespace eCxx {


//
// A simple generic, SPI-like, addressable digital LED 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
 *   data and clock lines.
 *
 * # Automatic DisplayFrameFormatT deduction from template argument substituting ColorFX_T::BufferColorType
 *   is not always possible; manually specifying DisplayFrameFormatT may be required.
 *
 * # The raw byte buffer must match the pixel frame format of the addressable digital LED.
 *
 * # There is no need to call Application::setPinMode() manually because this driver does it automatically.
 */
template <
    uint16_t                 LEDCountN,
    uint8_t                  PinDataN,
    uint8_t                  PinClockN,
    uint32_t                 ClockFrequencyN,
    typename                 ColorFX_T,
    template<uint16_t> class DisplayFrameFormatT = DFFAutoSelector<ColorFX_T>::template Type
>
class GenericSPILEDDriver : public Uncopyable {
    static_assert(
        ClockFrequencyN >= 1000000UL,
        "template argument substituting ClockFrequencyN is smaller than 1000000"
    );

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

    static_assert(
        ColorFX_T::ColorByteSize == DisplayFrameFormatT<LEDCountN>::PixelByteSize,
        "ColorFX_T::ColorByteSize is not equal to DisplayFrameFormatT<LEDCountN>::PixelByteSize"
    );

    public:
        static constexpr uint16_t MaximumNumberOfLEDs = 320;

        static_assert(
            LEDCountN <= MaximumNumberOfLEDs,
            "template argument substituting LEDCountN is larger than 320"
        );

    public:
        using ColorFXType            = ColorFX_T;
        using DisplayFrameFormatType = DisplayFrameFormatT<LEDCountN>;

        static constexpr uint16_t LEDCount       = LEDCountN;
        static constexpr uint8_t  PinData        = PinDataN;
        static constexpr uint8_t  PinClock       = PinClockN;
        static constexpr uint32_t ClockFrequency = ClockFrequencyN;

    public:
        inline GenericSPILEDDriver();
        inline ~GenericSPILEDDriver();

        inline bool begin(const uint8_t* rawByteBuffer);
        inline bool begin(const ColorFXType& colorFX);
        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);

        inline void update();

    private:
        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 ClockDelay0a     = (ClockLPeriodHalf > 1) ? (ClockLPeriodHalf - 1) : 0;
        static constexpr uint8_t ClockDelay0b     = (ClockLPeriodHalf > 5) ? (ClockLPeriodHalf - 5) : 0;
        static constexpr uint8_t ClockDelay1      = (ClockHPeriod     > 1) ? (ClockHPeriod     - 1) : 0;

    private:
        const PinDDX<PinData > _pinData;
        const PinDDX<PinClock> _pinClock;

        const uint8_t*         _rawByteBuffer;

        inline void _sendBit(bool value) const __ECXX_ATTR_ALWAYS_INLINE;
        inline void _sendByte(uint8_t value) const __ECXX_ATTR_ALWAYS_INLINE;
};


//
// Include the implementation file
//
#define INCLUDE_ECXX_PLATFORM_GENERIC_SPI_LED_DRIVER_TPP
#include "GenericSPILEDDriver.tpp"


} // namespace eCxx


#endif
