/**
 * @file lv_font_loader.c
 *
 */

/*********************
 *      INCLUDES
 *********************/

#include <stdint.h>
/* #include <stdbool.h> */
#include "generic/typedef.h"

#include "../lvgl.h"
#include "../misc/lv_fs.h"
#include "lv_font_loader.h"

/**********************
 *      TYPEDEFS
 **********************/
typedef struct {
    lv_fs_file_t *fp;
    int8_t bit_pos;
    uint8_t byte_value;
} bit_iterator_t;

typedef struct font_header_bin {
    uint32_t version;
    uint16_t tables_count;
    uint16_t font_size;
    uint16_t ascent;
    int16_t descent;
    uint16_t typo_ascent;
    int16_t typo_descent;
    uint16_t typo_line_gap;
    int16_t min_y;
    int16_t max_y;
    uint16_t default_advance_width;
    uint16_t kerning_scale;
    uint8_t index_to_loc_format;
    uint8_t glyph_id_format;
    uint8_t advance_width_format;
    uint8_t bits_per_pixel;
    uint8_t xy_bits;
    uint8_t wh_bits;
    uint8_t advance_width_bits;
    uint8_t compression_id;
    uint8_t subpixels_mode;
    uint8_t padding;
    int16_t underline_position;
    uint16_t underline_thickness;
} font_header_bin_t;

typedef struct cmap_table_bin {
    uint32_t data_offset;
    uint32_t range_start;
    uint16_t range_length;
    uint16_t glyph_id_start;
    uint16_t data_entries_count;
    uint8_t format_type;
    uint8_t padding;
} cmap_table_bin_t;

/**********************
 *  STATIC PROTOTYPES
 **********************/
static bit_iterator_t init_bit_iterator(lv_fs_file_t *fp);
static bool lvgl_load_font(lv_fs_file_t *fp, lv_font_t *font);
int32_t load_kern(lv_fs_file_t *fp, lv_font_fmt_txt_dsc_t *font_dsc, uint8_t format, uint32_t start);

static int read_bits_signed(bit_iterator_t *it, int n_bits, lv_fs_res_t *res);
static unsigned int read_bits(bit_iterator_t *it, int n_bits, lv_fs_res_t *res);

/**********************
 *      MACROS
 **********************/

/**********************
 *   GLOBAL FUNCTIONS
 **********************/

/**
 * Loads a `lv_font_t` object from a binary font file
 * @param font_name filename where the font file is located
 * @return a pointer to the font or NULL in case of error
 */
lv_font_t *lv_font_load(const char *font_name)
{
    lv_fs_file_t file;
    lv_fs_res_t res = lv_fs_open(&file, font_name, LV_FS_MODE_RD);
    if (res != LV_FS_RES_OK) {
        return NULL;
    }

    lv_font_t *font = lv_mem_alloc(sizeof(lv_font_t));
    if (font) {
        memset(font, 0, sizeof(lv_font_t));
        if (!lvgl_load_font(&file, font)) {
            LV_LOG_WARN("Error loading font file: %s\n", font_name);
            /*
            * When `lvgl_load_font` fails it can leak some pointers.
            * All non-null pointers can be assumed as allocated and
            * `lv_font_free` should free them correctly.
            */
            lv_font_free(font);
            font = NULL;
        }
    }

    lv_fs_close(&file);

    return font;
}

/**
 * Frees the memory allocated by the `lv_font_load()` function
 * @param font lv_font_t object created by the lv_font_load function
 */
void lv_font_free(lv_font_t *font)
{
    if (NULL != font) {
        lv_font_fmt_txt_dsc_t *dsc = (lv_font_fmt_txt_dsc_t *)font->dsc;

        if (NULL != dsc) {

            if (dsc->kern_classes == 0) {
                lv_font_fmt_txt_kern_pair_t *kern_dsc =
                    (lv_font_fmt_txt_kern_pair_t *)dsc->kern_dsc;

                if (NULL != kern_dsc) {
                    if (kern_dsc->glyph_ids) {
                        lv_mem_free((void *)kern_dsc->glyph_ids);
                    }

                    if (kern_dsc->values) {
                        lv_mem_free((void *)kern_dsc->values);
                    }

                    lv_mem_free((void *)kern_dsc);
                }
            } else {
                lv_font_fmt_txt_kern_classes_t *kern_dsc =
                    (lv_font_fmt_txt_kern_classes_t *)dsc->kern_dsc;

                if (NULL != kern_dsc) {
                    if (kern_dsc->class_pair_values) {
                        lv_mem_free((void *)kern_dsc->class_pair_values);
                    }

                    if (kern_dsc->left_class_mapping) {
                        lv_mem_free((void *)kern_dsc->left_class_mapping);
                    }

                    if (kern_dsc->right_class_mapping) {
                        lv_mem_free((void *)kern_dsc->right_class_mapping);
                    }

                    lv_mem_free((void *)kern_dsc);
                }
            }

            lv_font_fmt_txt_cmap_t *cmaps =
                (lv_font_fmt_txt_cmap_t *)dsc->cmaps;

            if (NULL != cmaps) {
                for (int i = 0; i < dsc->cmap_num; ++i) {
                    if (NULL != cmaps[i].glyph_id_ofs_list) {
                        lv_mem_free((void *)cmaps[i].glyph_id_ofs_list);
                    }
                    if (NULL != cmaps[i].unicode_list) {
                        lv_mem_free((void *)cmaps[i].unicode_list);
                    }
                }
                lv_mem_free(cmaps);
            }

            if (NULL != dsc->glyph_bitmap) {
                lv_mem_free((void *)dsc->glyph_bitmap);
            }
            if (NULL != dsc->glyph_dsc) {
                lv_mem_free((void *)dsc->glyph_dsc);
            }
            lv_mem_free(dsc);
        }
        lv_mem_free(font);
    }
}

/**********************
 *   STATIC FUNCTIONS
 **********************/

static bit_iterator_t init_bit_iterator(lv_fs_file_t *fp)
{
    bit_iterator_t it;
    it.fp = fp;
    it.bit_pos = -1;
    it.byte_value = 0;
    return it;
}

static unsigned int read_bits(bit_iterator_t *it, int n_bits, lv_fs_res_t *res)
{
    unsigned int value = 0;
    while (n_bits--) {
        it->byte_value = it->byte_value << 1;
        it->bit_pos--;

        if (it->bit_pos < 0) {
            it->bit_pos = 7;
            *res = lv_fs_read(it->fp, &(it->byte_value), 1, NULL);
            if (*res != LV_FS_RES_OK) {
                return 0;
            }
        }
        int8_t bit = (it->byte_value & 0x80) ? 1 : 0;

        value |= (bit << n_bits);
    }
    *res = LV_FS_RES_OK;
    return value;
}

static int read_bits_signed(bit_iterator_t *it, int n_bits, lv_fs_res_t *res)
{
    unsigned int value = read_bits(it, n_bits, res);
    if (value & (1 << (n_bits - 1))) {
        value |= ~0u << n_bits;
    }
    return value;
}

static int read_label(lv_fs_file_t *fp, int start, const char *label)
{
    lv_fs_seek(fp, start, LV_FS_SEEK_SET);

    uint32_t length;
    char buf[4];

    if (lv_fs_read(fp, &length, 4, NULL) != LV_FS_RES_OK
        || lv_fs_read(fp, buf, 4, NULL) != LV_FS_RES_OK
        || memcmp(label, buf, 4) != 0) {
        LV_LOG_WARN("Error reading '%s' label.", label);
        return -1;
    }

    return length;
}

static bool load_cmaps_tables(lv_fs_file_t *fp, lv_font_fmt_txt_dsc_t *font_dsc,
                              uint32_t cmaps_start, cmap_table_bin_t *cmap_table)
{
    if (lv_fs_read(fp, cmap_table, font_dsc->cmap_num * sizeof(cmap_table_bin_t), NULL) != LV_FS_RES_OK) {
        return false;
    }

    for (unsigned int i = 0; i < font_dsc->cmap_num; ++i) {
        lv_fs_res_t res = lv_fs_seek(fp, cmaps_start + cmap_table[i].data_offset, LV_FS_SEEK_SET);
        if (res != LV_FS_RES_OK) {
            return false;
        }

        lv_font_fmt_txt_cmap_t *cmap = (lv_font_fmt_txt_cmap_t *) & (font_dsc->cmaps[i]);

        cmap->range_start = cmap_table[i].range_start;
        cmap->range_length = cmap_table[i].range_length;
        cmap->glyph_id_start = cmap_table[i].glyph_id_start;
        cmap->type = cmap_table[i].format_type;

        switch (cmap_table[i].format_type) {
        case LV_FONT_FMT_TXT_CMAP_FORMAT0_FULL: {
            uint8_t ids_size = sizeof(uint8_t) * cmap_table[i].data_entries_count;
            uint8_t *glyph_id_ofs_list = lv_mem_alloc(ids_size);

            cmap->glyph_id_ofs_list = glyph_id_ofs_list;

            if (lv_fs_read(fp, glyph_id_ofs_list, ids_size, NULL) != LV_FS_RES_OK) {
                return false;
            }

            cmap->list_length = cmap->range_length;
            break;
        }
        case LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY:
            break;
        case LV_FONT_FMT_TXT_CMAP_SPARSE_FULL:
        case LV_FONT_FMT_TXT_CMAP_SPARSE_TINY: {
            uint32_t list_size = sizeof(uint16_t) * cmap_table[i].data_entries_count;
            uint16_t *unicode_list = (uint16_t *)lv_mem_alloc(list_size);

            cmap->unicode_list = unicode_list;
            cmap->list_length = cmap_table[i].data_entries_count;

            if (lv_fs_read(fp, unicode_list, list_size, NULL) != LV_FS_RES_OK) {
                return false;
            }

            if (cmap_table[i].format_type == LV_FONT_FMT_TXT_CMAP_SPARSE_FULL) {
                uint16_t *buf = lv_mem_alloc(sizeof(uint16_t) * cmap->list_length);

                cmap->glyph_id_ofs_list = buf;

                if (lv_fs_read(fp, buf, sizeof(uint16_t) * cmap->list_length, NULL) != LV_FS_RES_OK) {
                    return false;
                }
            }
            break;
        }
        default:
            LV_LOG_WARN("Unknown cmaps format type %d.", cmap_table[i].format_type);
            return false;
        }
    }
    return true;
}

static int32_t load_cmaps(lv_fs_file_t *fp, lv_font_fmt_txt_dsc_t *font_dsc, uint32_t cmaps_start)
{
    int32_t cmaps_length = read_label(fp, cmaps_start, "cmap");
    if (cmaps_length < 0) {
        return -1;
    }

    uint32_t cmaps_subtables_count;
    if (lv_fs_read(fp, &cmaps_subtables_count, sizeof(uint32_t), NULL) != LV_FS_RES_OK) {
        return -1;
    }

    lv_font_fmt_txt_cmap_t *cmaps =
        lv_mem_alloc(cmaps_subtables_count * sizeof(lv_font_fmt_txt_cmap_t));

    memset(cmaps, 0, cmaps_subtables_count * sizeof(lv_font_fmt_txt_cmap_t));

    font_dsc->cmaps = cmaps;
    font_dsc->cmap_num = cmaps_subtables_count;

    cmap_table_bin_t *cmaps_tables = lv_mem_alloc(sizeof(cmap_table_bin_t) * font_dsc->cmap_num);

    bool success = load_cmaps_tables(fp, font_dsc, cmaps_start, cmaps_tables);

    lv_mem_free(cmaps_tables);

    return success ? cmaps_length : -1;
}

static int32_t load_glyph(lv_fs_file_t *fp, lv_font_fmt_txt_dsc_t *font_dsc,
                          uint32_t start, uint32_t *glyph_offset, uint32_t loca_count, font_header_bin_t *header)
{
    int32_t glyph_length = read_label(fp, start, "glyf");
    if (glyph_length < 0) {
        return -1;
    }

    lv_font_fmt_txt_glyph_dsc_t *glyph_dsc = (lv_font_fmt_txt_glyph_dsc_t *)
            lv_mem_alloc(loca_count * sizeof(lv_font_fmt_txt_glyph_dsc_t));

    memset(glyph_dsc, 0, loca_count * sizeof(lv_font_fmt_txt_glyph_dsc_t));

    font_dsc->glyph_dsc = glyph_dsc;

    int cur_bmp_size = 0;

    for (unsigned int i = 0; i < loca_count; ++i) {
        lv_font_fmt_txt_glyph_dsc_t *gdsc = &glyph_dsc[i];

        lv_fs_res_t res = lv_fs_seek(fp, start + glyph_offset[i], LV_FS_SEEK_SET);
        if (res != LV_FS_RES_OK) {
            return -1;
        }

        bit_iterator_t bit_it = init_bit_iterator(fp);

        if (header->advance_width_bits == 0) {
            gdsc->adv_w = header->default_advance_width;
        } else {
            gdsc->adv_w = read_bits(&bit_it, header->advance_width_bits, &res);
            if (res != LV_FS_RES_OK) {
                return -1;
            }
        }

        if (header->advance_width_format == 0) {
            gdsc->adv_w *= 16;
        }

        gdsc->ofs_x = read_bits_signed(&bit_it, header->xy_bits, &res);
        if (res != LV_FS_RES_OK) {
            return -1;
        }

        gdsc->ofs_y = read_bits_signed(&bit_it, header->xy_bits, &res);
        if (res != LV_FS_RES_OK) {
            return -1;
        }

        gdsc->box_w = read_bits(&bit_it, header->wh_bits, &res);
        if (res != LV_FS_RES_OK) {
            return -1;
        }

        gdsc->box_h = read_bits(&bit_it, header->wh_bits, &res);
        if (res != LV_FS_RES_OK) {
            return -1;
        }

        int nbits = header->advance_width_bits + 2 * header->xy_bits + 2 * header->wh_bits;
        int next_offset = (i < loca_count - 1) ? glyph_offset[i + 1] : (uint32_t)glyph_length;
        int bmp_size = next_offset - glyph_offset[i] - nbits / 8;

        if (i == 0) {
            gdsc->adv_w = 0;
            gdsc->box_w = 0;
            gdsc->box_h = 0;
            gdsc->ofs_x = 0;
            gdsc->ofs_y = 0;
        }

        gdsc->bitmap_index = cur_bmp_size;
        if (gdsc->box_w * gdsc->box_h != 0) {
            cur_bmp_size += bmp_size;
        }
    }

    uint8_t *glyph_bmp = (uint8_t *)lv_mem_alloc(sizeof(uint8_t) * cur_bmp_size);

    font_dsc->glyph_bitmap = glyph_bmp;

    cur_bmp_size = 0;

    for (unsigned int i = 1; i < loca_count; ++i) {
        lv_fs_res_t res = lv_fs_seek(fp, start + glyph_offset[i], LV_FS_SEEK_SET);
        if (res != LV_FS_RES_OK) {
            return -1;
        }
        bit_iterator_t bit_it = init_bit_iterator(fp);

        int nbits = header->advance_width_bits + 2 * header->xy_bits + 2 * header->wh_bits;

        read_bits(&bit_it, nbits, &res);
        if (res != LV_FS_RES_OK) {
            return -1;
        }

        if (glyph_dsc[i].box_w * glyph_dsc[i].box_h == 0) {
            continue;
        }

        int next_offset = (i < loca_count - 1) ? glyph_offset[i + 1] : (uint32_t)glyph_length;
        int bmp_size = next_offset - glyph_offset[i] - nbits / 8;

        if (nbits % 8 == 0) { /*Fast path*/
            if (lv_fs_read(fp, &glyph_bmp[cur_bmp_size], bmp_size, NULL) != LV_FS_RES_OK) {
                return -1;
            }
        } else {
            for (int k = 0; k < bmp_size - 1; ++k) {
                glyph_bmp[cur_bmp_size + k] = read_bits(&bit_it, 8, &res);
                if (res != LV_FS_RES_OK) {
                    return -1;
                }
            }
            glyph_bmp[cur_bmp_size + bmp_size - 1] = read_bits(&bit_it, 8 - nbits % 8, &res);
            if (res != LV_FS_RES_OK) {
                return -1;
            }

            /*The last fragment should be on the MSB but read_bits() will place it to the LSB*/
            glyph_bmp[cur_bmp_size + bmp_size - 1] = glyph_bmp[cur_bmp_size + bmp_size - 1] << (nbits % 8);

        }

        cur_bmp_size += bmp_size;
    }
    return glyph_length;
}

/*
 * Loads a `lv_font_t` from a binary file, given a `lv_fs_file_t`.
 *
 * Memory allocations on `lvgl_load_font` should be immediately zeroed and
 * the pointer should be set on the `lv_font_t` data before any possible return.
 *
 * When something fails, it returns `false` and the memory on the `lv_font_t`
 * still needs to be freed using `lv_font_free`.
 *
 * `lv_font_free` will assume that all non-null pointers are allocated and
 * should be freed.
 */
static bool lvgl_load_font(lv_fs_file_t *fp, lv_font_t *font)
{
    lv_font_fmt_txt_dsc_t *font_dsc = (lv_font_fmt_txt_dsc_t *)
                                      lv_mem_alloc(sizeof(lv_font_fmt_txt_dsc_t));

    memset(font_dsc, 0, sizeof(lv_font_fmt_txt_dsc_t));

    font->dsc = font_dsc;

    /*header*/
    int32_t header_length = read_label(fp, 0, "head");
    if (header_length < 0) {
        return false;
    }

    font_header_bin_t font_header;
    if (lv_fs_read(fp, &font_header, sizeof(font_header_bin_t), NULL) != LV_FS_RES_OK) {
        return false;
    }

    font->base_line = -font_header.descent;
    font->line_height = font_header.ascent - font_header.descent;
    font->get_glyph_dsc = lv_font_get_glyph_dsc_fmt_txt;
    font->get_glyph_bitmap = lv_font_get_bitmap_fmt_txt;
    font->subpx = font_header.subpixels_mode;
    font->underline_position = font_header.underline_position;
    font->underline_thickness = font_header.underline_thickness;

    font_dsc->bpp = font_header.bits_per_pixel;
    font_dsc->kern_scale = font_header.kerning_scale;
    font_dsc->bitmap_format = font_header.compression_id;

    /*cmaps*/
    uint32_t cmaps_start = header_length;
    int32_t cmaps_length = load_cmaps(fp, font_dsc, cmaps_start);
    if (cmaps_length < 0) {
        return false;
    }

    /*loca*/
    uint32_t loca_start = cmaps_start + cmaps_length;
    int32_t loca_length = read_label(fp, loca_start, "loca");
    if (loca_length < 0) {
        return false;
    }

    uint32_t loca_count;
    if (lv_fs_read(fp, &loca_count, sizeof(uint32_t), NULL) != LV_FS_RES_OK) {
        return false;
    }

    bool failed = false;
    uint32_t *glyph_offset = lv_mem_alloc(sizeof(uint32_t) * (loca_count + 1));

    if (font_header.index_to_loc_format == 0) {
        for (unsigned int i = 0; i < loca_count; ++i) {
            uint16_t offset;
            if (lv_fs_read(fp, &offset, sizeof(uint16_t), NULL) != LV_FS_RES_OK) {
                failed = true;
                break;
            }
            glyph_offset[i] = offset;
        }
    } else if (font_header.index_to_loc_format == 1) {
        if (lv_fs_read(fp, glyph_offset, loca_count * sizeof(uint32_t), NULL) != LV_FS_RES_OK) {
            failed = true;
        }
    } else {
        LV_LOG_WARN("Unknown index_to_loc_format: %d.", font_header.index_to_loc_format);
        failed = true;
    }

    if (failed) {
        lv_mem_free(glyph_offset);
        return false;
    }

    /*glyph*/
    uint32_t glyph_start = loca_start + loca_length;
    int32_t glyph_length = load_glyph(
                               fp, font_dsc, glyph_start, glyph_offset, loca_count, &font_header);

    lv_mem_free(glyph_offset);

    if (glyph_length < 0) {
        return false;
    }

    if (font_header.tables_count < 4) {
        font_dsc->kern_dsc = NULL;
        font_dsc->kern_classes = 0;
        font_dsc->kern_scale = 0;
        return true;
    }

    uint32_t kern_start = glyph_start + glyph_length;

    int32_t kern_length = load_kern(fp, font_dsc, font_header.glyph_id_format, kern_start);

    return kern_length >= 0;
}

int32_t load_kern(lv_fs_file_t *fp, lv_font_fmt_txt_dsc_t *font_dsc, uint8_t format, uint32_t start)
{
    int32_t kern_length = read_label(fp, start, "kern");
    if (kern_length < 0) {
        return -1;
    }

    uint8_t kern_format_type;
    int32_t padding;
    if (lv_fs_read(fp, &kern_format_type, sizeof(uint8_t), NULL) != LV_FS_RES_OK ||
        lv_fs_read(fp, &padding, 3 * sizeof(uint8_t), NULL) != LV_FS_RES_OK) {
        return -1;
    }

    if (0 == kern_format_type) { /*sorted pairs*/
        lv_font_fmt_txt_kern_pair_t *kern_pair = lv_mem_alloc(sizeof(lv_font_fmt_txt_kern_pair_t));

        memset(kern_pair, 0, sizeof(lv_font_fmt_txt_kern_pair_t));

        font_dsc->kern_dsc = kern_pair;
        font_dsc->kern_classes = 0;

        uint32_t glyph_entries;
        if (lv_fs_read(fp, &glyph_entries, sizeof(uint32_t), NULL) != LV_FS_RES_OK) {
            return -1;
        }

        int ids_size;
        if (format == 0) {
            ids_size = sizeof(int8_t) * 2 * glyph_entries;
        } else {
            ids_size = sizeof(int16_t) * 2 * glyph_entries;
        }

        uint8_t *glyph_ids = lv_mem_alloc(ids_size);
        int8_t *values = lv_mem_alloc(glyph_entries);

        kern_pair->glyph_ids_size = format;
        kern_pair->pair_cnt = glyph_entries;
        kern_pair->glyph_ids = glyph_ids;
        kern_pair->values = values;

        if (lv_fs_read(fp, glyph_ids, ids_size, NULL) != LV_FS_RES_OK) {
            return -1;
        }

        if (lv_fs_read(fp, values, glyph_entries, NULL) != LV_FS_RES_OK) {
            return -1;
        }
    } else if (3 == kern_format_type) { /*array M*N of classes*/

        lv_font_fmt_txt_kern_classes_t *kern_classes = lv_mem_alloc(sizeof(lv_font_fmt_txt_kern_classes_t));

        memset(kern_classes, 0, sizeof(lv_font_fmt_txt_kern_classes_t));

        font_dsc->kern_dsc = kern_classes;
        font_dsc->kern_classes = 1;

        uint16_t kern_class_mapping_length;
        uint8_t kern_table_rows;
        uint8_t kern_table_cols;

        if (lv_fs_read(fp, &kern_class_mapping_length, sizeof(uint16_t), NULL) != LV_FS_RES_OK ||
            lv_fs_read(fp, &kern_table_rows, sizeof(uint8_t), NULL) != LV_FS_RES_OK ||
            lv_fs_read(fp, &kern_table_cols, sizeof(uint8_t), NULL) != LV_FS_RES_OK) {
            return -1;
        }

        int kern_values_length = sizeof(int8_t) * kern_table_rows * kern_table_cols;

        uint8_t *kern_left = lv_mem_alloc(kern_class_mapping_length);
        uint8_t *kern_right = lv_mem_alloc(kern_class_mapping_length);
        int8_t *kern_values = lv_mem_alloc(kern_values_length);

        kern_classes->left_class_mapping  = kern_left;
        kern_classes->right_class_mapping = kern_right;
        kern_classes->left_class_cnt = kern_table_rows;
        kern_classes->right_class_cnt = kern_table_cols;
        kern_classes->class_pair_values = kern_values;

        if (lv_fs_read(fp, kern_left, kern_class_mapping_length, NULL) != LV_FS_RES_OK ||
            lv_fs_read(fp, kern_right, kern_class_mapping_length, NULL) != LV_FS_RES_OK ||
            lv_fs_read(fp, kern_values, kern_values_length, NULL) != LV_FS_RES_OK) {
            return -1;
        }
    } else {
        LV_LOG_WARN("Unknown kern_format_type: %d", kern_format_type);
        return -1;
    }

    return kern_length;
}
