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

#include <fonts/CompressedUnmappedFontCache.hpp>
#include <touchgfx/hal/HAL.hpp>
#include <touchgfx/hal/Paint.hpp>

namespace touchgfx
{
LOCATION_PRAGMA_32("TouchGFX_CompressedUnmappedFontCache")
uint32_t CompressedUnmappedFontCache::bitmapFontCache[cacheWords] LOCATION_ATTRIBUTE_32("TouchGFX_CompressedUnmappedFontCache");
uint8_t* CompressedUnmappedFontCache::pixelsTop = (uint8_t*)CompressedUnmappedFontCache::bitmapFontCache;
int CompressedUnmappedFontCache::glyphsAllocated = 0;
int CompressedUnmappedFontCache::cacheClearCounter = 0;

namespace
{
/* Read nibbles from compressed data in unmapped flash. Must read through reader object */
struct NibUnmapReader
{
    NibUnmapReader(const uint8_t* srcAddress, touchgfx::FlashDataReader* reader)
        : address(srcAddress), reader(reader), byteIndex(0), low(true)
        {
            address += bufferSize;
            reader->copyData(srcAddress, buffer, sizeof(buffer));
        }

    const uint8_t* address; //next reading address
    touchgfx::FlashDataReader* reader;
    static const int bufferSize = 16;
    uint8_t buffer[bufferSize];
    uint8_t value;
    int byteIndex;
    bool low;
    uint8_t getNext()
    {
        if (low)
        {
            value = buffer[byteIndex];
            const uint8_t val = value & 0xF;
            low = false;
            return val;
        }
        const uint8_t val = value >> 4;
        low = true;
        if (byteIndex == bufferSize - 1)
        {
            reader->copyData(address, buffer, bufferSize);
            address += bufferSize;
            byteIndex = 0;
        }
        else
        {
            byteIndex++;
        }

        return val;
    }
};

struct NibWriter
{
    NibWriter(uint8_t* buffer, int size)
        : buffer(buffer), bufferEnd(buffer + size), low(true) {}

    uint8_t* buffer;
    uint8_t* bufferEnd;
    int low;
    void put(uint8_t v)
    {
        if (low)
        {
            *buffer = v;
            low = false;
            return;
        }
        *buffer |= (v << 4);
        buffer++;
        low = true;
    }
    bool eof() { return buffer >= bufferEnd; }
};
} // anonymous namespace

uint8_t* CompressedUnmappedFontCache::decompressGlyph(uint8_t* pixelsTop, const GlyphNode* glyphNode, const uint8_t* compressedData, touchgfx::FlashDataReader* flashReader)
{
    const int byteSize = (glyphNode->width() + 1) / 2 * glyphNode->height();
    NibWriter writer(pixelsTop, byteSize);
    NibUnmapReader reader(compressedData, flashReader);
    uint8_t* const pixelsEnd = pixelsTop + byteSize;
    const int algorithm = glyphNode->dataOffset >> 30;
    if (algorithm == 1)
    {
        // RLE1
        while (!writer.eof())
        {
            const uint8_t value = reader.getNext();
            if (value == 0)
            {
                int zeroCount = reader.getNext();
                while (zeroCount)
                {
                    writer.put(0);
                    zeroCount--;
                }
            }
            writer.put(value);
        }
    }
    else if (algorithm == 2)
    {
        // RLE2
        while (!writer.eof())
        {
            const uint8_t value = reader.getNext();
            if (value == 0 || value == 0xF)
            {
                int count = reader.getNext();
                while (count)
                {
                    writer.put(value);
                    count--;
                }
            }
            writer.put(value);
        }
    }
    else
    {
        // Uncompressed
        flashReader->copyData(compressedData, pixelsTop, byteSize);
    }

    return pixelsEnd;
}

void CompressedUnmappedFontCache::clearCache()
{
    // Wait for DMA2D/GPU2D to finish drawing
    HAL::getInstance()->lockUnlockFrameBuffer();
    glyphsAllocated = 0;
    cacheClearCounter++;
    pixelsTop = (uint8_t*)bitmapFontCache;
}

void CompressedUnmappedFontCache::unableToCache(const GlyphNode* glyphNode, int byteSize)
{
    while(1);
}

const GlyphNode* CompressedUnmappedFontCache::hasCachedGlyphNode(const GlyphNode* glyphNode)
{
    const BitmapFontCacheKey* end = (const BitmapFontCacheKey*)&bitmapFontCache[cacheWords];
    const BitmapFontCacheKey* first = end - glyphsAllocated;
    while (first < end)
    {
        if (first->glyphNodeSrc == glyphNode)
        {
            return &first->glyphNodeCopy;
        }
        first++;
    }
    return 0;
}

const GlyphNode* CompressedUnmappedFontCache::hasCachedGlyphData(const GlyphNode* glyphNode, const uint8_t*& pixelData)
{
    const BitmapFontCacheKey* end = (const BitmapFontCacheKey*)&bitmapFontCache[cacheWords];
    const BitmapFontCacheKey* first = end - glyphsAllocated;
    while (first < end)
    {
        if (first->glyphNodeSrc == glyphNode)
        {
            //Pixel data can be zero!
            pixelData = first->pixels;
            return &first->glyphNodeCopy;
        }
        first++;
    }
    return 0;
}

GlyphNode* CompressedUnmappedFontCache::cacheGlyphNode(const GlyphNode* glyph)
{
    const BitmapFontCacheKey* end = (const BitmapFontCacheKey*)&bitmapFontCache[cacheWords];
    if (pixelsTop + sizeof(BitmapFontCacheKey) > (const uint8_t*)&end[-glyphsAllocated])
    {
        // Unable to fit GlyphNode in data, clear cache
        clearCache();
    }

    glyphsAllocated++;
    BitmapFontCacheKey* key = const_cast<BitmapFontCacheKey*>(end - glyphsAllocated);
    key->glyphNodeSrc = glyph;
    key->pixels = 0;

    return &key->glyphNodeCopy;
}

const uint8_t* CompressedUnmappedFontCache::cacheGlyphData(const GlyphNode* glyph, const uint8_t* compressedData, touchgfx::FlashDataReader* reader)
{
    // Search keys for glyph
    BitmapFontCacheKey* end = (BitmapFontCacheKey*)&bitmapFontCache[cacheWords];
    BitmapFontCacheKey* it = end - glyphsAllocated;
    while (it < end)
    {
        if (it->glyphNodeSrc == glyph)
        {
            break;
        }
        it++;
    }

    if (it == end)
    {
        // broken invariant!
        return 0;
    }

    const int byteSize = (it->glyphNodeCopy.width() + 1) / 2 * it->glyphNodeCopy.height();
    // Check if pixels can fit in cache
    if (pixelsTop + byteSize > (const uint8_t*)&end[-glyphsAllocated])
    {
        // Can it fit in a clean cache?
        if (byteSize + sizeof(BitmapFontCacheKey) > cacheSizeBytes)
        {
            unableToCache(glyph, byteSize);
            return 0;
        }

        // Unable to fit pixel in data, must clear cache

        // Save address of GlyphNode in cache
        const GlyphNode* cachedGn = &it->glyphNodeCopy;

        // Now clear (reset pointer)
        clearCache();

        // Insert glyph node again
        glyphsAllocated++;
        it = const_cast<BitmapFontCacheKey*>(end - 1);
        it->glyphNodeSrc = glyph;
        it->pixels = 0;
        // Copy GlyphNode content from previous position in cache
        it->glyphNodeCopy = *cachedGn;
    }

    //Now decompress data
    const uint8_t* glyphData = pixelsTop;
    pixelsTop = decompressGlyph(pixelsTop, &it->glyphNodeCopy, compressedData, reader);

    // Flush the cache lines, must be 32byte aligned
    uint8_t* rowStart = reinterpret_cast<uint8_t*>(reinterpret_cast<uintptr_t>(glyphData) & ~0x1F);
    int alignmentOffset = glyphData - rowStart;
    paint::flushLine((uint32_t*)rowStart, byteSize + alignmentOffset);
    paint::invalidateTextureCache();

    it->pixels = glyphData;

    return glyphData;
}

} // namespace touchgfx