/* DO NOT EDIT THIS FILE */
/* This file is autogenerated by the text-database code generator */

#include <fonts/FontCache.hpp>
#include <fonts/CachedFont.hpp>
#include <texts/TypedTextDatabase.hpp>
#include <touchgfx/TextProvider.hpp>
#include <touchgfx/Utils.hpp>
#include <cstring>

namespace touchgfx
{
FontCache::FontCache()
    : memorySize(0), memory(0), top(0), gsubStart(0), reader(0)
{
}

void FontCache::clear(bool keepGsubTable /* = false */)
{
    memset(fontTable, 0, sizeof(fontTable));

    //top is beginning of memory, no glyphs are cached yet
    top = memory;

    if (!keepGsubTable)
    {
        //gsubStart points to end of memory (nothing loaded yet)
        gsubStart = memory + memorySize;
    }
}

void FontCache::setMemory(uint8_t* _memory, uint32_t size)
{
    memory = _memory;
    memorySize = size;

    clear();
}

void FontCache::setReader(FontDataReader* _reader)
{
    reader = _reader;
}

const GlyphNode* FontCache::getGlyph(Unicode::UnicodeChar unicode, FontId font) const
{
    GlyphNode* g = (GlyphNode*)fontTable[font].first;
    while (g)
    {
        if (g->unicode == unicode)
        {
            return g;
        }
        GlyphNode** next = (GlyphNode**)((uint8_t*)g + SizeGlyphNode);
        g = *next;
    }
    return 0;
}

void FontCache::open()
{
    if (reader)
    {
        reader->open();
    }
}

void FontCache::close()
{
    if (reader)
    {
        reader->close();
    }
}

void FontCache::initializeCachedFont(TypedText t, CachedFont* font, bool loadGsubTable /*= false*/)
{
    //get font index from typed text
    FontId fontId = t.getFontId();
    //reset to start of file
    open();
    setPosition(0);

    assert(sizeof(touchgfx::BinaryFontData) < MAX_BUFFER_SIZE);
    readData(buffer, sizeof(touchgfx::BinaryFontData));
    const struct touchgfx::BinaryFontData* binaryFontData = reinterpret_cast<const struct touchgfx::BinaryFontData*>(buffer);

    const Font** flashFonts = TypedTextDatabase::getFonts();
    const GeneratedFont* flashFont = static_cast<const GeneratedFont*>(flashFonts[fontId]);
    *font = CachedFont(reinterpret_cast<const struct touchgfx::BinaryFontData*>(buffer), fontId, this, flashFont);

    if (loadGsubTable)
    {
        setPosition(binaryFontData->offsetToGSUB);

        const uint32_t sizeOfGSUB = binaryFontData->sizeOfFontData - binaryFontData->offsetToGSUB;
        if (top + sizeOfGSUB < gsubStart) //room for this GSUB table
        {
            uint8_t* const gsubPosition = gsubStart - sizeOfGSUB;
            readData(gsubPosition, sizeOfGSUB);
            font->setGSUBTable(reinterpret_cast<uint16_t*>(gsubPosition));
            gsubStart -= sizeOfGSUB;
        }
        else
        {
            font->setGSUBTable(0);
        }
    }
    close();
}

bool FontCache::cacheString(TypedText t, const Unicode::UnicodeChar* string)
{
    open();
    if (!createSortedString(string))
    {
        close();
        return false;
    }
    const bool result = cacheSortedString(t);
    close();
    return result;
}

bool FontCache::cacheLigatures(CachedFont* font, TypedText t, const Unicode::UnicodeChar* string)
{
    open();
    if (!createSortedLigatures(font, t, string, 0, 0))
    {
        close();
        return false;
    }
    const bool result = cacheSortedString(t);
    close();
    return result;
}

bool FontCache::cacheSortedString(TypedText t)
{
    setPosition(8); //skip font index and size
    uint32_t glyphNodeOffset;
    uint32_t dummy;
    readData(&glyphNodeOffset, sizeof(uint32_t));
    readData(&dummy, sizeof(uint32_t));
    readData(&glyphDataOffset, sizeof(uint32_t));
    readData(&dummy, sizeof(uint32_t));
    readData(&numGlyphs, sizeof(uint16_t));

    FontId fontId = t.getFontId(); // Get font index from typed text
    uint32_t bpp = t.getFont()->getBitsPerPixel(); // Get BPP from standard font

    setPosition(glyphNodeOffset); // Go to glyph nodes for font
    currentFileGlyphNumber = 0;
    currentFileGlyphNode.unicode = 0; // Force reading of first glyph

    const Unicode::UnicodeChar* string = sortedString;
    Unicode::UnicodeChar last = 0;
    GlyphNode* firstNewGlyph = 0;
    bool outOfMemory = false;
    while (*string)
    {
        Unicode::UnicodeChar ch = *string;
        if (ch != last)
        {
            if (!contains(ch, fontId))
            {
                insert(ch, fontId, bpp, outOfMemory);
                if (outOfMemory)
                {
                    break;
                }
                if (firstNewGlyph == 0)
                {
                    firstNewGlyph = (GlyphNode*)fontTable[fontId].last;
                }
            }
        }
        last = ch;
        string++;
    }

    cacheData(bpp, firstNewGlyph);
    return !outOfMemory;
}

bool FontCache::contains(Unicode::UnicodeChar unicode, FontId font) const
{
    GlyphNode* g = (GlyphNode*)fontTable[font].first;
    while (g)
    {
        if (g->unicode == unicode)
        {
            return true;
        }
        GlyphNode** next = (GlyphNode**)((uint8_t*)g + SizeGlyphNode);
        g = *next;
    }
    return false;
}

void FontCache::insert(Unicode::UnicodeChar unicode, FontId font, uint32_t bpp, bool& outOfMemory)
{
    //insert new glyphnode and glyph after last for font.
    uint8_t* oldTop = top;
    top = copyGlyph(top, unicode, font, bpp, outOfMemory);

    if (top == oldTop)
    {
        return;
    }

    if (fontTable[font].last == 0)
    {
        //first glyph
        fontTable[font].first = oldTop;
        fontTable[font].last = oldTop;
    }
    else
    {
        //set next pointer of old last glyph
        uint8_t** old_next = (uint8_t**)(fontTable[font].last + SizeGlyphNode);
        *old_next = oldTop;

        //save new glyph as last glyph
        fontTable[font].last = oldTop;
    }
}

uint8_t* FontCache::copyGlyph(uint8_t* top, Unicode::UnicodeChar unicode, FontId font, uint32_t bpp, bool& outOfMemory)
{
    while (currentFileGlyphNumber < numGlyphs && currentFileGlyphNode.unicode < unicode)
    {
        readData(&currentFileGlyphNode, sizeof(GlyphNode));
        currentFileGlyphNumber++;
    }
    if (currentFileGlyphNode.unicode != unicode)
    {
        //glyphnode not found
        return top;
    }

    //glyphnode found
    uint32_t glyphSize = ((currentFileGlyphNode.width() + 1) & ~1) * currentFileGlyphNode.height() * bpp / 8;
    glyphSize = (glyphSize + 3) & ~0x03;
    uint32_t requiredMem = SizeGlyphNode + 4 + glyphSize; // GlyphNode + next ptr + glyph

    //is space available before sortedString
    if (top + requiredMem > (uint8_t*)sortedString)
    {
        outOfMemory = true;
        return top;
    }

    *(GlyphNode*)top = currentFileGlyphNode;

    //clear next pointer
    uint8_t** next = (uint8_t**)(top + SizeGlyphNode);
    *next = 0;
    top += requiredMem;
    return top;
}

void FontCache::cacheData(uint32_t bpp, GlyphNode* first)
{
    GlyphNode* gn = first;
    while (gn)
    {
        uint8_t* p = (uint8_t*)gn;
        if (gn->dataOffset != 0xFFFFFFFF)
        {
            p += SizeGlyphNode;
            //next pointer
            p += 4;

            //seek and copy
            setPosition(glyphDataOffset + gn->dataOffset);
            uint32_t glyphSize = ((gn->width() + 1) & ~1) * gn->height() * bpp / 8;
            readData(p, glyphSize);

            //mark glyphNode as cached
            gn->dataOffset = 0xFFFFFFFF;
        }

        GlyphNode** next = (GlyphNode**)((uint8_t*)gn + SizeGlyphNode);
        gn = *next;
    }
}

bool FontCache::createSortedString(const Unicode::UnicodeChar* string)
{
    int length = Unicode::strlen(string);
    //sorted string is allocated at end of buffer
    sortedString = (Unicode::UnicodeChar*)(gsubStart - (length + 1) * 2);
    if ((uint8_t*)sortedString < top)
    {
        //unable to allocate string buffer in end of memory
        return false;
    }
    int n = 0;
    Unicode::UnicodeChar* uc = sortedString;
    while (*string)
    {
        *uc++ = *string++;
        n++;
    }
    *uc = 0;
    return sortSortedString(n);
}

bool FontCache::createSortedLigatures(CachedFont* font, TypedText t, const Unicode::UnicodeChar* string, ...)
{
    va_list pArg;
    va_start(pArg, string);
    TextProvider tp;
    tp.initialize(string, pArg, font->getGSUBTable());
    va_end(pArg);
    Unicode::UnicodeChar ligature;
    sortedString = (Unicode::UnicodeChar*)(gsubStart);
    if ((uint8_t*)(sortedString - 1) < top)
    {
        return false;
    }
    *--sortedString = 0;
    int n = 0;
    while ((ligature = tp.getNextLigature(t.getTextDirection())) != 0)
    {
        if ((uint8_t*)(sortedString - 1) < top)
        {
            return false;
        }
        *--sortedString = ligature;
        n++;
    }
    return sortSortedString(n);
}

bool FontCache::sortSortedString(int n)
{
    Unicode::UnicodeChar* uc = sortedString;
    for (int i = 0; i < n - 1; i++)
    {
        bool swapped = false;
        for (int j = 0; j < n - i - 1; j++)
        {
            if (uc[j] > uc[j + 1])
            {
                Unicode::UnicodeChar temp = uc[j];
                uc[j] = uc[j + 1];
                uc[j + 1] = temp;
                swapped = true;
            }
        }

        //if no two elements were swapped by inner loop, then break
        if (!swapped)
        {
            break;
        }
    }
    return true;
}

void FontCache::setPosition(uint32_t position)
{
    if (reader)
    {
        reader->setPosition(position);
    }
}

void FontCache::readData(void* out, uint32_t numberOfBytes)
{
    if (reader)
    {
        reader->readData(out, numberOfBytes);
    }
}
} // namespace touchgfx
