/******************************************************************************
* Copyright (c) 2018(-2023) STMicroelectronics.
* All rights reserved.
*
* This file is part of the TouchGFX 4.22.0 distribution.
*
* This software is licensed under terms that can be found in the LICENSE file in
* the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
*******************************************************************************/

/**
 * @file touchgfx/Bitmap.hpp
 *
 * Declares the touchgfx::Bitmap class.
 */
#ifndef TOUCHGFX_BITMAP_HPP
#define TOUCHGFX_BITMAP_HPP

#include <touchgfx/hal/Types.hpp>

#if defined(__ARMCC_VERSION) && (__ARMCC_VERSION < 6000000)
// Keil5 compiler issues irrelevant warning relating to missing ctor initialization for BitmapData.
#pragma diag_suppress 368
#endif

namespace touchgfx
{
/**
 * This type shall be used by the application to define unique IDs for all bitmaps in the
 * system. The application shall define bitmap IDs in the range [0, number of bitmaps -
 * 1].
 */
typedef uint16_t BitmapId;

const BitmapId BITMAP_ANIMATION_STORAGE = 0xFFFEU; ///< A virtual id representing animation storage.
const BitmapId BITMAP_INVALID = 0xFFFFU;           ///< Define the bitmapId of an invalid bitmap

/**
 * This class provides a proxy object for a bitmap image stored in the application specific
 * bitmap database. The proxy provides access to the raw bitmap data as well as metadata.
 */
class Bitmap
{
public:
    /** Color data of a clut can be stored in the following formats. */
    enum ClutFormat
    {
        CLUT_FORMAT_L8_ARGB8888, ///< 32-bit, 8 bits for each of red, green, blue and alpha
        CLUT_FORMAT_L8_RGB888,   ///< 24-bit, 8 bits for each of red, green and blue. No per pixel alpha channel
        CLUT_FORMAT_L8_RGB565    ///< 16-bit, 5 bits for red, 6 bits for green, 5 bits for blue. No per pixel alpha channel
    };

    /** Data of a bitmap can be stored in the following formats. */
    enum BitmapFormat
    {
        RGB565,   ///< 16-bit, 5 bits for red, 6 bits for green, 5 bits for blue. No alpha channel
        RGB888,   ///< 24-bit, 8 bits for each of red, green and blue. No alpha channel
        ARGB8888, ///< 32-bit, 8 bits for each of red, green, blue and alpha channel
        BW,       ///< 1-bit, black / white. No alpha channel
        BW_RLE,   ///< 1-bit, black / white. No alpha channel. Image is compressed with horizontal RLE
        GRAY2,    ///< 2-bit grayscale
        GRAY4,    ///< 4-bit grayscale
        ARGB2222, ///< 8-bit color
        ABGR2222, ///< 8-bit color
        RGBA2222, ///< 8-bit color
        BGRA2222, ///< 8-bit color
        L8,       ///< 8-bit indexed color
        A4,       ///< 4-bit alpha level
        CUSTOM    ///< Non-standard platform specific format
    };

    /** Algorithms used for L8 images compression can be stored in the following formats. */
    enum Compression
    {
        COMPRESSION_L8_NONE = 0, ///< No compression applied on the L8 image
        COMPRESSION_L8_L4 = 1,   ///< L4 compression applied on the L8 image
        COMPRESSION_L8_RLE = 2,  ///< RLE compression applied on the L8 image
        COMPRESSION_L8_LZW9 = 3  ///< LZW9 compression applied on the L8 image
    };

    /** Data of a bitmap. */
    struct BitmapData
    {
        const uint8_t* const data;            ///< The data of this Bitmap
        const uint8_t* const extraData;       ///< The data of either the alpha channel (if present) or clut data in case of indexed color bitmap. 0 if not used
        const uint16_t width;                 ///< The width of the Bitmap
        const uint16_t height;                ///< The height of the Bitmap
        const uint16_t solidRect_x;           ///< The x coordinate of the maximum solid rectangle of the Bitmap
        const uint16_t solidRect_y;           ///< The y coordinate of the maximum solid rectangle of the Bitmap
        const uint16_t solidRect_width : 13;  ///< The width of the maximum solid rectangle of the Bitmap
        const uint16_t format_hi : 3;         ///< Determine the format of the data (high 3 bits)
        const uint16_t solidRect_height : 13; ///< The height of the maximum solid rectangle of the Bitmap
        const uint16_t format_lo : 3;         ///< Determine the format of the data (low 3 bits)

        /**
         * Gets the Bitmap format by combining the high and low parts (format_hi << 3) | format_lo.
         *
         * @return The BitmapFormat
         */
        BitmapFormat getFormat() const
        {
            return (BitmapFormat)((format_hi << 3) | format_lo);
        }
    };

    /** Data of a dynamic Bitmap. */
    struct DynamicBitmapData
    {
        Rect solid;              ///< The solidRect of this Bitmap
        uint16_t width;          ///< The width of the Bitmap
        uint16_t height;         ///< The height of the Bitmap
        uint8_t format : 5;      ///< Determine the format of the data
        uint8_t inuse : 1;       ///< Zero if not in use
        uint8_t extra : 2;       ///< Extra data field, dependent on format
        uint8_t customSubformat; ///< Custom format specifier (or L8 palette length)
    };

    /** Cache bookkeeping. */
    struct CacheTableEntry
    {
        uint8_t* data; ///< Pointer to location of image data for this Bitmap in the cache. 0 if bitmap not cached.
    };

    /**
     * Creates and binds a Bitmap instance to the corresponding entry in the BitmapData
     * array.
     *
     * @param  id (Optional) The unique bitmap identifier.
     */
    Bitmap(const BitmapId id = BITMAP_INVALID)
        : bitmapId(id)
    {
    }

    /**
     * Gets the id of this Bitmap.
     *
     * @return The id of this Bitmap.
     */
    BitmapId getId() const
    {
        assert(bitmaps != 0 && "Bitmap database has not been initialized.");
        return bitmapId;
    }

    /**
     * Gets the id of this Bitmap.
     *
     * @return The id of this Bitmap.
     */
    operator BitmapId() const
    {
        return getId();
    }

    /**
     * Gets a pointer to the Bitmap data.
     *
     * @return A pointer to the raw Bitmap data.
     *
     * @note If this Bitmap is cached, it will return the cached version of Bitmap data.
     */
    const uint8_t* getData() const;

    /**
     * Gets a pointer to the extra (alpha) data, if present in the Bitmap. For images stored
     * in L8 format, a pointer to the CLUT will be returned. For non-opaque RGB565 images, a
     * pointer to the alpha channel will be returned.
     *
     * @return A pointer to the raw alpha channel data or CLUT. If no alpha channel or CLUT
     *         exist for the given Bitmap, 0 is returned.
     *
     * @note If this Bitmap is cached, it will return the cached version of alpha data for this
     *       Bitmap.
     */
    const uint8_t* getExtraData() const;

    /**
     * Gets the format of how the Bitmap is stored.
     *
     * @return The format of how the Bitmap data is stored.
     */
    BitmapFormat getFormat() const;

    /**
     * Gets the width of the Bitmap in pixels.
     *
     * @return The Bitmap width in pixels.
     */
    int16_t getWidth() const;

    /**
     * Gets the height of the Bitmap in pixels.
     *
     * @return The Bitmap height in pixels.
     */
    int16_t getHeight() const;

    /**
     * Gets the rectangle describing the dimensions of the Bitmap.
     *
     * @return a Rect describing the dimensions of this Bitmap.
     */
    Rect getRect() const
    {
        return Rect(0, 0, getWidth(), getHeight());
    }

    /**
     * Query if this object has an alpha channel.
     *
     * @return True if the bitmap contains an alpha channel (an alpha value for each pixel)
     */
    bool isAlphaPerPixel() const
    {
        assert(bitmaps != 0 && "Bitmap database has not been initialized.");
        if (getFormat() == L8)
        {
            return false; // No Alpha channel for indexed L8 bitmaps
        }
        return ((bitmaps != 0) && (bitmapId < numberOfBitmaps)) ? (bitmaps[bitmapId].extraData != 0) : false;
    }

    /**
     * Gets the largest solid, i.e. not transparent, rectangle in the Bitmap.
     *
     * @return The maximum solid rectangle of the Bitmap.
     */
    Rect getSolidRect() const;

    /**
     * Query if this object has transparent pixels.
     *
     * @return True if this bitmap has transparent pixels.
     */
    bool hasTransparentPixels() const;

    /**
     * Registers an array of bitmaps. All Bitmap instances are bound to this database. This
     * function is called automatically from HAL::touchgfx_generic_init().
     *
     * @param      data                   A reference to the BitmapData storage array.
     * @param      n                      The number of bitmaps in the array.
     * @param [in] cachep                 (Optional) Pointer to memory region in which bitmap
     *                                    data can be cached.
     * @param      csize                  (Optional) Size of cache memory region in bytes (0 if
     *                                    unused)
     * @param      numberOfDynamicBitmaps (Optional) Number of dynamic bitmaps to be allowed in
     *                                    the cache.
     */
    static void registerBitmapDatabase(const BitmapData* data, const uint16_t n, uint16_t* cachep = 0, uint32_t csize = 0,
                                       uint32_t numberOfDynamicBitmaps = 0);

    /**
     * Cache this Bitmap into unused RAM in the bitmap cache. A memory region large enough
     * to hold this bitmap must be configured and a large enough part of it must be
     * available. Caching of a bitmap may involve a defragmentation of the bitmap cache.
     *
     * @param  id The id of the Bitmap to cache.
     *
     * @return true if caching went well, false otherwise.
     *
     * @see registerBitmapDatabase, compactCache
     */
    static bool cache(BitmapId id);

    /**
     * Replace a Bitmap in RAM with another Bitmap. The Bitmaps must have same size.
     *
     * @param  out The id of the Bitmap to remove from the cache.
     * @param  in  The id of the Bitmap to cache.
     *
     * @return true if the replacement went well, false otherwise.
     */
    static bool cacheReplaceBitmap(BitmapId out, BitmapId in);

    /**
     * Remove this Bitmap from the RAM cache. Unless the Bitmap is
     * otherwise stored in (slower) memory it can not be drawn anymore
     * and must be cached again before use. The RAM freed can be used
     * for caching of another bitmap.
     *
     * @param  id The id of the Bitmap to cache.
     *
     * @return true if Bitmap was found and removed, false otherwise.
     *
     * @see registerBitmapDatabase
     */
    static bool cacheRemoveBitmap(BitmapId id);

    /**
     * Get address of cache buffer for this Bitmap.
     *
     * @param  id The id of the Bitmap in cache.
     *
     * @return Address if Bitmap was found, zero otherwise.
     *
     * @note The address is only valid until next Bitmap::cache() call.
     */
    static uint8_t* cacheGetAddress(BitmapId id);

    /**
     * Check if the Bitmap is cached.
     *
     * @param  id The id of the Bitmap.
     *
     * @return true if Bitmap is cached.
     */
    static bool cacheIsCached(BitmapId id);

    /**
     * Cache all bitmaps from the Bitmap Database into RAM. A memory region large enough to
     * hold all bitmaps must be configured.
     *
     * @return True if all bitmaps where cached.
     *
     * @see cache
     */
    static bool cacheAll();

    /** Clears the cached bitmaps from RAM. */
    static void clearCache();

    /**
     * Decompress a compressed bitmap into the bitmap cache. The
     * decompressed bitmap will automatically be used by all Widgets.
     *
     * Only compressed L8 images can be decompressed. The decompressed
     * bitmap will be L8 with an unchanged palette.
     *
     * When decompressing L8 bitmaps compressed with LZW an array of
     * size 2048 bytes (16 bit aligned) must be supplied. The array is
     * only used during decompressing.
     *
     * @param  id The id of the Bitmap to decompress.
     * @param  buffer Pointer to a buffer for LZW decompression.
     * @return true if the bitmap was decompressed.
     */
    static bool decompress(BitmapId id, uint16_t* buffer = 0);

    /**
     * Create a dynamic Bitmap. The clutFormat parameter is ignored for bitmaps not in L8 format
     * (consider using dynamicBitmapCreateL8 instead). Creation of a new dynamic bitmap may cause
     * existing dynamic bitmaps to be moved in memory. Do not rely on bitmap memory addresses of
     * dynamic bitmaps obtained from dynamicBitmapGetAddress() to be valid across calls to
     * dynamicBitmapCreate().
     *
     * @param   width       Width of the Bitmap.
     * @param   height      Height of the Bitmap.
     * @param   format      Bitmap format of the Bitmap.
     * @param   clutFormat  (Optional) Color LookUp Table format of the Bitmap (only used if format
     *                      is Bitmap::L8).
     *
     * @return  BitmapId of the new Bitmap or #BITMAP_INVALID if cache memory is full.
     *
     * @see DynamicBitmapData, dynamicBitmapCreateL8, dynamicBitmapCreateCopy
     */
    static BitmapId dynamicBitmapCreate(const uint16_t width, const uint16_t height, BitmapFormat format, ClutFormat clutFormat = CLUT_FORMAT_L8_ARGB8888);

    /**
     * Create a dynamic Bitmap as a copy of an existing bitmap.
     *
     * @param   id  The ID of the bitmap to copy.
     *
     * @return  BitmapId of the new Bitmap or #BITMAP_INVALID if cache memory is full.
     *
     * @see dynamicBitmapCreate
     */
    static BitmapId dynamicBitmapCreateCopy(const BitmapId id);

    /**
     * Create a dynamic L8 Bitmap. Creation of a new dynamic bitmap may cause existing dynamic
     * bitmaps to be moved in memory. Do not rely on bitmap memory addresses of dynamic bitmaps
     * obtained from dynamicBitmapGetAddress() to be valid across calls to dynamicBitmapCreate().
     *
     * @param   width       Width of the Bitmap.
     * @param   height      Height of the Bitmap.
     * @param   clutFormat  Color LookUp Table format of the L8 Bitmap.
     * @param   clutSize    (Optional) Color LookUp Table palette size (default=256).
     *
     * @return  BitmapId of the new Bitmap or #BITMAP_INVALID if cache memory is full.
     *
     * @see DynamicBitmapData, dynamicBitmapCreate
     */
    static BitmapId dynamicBitmapCreateL8(const uint16_t width, const uint16_t height, ClutFormat clutFormat, uint16_t clutSize = 256);

    /**
     * Create a dynamic bitmap in custom format. size number of bytes
     * is reserved in the dynamic bitmap cache. A more specific format
     * can be given in the customSubformat parameter for use when
     * handling more than one CUSTOM format. Set the solid rect if
     * applicable.
     *
     * @param width           Width of the bitmap.
     * @param height          Height of the bitmap.
     * @param customSubformat Custom format specifier
     * @param size            Size in bytes of the dynamic bitmap
     *
     * @return BitmapId of the new bitmap or BITMAP_INVALID if cache memory is full.
     *
     * @note Creation of a new dynamic bitmap may cause existing dynamic bitmaps to be moved in
     *       memory. Do not rely on bitmap memory addresses of dynamic bitmaps obtained from
     *       dynamicBitmapGetAddress() to be valid across calls to dynamicBitmapCreateCustom() .
     *
     * @see dynamicBitmapGetAddress, dynamicBitmapCreate, dynamicBitmapSetSolidRect
     */
    static BitmapId dynamicBitmapCreateCustom(const uint16_t width, const uint16_t height, uint8_t customSubformat, uint32_t size);

    /**
     * Create a dynamic bitmap without reserving memory in the dynamic
     * bitmap cache. The pixels must be already available in the
     * memory, e.g. in flash. No copying is performed.
     *
     * @param width           Width of the bitmap.
     * @param height          Height of the bitmap.
     * @param pixels          Pointer to the bitmap pixels.
     * @param format          Bitmap format of the bitmap.
     * @param customSubformat Custom format specifier
     *
     * @return BitmapId of the new bitmap or BITMAP_INVALID if not possible.
     *
     * @see dynamicBitmapGetAddress, dynamicBitmapCreate, dynamicBitmapSetSolidRect
     */
    static BitmapId dynamicBitmapCreateExternal(const uint16_t width, const uint16_t height, const void* pixels, BitmapFormat format, uint8_t customSubformat = 0);

    /**
     * Fill a dynamic Bitmap with a color. If alpha is less than 255, the color will be blended onto
     * the existing data in the dynamic bitmap.
     *
     * @param   id      The ID of the dynamic bitmap to fill.
     * @param   color   The color.
     * @param   alpha   (Optional) The alpha (default is 255, i.e. solid).
     *
     * @see dynamicBitmapCreateCopy, dynamicBitmapFillRect
     */
    static void dynamicBitmapFill(const BitmapId id, const colortype color, const uint8_t alpha = 255);

    /**
     * Fill parts of a dynamic Bitmap with a color. If alpha is less than 255, the color will be
     * blended onto the existing data in the dynamic bitmap.
     *
     * @param   id      The ID of the dynamic bitmap to fill.
     * @param   rect    The rectangle to fill.
     * @param   color   The color.
     * @param   alpha   (Optional) The alpha (default is 255, i.e. solid).
     *
     * @see dynamicBitmapCreateCopy, dynamicBitmapFill
     */
    static void dynamicBitmapFillRect(const BitmapId id, const Rect& rect, const colortype color, const uint8_t alpha = 255);

    /**
     * Delete a dynamic bitmap.
     *
     * @param  id The BitmapId of the dynamic Bitmap.
     *
     * @return true if it succeeds, false if it fails.
     */
    static bool dynamicBitmapDelete(BitmapId id);

    /**
     * Check if a given bitmap id is the id of a dynamic bitmap.
     *
     * @param  id The BitmapId of the dynamic Bitmap.
     *
     * @return true if the bitmap is dynamic, false otherwise.
     */
    static bool isDynamicBitmap(BitmapId id);

    /**
     * Get the address of the dynamic Bitmap data. It is important that the address of a
     * dynamic Bitmap is not stored elsewhere as a dynamic Bitmap may be moved in memory
     * when other bitmaps are added and removed. Only store the BitmapId and ask for the
     * address of the Bitmap data when needed. The address of a dynamic bitmap may change
     * when other dynamic bitmaps are added and removed.
     *
     * @param  id The BitmapId of the dynamic bitmap.
     *
     * @return null if it fails, else an uint8_t*.
     *
     * @note Never keep the address of dynamic images, only store the BitmapId as that will not
     *       change.
     */
    static uint8_t* dynamicBitmapGetAddress(BitmapId id);

    /**
     * Set the solid rectangle of a dynamic Bitmap. Only relevant for ARGB8888 Bitmap and
     * 8bpp Bitmap formats, as these formats include an alpha channel. The solid part of the
     * Bitmap is drawn faster than the transparent parts.
     *
     * @param  id        The identifier.
     * @param  solidRect The solid rectangle.
     *
     * @return true if it succeeds, false if it fails.
     */
    static bool dynamicBitmapSetSolidRect(BitmapId id, const Rect& solidRect);

    /**
     * Updates the solid rectangle of a dynamic Bitmap to include the given rectangle. Only
     * relevant for ARGB8888 bitmap and 8bpp bitmap formats, as these formats include an
     * alpha channel. The solid part of the Bitmap is drawn faster than the transparent
     * parts.
     *
     * @param  id        The identifier.
     * @param  solidRect The solid rectangle.
     *
     * @return true if it succeeds, false if it fails.
     */
    static bool dynamicBitmapAddSolidRect(BitmapId id, const Rect& solidRect);

    /// @cond
    /**
     * Gets the subformat of the dynamic bitmap. Zero is returned if
     * the subformat was not set.
     *
     * @return the subformat
     */
    static uint8_t dynamicBitmapGetCustomSubformat(BitmapId id);

    /**
     * @return The number of dynamic bitmaps.
     */
    static uint32_t dynamicBitmapGetNumberOfBitmaps();
    /// @endcond

    /**
     * Register a memory region in which Bitmap data can be cached.
     *
     * @param [in] cachep                 Pointer to memory region in which bitmap data can be
     *                                    cached.
     * @param      csize                  Size of cache memory region in bytes.
     * @param      numberOfDynamicBitmaps (Optional) Number of dynamic bitmaps to be allowed in
     *                                    the cache.
     */
    static void setCache(uint16_t* cachep, uint32_t csize, uint32_t numberOfDynamicBitmaps = 0);

    /**
     * Removes the Bitmap cache. The memory can hereafter be used for other purposes. All
     * dynamic Bitmap IDs are invalid after this.
     */
    static void removeCache();

    /**
     * Gets the address of the first unused memory in the cache. Can be used in advanced
     * application to reduce power consumption of external RAM by turning off unused RAM.
     *
     * @return Returns the highest used address in the cache.
     */
    static uint8_t* getCacheTopAddress()
    {
        return nextFreeData;
    }

    /**
     * Compact the bitmap cache to get continuous free memory on top. This method is called
     * by Bitmap::cache when required.
     */
    static void compactCache();

private:
    static uint32_t getSizeOfBitmap(BitmapId id);
    static uint32_t getSizeOfBitmapData(BitmapId id);
    static uint32_t getSizeOfBitmapExtraData(BitmapId id);
    static bool cacheInternal(BitmapId id, uint32_t size, bool doCopy = true);
    static bool isCustomDynamicBitmap(BitmapId id);
    static bool copyBitmapToCache(BitmapId id, uint8_t* const dst);
    static uint32_t firstFreeDynamicBitmapId();
    static uint32_t decompressL8_L4(uint8_t* dst, BitmapId id);
    static uint32_t decompressL8_RLE(uint8_t* dst, BitmapId id);
    static uint32_t decompressL8_LZW9(uint8_t* dst, BitmapId id, uint16_t* buffer = 0);

    BitmapId bitmapId;
    static const BitmapData* bitmaps;
    static DynamicBitmapData* dynBitmaps;
    static CacheTableEntry* cacheTable; ///< Address of allocation point cache
    static BitmapId* allocationTable;   ///< Order of allocations in cache
    static uint8_t* nextFreeData;
    static uint16_t nextAllocationIndex;
    static uint32_t memoryRemaining;
    static uint32_t totalMemory;
    static uint16_t numberOfBitmaps;
    static uint16_t numberOfDynamicBitmaps;
    static uint16_t uncachedCount; ///< Uncached images, sort of ...

    static const uint16_t RLE_BLOCK_SIZE = 1024U;
};

} // namespace touchgfx

#endif // TOUCHGFX_BITMAP_HPP