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


#ifndef ECXX_CTL_GFX_SPRITE_SHEET_2D_H
#define ECXX_CTL_GFX_SPRITE_SHEET_2D_H


#include "ColorBufferPolicy.h"


namespace eCxx {


/*
 * A simple 2D sprite sheet
 *
 * # 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,
 *   and RGB, (not in GRB, BGR, etc.).
 * # Color data in program memory must be stored in CBP::BlockRowMajorPixelRowMajor format.
 * # Not all color types can be used with 2D sprite sheet.
 *
 * # Template argument PGMPointerT is used when reading sprite data from program memory, it must
 *   be one of PGMCharPointer and PGMCharPointerFar.
 *
 * -------------------------------------------------------------------------------------------------
 *
 * Example of a 2D sprite sheet with 3 columns and 2 rows of which each sprite is 5x3 pixels:
 *
 * Visual grid layout
 *
 *     ⬤⬤⬤⬤⬤   ⬤⬤⬤⬤⬤   ⬤⬤⬤⬤⬤   |   sprite   sprite   sprite
 *     ⬤⬤⬤⬤⬤   ⬤⬤⬤⬤⬤   ⬤⬤⬤⬤⬤   |    0, 0     1, 0     2, 0
 *     ⬤⬤⬤⬤⬤   ⬤⬤⬤⬤⬤   ⬤⬤⬤⬤⬤   |
 *
 *     ⬤⬤⬤⬤⬤   ⬤⬤⬤⬤⬤   ⬤⬤⬤⬤⬤   |   sprite   sprite   sprite
 *     ⬤⬤⬤⬤⬤   ⬤⬤⬤⬤⬤   ⬤⬤⬤⬤⬤   |    0, 1     1, 1     2, 1
 *     ⬤⬤⬤⬤⬤   ⬤⬤⬤⬤⬤   ⬤⬤⬤⬤⬤   |
 *
 * Memory layout
 *
 *     static const eCxx::uint8_t PROGMEM pgmData[eCxx::RGBColor::RawByteSize * 3 * 2 * 5 * 3] = {
 *         // Sprite (0, 0)
 *         <pixel 0,0>, <pixel 1,0>, <pixel 2,0>, <pixel 3,0>, <pixel 4,0>,
 *         <pixel 0,1>, <pixel 1,1>, <pixel 2,1>, <pixel 3,1>, <pixel 4,1>,
 *         <pixel 0,2>, <pixel 1,2>, <pixel 2,2>, <pixel 3,2>, <pixel 4,2>,
 *         // Sprite (1, 0)
 *         <pixel 0,0>, <pixel 1,0>, <pixel 2,0>, <pixel 3,0>, <pixel 4,0>,
 *         <pixel 0,1>, <pixel 1,1>, <pixel 2,1>, <pixel 3,1>, <pixel 4,1>,
 *         <pixel 0,2>, <pixel 1,2>, <pixel 2,2>, <pixel 3,2>, <pixel 4,2>,
 *         // Sprite (2, 0)
 *         <pixel 0,0>, <pixel 1,0>, <pixel 2,0>, <pixel 3,0>, <pixel 4,0>,
 *         <pixel 0,1>, <pixel 1,1>, <pixel 2,1>, <pixel 3,1>, <pixel 4,1>,
 *         <pixel 0,2>, <pixel 1,2>, <pixel 2,2>, <pixel 3,2>, <pixel 4,2>,
 *         // Sprite (0, 1)
 *         <pixel 0,0>, <pixel 1,0>, <pixel 2,0>, <pixel 3,0>, <pixel 4,0>,
 *         <pixel 0,1>, <pixel 1,1>, <pixel 2,1>, <pixel 3,1>, <pixel 4,1>,
 *         <pixel 0,2>, <pixel 1,2>, <pixel 2,2>, <pixel 3,2>, <pixel 4,2>,
 *         // Sprite (1, 1)
 *         <pixel 0,0>, <pixel 1,0>, <pixel 2,0>, <pixel 3,0>, <pixel 4,0>,
 *         <pixel 0,1>, <pixel 1,1>, <pixel 2,1>, <pixel 3,1>, <pixel 4,1>,
 *         <pixel 0,2>, <pixel 1,2>, <pixel 2,2>, <pixel 3,2>, <pixel 4,2>,
 *         // Sprite (2, 1)
 *         <pixel 0,0>, <pixel 1,0>, <pixel 2,0>, <pixel 3,0>, <pixel 4,0>,
 *         <pixel 0,1>, <pixel 1,1>, <pixel 2,1>, <pixel 3,1>, <pixel 4,1>,
 *         <pixel 0,2>, <pixel 1,2>, <pixel 2,2>, <pixel 3,2>, <pixel 4,2>
 *     };
 *
 * The 2D sprite sheet above uses RGB color, therefore, each pixel is composed of 3 uint8_t values.
 */
template <
    typename   ColorT,
    CBP::SUInt SpriteXCountN,
    CBP::SUInt SpriteYCountN,
    CBP::SUInt SpriteXSizeN,
    CBP::SUInt SpriteYSizeN,
    typename   PGMPointerT = PGMCharPointer
>
class SpriteSheet2D {
    public:
        ECXX_CHECK_IF_TEMPLATE_ARG_IS_PGM_CHAR_POINTER(PGMPointerT);

    public:
        using ColorType      = ColorT;
        using PGMPointerType = PGMPointerT;

        static constexpr CBP::SUInt XCount = SpriteXCountN; // Number of columns (sprites in one row   )
        static constexpr CBP::SUInt YCount = SpriteYCountN; // Number of rows    (sprites in one column)

        static constexpr CBP::SUInt XSize  = SpriteXSizeN;  // Sprite X size (width )
        static constexpr CBP::SUInt YSize  = SpriteYSizeN;  // Sprite Y size (height)

        static constexpr uint8_t    ColorByteSize  = ColorType::RawByteSize;
        static constexpr CBP::DUInt SpriteByteSize = static_cast<CBP::DUInt>(ColorByteSize) * static_cast<CBP::DUInt>(XSize ) * static_cast<CBP::DUInt>(YSize );
        static constexpr CBP::DUInt BufferByteSize = SpriteByteSize                         * static_cast<CBP::DUInt>(XCount) * static_cast<CBP::DUInt>(YCount);

    public:
        inline SpriteSheet2D() = delete;
        inline ~SpriteSheet2D() = default;

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

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

    public:
        inline SpriteSheet2D(PGMPointerType ssData);

        inline void obtainSCDBegin(CBP::SUInt xIndex, CBP::SUInt yIndex) const;
        inline const ColorType obtainSCDGet() const;

    private:
                PGMPointerType _ssData;
        mutable PGMPointerType _ssIt;
};


} // namespace eCxx


//
// Include the implementation file
//
#define INCLUDE_ECXX_CTL_GFX_SPRITE_SHEET_2D_TPP
#include "SpriteSheet2D.tpp"


#endif
