/*
 * 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_2D_H
#define ECXX_CTL_COLOR_FX_2D_H


#include "ColorFX1D.h"
#include "SpriteSheet2D.h"


namespace eCxx {


//
// Helper structure for ColorFX2D_T<T> color buffer operations
//
template <typename ColorFX2D_T, typename BufferT, CBP::SUInt XSizeN, CBP::SUInt YSizeN, typename LayoutPolicyT, typename BufferPolicyT, CBP::SUInt BlockXSizeN, CBP::SUInt BlockYSizeN>
struct ColorFX2D_CMHelper;


//
// A simple 2-dimensional color array and effect class (it is basically a simple image 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::SUInt XSizeN,
    CBP::SUInt YSizeN,
    typename   LayoutPolicyT        = CBP::DefaultLayout,
    typename   BufferPolicyT        = CBP::EncapsulatedBuffer,
    CBP::SUInt BlockXSizeN          = 0,
    CBP::SUInt BlockYSizeN          = 0,
    CBP::SUInt InterYSizeN          = 0,
    bool       UseDoubleBufferring_ = false,
    typename   ColorPGMPtrT         = PGMCharPointer
>
class ColorFX2D : public ColorFX1D<BufferColorT, XSizeN * YSizeN, BufferPolicyT, CBP::DUInt, UseDoubleBufferring_, ColorPGMPtrT> {
    private:
        using _CFX1D = ColorFX1D<BufferColorT, XSizeN * YSizeN, BufferPolicyT, CBP::DUInt, UseDoubleBufferring_, ColorPGMPtrT>;
        using _CBLZX = ColorBufferLayoutElementSizeExchanger<XSizeN, YSizeN, LayoutPolicyT>;
        using _CBLP  = ColorBufferLayoutPolicy<XSizeN, YSizeN, BlockXSizeN, BlockYSizeN, InterYSizeN, LayoutPolicyT>;

    private:
        using __ColorFX2D__ = void;

        template <uint8_t SegmentCountN, typename ColorFX2D_T>
        friend class ColorAnimator2D;

    public:
        using ColorPGMPointerType = typename _CFX1D::ColorPGMPointerType;
        using BufferPolicyType    = typename _CFX1D::BufferPolicyType;
        using LayoutPolicyType    = LayoutPolicyT;

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

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

        static constexpr CBP::SUInt         BlockXSize          = BlockXSizeN;
        static constexpr CBP::SUInt         BlockYSize          = BlockYSizeN;
        static constexpr CBP::SUInt         InterYSize          = InterYSizeN;

        static constexpr CBP::SUInt         XElementCount       = _CBLZX::XSize;
        static constexpr CBP::SUInt         YElementCount       = _CBLZX::YSize;

        static constexpr uint8_t            ColorByteSize       = _CFX1D::ColorByteSize;
        static constexpr BufferSizeType     BufferElementCount  = _CFX1D::BufferElementCount;
        static constexpr BufferByteSizeType BufferByteSize      = _CFX1D::BufferByteSize;

        static constexpr bool               UseDoubleBufferring = _CFX1D::UseDoubleBufferring;

    private:
        using _CMHelper = ColorFX2D_CMHelper<ColorFX2D, BufferArrayType, XElementCount, YElementCount, LayoutPolicyType, BufferPolicyType, BlockXSize, BlockYSize>;

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

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

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

    public:
        // Get one color
        //     # This function will always return a 'const BufferColorT'
        inline const BufferColorType getXY(CBP::SUInt x, CBP::SUInt y) const;

        // Set one color
        template <typename ColorT>
        inline void setXY(CBP::SUInt x, CBP::SUInt y, const ColorT& color);

        template <typename ColorT>
        inline void setXY(CBP::SUInt x, CBP::SUInt y, ColorPGMPointerType color);

        // Set all colors from program memory
        // # 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 CBP::DefaultLayout (CBP::StandardLayout/CBP::RowMajor)
        //   format.
        template <typename ColorT, typename ColorPGMPtrT_>
        inline void setXY(ColorPGMPtrT_ colors);

        // Get a strip of colors
        template <CBP::DUInt BufferSizeN_, typename BufferPolicyT_, typename BufferSizeT_>
        inline void getVStrip(CBP::DSInt x, CBP::DSInt y, ColorFX1D<BufferColorType, BufferSizeN_, BufferPolicyT_, BufferSizeT_>& strip);

        template <CBP::DUInt BufferSizeN_, typename BufferPolicyT_, typename BufferSizeT_>
        inline void getHStrip(CBP::DSInt x, CBP::DSInt y, ColorFX1D<BufferColorType, BufferSizeN_, BufferPolicyT_, BufferSizeT_>& strip);

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

        template <typename BufferColorT_, CBP::DUInt BufferSizeN_, typename BufferPolicyT_, typename BufferSizeT_>
        inline void putHStrip(CBP::DSInt x, CBP::DSInt y, const ColorFX1D<BufferColorT_, BufferSizeN_, BufferPolicyT_, BufferSizeT_>& strip);

        // Get a block of colors
        template <CBP::SUInt XSizeN_, CBP::SUInt YSizeN_, typename LayoutPolicyT_, typename BufferPolicyT_, CBP::SUInt BlockXSizeN_, CBP::SUInt BlockYSizeN_>
        inline void getXY(CBP::DSInt x, CBP::DSInt y, ColorFX2D<BufferColorType, XSizeN_, YSizeN_, LayoutPolicyT_, BufferPolicyT_, BlockXSizeN_, BlockYSizeN_>& block);

        // Put a block of colors
        template <typename BufferColorT_, CBP::SUInt XSizeN_, CBP::SUInt YSizeN_, typename LayoutPolicyT_, typename BufferPolicyT_, CBP::SUInt BlockXSizeN_, CBP::SUInt BlockYSizeN_>
        inline void putXY(CBP::DSInt x, CBP::DSInt y, const ColorFX2D<BufferColorT_, XSizeN_, YSizeN_, LayoutPolicyT_, BufferPolicyT_, BlockXSizeN_, BlockYSizeN_>& block);

        // Put a sprite from a sprite sheet
        template <typename ColorT, CBP::SUInt SpriteXCountN, CBP::SUInt SpriteYCountN, CBP::SUInt SpriteXSizeN, CBP::SUInt SpriteYSizeN, typename SSPGMPointerT>
        inline void putSpriteXY(CBP::DSInt x, CBP::DSInt y, const SpriteSheet2D<ColorT, SpriteXCountN, SpriteYCountN, SpriteXSizeN, SpriteYSizeN, SSPGMPointerT>& ss, CBP::DSInt xIndex, CBP::SUInt yIndex);

        template <typename ColorT, CBP::SUInt SpriteXCountN, CBP::SUInt SpriteYCountN, CBP::SUInt SpriteXSizeN, CBP::SUInt SpriteYSizeN, typename SSPGMPointerT>
        inline void putSpriteXY(CBP::DSInt x, CBP::DSInt y, const SpriteSheet2D<ColorT, SpriteXCountN, SpriteYCountN, SpriteXSizeN, SpriteYSizeN, SSPGMPointerT>& ss, CBP::DSInt xIndex, CBP::SUInt yIndex, const ColorT& transparentColor);

        // Generate vertical gradient by using linear interpolation between two strips
        template < uint8_t    ChannelIndexes,
                   typename   BufferColorT_,
                   CBP::DUInt BufferSize1N_, typename BufferPolicy1T_, typename BufferSize1T_,
                   CBP::DUInt BufferSize2N_, typename BufferPolicy2T_, typename BufferSize2T_ >
        inline void lerpGV(
            CBP::SUInt xFrom, const ColorFX1D<BufferColorT_, BufferSize1N_, BufferPolicy1T_, BufferSize1T_>& stripFrom, CBP::DUInt ofsFrom,
            CBP::SUInt xTo,   const ColorFX1D<BufferColorT_, BufferSize2N_, BufferPolicy2T_, BufferSize2T_>& stripTo,   CBP::DUInt ofsTo,
            CBP::SUInt y1 = 0,
            CBP::SUInt y2 = YElementCount - 1
        );

        template < uint8_t    ChannelIndexes,
                   typename   BufferColorT_,
                   CBP::DUInt BufferSize1N_, typename BufferPolicy1T_, typename BufferSize1T_,
                   CBP::DUInt BufferSize2N_, typename BufferPolicy2T_, typename BufferSize2T_ >
        inline void lerpGV(
            CBP::SUInt xFrom, const ColorFX1D<BufferColorT_, BufferSize1N_, BufferPolicy1T_, BufferSize1T_>& stripFrom,
            CBP::SUInt xTo,   const ColorFX1D<BufferColorT_, BufferSize2N_, BufferPolicy2T_, BufferSize2T_>& stripTo,
            CBP::SUInt y1 = 0,
            CBP::SUInt y2 = YElementCount - 1
        );

        template < typename   BufferColorT_,
                   CBP::DUInt BufferSize1N_, typename BufferPolicy1T_, typename BufferSize1T_,
                   CBP::DUInt BufferSize2N_, typename BufferPolicy2T_, typename BufferSize2T_ >
        inline void lerpGV(
            CBP::SUInt xFrom, const ColorFX1D<BufferColorT_, BufferSize1N_, BufferPolicy1T_, BufferSize1T_>& stripFrom, CBP::DUInt ofsFrom,
            CBP::SUInt xTo,   const ColorFX1D<BufferColorT_, BufferSize2N_, BufferPolicy2T_, BufferSize2T_>& stripTo,   CBP::DUInt ofsTo,
            CBP::SUInt y1 = 0,
            CBP::SUInt y2 = YElementCount - 1
        );

        template < typename   BufferColorT_,
                   CBP::DUInt BufferSize1N_, typename BufferPolicy1T_, typename BufferSize1T_,
                   CBP::DUInt BufferSize2N_, typename BufferPolicy2T_, typename BufferSize2T_ >
        inline void lerpGV(
            CBP::SUInt xFrom, const ColorFX1D<BufferColorT_, BufferSize1N_, BufferPolicy1T_, BufferSize1T_>& stripFrom,
            CBP::SUInt xTo,   const ColorFX1D<BufferColorT_, BufferSize2N_, BufferPolicy2T_, BufferSize2T_>& stripTo,
            CBP::SUInt y1 = 0,
            CBP::SUInt y2 = YElementCount - 1
        );

        // Generate horizontal gradient by using linear interpolation between two strips
        template < uint8_t    ChannelIndexes,
                   typename   BufferColorT_,
                   CBP::DUInt BufferSize1N_, typename BufferPolicy1T_, typename BufferSize1T_,
                   CBP::DUInt BufferSize2N_, typename BufferPolicy2T_, typename BufferSize2T_ >
        inline void lerpGH(
            CBP::SUInt yFrom, const ColorFX1D<BufferColorT_, BufferSize1N_, BufferPolicy1T_, BufferSize1T_>& stripFrom, CBP::DUInt ofsFrom,
            CBP::SUInt yTo,   const ColorFX1D<BufferColorT_, BufferSize2N_, BufferPolicy2T_, BufferSize2T_>& stripTo,   CBP::DUInt ofsTo,
            CBP::SUInt x1 = 0,
            CBP::SUInt x2 = XElementCount - 1
        );

        template < uint8_t    ChannelIndexes,
                   typename   BufferColorT_,
                   CBP::DUInt BufferSize1N_, typename BufferPolicy1T_, typename BufferSize1T_,
                   CBP::DUInt BufferSize2N_, typename BufferPolicy2T_, typename BufferSize2T_ >
        inline void lerpGH(
            CBP::SUInt yFrom, const ColorFX1D<BufferColorT_, BufferSize1N_, BufferPolicy1T_, BufferSize1T_>& stripFrom,
            CBP::SUInt yTo,   const ColorFX1D<BufferColorT_, BufferSize2N_, BufferPolicy2T_, BufferSize2T_>& stripTo,
            CBP::SUInt x1 = 0,
            CBP::SUInt x2 = XElementCount - 1
        );

        template < typename   BufferColorT_,
                   CBP::DUInt BufferSize1N_, typename BufferPolicy1T_, typename BufferSize1T_,
                   CBP::DUInt BufferSize2N_, typename BufferPolicy2T_, typename BufferSize2T_ >
        inline void lerpGH(
            CBP::SUInt yFrom, const ColorFX1D<BufferColorT_, BufferSize1N_, BufferPolicy1T_, BufferSize1T_>& stripFrom, CBP::DUInt ofsFrom,
            CBP::SUInt yTo,   const ColorFX1D<BufferColorT_, BufferSize2N_, BufferPolicy2T_, BufferSize2T_>& stripTo,   CBP::DUInt ofsTo,
            CBP::SUInt x1 = 0,
            CBP::SUInt x2 = XElementCount - 1
        );

        template < typename   BufferColorT_,
                   CBP::DUInt BufferSize1N_, typename BufferPolicy1T_, typename BufferSize1T_,
                   CBP::DUInt BufferSize2N_, typename BufferPolicy2T_, typename BufferSize2T_ >
        inline void lerpGH(
            CBP::SUInt yFrom, const ColorFX1D<BufferColorT_, BufferSize1N_, BufferPolicy1T_, BufferSize1T_>& stripFrom,
            CBP::SUInt yTo,   const ColorFX1D<BufferColorT_, BufferSize2N_, BufferPolicy2T_, BufferSize2T_>& stripTo,
            CBP::SUInt x1 = 0,
            CBP::SUInt x2 = XElementCount - 1
        );

        // Flip
        inline void flipHorizontal();

        inline void flipVertical();

        // Rotate 90°
        //     # These functions can only be used if the template argument XSizeN and YSizeN are equal.
        inline void rotate90CW();

        inline void rotate90CCW();

        // Rotate 180°
        inline void rotate180();

    private:
        inline void _transpose();
};


} // namespace eCxx


//
// Include the implementation file
//
#define INCLUDE_ECXX_CTL_GFX_COLOR_FX_2D_TPP
#include "ColorFX2D.tpp"


//
// Convenience shortcuts
//
namespace eCxx {

template <
    CBP::SUInt XSizeN,
    CBP::SUInt YSizeN,
    bool       UseDoubleBufferring = false,
    typename   ColorPGMPtrT        = PGMCharPointer
>
using ColorFX2D_RawRGBRowMajor = ColorFX2D<RGBColor, XSizeN, YSizeN, eCxx::CBP::RowMajor, eCxx::CBP::RawBuffer, 0, 0, 0, UseDoubleBufferring, ColorPGMPtrT>;

template <
    CBP::SUInt XSizeN,
    CBP::SUInt YSizeN,
    CBP::SUInt InterYSizeN,
    bool       UseDoubleBufferring = false,
    typename   ColorPGMPtrT        = PGMCharPointer
>
using ColorFX2D_RawRGBInterRowMajor = ColorFX2D<RGBColor, XSizeN, YSizeN, eCxx::CBP::InterRowMajor, eCxx::CBP::RawBuffer, 0, 0, InterYSizeN, UseDoubleBufferring, ColorPGMPtrT>;

} // namespace eCxx


#endif
