/***************************************************************************
 *   crengine-ng                                                           *
 *   Copyright (C) 2007-2009 Vadim Lopatin <coolreader.org@gmail.com>      *
 *   Copyright (C) 2020,2021 poire-z <poire-z@users.noreply.github.com>    *
 *   Copyright (C) 2020,2021 Aleksey Chernov <valexlin@gmail.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 program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the Free Software           *
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,            *
 *   MA 02110-1301, USA.                                                   *
 ***************************************************************************/

#include <lvfnt.h>
#include <lvbyteorder.h>

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#define MIN_FONT_SIZE 2048
#define MAX_FONT_SIZE 0x100000

#ifndef __cplusplus
int lvfontIsUnicodeSpace(lChar32 code) {
    /* TODO: add other space codes here */
    return (code == 0x0020);
}
#endif

const lvfont_header_t* lvfontGetHeader(const lvfont_handle pfont) {
    return (const tag_lvfont_header*)pfont;
}

const hrle_decode_info_t* lvfontGetDecodeTable(const lvfont_handle pfont) {
    return (hrle_decode_info_t*)((const lUInt8*)pfont + ((const tag_lvfont_header*)pfont)->decodeTableOffset);
}

const lvfont_glyph_t* lvfontGetGlyph(const lvfont_handle pfont, lUInt16 code) {
    const tag_lvfont_header* hdr = (const tag_lvfont_header*)pfont;
    if (code > hdr->maxCode)
        return NULL;
    lUInt32 rangeoffset = hdr->rangesOffset[(code >> 6) & 0x3FF];
    if (rangeoffset == 0 || rangeoffset > hdr->fileSize)
        return NULL;
    const lvfont_range_t* range = (const lvfont_range_t*)(((const char*)pfont) + rangeoffset);
    lUInt32 glyphoffset = range->glyphsOffset[code & 0x3F];
    if (glyphoffset == 0 || glyphoffset > hdr->fileSize)
        return NULL;
    return (const lvfont_glyph_t*)(((const char*)range) + glyphoffset);
}

int lvfontOpen(const char* fname, lvfont_handle* pfont) {
    static lvByteOrderConv cnv;

    FILE* f = fopen(fname, "rb");
    if (f == NULL)
        return 0;
    fseek(f, 0, SEEK_END);
    lUInt32 sz = ftell(f);
    if (sz < MIN_FONT_SIZE || sz > MAX_FONT_SIZE) {
        fclose(f);
        return 0;
    }
    *pfont = (lvfont_handle)malloc(sz);
    fseek(f, 0, SEEK_SET);
    if (fread(*pfont, sz, 1, f) != sz) {
        fclose(f);
        return 0;
    }
    fclose(f);
    tag_lvfont_header* hdr = (tag_lvfont_header*)lvfontGetHeader(*pfont);

    cnv.lsf(&hdr->fileSize);

    if (hdr->fileSize != sz || hdr->magic[0] != 'L' || hdr->magic[1] != 'F' || hdr->magic[2] != 'N' || hdr->magic[3] != 'T' || hdr->version[0] != '1' || hdr->version[1] != '.' || hdr->version[2] != '0' || hdr->version[3] != '0') {
        free(*pfont);
        return 0;
    }
    if (cnv.msf()) {
        cnv.rev(&hdr->minCode);
        cnv.rev(&hdr->maxCode);
        cnv.rev(&hdr->decodeTableOffset);
        int ntables = (hdr->maxCode >> 6);
        for (int i = 0; i < ntables; i++) {
            cnv.rev(&hdr->rangesOffset[i]);
            int rangeoffset = hdr->rangesOffset[i];
            if (rangeoffset <= 0 || rangeoffset > (int)sz)
                continue;
            lvfont_range_t* range = (lvfont_range_t*)(((const char*)*pfont) + rangeoffset);

            for (int j = 0; j < 64; j++) {
                cnv.rev(&range->glyphsOffset[j]);
                int glyphoffset = range->glyphsOffset[j];
                if (glyphoffset <= 0 || glyphoffset + rangeoffset >= (int)sz)
                    continue;
                lvfont_glyph_t* glyph = (lvfont_glyph_t*)(((const char*)range) + glyphoffset);
                cnv.rev(&glyph->glyphSize);
            }
        }
    }
    return 1;
}

void lvfontClose(lvfont_handle pfont) {
    if (pfont) {
        free(pfont);
    }
}

lUInt16 lvfontMeasureText(const lvfont_handle pfont,
                          const lChar32* text, int len,
                          lUInt16* widths,
                          lUInt8* flags,
                          int max_width,
                          lChar32 def_char) {
    lUInt16 wsum = 0;
    lUInt16 nchars = 0;
    const lvfont_glyph_t* glyph;
    lUInt16 gwidth = 0;
    lUInt16 hyphwidth = 0;
    lUInt8 bflags;
    int isSpace;
    lChar32 ch;
    int hwStart, hwEnd;

    glyph = lvfontGetGlyph(pfont, UNICODE_SOFT_HYPHEN_CODE);
    hyphwidth = glyph ? glyph->width : 0;

    for (; wsum < max_width && nchars < len; nchars++) {
        bflags = 0;
        ch = text[nchars];
        isSpace = lvfontIsUnicodeSpace(ch);
        if (isSpace || ch == UNICODE_SOFT_HYPHEN_CODE)
            bflags |= LCHAR_ALLOW_WRAP_AFTER;
        if (ch == '-')
            bflags |= LCHAR_DEPRECATED_WRAP_AFTER;
        if (isSpace)
            bflags |= LCHAR_IS_SPACE;
        glyph = lvfontGetGlyph(pfont, ch);
        if (!glyph && def_char)
            glyph = lvfontGetGlyph(pfont, def_char);
        gwidth = glyph ? glyph->width : 0;
        widths[nchars] = wsum + gwidth;
        if (ch != UNICODE_SOFT_HYPHEN_CODE)
            wsum += gwidth; /* don't include hyphens to width */
        flags[nchars] = bflags;
    }

#ifdef __cplusplus
    //try to add hyphen
    for (hwStart = nchars - 1; hwStart > 0; hwStart--) {
        if (lvfontIsUnicodeSpace(text[hwStart])) {
            hwStart++;
            break;
        }
    }
    for (hwEnd = nchars; hwEnd < len; hwEnd++) {
        lChar32 ch = text[hwEnd];
        if (lvfontIsUnicodeSpace(ch))
            break;
        if (flags[hwEnd - 1] & LCHAR_ALLOW_WRAP_AFTER)
            break;
        if (ch == '.' || ch == ',' || ch == '!' || ch == '?' || ch == '?')
            break;
    }
    HyphMan::hyphenate(text + hwStart, hwEnd - hwStart, widths + hwStart, flags + hwStart, hyphwidth, max_width);
#endif

    return nchars;
}

void lvfontUnpackGlyph(const lUInt8* packed, const hrle_decode_info_t* table, lUInt8* unpacked, int unp_size) {
    lUInt16 b;
    hrle_decode_table_t code;
    int srcshift;
    int srcdata;
    int srccount;
    int inx;

    srcshift = 0; //(srcskip & 3);
    // srccount = 0;
    /* newline */
    lUInt8* unp_end = unpacked + unp_size;
    for (; unpacked < unp_end;) {
        // read source
        b = (((lUInt16)(packed[0])) << 8) | (packed[1]);
        inx = (b >> (16 - table->bitcount - srcshift)) & table->rightmask;
        code = table->table[inx];
        srcdata = code.value << 6;
        srccount = code.count;
        srcshift += code.codelen;
        if (srcshift & 8) {
            srcshift &= 7;
            packed++;
        }
        // write to dest
        for (; srccount; srccount--)
            *unpacked++ = srcdata;
    }
}
