/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 *
 * Copyright (c) 2002 by Greg Haerr <greg@censoft.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/
/*
 * Rockbox startup font initialization
 * This file specifies which fonts get compiled-in and
 * loaded at startup, as well as their mapping into
 * the FONT_SYSFIXED, FONT_UI and FONT_MP3 ids.
 */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <stddef.h>

#include "config.h"
#include "system.h"
#include "kernel.h"
#include "lcd.h"
#include "string-extra.h"
#include "font.h"
#include "file.h"
#include "core_alloc.h"
#include "debug.h"
#include "panic.h"
#include "rbunicode.h"
#include "diacritic.h"
#include "rbpaths.h"

/* Define LOGF_ENABLE to enable logf output in this file */
//#define LOGF_ENABLE
#include "logf.h"

#define MAX_FONTSIZE_FOR_16_BIT_OFFSETS 0xFFDB

#define FONT_EXT "fnt"
#define GLYPH_CACHE_EXT "gc"

#ifdef UNICODE32
#define FC_HEADER_VAL 0x01000020
#else
#define FC_HEADER_VAL 0x01000010
#endif

/* max static loadable font buffer size */
#ifndef MAX_FONT_SIZE
#if LCD_HEIGHT > 64
#if MEMORYSIZE > 2
#define MAX_FONT_SIZE   60000
#else
#define MAX_FONT_SIZE   10000
#endif
#else
#define MAX_FONT_SIZE   4000
#endif
#endif
#define GLYPHS_TO_CACHE 256

#if MEMORYSIZE < 4
#define FONT_HARD_LIMIT
#endif

#ifndef FONT_HEADER_SIZE
#define FONT_HEADER_SIZE 36
#endif

#ifndef BOOTLOADER
/* Font cache includes */
#include "font_cache.h"
#include "lru.h"
#endif

#ifndef O_BINARY
#define O_BINARY 0
#endif

/* Define this to try loading /.rockbox/.glyphcache    *
 * when a font specific file fails. This requires the  *
 * user to copy and rename a font glyph cache file     */
//#define TRY_DEFAULT_GLYPHCACHE

/* compiled-in font */
extern struct font sysfont;

#if !defined(BOOTLOADER) || defined(SONY_NWZ_LINUX) || defined(HIBY_LINUX) || defined(FIIO_M3K_LINUX)

struct buflib_alloc_data {
    struct font font;    /* must be the first member! */
    char *path; /* font path and filename (allocd at end of buffer) */
    size_t path_bufsz; /* size of path buffer */
    int refcount;        /* how many times has this font been loaded? */
    unsigned char buffer[];
};
static int buflib_allocations[MAXFONTS];

static int cache_fd;
static struct font* cache_pf;

static void glyph_cache_save(int font_id);

static int buflibmove_callback(int handle, void* current, void* new)
{
    (void)handle;
    struct buflib_alloc_data *alloc = (struct buflib_alloc_data*)current;
    ptrdiff_t diff = new - current;

    logf("%s %s", __func__, alloc->path);
#define UPDATE(x) if (x) { x = PTR_ADD(x, diff); }

    UPDATE(alloc->font.bits);
    UPDATE(alloc->font.offset);
    UPDATE(alloc->font.width);

    UPDATE(alloc->font.buffer_start);
    UPDATE(alloc->font.buffer_end);
    UPDATE(alloc->font.buffer_position);
    UPDATE(alloc->path);

    UPDATE(alloc->font.cache._index);
    UPDATE(alloc->font.cache._lru._base);
    return BUFLIB_CB_OK;
}
static void lock_font_handle(int handle, bool lock)
{
    if ( handle < 0 )
        return;

    if (lock)
        core_pin(handle);
    else
        core_unpin(handle);
}

void font_lock(int font_id, bool lock)
{
    if( font_id < 0 || font_id >= MAXFONTS )
        return;
    if( buflib_allocations[font_id] > 0 )
        lock_font_handle(buflib_allocations[font_id], lock);
}

static struct buflib_callbacks buflibops = {buflibmove_callback, NULL, NULL };

static inline unsigned char *buffer_from_handle(int handle)
{
    struct buflib_alloc_data *alloc = core_get_data(handle);
    unsigned char* buffer = alloc->buffer;
    return buffer;
}

/* Font cache structures */
static void cache_create(struct font* pf);
static NO_INLINE void glyph_cache_load(const char *font_path, struct font *pf);
/* End Font cache structures */

void font_init(void)
{
    int i = 0;
    cache_fd = -1;
    while (i<MAXFONTS)
        buflib_allocations[i++] = -1;
}

/* Check if we have x bytes left in the file buffer */
#define HAVEBYTES(x) (pf->buffer_position + (x) <= pf->buffer_end)

/* Helper functions to read big-endian unaligned short or long from
   the file buffer.  Bounds-checking must be done in the calling
   function.
 */

static short readshort(struct font *pf)
{
    uint16_t s;

    s = *pf->buffer_position++ & 0xff;
    s |= (*pf->buffer_position++ << 8);
    return s;
}

static int32_t readlong(struct font *pf)
{
    uint32_t l;

    l = *pf->buffer_position++ & 0xff;
    l |= *pf->buffer_position++ << 8;
    l |= ((uint32_t)(*pf->buffer_position++)) << 16;
    l |= ((uint32_t)(*pf->buffer_position++)) << 24;
    return l;
}

static int glyph_bytes( struct font *pf, int width )
{
    int ret;
    if (pf->depth)
        ret = ( pf->height * width + 1 ) / 2;
    else
        ret = width * ((pf->height + 7) / 8);
    return (ret + 1) & ~1;
}

/* Load memory font */
static struct font* font_load_in_memory(struct font* pf,
                                        int32_t nwidth,
                                        int32_t noffset )
{
    int i;
    /* variable font data*/
    pf->buffer_position = pf->buffer_start + 36;
    pf->bits = (unsigned char *)pf->buffer_position;
    pf->buffer_position += pf->bits_size*sizeof(unsigned char);

    if (pf->bits_size < MAX_FONTSIZE_FOR_16_BIT_OFFSETS)
    {
        /* pad to 16-bit boundary */
        pf->buffer_position = (unsigned char *)(((intptr_t)pf->buffer_position + 1) & ~1);
    }
    else
    {
        /* pad to 32-bit boundary*/
        pf->buffer_position = (unsigned char *)(((intptr_t)pf->buffer_position + 3) & ~3);
    }

    if (noffset)
    {
        if (pf->bits_size < MAX_FONTSIZE_FOR_16_BIT_OFFSETS)
        {
            pf->long_offset = 0;
            pf->offset = (uint16_t*)pf->buffer_position;

            /* Check we have sufficient buffer */
            if (!HAVEBYTES(noffset * sizeof(uint16_t)))
                return NULL;

            for (i=0; i<noffset; ++i)
            {
                ((uint16_t*)(pf->offset))[i] = (uint16_t)readshort(pf);
            }
        }
        else
        {
            pf->long_offset = 1;
            pf->offset = (uint16_t*)pf->buffer_position;

            /* Check we have sufficient buffer */
            if (!HAVEBYTES(noffset * sizeof(int32_t)))
                return NULL;

            for (i=0; i<noffset; ++i)
            {
                ((uint32_t*)(pf->offset))[i] = (uint32_t)readlong(pf);
            }
        }
    }
    else
        pf->offset = NULL;

    if (nwidth) {
        pf->width = (unsigned char *)pf->buffer_position;
        pf->buffer_position += nwidth*sizeof(unsigned char);
    }
    else
        pf->width = NULL;

    if (pf->buffer_position > pf->buffer_end)
        return NULL;

    return pf;    /* success!*/
}

/* Load cached font */
static struct font* font_load_cached(struct font* pf,
                                     int32_t nwidth,
                                     int32_t noffset)
{
    /* We are now at the bitmap data, this is fixed at 36.. */
    pf->width = NULL;
    pf->bits = NULL;

    /* Calculate offset to offset data */
    pf->buffer_position += pf->bits_size * sizeof(unsigned char);

    if (pf->bits_size < MAX_FONTSIZE_FOR_16_BIT_OFFSETS)
    {
        pf->long_offset = 0;
        /* pad to 16-bit boundary */
        pf->buffer_position = (unsigned char *)(((intptr_t)pf->buffer_position + 1) & ~1);
    }
    else
    {
        pf->long_offset = 1;
        /* pad to 32-bit boundary*/
        pf->buffer_position = (unsigned char *)(((intptr_t)pf->buffer_position + 3) & ~3);
    }

    if (noffset)
        pf->file_offset_offset = (uint32_t)(pf->buffer_position - pf->buffer_start);
    else
        pf->file_offset_offset = 0;

    /* Calculate offset to widths data */
    if (pf->bits_size < MAX_FONTSIZE_FOR_16_BIT_OFFSETS)
        pf->buffer_position += noffset * sizeof(uint16_t);
    else
        pf->buffer_position += noffset * sizeof(uint32_t);

    if (nwidth)
        pf->file_width_offset = (uint32_t)(pf->buffer_position - pf->buffer_start);
    else
        pf->file_width_offset = 0;

    /* Create the cache */
    cache_create(pf);

    return pf;
}

bool font_filename_matches_loaded_id(int font_id, const char *filename)
{
    if ( font_id >= 0 && font_id < MAXFONTS )
    {
        int handle = buflib_allocations[font_id];
        if (handle > 0)
        {
            struct buflib_alloc_data *data = core_get_data(handle);
            logf("%s id: [%d], %s", __func__, font_id, data->path);
            return strcmp(data->path, filename) == 0;
        }
    }
    return false;
}

static int find_font_index(const char* path)
{
    for(int index = 0; index < MAXFONTS; index++)
    {
        if(font_filename_matches_loaded_id(index, path))
        {
            logf("%s Found id: [%d], %s", __func__, index, path);
            return index;
        }
    }
    logf("%s %s Not found using id: [%d], FONT_SYSFIXED",
         __func__, path, FONT_SYSFIXED);
    return FONT_SYSFIXED;
}

static size_t font_glyphs_to_bufsize(struct font *pf, int glyphs)
{
    size_t bufsize;

    /* LRU bytes per glyph */
    bufsize = LRU_SLOT_OVERHEAD + sizeof(struct font_cache_entry) +
        sizeof(unsigned short);
    /* Image bytes per glyph */
    bufsize += glyph_bytes(pf, pf->maxwidth);
    bufsize *= glyphs;

    return bufsize;
}

static struct font* font_load_header(int fd, struct font *pheader,
                                     struct font *pf,
                                     uint32_t *nwidth, uint32_t *noffset)
{
    /* Load the header. Readshort() and readlong()              *
     * update buffer_position address as they read              */
    pheader->buffer_start = pheader->buffer_position = (char *)pheader;
    pheader->buffer_size = FONT_HEADER_SIZE;
    pheader->buffer_end = pheader->buffer_start + pheader->buffer_size;

    if (read(fd, pheader, FONT_HEADER_SIZE) != FONT_HEADER_SIZE)
        return NULL;

    /* read magic and version #*/
    if (memcmp(pheader->buffer_position, VERSION, 4) != 0)
        return NULL;

    pheader->buffer_position += 4;

    /* font info*/
    pf->maxwidth      = readshort(pheader);
    pf->height        = readshort(pheader);
    pf->ascent        = readshort(pheader);
    pf->depth         = readshort(pheader);
    pf->firstchar     = readlong(pheader);
    pf->defaultchar   = readlong(pheader);
    pf->size          = readlong(pheader);

    /* get variable font data sizes*/
    /* # words of bitmap_t*/
    pf->bits_size     = readlong(pheader);
    *noffset          = readlong(pheader);
    *nwidth           = readlong(pheader);

    return pf;
}

/* load a font with room for glyphs, limited to bufsize if not zero */
int font_load_ex( const char *path, size_t buf_size, int glyphs )
{
    /* needed to handle the font properly after it's loaded */
    size_t path_len = strlen(path);
    if ( path_len >= MAX_PATH )
        return -1;

    //printf("\nfont_load_ex(%s, %d, %d)\n", path, buf_size, glyphs);
    int fd = open(path, O_RDONLY|O_BINARY);
    if ( fd < 0 )
        return -1;

#ifdef UNICODE32
    if (glyphs && glyphs < 3)
        glyphs = 3; /* Guarantee we'll always have at least 2 after alignment */
#else
    if (glyphs && glyphs < 2)
        glyphs = 2; /* Guarantee we'll always have at least 1 after alignment */
#endif

    /* load font struct f with file header */
    int file_size = filesize( fd );
    struct font header;
    struct font f;

    uint32_t nwidth, noffset;
    if ( !font_load_header( fd, &header, &f, &nwidth, &noffset )
#if LCD_DEPTH < 16
        || f.depth
#endif
    )
    {
        close(fd);
        return -1;
    }

    /* examine f and calc buffer size */
    bool cached = false;
    size_t bufsize = buf_size;
    size_t glyph_buf_size = font_glyphs_to_bufsize( &f, glyphs );

    if ( bufsize && glyphs && bufsize > glyph_buf_size)
         bufsize = glyph_buf_size;
    else
    {
        if ( glyphs )
            bufsize = glyph_buf_size;
        else
            bufsize = MAX_FONT_SIZE;
    }
#ifdef FONT_HARD_LIMIT
    if ( bufsize > MAX_FONT_SIZE )
        bufsize = MAX_FONT_SIZE;
#endif
    if ( bufsize < (size_t) file_size )
        cached = true;
    else
        bufsize = file_size;

    /* check already loaded */
    int font_id = find_font_index(path);

    if (font_id > FONT_SYSFIXED)
    {
        /* already loaded, no need to reload */
        struct buflib_alloc_data *pd = core_get_data(buflib_allocations[font_id]);
        if (pd->font.buffer_size < bufsize || pd->path_bufsz < path_len)
        {
            int old_refcount, old_id;
            size_t old_bufsize = pd->font.buffer_size;
            bool failed = false;
            /* reload the font:
             * 1) save of refcont and id
             * 2) force unload (set refcount to 1 to make sure it get unloaded)
             * 3) reload with the larger buffer
             * 4) restore the id and refcount
             */
            old_id = font_id;
            old_refcount = pd->refcount;
            pd->refcount = 1;
            font_unload(font_id);
            font_id = font_load_ex(path, bufsize, glyphs);
            if (font_id < 0)
            {
                failed = true;
                font_id = font_load_ex(path, old_bufsize, 0);
                /* we couldn't even get the old size, this shouldn't happen */
                if ( font_id < 0 )
                    return -1;
            }
            if (old_id != font_id)
            {
                buflib_allocations[old_id] = buflib_allocations[font_id];
                buflib_allocations[font_id] = -1;
                font_id = old_id;
            }
            pd = core_get_data(buflib_allocations[font_id]);
            pd->refcount = old_refcount;
            if(failed)
                /* return error because we didn't satisfy the new buffer size */
                return -1;
        }
        pd->refcount++;
        //printf("reusing handle %d for %s (count: %d)\n", font_id, path, pd->refcount);
        close(fd);
        return font_id;
    }

    int open_slot = -1;

    for (font_id = FONT_FIRSTUSERFONT; font_id < MAXFONTS; font_id++)
    {
        if (buflib_allocations[ font_id ] < 0)
        {
            open_slot = font_id;
            break;
        }
    }
    if ( open_slot == -1 )
        return -1;
    font_id = open_slot;
    size_t path_bufsz = MAX(path_len + 1, 64); /* enough size for common case */
    /* allocate mem */
    int handle = core_alloc_ex(
                     bufsize + path_bufsz + sizeof( struct buflib_alloc_data ),
                     &buflibops );
    if ( handle <= 0 )
    {
        return -1;
    }
    struct buflib_alloc_data *pdata;

    pdata = core_get_data_pinned(handle);
    pdata->refcount     = 1;
    pdata->path = pdata->buffer + bufsize;
    /* save load path so we can recognize this font later */
    memcpy(pdata->path, path, path_len+1);

    /* load and init */
    struct font *pf = &pdata->font;
    memcpy(pf, &f, sizeof( struct font) );

    pf->fd = fd;
    pf->fd_width = pf->fd_offset = -1;
    pf->handle = handle;
    pf->disabled = false;

    pf->buffer_start = buffer_from_handle( pf->handle );
    pf->buffer_position = pf->buffer_start + FONT_HEADER_SIZE;
    pf->buffer_size = bufsize;
    pf->buffer_end = pf->buffer_start + bufsize;

    if ( cached )
    {
        if ( ! font_load_cached( pf, nwidth, noffset ) )
        {
            core_free( handle );
            return -1;
        }

        /* trick to get a small cache for each file section   *
         * during glyph_cache_load()                          */
        pf->fd_width  = open( path, O_RDONLY|O_BINARY );
        pf->fd_offset = open( path, O_RDONLY|O_BINARY );

        glyph_cache_load( path, pf );

        /* cached font: pf->fd stays open until the font is unloaded */
        close( pf->fd_width );
        pf->fd_width = -1;
        close( pf->fd_offset );
        pf->fd_offset = -1;
    }
    else
    {
        lseek( fd, 0, SEEK_SET);
        read(fd, pf->buffer_start, pf->buffer_size);

        close( fd );
        pf->fd = -1;

        if ( ! font_load_in_memory( pf, nwidth, noffset ) )
        {
            core_free( handle );
            return -1;
        }
    }
    buflib_allocations[font_id] = handle;
    //printf("%s -> [%d] -> %d\n", path, font_id, *handle);
    core_put_data_pinned(pdata);
    logf("%s id: [%d], %s", __func__, font_id, path);
    return font_id; /* success!*/
}

int font_load(const char *path)
{
    return font_load_ex(path, MAX_FONT_SIZE, GLYPHS_TO_CACHE);
}

void font_unload(int font_id)
{
    if ( font_id < 0 || font_id >= MAXFONTS )
        return;
    int handle = buflib_allocations[font_id];
    if ( handle < 0 )
        return;
    struct buflib_alloc_data *pdata = core_get_data(handle);
    struct font* pf = &pdata->font;
    pdata->refcount--;
    if (pdata->refcount < 1)
    {
        logf("%s %s", __func__, pdata->path);
        if (pf && pf->fd >= 0)
        {
            glyph_cache_save(font_id);
            close(pf->fd);
        }
        core_free(handle);
        buflib_allocations[font_id] = -1;

    }
}

void font_unload_all(void)
{
    int i;
    for (i=0; i<MAXFONTS; i++)
    {
        if (buflib_allocations[i] > 0)
        {
            struct buflib_alloc_data *alloc = core_get_data(buflib_allocations[i]);
            alloc->refcount = 1; /* force unload */
            font_unload(i);
        }
    }
}

static void font_disable(int font_id)
{
    if ( font_id < 0 || font_id >= MAXFONTS )
        return;
    int handle = buflib_allocations[font_id];
    if ( handle < 0 )
        return;
    struct buflib_alloc_data *pdata = core_get_data(handle);
    struct font *pf = &pdata->font;

    if (pf->fd >= 0)
    {
        /* save the cache, but it keep it in-RAM so that cache lookups
         * can still succeed on the same font */
        glyph_cache_save(font_id);
        close(pf->fd);
        pf->fd = -1;
        pf->disabled = true;
    }
}

void font_disable_all(void)
{
    for(int i = 0; i < MAXFONTS; i++)
        font_disable(i);
}

static void font_enable(int font_id)
{
    if ( font_id < 0 || font_id >= MAXFONTS )
        return;
    int handle = buflib_allocations[font_id];
    if ( handle < 0 )
        return;
    struct buflib_alloc_data *pdata = core_get_data_pinned(handle);
    struct font *pf = &pdata->font;

    if (pf->disabled && pf->fd < 0)
    {
        pf->fd = open(pdata->path, O_RDONLY);
        pf->disabled = false;
    }
    core_put_data_pinned(pdata);
}

void font_enable_all(void)
{
    for(int i = 0; i < MAXFONTS; i++)
        font_enable(i);
}


/*
 * Return a pointer to an incore font structure.
 * If the requested font isn't loaded/compiled-in,
 * decrement the font number and try again.
 */
struct font* font_get(int font)
{
    struct font* pf;
    if (font == FONT_UI)
        font = MAXFONTS-1;
    if (font <= FONT_SYSFIXED || font >= MAXFONTS)
        return &sysfont;

    while (1) {
        if (buflib_allocations[font] > 0)
        {
            struct buflib_alloc_data *alloc = core_get_data(buflib_allocations[font]);
            pf = &alloc->font;
            if (pf && pf->height)
                return pf;
        }
        if (--font < 0)
            return &sysfont;
    }
}

/*
 * Reads an entry into cache entry
 */
static void
load_cache_entry(struct font_cache_entry* p, void* callback_data)
{
    struct font* pf = callback_data;

    ucschar_t char_code = p->_char_code;
    int fd;

    lock_font_handle(pf->handle, true);
    if (pf->file_width_offset)
    {
        int width_offset = pf->file_width_offset + char_code;
        /* load via different fd to get this file section cached */
        if(pf->fd_width >=0 )
            fd = pf->fd_width;
        else
            fd = pf->fd;
        lseek(fd, width_offset, SEEK_SET);
        read(fd, &(p->width), 1);
    }
    else
    {
        p->width = pf->maxwidth;
    }

    int32_t bitmap_offset = 0;

    if (pf->file_offset_offset)
    {
        int32_t offset = pf->file_offset_offset + char_code * (pf->long_offset ? sizeof(int32_t) : sizeof(int16_t));
        /* load via different fd to get this file section cached */
        if(pf->fd_offset >=0 )
            fd = pf->fd_offset;
        else
            fd = pf->fd;
        lseek(fd, offset, SEEK_SET);
        unsigned char tmp[2];
        if (read (fd, tmp, 2) == 2)
        {
            bitmap_offset = tmp[0] | (tmp[1] << 8);
            if (pf->long_offset) {
                if (read (fd, tmp, 2) == 2)
                    bitmap_offset |= (tmp[0] << 16) | (tmp[1] << 24);
            }
        }
    }
    else
    {
        bitmap_offset = char_code * glyph_bytes(pf, p->width);
    }

    int32_t file_offset = FONT_HEADER_SIZE + bitmap_offset;
    lseek(pf->fd, file_offset, SEEK_SET);
    int src_bytes = glyph_bytes(pf, p->width);
    read(pf->fd, p->bitmap, src_bytes);

    lock_font_handle(pf->handle, false);
}

/*
 * Converts cbuf into a font cache
 */
static void cache_create(struct font* pf)
{
    /* maximum size of rotated bitmap */
    int bitmap_size = glyph_bytes(pf, pf->maxwidth);
    /* reserve one blank glyph that is guaranteed to be available, even
     * when the font file is closed during USB */
    unsigned char *cache_buf = pf->buffer_start + bitmap_size;
    size_t cache_size = pf->buffer_size - bitmap_size;
    ALIGN_BUFFER(cache_buf, cache_size, sizeof(ucschar_t));
    memset(pf->buffer_start, 0, bitmap_size);
    /* Initialise cache */
    font_cache_create(&pf->cache, cache_buf, cache_size, bitmap_size);
}

/*
 * Returns width of character
 */
int font_get_width(struct font* pf, ucschar_t char_code)
{
    int width;
    struct font_cache_entry *e;

    /* check input range*/
    if (char_code < pf->firstchar || char_code >= pf->firstchar+pf->size)
        char_code = pf->defaultchar;
    char_code -= pf->firstchar;

    if (pf->fd >= 0 && pf != &sysfont)
        width = font_cache_get(&pf->cache,char_code,false,load_cache_entry,pf)->width;
    else if (pf->disabled &&
        (e = font_cache_get(&pf->cache,char_code,true,load_cache_entry,pf)))
        width = e->width; /* falls back to pf->maxwidth if !e */
    else if (pf->width)
        width = pf->width[char_code];
    else
        width = pf->maxwidth;

    return width;
}

const unsigned char* font_get_bits(struct font* pf, ucschar_t char_code)
{
    const unsigned char* bits;

    /* check input range*/
    if (char_code < pf->firstchar || char_code >= pf->firstchar+pf->size)
        char_code = pf->defaultchar;
    char_code -= pf->firstchar;

    if (pf->fd >= 0 && pf != &sysfont)
    {
        bits =
            (unsigned char*)font_cache_get(&pf->cache, char_code,
                                false, load_cache_entry, pf)->bitmap;
    }
    else if (pf->disabled)
    {
        /* the font handle is closed, but the cache is intact. Attempt
         * a lookup, which is very likely to succeed. Return a placeholder
         * glyph on miss (again, this is very unlikely */
        struct font_cache_entry *e = font_cache_get(&pf->cache, char_code,
                                                    true, NULL, NULL);
        if (LIKELY(e))
            bits = (unsigned char *) e->bitmap;
        else
        {
            /* Could attempt to find a suitable fallback glyph from the same
             * font. For now just return blank space which is
             * reserved by cache_create() at buffer_start */
            bits = pf->buffer_start;
        }
    }
    else
    {
        /* This font is entirely in RAM */
        bits = pf->bits;
        if (pf->offset)
        {
            if (pf->bits_size < MAX_FONTSIZE_FOR_16_BIT_OFFSETS)
                bits += ((uint16_t*)(pf->offset))[char_code];
            else
                bits += ((uint32_t*)(pf->offset))[char_code];
        }
        else
            bits += char_code * glyph_bytes(pf, pf->maxwidth);
    }

    return bits;
}

extern int open_pathfmt(char *buf, size_t size, int oflag, const char *pathfmt, ...); /*misc.c*/
static NO_INLINE int font_path_open_glyphcache(const char *font_path, int oflags)
{
    char glyph_path[MAX_PATH];
    int dotidx = strlen(font_path) - sizeof(FONT_EXT);
    logf("%s %.*s.%s", __func__, dotidx, font_path, GLYPH_CACHE_EXT);
    return open_pathfmt(glyph_path, sizeof(glyph_path), oflags, 
                        "%.*s.%s", dotidx, font_path, GLYPH_CACHE_EXT);
}

/* call with NULL to flush */
static void glyph_file_write(void* data)
{
    struct font_cache_entry* p = data;
    struct font* pf = cache_pf;
    ucschar_t ch;
    static int buffer_pos = 0;
#define WRITE_BUFFER 256
    static unsigned char buffer[WRITE_BUFFER];

    /* flush buffer & reset */
    if ( data == NULL || buffer_pos >= WRITE_BUFFER)
    {
        write(cache_fd, buffer, buffer_pos);
        buffer_pos = 0;
        if ( data == NULL )
            return;
    }
    if ( p->_char_code == 0xffff )
        return;

    ch = p->_char_code + pf->firstchar;
#ifdef UNICODE32
    buffer[buffer_pos] = (ch >> 24) & 0xff;
    buffer[buffer_pos+1] = (ch >> 16) & 0xff;
    buffer[buffer_pos+2] = (ch >> 8) & 0xff;
    buffer[buffer_pos+3] = ch & 0xff;
    buffer_pos += 4;
#else
    buffer[buffer_pos] = (ch >> 8) & 0xff;
    buffer[buffer_pos+1] = ch & 0xff;
    buffer_pos += 2;
#endif
    return;
}

/* save the char codes of the loaded glyphs to a file */
static void glyph_cache_save(int font_id)
{
    int fd;

    if( font_id < 0 )
        return;
    int handle = buflib_allocations[font_id];
    if ( handle < 0 )
        return;

    struct buflib_alloc_data *pdata = core_get_data_pinned(handle);
    struct font *pf = &pdata->font;

    if(pf && pf->fd >= 0)
    {
        fd = font_path_open_glyphcache(pdata->path, O_WRONLY|O_CREAT|O_TRUNC);
        if (fd >= 0)
        {
            uint32_t header = FC_HEADER_VAL;
            write(fd, &header, sizeof(header));
            cache_pf = pf;
            cache_fd = fd;
            lru_traverse(&cache_pf->cache._lru, glyph_file_write);
            glyph_file_write(NULL);
            if (cache_fd >= 0)
            {
                close(cache_fd);
                cache_fd = -1;
            }
        }
    }
    core_put_data_pinned(pdata);
    return;
}


static int ucscharcmp(const void *a, const void *b)
{
    return ((int)(*(ucschar_t*)a - *(ucschar_t*)b));
}

static NO_INLINE void glyph_cache_load(const char *font_path, struct font *pf)
{
#define MAX_SORT 256
    if (pf->fd >= 0) {
        int i, size, fd;
        unsigned char tmp[sizeof(ucschar_t)];
        ucschar_t ch;
        ucschar_t glyphs[MAX_SORT];
        ucschar_t glyphs_lru_order[MAX_SORT];
        unsigned int glyph_file_skip=0, glyph_file_size=0;

        int sort_size = pf->cache._capacity;
        if ( sort_size > MAX_SORT )
             sort_size = MAX_SORT;

        fd = font_path_open_glyphcache(font_path, O_RDONLY|O_BINARY);
#ifdef TRY_DEFAULT_GLYPHCACHE
        /* if font specific file fails, try default */
        if (fd < 0)
            fd = open(GLYPH_CACHE_FILE, O_RDONLY|O_BINARY);
#endif
        if (fd >= 0) {
            /* Header */
            uint32_t hdr = 0;
            read(fd, &hdr, sizeof(hdr));
            if (hdr != FC_HEADER_VAL)
                goto latin;
            /* only read what fits */
            glyph_file_size = filesize( fd );
            if (glyph_file_size < sizeof(uint32_t))
                goto latin;
            glyph_file_size -= sizeof(uint32_t);
            if ( glyph_file_size > (int)sizeof(ucschar_t)*pf->cache._capacity ) {
                glyph_file_skip = glyph_file_size - sizeof(ucschar_t)*pf->cache._capacity;
                lseek( fd, glyph_file_skip + sizeof(uint32_t), SEEK_SET );
            }
            while(1) {
                for ( size = 0;
                      read( fd, tmp, sizeof(tmp) ) == sizeof(tmp) && size < sort_size;
                      size++ )
                {
#ifdef UNICODE32
                    glyphs[size] = (tmp[0] << 24) | (tmp[1] << 16) | (tmp[2] << 8) | tmp[3];
#else
                    glyphs[size] = (tmp[0] << 8) | tmp[1];
#endif
                    glyphs_lru_order[size] = glyphs[size];
                }

                /* sort glyphs array to make sector cache happy */
                qsort((void *)glyphs, size, sizeof(ucschar_t),
                      ucscharcmp );

                /* load font bitmaps */
                for( i = 0; i < size ; i++ )
                    font_get_bits(pf, glyphs[i]);

                /* redo to fix lru order */
                for ( i = 0; i < size ; i++)
                    font_get_bits(pf, glyphs_lru_order[i]);

                if ( size < sort_size )
                    break;
            }

            close(fd);
        } else {
        latin:
            /* load latin1 chars into cache */
            for ( ch = 32 ; ch < 256  && ch < pf->cache._capacity + 32; ch++ )
                font_get_bits(pf, ch);
        }
    }
    return;
}
#else /* BOOTLOADER */

void font_init(void)
{
}

void font_lock(int font_id, bool lock)
{
    (void)font_id;
    (void)lock;
}

/*
 * Bootloader only supports the built-in sysfont.
 */
struct font* font_get(int font)
{
    (void)font;
    return &sysfont;
}

/*
 * Returns width of character
 */
int font_get_width(struct font* pf, ucschar_t char_code)
{
    /* check input range*/
    if (char_code < pf->firstchar || char_code >= pf->firstchar+pf->size)
        char_code = pf->defaultchar;
    char_code -= pf->firstchar;

    return pf->width? pf->width[char_code]: pf->maxwidth;
}

const unsigned char* font_get_bits(struct font* pf, ucschar_t char_code)
{
    const unsigned char* bits;

    /* check input range*/
    if (char_code < pf->firstchar || char_code >= pf->firstchar+pf->size)
        char_code = pf->defaultchar;
    char_code -= pf->firstchar;

    /* assume small font with uint16_t offsets*/
    bits = pf->bits + (pf->offset?
            ((uint16_t*)(pf->offset))[char_code]:
            (((pf->height + 7) / 8) * pf->maxwidth * char_code));

    return bits;
}

#endif /* BOOTLOADER */

/*
 * Returns the length (in bytes) of a given NULL terminated string
 *  stops after maxwidth, maxbytes or NULL (\0) whichever occurs first.
 * maxbytes = -1 ignores maxbytes and relies on NULL terminator (\0)
 *  to terminate the string
 * maxwidth = -1 ignores maxwidth
 * stringsize pixel width and height will be returned in *w and *h
 */
int font_measurestring(const unsigned char *str, size_t maxbytes, size_t maxwidth, int *w, int *h, int fontnum)
{
    const unsigned char *start = str;
    size_t bytes;
    struct font* pf = font_get(fontnum);
    font_lock( fontnum, true );
    ucschar_t ch;
    size_t width = 0;

    while (true)
    {
        bytes = str - start;
        str = utf8decode(str, &ch);
        if (ch == 0 || bytes >= maxbytes)
        {
            break;
        }
        if (IS_DIACRITIC(ch))
            continue;
        int fw = font_get_width(pf,ch);
        width += fw;
        if (width > maxwidth)
        {
            width -= fw;
            break;
        }
    }

    if ( h )
        *h = pf->height;
    if ( w )
        *w = width;

    font_lock( fontnum, false );

    return bytes;
}

/*
 * Returns the stringsize of a given NULL terminated string
 *  stops after maxbytes or NULL (\0) whichever occurs first.
 * maxbytes = -1 ignores maxbytes and relies on NULL terminator (\0)
 *  to terminate the string
 */
int font_getstringnsize(const unsigned char *str, size_t maxbytes, int *w, int *h, int fontnum)
{
    int width = 0;
    if (!w)
        w = &width;
    font_measurestring(str, maxbytes, -1, w, h, fontnum);
    return *w;
}

/*
 * Returns the stringsize of a given NULL terminated string.
 */
int font_getstringsize(const unsigned char *str, int *w, int *h, int fontnumber)
{
    return font_getstringnsize(str, -1, w, h, fontnumber);
}

/* -----------------------------------------------------------------
 * vim: et sw=4 ts=8 sts=4 tw=78
 */
