/*
 * 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_DISPLAY_FRAME_FORMAT_H
#define ECXX_PLATFORM_GENERIC_SPI_LED_DRIVER_DISPLAY_FRAME_FORMAT_H


namespace eCxx {


//
// Display frame format for various addressable digital LEDs
//
/*
 * All implementations of display frame format structure must have these member constants defined:
 *
 *     static constexpr uint8_t  PixelByteSize;
 *     static constexpr uint8_t  PixelTransformedByteSize;
 *
 *     static constexpr uint16_t PreWait_uS;
 *
 *     static constexpr uint8_t  StartFrameValue;
 *     static constexpr uint8_t  StartFrameCount;
 *
 *     static constexpr uint8_t  EndFrameValue;
 *     static constexpr uint8_t  EndFrameCount;
 *
 *     static constexpr uint8_t  EndFrameExtraValue;
 *     static constexpr uint8_t  EndFrameExtraCount;
 *     static constexpr uint8_t  EndFrameExtraBit0Count;
 *
 *     static constexpr bool     ChannelStartBit1;
 *
 *     static constexpr bool     TransformPixel;
 *     static constexpr bool     TransformChannel;
 *
 *
 * All implementations of display frame format structure must have these member functions defined:
 *
 *     static inline void transformPixel(uint8_t* out, const uint8_t* in);
 *     static inline uint8_t transformChannel(uint8_t in);
 *
 *
 * Functional description:
 *
 *     PixelByteSize
 *         + The number of bytes per pixel of the color buffer.
 *         + It must be equal to the ColorByteSize of the ColorFX1D<T> or ColorFX2D<T> instance
 *           that will be attached to the GenericSPILEDDriver<T> instance.
 *
 *     PixelTransformedByteSize
 *         + The number of bytes of the transformed pixel.
 *         + If the TransformPixel flag is set to true, set this size to the correct value;
 *           otherwise, set it to -1.
 *
 *     PreWait_uS
 *         + Some LED controllers need pre-frame wait delay instead of start frame values.
 *         + Set this to 0 if not needed; otherwise, set it to the correct microseconds.
 *
 *     StartFrameValue
 *         + The special value to be sent as the start frame data.
 *
 *     StartFrameCount
 *         + How many times the above value (StartFrameValue) must be sent.
 *         + Set this to 0 if not needed; otherwise, set it to the correct number.
 *
 *     EndFrameValue
 *         + The special value to be sent as the end frame data.
 *
 *     EndFrameCount
 *         + How many times the above value (EndFrameValue) must be sent.
 *         + Set this to 0 if not needed; otherwise, set it to the correct number.
 *
 *     EndFrameExtraValue
 *         + The special value to be sent as the extra end frame data.
 *
 *     EndFrameExtraCount
 *         + How many times the above value (EndFrameExtraValue) must be sent.
 *         + Set this to 0 if not needed; otherwise, set it to the correct number.
 *
 *     EndFrameExtraBit0Count
 *         + How many extra low bit(s) must be sent as the extra end frame data.
 *         + Set this to 0 if not needed; otherwise, set it to the correct number.
 *
 *     ChannelStartBit1
 *         + Some LED controllers need a high start bit for every channel data.
 *         + Set this flag to true to send a high start bit; otherwise, set it to false.
 *
 *     TransformPixel
 *         + Set this flag to true to transform the pixel data before sending; otherwise, set it to false.
 *         + This flag takes precedence over the TransformChannel flag.
 *         + If this flag is set to true, then the function transformPixel() must be properly defined;
 *           otherwise, simply make the function no-op.
 *
 *     TransformChannel
 *         + Set this flag to true to transform the channel value before sending; otherwise, set it to false.
 *         + If this flag is set to true, then the function transformChannel() must be properly defined;
 *           otherwise, simply make the function no-op.
 */
template <uint16_t LEDCountN>
struct DisplayFrameFormatAPA102X : public Uninstantiable {
    static constexpr uint8_t  PixelByteSize            =  4;
    static constexpr uint8_t  PixelTransformedByteSize = -1;

    static constexpr uint16_t PreWait_uS               = 0;

    static constexpr uint8_t  StartFrameValue          = 0x00;
    static constexpr uint8_t  StartFrameCount          = 4;

    static constexpr uint8_t  EndFrameValue            = 0x00;
    static constexpr uint8_t  EndFrameCount            = 4;

    static constexpr uint8_t  EndFrameExtraValue       = 0x00;
    static constexpr uint8_t  EndFrameExtraCount       = (LEDCountN / 2 + 3) & ~3;
    static constexpr uint8_t  EndFrameExtraBit0Count   = 0;

    static constexpr bool     ChannelStartBit1         = false;

    static constexpr bool     TransformPixel           = false;
    static constexpr bool     TransformChannel         = false;

    static inline void transformPixel(uint8_t*, const uint8_t*) {}
    static inline uint8_t transformChannel(uint8_t) { return 0; }
};

template <uint16_t LEDCountN>
using DisplayFrameFormatSK9822 = DisplayFrameFormatAPA102X<LEDCountN>;


template <uint16_t LEDCountN>
struct DisplayFrameFormatP9813BGR : public Uninstantiable {
    static constexpr uint8_t  PixelByteSize            =  4;
    static constexpr uint8_t  PixelTransformedByteSize = -1;

    static constexpr uint16_t PreWait_uS               = 0;

    static constexpr uint8_t  StartFrameValue          = 0x00;
    static constexpr uint8_t  StartFrameCount          = 4;

    static constexpr uint8_t  EndFrameValue            = 0x00;
    static constexpr uint8_t  EndFrameCount            = 4;

    static constexpr uint8_t  EndFrameExtraValue       = 0x00;
    static constexpr uint8_t  EndFrameExtraCount       = 0;
    static constexpr uint8_t  EndFrameExtraBit0Count   = 0;

    static constexpr bool     ChannelStartBit1         = false;

    static constexpr bool     TransformPixel           = false;
    static constexpr bool     TransformChannel         = false;

    static inline void transformPixel(uint8_t*, const uint8_t*) {}
    static inline uint8_t transformChannel(uint8_t) { return 0; }
};


template <uint16_t LEDCountN>
struct DisplayFrameFormatLPD8806 : public Uninstantiable {
    static constexpr uint8_t  PixelByteSize            =  3;
    static constexpr uint8_t  PixelTransformedByteSize = -1;

    static constexpr uint16_t PreWait_uS               = 0;

    static constexpr uint8_t  StartFrameValue          = 0x00;
    static constexpr uint8_t  StartFrameCount          = (LEDCountN + 31) / 32;

    static constexpr uint8_t  EndFrameValue            = 0xFF;
    static constexpr uint8_t  EndFrameCount            = (LEDCountN + 31) / 32;

    static constexpr uint8_t  EndFrameExtraValue       = 0x00;
    static constexpr uint8_t  EndFrameExtraCount       = 0;
    static constexpr uint8_t  EndFrameExtraBit0Count   = 0;

    static constexpr bool     ChannelStartBit1         = false;

    static constexpr bool     TransformPixel           = false;
    static constexpr bool     TransformChannel         = true;

    static inline void transformPixel(uint8_t*, const uint8_t*)
    {}

    static inline uint8_t transformChannel(uint8_t in)
    { return (in >> 1) | 0x80; }
};


template <uint16_t LEDCountN>
struct DisplayFrameFormatLPD6803 : public Uninstantiable {
    static constexpr uint8_t  PixelByteSize            = 3;
    static constexpr uint8_t  PixelTransformedByteSize = 2;

    static constexpr uint16_t PreWait_uS               = 0;

    static constexpr uint8_t  StartFrameValue          = 0x00;
    static constexpr uint8_t  StartFrameCount          = 4;

    static constexpr uint8_t  EndFrameValue            = 0x00;
    static constexpr uint8_t  EndFrameCount            = 0;

    static constexpr uint8_t  EndFrameExtraValue       = 0x00;
    static constexpr uint8_t  EndFrameExtraCount       = 0;
    static constexpr uint8_t  EndFrameExtraBit0Count   = 0;

    static constexpr bool     ChannelStartBit1         = false;

    static constexpr bool     TransformPixel           = true;
    static constexpr bool     TransformChannel         = false;

    static inline void transformPixel(uint8_t* out, const uint8_t* in)
    {
        // Frame format : bit#       - FEDCBA9876543210
        //                allocation - 1XXXXXYYYYYZZZZZ

        // Byte #0  Byte #1
        // 76543210 76543210
        // 1XXXXXYY YYYZZZZZ

        out[0] = 0b10000000 | ( (in[0] & 0b11111000) >> 1 ) | ( in[1] >> 6 );
        out[1] =              ( (in[1] & 0b00111000) << 2 ) | ( in[2] >> 3 );
    }

    static inline uint8_t transformChannel(uint8_t)
    { return 0; }
};


template <uint16_t LEDCountN>
struct DisplayFrameFormatWS2801 : public Uninstantiable {
    static constexpr uint8_t  PixelByteSize            =  3;
    static constexpr uint8_t  PixelTransformedByteSize = -1;

    static constexpr uint16_t PreWait_uS               = 1000;

    static constexpr uint8_t  StartFrameValue          = 0x00;
    static constexpr uint8_t  StartFrameCount          = 0;

    static constexpr uint8_t  EndFrameValue            = 0x00;
    static constexpr uint8_t  EndFrameCount            = 0;

    static constexpr uint8_t  EndFrameExtraValue       = 0x00;
    static constexpr uint8_t  EndFrameExtraCount       = 0;
    static constexpr uint8_t  EndFrameExtraBit0Count   = 0;

    static constexpr bool     ChannelStartBit1         = false;

    static constexpr bool     TransformPixel           = false;
    static constexpr bool     TransformChannel         = false;

    static inline void transformPixel(uint8_t*, const uint8_t*) {}
    static inline uint8_t transformChannel(uint8_t) { return 0; }
};


template <uint16_t LEDCountN>
struct DisplayFrameFormatSM16716 : public Uninstantiable {
    static constexpr uint8_t  PixelByteSize            =  3;
    static constexpr uint8_t  PixelTransformedByteSize = -1;

    static constexpr uint16_t PreWait_uS               = 1000;

    static constexpr uint8_t  StartFrameValue          = 0x00;
    static constexpr uint8_t  StartFrameCount          = 0;

    static constexpr uint8_t  EndFrameValue            = 0x00;
    static constexpr uint8_t  EndFrameCount            = 6;

    static constexpr uint8_t  EndFrameExtraValue       = 0x00;
    static constexpr uint8_t  EndFrameExtraCount       = 0;
    static constexpr uint8_t  EndFrameExtraBit0Count   = 2;

    static constexpr bool     ChannelStartBit1         = true;

    static constexpr bool     TransformPixel           = false;
    static constexpr bool     TransformChannel         = false;

    static inline void transformPixel(uint8_t*, const uint8_t*) {}
    static inline uint8_t transformChannel(uint8_t) { return 0; }
};


//
// Auto selector for display frame format based on the ColorFX_T::BufferColorType's XPolicy
//
namespace internal {

    template <typename ColorXPolicyT>
    struct DFFAutoSelector {
        static_assert(
            !eCxx::isSame<ColorXPolicyT, bool>::Value,
            "there is no supported DisplayFrameFormatT for template argument substituting ColorFX_T::BufferColorType"
        );

        static_assert(
            !eCxx::isSame<ColorXPolicyT, void>::Value,
            "automatic DisplayFrameFormatT deduction from template argument substituting ColorFX_T::BufferColorType is not possible; manually specifying DisplayFrameFormatT is required"
        );
    };

    template <>
    struct DFFAutoSelector<APA102XPolicy> {
        template <uint16_t LEDCountN>
        using Type = DisplayFrameFormatAPA102X<LEDCountN>;
    };

    template <>
    struct DFFAutoSelector<P9813BGRXPolicy> {
        template <uint16_t LEDCountN>
        using Type = DisplayFrameFormatP9813BGR<LEDCountN>;
    };

} // namespace internal

template <typename ColorFX_T>
struct DFFAutoSelector : public Uninstantiable {
    template <uint16_t LEDCountN>
    using Type = typename internal::DFFAutoSelector<typename ColorFX_T::BufferColorType::XPolicy>::template Type<LEDCountN>;
};


} // namespace eCxx


#endif
