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


#ifndef ECXX_CTL_COLOR_FX_1D_H
#define ECXX_CTL_COLOR_FX_1D_H


#include "ColorBufferPolicy.h"


namespace eCxx {


//
// Forward declaration of the 1-dimensional animator class
//
template <uint8_t SegmentCountN, typename ColorFX1D_T>
class ColorAnimator1D;


//
// Helper structures for ColorFX1D<T> color buffer operations
//
template <typename ColorFX1D_T, typename BufferT, CBP::DUInt BufferElemCountN, typename BufferPolicyT, typename BufferElemCountT>
struct ColorFX1D_SRHelper;

template <typename ColorFX1D_T, typename BufferT, CBP::DUInt BufferElemCountN, typename BufferPolicyT, typename BufferElemCountT>
struct ColorFX1D_CMHelper;


//
// Helper classes for single and double buffering
//
template <typename BufferArrayT, bool UseDoubleBufferring>
class BufferArrayStruct;

template <typename BufferArrayT>
class BufferArrayStruct<BufferArrayT, false> {
    private:
        BufferArrayT _buff;
    public:
        inline void  flipBuffer               ()       {               }
        inline       BufferArrayT& backBuffer ()       { return _buff; }
        inline const BufferArrayT& backBuffer () const { return _buff; }
        inline       BufferArrayT& frontBuffer()       { return _buff; }
        inline const BufferArrayT& frontBuffer() const { return _buff; }
        inline       BufferArrayT& operator*  ()       { return _buff; }
        inline const BufferArrayT& operator*  () const { return _buff; }
};

template <typename BufferArrayT>
class BufferArrayStruct<BufferArrayT, true> {
    private:
        BufferArrayT  _buff[2];
        BufferArrayT* _bb = &_buff[0];
        BufferArrayT* _fb = &_buff[1];
    public:
        inline void  flipBuffer               ()       { eCxx::swap(_bb, _fb); }
        inline       BufferArrayT& backBuffer ()       { return *_bb;          }
        inline const BufferArrayT& backBuffer () const { return *_bb;          }
        inline       BufferArrayT& frontBuffer()       { return *_fb;          }
        inline const BufferArrayT& frontBuffer() const { return *_fb;          }
        inline       BufferArrayT& operator*  ()       { return backBuffer();  }
        inline const BufferArrayT& operator*  () const { return backBuffer();  }
};


//
// A simple 1-dimensional color array and effect class
//
// # Multibyte integers (16 bits or more) for color data that are stored in program memory must
//   be stored in little endian mode.
// # Color data in program memory must be stored in their standard/natural order, such as: HSV,
//   HSL, RGB, RGB+W, and X+RGB (not in BGR, W+RGB, etc.).
//
// # Depending on the hardware, when sending the data stream, it may also be necessary to reverse
//   the data stream direction by sending the last pixel data first and reversing the order of the
//   color elements (e.g. RGB to BGR, RGB+W to W+BGR etc.).
//
// # Template argument ColorPGMPtrT is used when reading color data from program memory, it must
//   be one of PGMCharPointer and PGMCharPointerFar.
//
template <
    typename   BufferColorT,
    CBP::DUInt BufferSizeN,
    typename   BufferPolicyT        = CBP::EncapsulatedBuffer,
    typename   BufferSizeT          = CBP::SUInt,
    bool       UseDoubleBufferring_ = false,
    typename   ColorPGMPtrT         = PGMCharPointer
>
class ColorFX1D {
    public:
        ECXX_CHECK_IF_TEMPLATE_ARG_IS_PGM_CHAR_POINTER(ColorPGMPtrT);

    private:
        using _CBSP = ColorBufferStoragePolicy<BufferColorT, BufferSizeT, BufferSizeN, BufferPolicyT>;

    private:
        using __ColorFX1D__ = void;

        template <uint8_t SegmentCountN, typename ColorFX1D_T>
        friend class ColorAnimator1D;

    public:
        using ColorPGMPointerType  = ColorPGMPtrT;
        using BufferPolicyType     = BufferPolicyT;

        using BufferColorType      = typename _CBSP::BufferColorType;
        using BufferSizeType       = typename _CBSP::BufferSizeType;
        using BufferByteSizeType   = typename _CBSP::BufferByteSizeType;

        using BufferArrayType      = typename _CBSP::BufferArrayType;
        using Iterator             = typename _CBSP::Iterator;
        using ConstIterator        = typename _CBSP::ConstIterator;

        using ReverseIterator      = eCxx::ReverseIterator<typename _CBSP::Iterator     >;
        using ConstReverseIterator = eCxx::ReverseIterator<typename _CBSP::ConstIterator>;

        static constexpr uint8_t            ColorByteSize       = _CBSP::ColorByteSize;
        static constexpr BufferSizeType     BufferElementCount  = _CBSP::BufferSize;
        static constexpr BufferByteSizeType BufferByteSize      = _CBSP::BufferByteSize;

        static constexpr bool               UseDoubleBufferring = UseDoubleBufferring_;

    private:
        using _SRHelper = ColorFX1D_SRHelper<ColorFX1D, BufferArrayType, BufferElementCount, BufferPolicyType, BufferSizeType>;
        using _CMHelper = ColorFX1D_CMHelper<ColorFX1D, BufferArrayType, BufferElementCount, BufferPolicyType, BufferSizeType>;

    public:
        inline ColorFX1D() = default;
        inline ~ColorFX1D() = default;

        inline ColorFX1D(const ColorFX1D&) = default;
        inline ColorFX1D(ColorFX1D&&) = delete;

        inline ColorFX1D& operator=(const ColorFX1D&) = default;
        inline ColorFX1D& operator=(ColorFX1D&&) = delete;

    public:
        // Flip (swap) the front buffer and back buffer (only meaningful if double bufferring is enabled)
        inline void flipBuffer();

        // Return a constant pointer/iterator to the color front buffer data; the data type of the returned
        // values depends on the value passed to the template argument BufferPolicyT:
        //     # RawBuffer          : return 'const uint8_t*'      with (BufferSizeN * BufferColorT::RawByteSize) bytes
        //     # EncapsulatedBuffer : return 'const BufferColorT*' with (BufferSizeN                            ) elements
        inline ConstIterator colorFrontBufferData() const;

        inline ConstReverseIterator rcolorFrontBufferData() const;

        // Return a constant pointer/iterator to the color back buffer data; the data type of the returned
        // values depends on the value passed to the template argument BufferPolicyT:
        //     # RawBuffer          : return 'const uint8_t*'      with (BufferSizeN * BufferColorT::RawByteSize) bytes
        //     # EncapsulatedBuffer : return 'const BufferColorT*' with (BufferSizeN                            ) elements
        inline ConstIterator colorBufferData() const; // This function is a synonym for cbegin().

        inline ConstReverseIterator rcolorBufferData() const; // This function is a synonym for crbegin().

        // Iterators; the data type of the returned values depends on the value passed to the template
        // argument BufferPolicyT:
        //     # RawBuffer          : return 'uint8_t*'      with (BufferSizeN * BufferColorT::RawByteSize) bytes
        //     # EncapsulatedBuffer : return 'BufferColorT*' with (BufferSizeN                            ) elements
        inline Iterator begin();

        inline Iterator end();

        inline ReverseIterator rbegin();

        inline ReverseIterator rend();

        // Constant iterators; the data type of the returned values depends on the value passed to the template
        // argument BufferPolicyT:
        //     # RawBuffer          : return 'const uint8_t*'      with (BufferSizeN * BufferColorT::RawByteSize) bytes
        //     # EncapsulatedBuffer : return 'const BufferColorT*' with (BufferSizeN                            ) elements
        inline ConstIterator cbegin() const;

        inline ConstIterator cend() const;

        inline ConstReverseIterator crbegin() const;

        inline ConstReverseIterator crend() const;

        // Clear all with zeroes
        inline void clear();

        // Get one color
        //     # This function will always return a 'const BufferColorT'
        inline const BufferColorType get(BufferSizeType index) const;

        // Set all colors
        template <typename ColorT>
        inline void set(const ColorT& color);

        template <typename ColorT>
        inline void set(const ColorT* colors);

        template <typename ColorT>
        inline void set(ColorPGMPointerType colors);

        // Set one color
        template <typename ColorT>
        inline void set(BufferSizeType index, const ColorT& color);

        template <typename ColorT>
        inline void set(BufferSizeType index, ColorPGMPointerType color);

        // Put a strip of colors
        template <typename BufferColorT_, CBP::DUInt BufferSizeN_, typename BufferPolicyT_, typename BufferSizeT_>
        inline void put(CBP::DSInt index, const ColorFX1D<BufferColorT_, BufferSizeN_, BufferPolicyT_, BufferSizeT_>& strip);

        // Linear interpolation between two colors
        template <uint8_t ChannelIndexes, typename ColorT>
        inline void lerp(const ColorT& from, const ColorT& to, uint8_t progressPercent);

        template <typename ColorT>
        inline void lerp(const ColorT& from, const ColorT& to, uint8_t progressPercent);

        // Linear interpolation between one source color to destination colors
        template <uint8_t ChannelIndexes, typename ColorT> inline
        void lerp(const ColorT& from, const ColorT* to, uint8_t progressPercent);

        template <typename ColorT>
        inline void lerp(const ColorT& from, const ColorT* to, uint8_t progressPercent);

        template <uint8_t ChannelIndexes, typename ColorT>
        inline void lerp(const ColorT& from, ColorPGMPointerType to, uint8_t progressPercent);

        template <typename ColorT>
        inline void lerp(const ColorT& from, ColorPGMPointerType to, uint8_t progressPercent);

        // Linear interpolation between source colors to one destination color
        template <uint8_t ChannelIndexes, typename ColorT>
        inline void lerp(const ColorT* from, const ColorT& to, uint8_t progressPercent);

        template <typename ColorT> inline
        void lerp(const ColorT* from, const ColorT& to, uint8_t progressPercent);

        template <uint8_t ChannelIndexes, typename ColorT>
        inline void lerp(ColorPGMPointerType from, const ColorT& to, uint8_t progressPercent);

        template <typename ColorT>
        inline void lerp(ColorPGMPointerType from, const ColorT& to, uint8_t progressPercent);

        // Linear interpolation between source colors to destination colors
        template <uint8_t ChannelIndexes, typename ColorT>
        inline void lerp(const ColorT* from, const ColorT* to, uint8_t progressPercent);

        template <typename ColorT> inline
        void lerp(const ColorT* from, const ColorT* to, uint8_t progressPercent);

        template <uint8_t ChannelIndexes, typename ColorT>
        inline void lerp(ColorPGMPointerType from, const ColorT* to, uint8_t progressPercent);

        template <typename ColorT> inline
        void lerp(ColorPGMPointerType from, const ColorT* to, uint8_t progressPercent);

        template <uint8_t ChannelIndexes, typename ColorT>
        inline void lerp(const ColorT* from, ColorPGMPointerType to, uint8_t progressPercent);

        template <typename ColorT> inline
        void lerp(const ColorT* from, ColorPGMPointerType to, uint8_t progressPercent);

        template <uint8_t ChannelIndexes, typename ColorT>
        inline void lerp(ColorPGMPointerType from, ColorPGMPointerType to, uint8_t progressPercent);

        template <typename ColorT>
        inline void lerp(ColorPGMPointerType from, ColorPGMPointerType to, uint8_t progressPercent);

        // Generate gradient by using linear interpolation between two colors
        template <uint8_t ChannelIndexes, typename ColorT>
        inline void lerpG(const ColorT& from, const ColorT& to, BufferSizeType e1 = 0, BufferSizeType e2 = BufferElementCount - 1);

        template <typename ColorT>
        inline void lerpG(const ColorT& from, const ColorT& to, BufferSizeType e1 = 0, BufferSizeType e2 = BufferElementCount - 1);

        // Shift and rotate colors
        template <typename ColorT>
        inline void shiftRight(const ColorT& fillColor);

        template <typename ColorT>
        inline void shiftLeft(const ColorT& fillColor);

        inline void rotateRight();

        inline void rotateLeft();

        // Adjust colors
        //     # Adjust (add/substract) the colors by the given adjustment factor.
        template <uint8_t ChannelIndexes>
        inline void adjust(int8_t adjustmentFactor);

        inline void adjust(int8_t adjustmentFactor);

        // Adjust colors (16-bit adjustment factor)
        //     # Adjust (add/substract) the colors by the given adjustment factor.
        //     # These functions can only be used if:
        //           1. the template argument BufferPolicy is CBP::RawBuffer; or
        //           2. the template argument BufferColorT is the K variant.
        template <uint8_t ChannelIndexes>
        inline void adjust16(int16_t adjustmentFactor);

        inline void adjust16(int16_t adjustmentFactor);

        // Scale colors
        //     # Scale values of   0 to 127 means multiply the colors by 0.0 to 1.0.
        //     # Scale values of 128 to 255 means multiply the colors by 1.0 to 2.0.
        template <uint8_t ChannelIndexes>
        inline void scale(uint8_t scaleFactor);

        inline void scale(uint8_t scaleFactor);

        // Perform color correction using CIE-1931 lightness to approximate the response of the human eye
        //     # The input  values will be right-shifted as necessary to fit the index space of the selected LUT.
        //     # The output values will be right-aligned.
        //     # These functions can only be used if:
        //           1. the template argument BufferPolicy is CBP::RawBuffer; or
        //           2. the template argument BufferColorT is one of the RGB, W+RGB, X+RGB, RGB+W, RGB+X, and W variants.
        template <uint8_t N>
        inline void cie1931lcc(CIE1931LVal (&lut)[N]);

        inline void cie1931lcc23() { cie1931lcc(CIE1931L_MSB2_O3); }
        inline void cie1931lcc24() { cie1931lcc(CIE1931L_MSB2_O4); }
        inline void cie1931lcc25() { cie1931lcc(CIE1931L_MSB2_O5); }
        inline void cie1931lcc26() { cie1931lcc(CIE1931L_MSB2_O6); }
        inline void cie1931lcc27() { cie1931lcc(CIE1931L_MSB2_O7); }
        inline void cie1931lcc28() { cie1931lcc(CIE1931L_MSB2_O8); }

        inline void cie1931lcc35() { cie1931lcc(CIE1931L_MSB3_O5); }
        inline void cie1931lcc36() { cie1931lcc(CIE1931L_MSB3_O6); }
        inline void cie1931lcc37() { cie1931lcc(CIE1931L_MSB3_O7); }
        inline void cie1931lcc38() { cie1931lcc(CIE1931L_MSB3_O8); }

        inline void cie1931lcc47() { cie1931lcc(CIE1931L_MSB4_O7); }
        inline void cie1931lcc48() { cie1931lcc(CIE1931L_MSB4_O8); }

        inline void cie1931lcc58() { cie1931lcc(CIE1931L_MSB5_O8); }

        // Perform color correction using CIE-1931 lightness to approximate the response of the human eye
        //     # The input values will be directly used as indexes to read values from the selected LUT (unlike
        //       the above functions, there will be no right-shifting).
        //     # The output values will be right-aligned.
        //     # These functions can only be used if:
        //           1. the template argument BufferPolicy is CBP::RawBuffer; or
        //           2. the template argument BufferColorT is one of the RGB, W+RGB, X+RGB, RGB+W, RGB+X, and W variants.
        template <uint8_t N>
        inline void cie1931lut(CIE1931LVal (&lut)[N]);

        inline void cie1931lut23() { cie1931lut(CIE1931L_MSB2_O3); }
        inline void cie1931lut24() { cie1931lut(CIE1931L_MSB2_O4); }
        inline void cie1931lut25() { cie1931lut(CIE1931L_MSB2_O5); }
        inline void cie1931lut26() { cie1931lut(CIE1931L_MSB2_O6); }
        inline void cie1931lut27() { cie1931lut(CIE1931L_MSB2_O7); }
        inline void cie1931lut28() { cie1931lut(CIE1931L_MSB2_O8); }

        inline void cie1931lut35() { cie1931lut(CIE1931L_MSB3_O5); }
        inline void cie1931lut36() { cie1931lut(CIE1931L_MSB3_O6); }
        inline void cie1931lut37() { cie1931lut(CIE1931L_MSB3_O7); }
        inline void cie1931lut38() { cie1931lut(CIE1931L_MSB3_O8); }

        inline void cie1931lut47() { cie1931lut(CIE1931L_MSB4_O7); }
        inline void cie1931lut48() { cie1931lut(CIE1931L_MSB4_O8); }

        inline void cie1931lut58() { cie1931lut(CIE1931L_MSB5_O8); }

    private:
        BufferArrayStruct<BufferArrayType, UseDoubleBufferring> _buff;
};


} // namespace eCxx


//
// Include the implementation file
//
#define INCLUDE_ECXX_CTL_GFX_COLOR_FX_1D_TPP
#include "ColorFX1D.tpp"


#endif
