/******************************************************************************
* Copyright (c) 2018(-2024) STMicroelectronics.
* All rights reserved.
*
* This file is part of the TouchGFX 4.24.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/TextProvider.hpp
 *
 * Declares the touchgfx::TextProvider class.
 */
#ifndef TOUCHGFX_TEXTPROVIDER_HPP
#define TOUCHGFX_TEXTPROVIDER_HPP

#include <touchgfx/Font.hpp>
#include <touchgfx/Unicode.hpp>
#include <touchgfx/hal/Types.hpp>

namespace touchgfx
{
/**
 * The TextProvider is used in drawing basic strings and strings with one or two wildcards. The
 * TextProvider enables wildcard expansion of the string at the time it is written to
 * the LCD.
 *
 * Wildcards specified as &lt;placeholder&gt; are converted to Unicode value 2 by the
 * text converter tool, and the placeholders are automatically expanded with the
 * specified wildcard buffers at runtime.
 */
class TextProvider
{
public:
    static const uint32_t MAX_32BIT_INTEGER_DIGITS = 33U; ///< Max number of digits used for the text representation of a 32 bit integer.

    /**
     * Initializes a new instance of the TextProvider class.
     *
     * @note The user must call initialize() before characters can be provided.
     */
    TextProvider();

    /**
     * Initializes the TextProvider. Each '\2' character in the format is replaced by one
     * UnicodeChar* argument from pArg.
     *
     * @param  stringFormat The string to format.
     * @param  pArg         Format arguments in the form of a va_list.
     * @param  gsubTable    (Optional) Pointer to GSUB table with Unicode substitution rules.
     * @param  formsTable   (Optional) Pointer to contextual forms table with Unicode substitution rules (for arabic).
     */
    void initialize(const Unicode::UnicodeChar* stringFormat, va_list pArg, const uint16_t* gsubTable = 0, const FontContextualFormsTable* formsTable = 0);

    /**
     * Initializes the TextProvider. Each '\2' character in the format is replaced by one
     * UnicodeChar* argument from pArg.
     *
     * @param  stringFormat The string to format.
     * @param  gsubTable    (Optional) Pointer to GSUB table with Unicode substitution rules.
     * @param  formsTable   (Optional) Pointer to contextual forms table with Unicode substitution rules (for arabic).
     * @param  ...          Variable arguments providing additional information.
     */
    void initialize(const Unicode::UnicodeChar* stringFormat, const uint16_t* gsubTable = 0, const FontContextualFormsTable* formsTable = 0, ...);

    /**
     * Gets the next character. For Arabic and Thai, it is important to use the
     * getNextLigature instead.
     *
     * @return The next character of the expanded string or 0 if end of string is reached.
     *
     * @see TextProvider::getNextLigature
     */
    Unicode::UnicodeChar getNextChar();

    /**
     * Tells if the end of the string has been reached.
     *
     * @return True if the end of the string has been reached, false if not.
     *
     * @see TextProvider::getNextLigature()
     */
    bool endOfString();

    /**
     * Gets the next ligature. For most languages this is simply the next Unicode character
     * from the buffer, but e.g. Arabic has different ligatures for each character. Thai
     * character placement might also depend on previous characters. It is recommended to
     * use getNextLigature with font and glyph parameters to ensure coming glyphs in a text
     * are placed correctly.
     *
     * @param  direction The direction.
     *
     * @return The next character of the expanded string or 0 if end of string is reached.
     *
     * @see TextProvider::getNextChar
     *
     * @note Functions getNextLigature() and getNextChar() will advance through the same buffer
     *       and mixing the use of those functions is not recommended and may cause
     *       undesired results. Instead create two TextProviders and user getNextChar() on
     *       one and getNextLigature() on the other.
     */
    Unicode::UnicodeChar getNextLigature(TextDirection direction);

    /**
     * Gets the next ligature. For most languages this is simply the next Unicode character
     * from the buffer, but e.g. Arabic has different ligatures for each character.
     *
     * Also gets a glyph for the ligature in a font. For non-Thai Unicodes, this is
     * identical to using Font::getGlyph(), but for Thai characters where diacritics glyphs
     * are not always placed at the same relative position, an adjusted GlyphNode will be
     * generated with correct relative X/Y coordinates.
     *
     * @param       direction The direction.
     * @param       font      The font.
     * @param [out] glyph     The glyph.
     *
     * @return The next character of the expanded string or 0 if end of string i reached.
     *
     * @see TextProvider::getNextChar, Font::getGlyph
     *
     * @note Functions getNextLigature() and getNextChar() will advance through the same buffer
     *       and mixing the use of those functions is not recommended and may cause
     *       undesired results. Instead create two TextProviders and user getNextChar() on
     *       one and getNextLigature() on the other.
     */
    Unicode::UnicodeChar getNextLigature(TextDirection direction, const Font* font, const GlyphNode*& glyph);

    /**
     * Gets the next ligature. For most languages this is simply the next Unicode character
     * from the buffer, but e.g. Arabic has different ligatures for each character.
     *
     * Also gets a glyph for the ligature in a font. For non-Thai Unicodes, this is
     * identical to using Font::getGlyph(), but for Thai characters where diacritics glyphs
     * are not always placed at the same relative position, an adjusted GlyphNode will be
     * generated with correct relative X/Y coordinates.
     *
     * Furthermore a pointer to the glyph data and the bit depth of the font are returned in
     * parameters.
     *
     * @param       direction    The direction.
     * @param       font         The font.
     * @param [out] glyph        The glyph.
     * @param [out] pixelData    Information describing the pixel.
     * @param [out] bitsPerPixel The bits per pixel.
     *
     * @return The next character of the expanded string or 0 if end of string is reached.
     *
     * @see TextProvider::getNextChar, Font::getGlyph
     *
     * @note Functions getNextLigature() and getNextChar() will advance through the same buffer
     *       and mixing the use of those functions is not recommended and may cause
     *       undesired results. Instead create two TextProviders and user getNextChar() on
     *       one and getNextLigature() on the other.
     */
    Unicode::UnicodeChar getNextLigature(TextDirection direction, const Font* font, const GlyphNode*& glyph, const uint8_t*& pixelData, uint8_t& bitsPerPixel);

private:
    Unicode::UnicodeChar getNextCharInternal();
    const Unicode::UnicodeChar* original_format_string;
    const Unicode::UnicodeChar* format;
    const Unicode::UnicodeChar* subString[2];
    uint8_t nextSubString;
    const Unicode::UnicodeChar* substringPointer;
    bool isWritingWildcard;

    template <unsigned int size>
    class circularBuffer
    {
    public:
        circularBuffer()
            : pos(0), used(0)
        {
        }

        FORCE_INLINE_FUNCTION void flush()
        {
            used = 0;
        }

        FORCE_INLINE_FUNCTION bool isEmpty() const
        {
            return used == 0;
        }

        FORCE_INLINE_FUNCTION bool isFull() const
        {
            return used == size;
        }

        FORCE_INLINE_FUNCTION Unicode::UnicodeChar peekChar()
        {
            assert(used > 0);
            return buffer[pos];
        }

        FORCE_INLINE_FUNCTION Unicode::UnicodeChar peekChar(uint16_t offset)
        {
            assert(offset < used);
            const uint16_t index = pos + offset;
            return buffer[index < size ? index : index - size];
        }

        FORCE_INLINE_FUNCTION void dropFront(uint16_t num = 1)
        {
            assert(used >= num);
            used -= num;
            pos += num;
            if (pos >= size)
            {
                pos -= size;
            }
        }

        Unicode::UnicodeChar popFront()
        {
            assert(used > 0);
            const Unicode::UnicodeChar ch = buffer[pos];
            used--;
            pos++;
            if (pos >= size)
            {
                pos -= size;
            }
            return ch;
        }

        Unicode::UnicodeChar popBack()
        {
            assert(used > 0);
            return peekChar(used-- - 1);
        }

        void allocateFront(uint16_t num)
        {
            assert(used + num <= size);
            used += num;
            if (pos < num)
            {
                pos += size;
            }
            pos -= num;
        }

        void pushFrontForce(Unicode::UnicodeChar newChar)
        {
            // "use" one more entry, if already full overwrite back entry ("used" is unchanged)
            if (used < size)
            {
                used++;
            }
            // Move "pos" one back with overflow check
            if (pos == 0)
            {
                pos += size;
            }
            pos--;
            replaceAt0(newChar);
        }

        void pushFront(Unicode::UnicodeChar newChar)
        {
            allocateFront(1);
            replaceAt0(newChar);
        }

        FORCE_INLINE_FUNCTION void pushBack(Unicode::UnicodeChar newChar)
        {
            assert(used < size);
            replaceAt(++used - 1, newChar);
        }

        FORCE_INLINE_FUNCTION void replaceAt0(Unicode::UnicodeChar newChar)
        {
            buffer[pos] = newChar;
        }

        FORCE_INLINE_FUNCTION void replaceAt1(Unicode::UnicodeChar newChar)
        {
            assert(used > 1);
            const uint16_t index = pos + 1;
            buffer[index < size ? index : 0] = newChar;
        }

        FORCE_INLINE_FUNCTION void replaceAt(uint16_t offset, Unicode::UnicodeChar newChar)
        {
            assert(used > offset);
            const uint16_t index = pos + offset;
            buffer[index < size ? index : index - size] = newChar;
        }

    private:
        Unicode::UnicodeChar buffer[size];
        uint16_t pos;
        uint16_t used;
    };

    static const int NUM_PREV_CHARS = 2;
    static const int NUM_NEXT_CHARS = 10; // input + lookahead + delta(substitution)
    static const int NUM_XTRA_CHARS = 2;
    circularBuffer<NUM_PREV_CHARS> prevCharacters;
    circularBuffer<NUM_NEXT_CHARS> nextCharacters;
    circularBuffer<NUM_XTRA_CHARS> xtraCharacters; // In case we insert
    void replaceInputCharacters(uint16_t existingNumChars, uint16_t newNumChars, const Unicode::UnicodeChar* newChars);
    void fillInputBuffer();
    const uint16_t* fontGsubTable;
    const FontContextualFormsTable* contextualFormsTable;
    void substituteGlyphs();
    uint16_t gsubTableBinarySearch(const uint16_t numEntries, const uint16_t* unicodeLookupTable, const Unicode::UnicodeChar key) const;
    bool applyGsubRules(const uint16_t* nextTableEntry, const Unicode::UnicodeChar key);
    bool gsubRuleMatch(const uint16_t* tableEntry, uint16_t backtrack, uint16_t input, uint16_t lookahead);

    void initializeInternal();
    void unicodeConverterInit();
    Unicode::UnicodeChar unicodeConverter(const TextDirection direction);

    FORCE_INLINE_FUNCTION const Unicode::UnicodeChar* binarySearch(uint16_t key, const Unicode::UnicodeChar contextualFormTable[][5], int maxIndex) const;
    FORCE_INLINE_FUNCTION const Unicode::UnicodeChar* contextualFormForChar(const Unicode::UnicodeChar currChar) const;

    FORCE_INLINE_FUNCTION void adjustGlyph(Unicode::UnicodeChar originalCharacter, Unicode::UnicodeChar currentCharacter, const GlyphNode*& glyph, const Font* font);
    const GlyphNode* adjustHindiGlyph(const GlyphNode* glyph);
    const GlyphNode* thaiLookupGlyph(const GlyphNode* glyph, const Font* font, Unicode::UnicodeChar unicode) const;
    const GlyphNode* adjustThaiGlyph(const Font* font, const GlyphNode* glyph);
    const GlyphNode* adjustArabicGlyph(const Font* font, const GlyphNode* glyph, Unicode::UnicodeChar originalUnicode);
    GlyphNode modifiedGlyph;
    int16_t glyphPosTop;
    int16_t glyphPosBottom;
    int16_t glyphPosLeft;

    bool isContextualBeginning;
    bool lastGlyphIsAccent;
};

} // namespace touchgfx

#endif // TOUCHGFX_TEXTPROVIDER_HPP
