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


#ifndef ECXX_PLATFORM_HUB75_PIXEL_ADDRESS_MAPPER_H
#define ECXX_PLATFORM_HUB75_PIXEL_ADDRESS_MAPPER_H


namespace eCxx {


/*
 * The CRTP base class for all pixel address mapper classes used by GenericHUB75Driver<T>
 *
 * All derived (implementation) classes must implement these member functions:
 *
 *     static constexpr uint16_t rowPixelCount_impl();
 *
 *     static constexpr uint8_t rowDivisonCount_impl();
 *
 *     static constexpr uint16_t divPixelCount_impl();
 *
 *     static constexpr int8_t divAddressStep_impl();
 *
 *     static inline const uint8_t* getStartDivAddress_impl(const uint8_t* buff, uint8_t row, uint8_t div) __ECXX_ATTR_ALWAYS_INLINE;
 */
template <
    typename                   ImplementationT,
    typename                   ColorFX2D_T,
    HUB75Config::RowScanning   RowScanningE,
    HUB75Config::RowAddressing RowAddressingE
>
class HUB75PixelAddressMapper_CRTPBase : public Uninstantiable {
    public:
        using ColorFX2DType = ColorFX2D_T;

        static constexpr HUB75Config::RowScanning   RowScanningMode   = RowScanningE;
        static constexpr HUB75Config::RowAddressing RowAddressingMode = RowAddressingE;

        static constexpr uint8_t                    XPixelCount       = ColorFX2D_T::XElementCount;
        static constexpr uint8_t                    YPixelCount       = ColorFX2D_T::YElementCount;

    public:
        // The total number of pixels in all active rows:
        //     # A 16-pixel high panel with 1/8 scan will have 2 rows active at the same time.
        //     # A 32-pixel high panel with 1/8 scan will have 4 rows active at the same time, etc.
        static constexpr uint16_t rowPixelCount()
        { return ImplementationT::rowPixelCount_impl(); }

        // The total number of divisions for all active rows:
        //     # For a panel with a standard pixel ordering (normal left to right), it will return 1.
        //     # For a panel with a non standard pixel ordering (e.g. the pixels originating at the middle
        //       of the rows), it will return the number of divisions (column groups).
        static constexpr uint8_t rowDivisonCount()
        { return ImplementationT::rowDivisonCount_impl(); }

        // The total number of pixels in a division (column group)
        static constexpr uint16_t divPixelCount()
        { return ImplementationT::divPixelCount_impl(); }

        // Increment factor to select the next data byte:
        //     # For a panel with a standard pixel ordering (normal left to right), it will return 1.
        //     # For a panel with pixels originating at the middle of the rows, it may return -1.
        static constexpr int8_t divAddressStep()
        { return ImplementationT::divAddressStep_impl(); }

        // Get the starting byte address for the given row and division (column group)
        static inline const uint8_t* getStartDivAddress(const uint8_t* buff, uint8_t row, uint8_t div) __ECXX_ATTR_ALWAYS_INLINE
        { return ImplementationT::getStartDivAddress_impl(buff, row, div); }
};


//
// A straight HUB75 pixel address mapper class (it is usually used by indoor LED panels)
//
/*
 * Visual and hardware pixel flow/movement example for a 32x16 LED panel with RowScanningE = RowScanning::S_1_8
 *
 *    Visual                                 Hardware
 *  0 |   0   1  .   .   .   .   30  31 |    |   0   1  .   .   .   .   30  31 |   R0   Half top
 *  1 |  32  33  .   .   .   .   62  63 |    |  .   .   .   .   .   .   .   .  |   G0
 *  . |  .   .   .   .   .   .   .   .  |    | 192 193  .   .   .   .  222 223 |   B0
 *  . |  .   .   .   .   .   .   .   .  | -> | 256 257  .   .   .   .  286 287 |
 *  . | 192 193  .   .   .   .  222 223 |    |  .   .   .   .   .   .   .   .  |
 *  . | 224 225  .   .   .   .  254 255 |    | 448 449  .   .   .   .  478 479 |
 *  . | 256 257  .   .   .   .  286 287 |    |  32  33  .   .   .   .   62  63 |   R1   Half bottom
 *  . | 288 289  .   .   .   .  318 319 |    |  .   .   .   .   .   .   .   .  |   G1
 *  . |  .   .   .   .   .   .   .   .  |    | 224 225  .   .   .   .  254 255 |   B1
 *  . |  .   .   .   .   .   .   .   .  | -> | 288 289  .   .   .   .  318 319 |
 * 14 | 448 449  .   .   .   .  478 479 |    |  .   .   .   .   .   .   .   .  |
 * 15 | 480 481  .   .   .   .  510 511 |    | 480 481  .   .   .   .  510 511 |
 *  Y/X   0   1  .   .   .   .   30  31
 *
 * Buffer memory layout for the above example (assuming CBP::InterRowMajor)
 *
 *    |   0   1  .   .   .   .   30  31 |
 *    | 256 257  .   .   .   .  286 287 |
 *    |  32  33  .   .   .   .   62  63 |
 *    | 288 289  .   .   .   .  318 319 |
 *    |  .   .   .   .   .   .   .   .  |
 *    |  .   .   .   .   .   .   .   .  |
 *    |  .   .   .   .   .   .   .   .  |
 *    |  .   .   .   .   .   .   .   .  |
 *    | 192 193  .   .   .   .  222 223 |
 *    | 448 449  .   .   .   .  478 479 |
 *    | 224 225  .   .   .   .  254 255 |
 *    | 480 481  .   .   .   .  510 511 |
 *
 * The flow and memory layout described above match CBP::InterRowMajor, CBP::InterRowMajorRX,
 * CBP::InterRowMajorRY, and CBP::InterRowMajorRXY.
 */
template <
    typename                   ColorFX2D_T,
    HUB75Config::RowScanning   RowScanningE,
    HUB75Config::RowAddressing RowAddressingE
>
class HUB75PixelAddressMapper_Straight : public HUB75PixelAddressMapper_CRTPBase<
    HUB75PixelAddressMapper_Straight<ColorFX2D_T, RowScanningE, RowAddressingE>,
    ColorFX2D_T,
    RowScanningE,
    RowAddressingE
> {
    static_assert(
        ColorFX2D_T::ColorByteSize == 3,
        "template argument substituting ColorFX2D_T::ColorByteSize is not 3"
    );

    static_assert(
        eCxx::isSame<typename ColorFX2D_T::LayoutPolicyType, CBP::InterRowMajor   >::Value ||
        eCxx::isSame<typename ColorFX2D_T::LayoutPolicyType, CBP::InterRowMajorRX >::Value ||
        eCxx::isSame<typename ColorFX2D_T::LayoutPolicyType, CBP::InterRowMajorRY >::Value ||
        eCxx::isSame<typename ColorFX2D_T::LayoutPolicyType, CBP::InterRowMajorRXY>::Value,
        "template argument substituting ColorFX2D_T::LayoutPolicyType is not a CBP::InterRowMajor variant"
    );

    public:
        using HUB75PixelAddressMapperType = HUB75PixelAddressMapper_CRTPBase<HUB75PixelAddressMapper_Straight, ColorFX2D_T, RowScanningE, RowAddressingE>;
        using ColorFX2DType               = typename HUB75PixelAddressMapperType::ColorFX2DType;

        static constexpr HUB75Config::RowScanning   RowScanningMode   = HUB75PixelAddressMapperType::RowScanningMode;
        static constexpr HUB75Config::RowAddressing RowAddressingMode = HUB75PixelAddressMapperType::RowAddressingMode;

        static constexpr uint8_t                    XPixelCount       = HUB75PixelAddressMapperType::XPixelCount;
        static constexpr uint8_t                    YPixelCount       = HUB75PixelAddressMapperType::YPixelCount;

    private:
        static constexpr uint8_t  RowScanningCount = static_cast<uint8_t>(RowScanningMode);
        static constexpr uint16_t RowPixelCount    = static_cast<uint16_t>(XPixelCount                   )
                                                   * static_cast<uint16_t>(YPixelCount / RowScanningCount);

        static constexpr uint16_t ImgRowPixelCount = RowPixelCount; // Because of CBP::InterRowMajor variants

    public:
        static constexpr uint16_t rowPixelCount_impl()
        { return RowPixelCount; }

        static constexpr uint8_t rowDivisonCount_impl()
        { return 1; }

        static constexpr uint16_t divPixelCount_impl()
        { return rowPixelCount_impl(); }

        static constexpr int8_t divAddressStep_impl()
        { return 1; }

        static inline const uint8_t* getStartDivAddress_impl(const uint8_t* buff, uint8_t row, uint8_t) __ECXX_ATTR_ALWAYS_INLINE
        { return buff + ( static_cast<uint16_t>(row) * ImgRowPixelCount * ColorFX2DType::ColorByteSize ); }
};

template <typename ColorFX2D_T, HUB75Config::RowScanning RowScanningE, HUB75Config::RowAddressing RowAddressingE>
using HUB75PixelAddressMapper_StandardIndoor = HUB75PixelAddressMapper_Straight<ColorFX2D_T, RowScanningE, RowAddressingE>;

template <typename ColorFX2D_T, HUB75Config::RowScanning RowScanningE, HUB75Config::RowAddressing RowAddressingE>
using HUB75PixelAddressMapper_DefaultIndoor = HUB75PixelAddressMapper_Straight<ColorFX2D_T, RowScanningE, RowAddressingE>;


//
// A block HUB75 pixel address mapper class
// NOTE : If AddressReversed set to true, the color order also needs to be reversed from RGB to BGR
//
namespace internal {

    /*
     * Buffer memory layout for a 32x16 LED panel (assuming CBP::RowMajor)
     *
     *  0 |   0   1  .   .   .   .   30  31 |
     *  1 |  32  33  .   .   .   .   62  63 |
     *  2 |  64  65  .   .   .   .   94  95 |
     *  . |  .   .   .   .   .   .   .   .  |
     *  . |  .   .   .   .   .   .   .   .  |
     *  . |  .   .   .   .   .   .   .   .  |
     *  . |  .   .   .   .   .   .   .   .  |
     *  . |  .   .   .   .   .   .   .   .  |
     *  . |  .   .   .   .   .   .   .   .  |
     *  . |  .   .   .   .   .   .   .   .  |
     *  . |  .   .   .   .   .   .   .   .  |
     *  . |  .   .   .   .   .   .   .   .  |
     *  . |  .   .   .   .   .   .   .   .  |
     * 13 | 416 417  .   .   .   .  446 447 |
     * 14 | 448 449  .   .   .   .  478 479 |
     * 15 | 480 481  .   .   .   .  510 511 |
     *  Y/X   0   1  .   .   .   .   30  31
     *
     * Hardware pixel flow/movement example for a 32x16 LED panel with : RowScanningE      = RowScanning::S_1_4
     *                                                                   GroupXPixelCountN = 8
     *                                                                   AddressReversed   = false
     *
     *    Plane #0                                                              | #div
     *    000 001 ... 007 | 016 017 ... 023 | 032 033 ... 039 | 048 049 ... 055 | 00 02 04 06
     *     .                                   .                                |
     *     .                                   .                                |
     *     .                                   .                                |
     *    008 009 ... 015 | 024 025 ... 031 | 040 041 ... 047 | 056 057 ... 063 | 01 03 05 07
     *     .                                   .                                |
     *     .                                   .                                |
     *     .                                   .                                |
     *                                                                          |
     *    Plane #1                                                              | #div
     *    256 257 ... 263 | 272 273 ... 279 | 288 289 ... 295 | 304 305 ... 311 | 08 10 12 14
     *     .                                   .                                |
     *     .                                   .                                |
     *     .                                   .                                |
     *    264 265 ... 271 | 280 281 ... 287 | 296 297 ... 303 | 312 313 ... 319 | 09 11 13 15
     *     .                                   .                                |
     *     .                                   .                                |
     *     .                                   .                                |
     *
     * The flow and memory layout described above match CBP::RowMajor, CBP::RowMajorRX,
     * CBP::RowMajorRY, and CBP::RowMajorRXY.
     */
    template <
        typename                   ColorFX2D_T,
        HUB75Config::RowScanning   RowScanningE,
        HUB75Config::RowAddressing RowAddressingE,
        uint8_t                    GroupXPixelCountN,
        bool                       AddressReversed
    >
    class HUB75PixelAddressMapper_Block : public HUB75PixelAddressMapper_CRTPBase<
        HUB75PixelAddressMapper_Block<ColorFX2D_T, RowScanningE, RowAddressingE, GroupXPixelCountN, AddressReversed>,
        ColorFX2D_T,
        RowScanningE,
        RowAddressingE
    > {
        static_assert(
            ColorFX2D_T::ColorByteSize == 3,
            "template argument substituting ColorFX2D_T::ColorByteSize is not 3"
        );

        static_assert(
            eCxx::isSame<typename ColorFX2D_T::LayoutPolicyType, CBP::RowMajor   >::Value ||
            eCxx::isSame<typename ColorFX2D_T::LayoutPolicyType, CBP::RowMajorRX >::Value ||
            eCxx::isSame<typename ColorFX2D_T::LayoutPolicyType, CBP::RowMajorRY >::Value ||
            eCxx::isSame<typename ColorFX2D_T::LayoutPolicyType, CBP::RowMajorRXY>::Value,
            "template argument substituting ColorFX2D_T::LayoutPolicyType is not a CBP::RowMajor variant"
        );

        public:
            using HUB75PixelAddressMapperType = HUB75PixelAddressMapper_CRTPBase<HUB75PixelAddressMapper_Block, ColorFX2D_T, RowScanningE, RowAddressingE>;
            using ColorFX2DType               = typename HUB75PixelAddressMapperType::ColorFX2DType;

            static constexpr HUB75Config::RowScanning   RowScanningMode   = HUB75PixelAddressMapperType::RowScanningMode;
            static constexpr HUB75Config::RowAddressing RowAddressingMode = HUB75PixelAddressMapperType::RowAddressingMode;

            static constexpr uint8_t                    XPixelCount       = HUB75PixelAddressMapperType::XPixelCount;
            static constexpr uint8_t                    YPixelCount       = HUB75PixelAddressMapperType::YPixelCount;

        private:
            static constexpr uint8_t  RowScanningCount = static_cast<uint8_t>(RowScanningMode);
            static constexpr uint16_t RowPixelCount    = static_cast<uint16_t>(XPixelCount                   )
                                                       * static_cast<uint16_t>(YPixelCount / RowScanningCount);

            static constexpr uint16_t ImgRowPixelCount = XPixelCount; // Because of CBP::RowMajor variants

        private:
            static constexpr uint8_t PlaneCount       = 2; // There is always 2 planes
            static constexpr uint8_t GroupXPixelCount = GroupXPixelCountN;

            static constexpr uint8_t Plane1YOffset    = YPixelCount / PlaneCount;
            static constexpr uint8_t PlaneDivCount    = ( XPixelCount / GroupXPixelCount                                   )
                                                      * ( YPixelCount / PlaneCount / static_cast<uint8_t>(RowScanningMode) );

            static constexpr uint8_t PlaneYBlockCount = YPixelCount / PlaneCount / static_cast<uint8_t>(RowScanningMode);
            static constexpr uint8_t PlaneXBlockCount = PlaneDivCount / PlaneYBlockCount;

        public:
            static constexpr uint16_t rowPixelCount_impl()
            { return RowPixelCount; }

            static constexpr uint8_t rowDivisonCount_impl()
            { return PlaneDivCount * PlaneCount; }

            static constexpr uint16_t divPixelCount_impl()
            { return GroupXPixelCount; }

            static constexpr int8_t divAddressStep_impl()
            { return AddressReversed ? -1 : 1; }

            static inline const uint8_t* getStartDivAddress_impl(const uint8_t* buff, uint8_t row, uint8_t div) __ECXX_ATTR_ALWAYS_INLINE
            {
                // Determine if this is for the bottom half part of the panel
                const bool plane1 = (div >= PlaneDivCount);

                // Adjust the division number as needed
                if(plane1) div -= PlaneDivCount;

                // Calculate the division coordinates
                /*
                 * Scanning mode                 1_S_4           1_S_2
                 * Group X pixel count           8         16    8           16
                 *
                 * RowScanningCount              4         4     2           2
                 * RowPixelCount                 128       128   256         256
                 * ImgRowBytesCount              96        96    96          96
                 *
                 * PlaneDivCount                 8         4     16          8
                 * PlaneYBlockCount              2         2     4           4
                 * PlaneXBlockCount              4         2     4           2
                 *
                 * Division index for plane #0   0 2 4 6   0 2   0 4  8 12   0 4
                 *                               1 3 5 7   1 3   1 5  9 13   1 5
                 *                                               2 6 10 14   2 6
                 *                                               3 7 11 15   3 7
                 */
                const uint8_t yg = div % PlaneYBlockCount;
                const uint8_t xg = div / PlaneYBlockCount;

                const uint8_t x  = AddressReversed
                                 ? ( GroupXPixelCount - 1 - xg * GroupXPixelCount )
                                 : (                        xg * GroupXPixelCount );

                const uint8_t y  = ( plane1 ? Plane1YOffset : 0 ) + (yg * RowScanningCount) + row;

                // Return the start address
                const uint16_t offset = ( static_cast<uint16_t>(y) * ImgRowPixelCount + x )
                                      * ColorFX2DType::ColorByteSize;

                return buff + offset + ( AddressReversed ? (ColorFX2DType::ColorByteSize - 1) : 0 );
            }
    };

} // namespace internal

template <typename ColorFX2D_T, HUB75Config::RowScanning RowScanningE, HUB75Config::RowAddressing RowAddressingE>
using HUB75PixelAddressMapper_Block8 = internal::HUB75PixelAddressMapper_Block<
    ColorFX2D_T, RowScanningE, RowAddressingE, 8, false
>;

template <typename ColorFX2D_T, HUB75Config::RowScanning RowScanningE, HUB75Config::RowAddressing RowAddressingE>
using HUB75PixelAddressMapper_Block8R = internal::HUB75PixelAddressMapper_Block<
    /* NOTE : Reverse the color order from RGB to BGR when using this reserved address mapper */
    ColorFX2D_T, RowScanningE, RowAddressingE, 8, true
>;

template <typename ColorFX2D_T, HUB75Config::RowScanning RowScanningE, HUB75Config::RowAddressing RowAddressingE>
using HUB75PixelAddressMapper_Block16 = internal::HUB75PixelAddressMapper_Block<
    ColorFX2D_T, RowScanningE, RowAddressingE, 16, false
>;

template <typename ColorFX2D_T, HUB75Config::RowScanning RowScanningE, HUB75Config::RowAddressing RowAddressingE>
using HUB75PixelAddressMapper_Block16R = internal::HUB75PixelAddressMapper_Block<
    /* NOTE : Reverse the color order from RGB to BGR when using this reserved address mapper */
    ColorFX2D_T, RowScanningE, RowAddressingE, 16, true
>;


} // namespace eCxx


#endif
