/*
 * Convert ligature files to QEmacs binary internal format
 *
 * Copyright (c) 2002 Fabrice Bellard.
 * Copyright (c) 2007-2023 Charlie Gordon.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <getopt.h>
#include <assert.h>

#include "cutils.h"

static inline char *skipspaces(char *p) {
    while (isspace((unsigned char)*p))
        p++;
    return p;
}

static int utf8_encode(char *buf, char32_t c) {
    char *q;

    q = buf;
    if (c < 0x80) {
        *q++ = c;
    } else {
        if (c < 0x800) {
            *q++ = (c >> 6) | 0xc0;
        } else {
            if (c < 0x10000) {
                *q++ = (c >> 12) | 0xe0;
            } else {
                if (c < 0x00200000) {
                    *q++ = (c >> 18) | 0xf0;
                } else {
                    if (c < 0x04000000) {
                        *q++ = (c >> 24) | 0xf8;
                    } else {
                        *q++ = (c >> 30) | 0xfc;
                        *q++ = ((c >> 24) & 0x3f) | 0x80;
                    }
                    *q++ = ((c >> 18) & 0x3f) | 0x80;
                }
                *q++ = ((c >> 12) & 0x3f) | 0x80;
            }
            *q++ = ((c >> 6) & 0x3f) | 0x80;
        }
        *q++ = (c & 0x3f) | 0x80;
    }
    *q = '\0';
    return q - buf;
}

static void put_be16(FILE *outfile, int off)
{
    fputc((off >> 8) & 0xff, outfile);
    fputc((off) & 0xff, outfile);
}

#define MAX_LIGS 5000

typedef struct Ligature {
    int buf_in[20], buf_out[20];
    int buf_in_size, buf_out_size;
} Ligature;

static Ligature ligs[MAX_LIGS];
static int nb_ligs;

static int sort_func(const void *a1, const void *b1)
{
    const Ligature *a = a1;
    const Ligature *b = b1;
    int val;

    val = a->buf_in[0] - b->buf_in[0];
    if (val == 0
    &&  a->buf_in_size >= 2 && b->buf_in_size >= 2) {
        val = a->buf_in[1] - b->buf_in[1];
        if (val == 0
        &&  a->buf_in_size >= 3 && b->buf_in_size >= 3) {
            val = a->buf_in[2] - b->buf_in[2];
        }
    }
    return val;
}

static void help(void)
{
    printf("usage: ligtoqe [-u] sourcefile.lig output_ligature_file\n"
           "Build a ligature resource file for qemacs\n"
           "\n"
           "-u : output a UTF8 encoded summary on stdout\n");
    exit(1);
}

int main(int argc, char **argv)
{
    FILE *f, *outfile;
    char buf[1024], *p;
    Ligature *l;
    char buf1[128];
    int i, c, to_utf8, j, l1, l2, n;
    const char *lig_filename;
    const char *lig_res_filename;
    int subst1_count, subst2_count, subst_long_count;

    to_utf8 = 0;
    for (;;) {
        c = getopt(argc, argv, "uh");
        if (c == -1)
            break;
        switch (c) {
        case 'h':
            help();
            break;
        case 'u':
            to_utf8 = 1;
            break;
        }
    }
    if (optind + 2 > argc)
        help();
    lig_filename = argv[optind++];
    lig_res_filename = argv[optind++];

    f = fopen(lig_filename, "r");
    if (!f) {
        perror(lig_filename);
        return 1;
    }
    for (;;) {
        if (fgets(buf, sizeof(buf), f) == NULL)
            break;
        p = buf + strlen(buf);
        if (p > buf && p[-1] == '\n')
            *--p = '\0';

        p = skipspaces(buf);

        if (*p == '#' || *p == '\0') {
            if (to_utf8) {
                printf("%s\n", buf);
            }
            continue;
        }

        l = &ligs[nb_ligs++];

        l->buf_in_size = 0;
        for (;;) {
            p = skipspaces(p);
            if (*p == '=')
                break;
            if (*p == '\0') {
                fprintf(stderr, "'=' expected\n");
                exit(1);
            }
            l->buf_in[l->buf_in_size++] = strtoul(p, &p, 0);
        }
        p++;

        l->buf_out_size = 0;
        for (;;) {
            p = skipspaces(p);
            /* stop at the first comment */
            if (*p == '\0' || *p == '/')
                break;
            l->buf_out[l->buf_out_size++] = strtoul(p, &p, 0);
        }

        if (l->buf_in_size < 1 ||
            l->buf_out_size < 1) {
            fprintf(stderr, "syntax error: '%s'\n", buf);
            exit(1);
        }

        /* output UTF8 encoded list */
        if (to_utf8) {
            printf("%s // ", buf);
            for (i = 0; i < l->buf_in_size; i++) {
                utf8_encode(buf1, l->buf_in[i]);
                printf("%s ", buf1);
            }
            printf("=");
            for (i = 0; i < l->buf_out_size; i++) {
                utf8_encode(buf1, l->buf_out[i]);
                printf(" %s", buf1);
            }
            printf("\n");
        }
    }
    fclose(f);


    /* sort everything */
    qsort(ligs, nb_ligs, sizeof(Ligature), sort_func);

    outfile = fopen(lig_res_filename, "w");
    if (!outfile) {
        perror(lig_res_filename);
        exit(1);
    }

    /* header */
    fwrite("liga", 1, 4, outfile);

    /* number of entries */
    put_be16(outfile, 0);
    put_be16(outfile, 0);
    put_be16(outfile, 0);

    /* output the subst table */
    n = 0;
    for (i = 0; i < nb_ligs; i++) {
        l = &ligs[i];
        if (l->buf_in_size == 1) {
            if (l->buf_out_size != 1) {
                fprintf(stderr, "substitutions of only one char are handled\n");
                exit(1);
            }
            put_be16(outfile, l->buf_in[0]);
            put_be16(outfile, l->buf_out[0]);
            n++;
        }
    }
    subst1_count = n;

    /* output the ligature table (2 chars -> 1 char) */
    j = 0;
    l1 = 0;
    l2 = 0;
    n = 0;
    for (i = 0; i < nb_ligs; i++) {
        l = &ligs[i];
        if (l->buf_in_size >= 2) {
            if (l->buf_in_size > 2 || l->buf_out_size > 1) {
                if (l1 == l->buf_in[0] &&
                    l2 == l->buf_in[1]) {
                } else {
                    put_be16(outfile, l->buf_in[0]);
                    put_be16(outfile, l->buf_in[1]);
                    put_be16(outfile, 0);
                    l1 = l->buf_in[0];
                    l2 = l->buf_in[1];
                    n++;
                }
            } else {
                put_be16(outfile, l->buf_in[0]);
                put_be16(outfile, l->buf_in[1]);
                put_be16(outfile, l->buf_out[0]);
                n++;
            }
        }
    }

    subst2_count = n;

    /* output the long ligature table */
    n = 0;
    for (i = 0; i < nb_ligs; i++) {
        l = &ligs[i];
        if (l->buf_in_size > 2 || l->buf_out_size > 1) {
            put_be16(outfile, l->buf_in_size);
            put_be16(outfile, l->buf_out_size);
            for (j = 0; j < l->buf_in_size; j++)
                put_be16(outfile, l->buf_in[j]);
            for (j = 0; j < l->buf_out_size; j++)
                put_be16(outfile, l->buf_out[j]);
            n += 2 + l->buf_in_size + l->buf_out_size;
        }
    }
    put_be16(outfile, 0);
    n++;

    subst_long_count = n;

    fseek(outfile, 4, SEEK_SET);
    put_be16(outfile, subst1_count);
    put_be16(outfile, subst2_count);
    put_be16(outfile, subst_long_count);

    fclose(outfile);

    return 0;
}
