/*
 * Copyright (C) 2020 The HIME team, Taiwan
 * Copyright (C) 2011-2012 Huang, Kai-Chang (Solomon Huang) <kaichanh@gmail.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

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

#include <unistd.h>

#include <sys/types.h>
#if FREEBSD
#include <sys/param.h>
#include <sys/stat.h>
#endif

#include "hime.h"

#include "gtab.h"
#include "hime-endian.h"

int futf8cpy_bytes (FILE *fp, char *s, int n) {
    int tn = 0;

    while (tn < n && *s) {
        int sz = utf8_sz (s);

        fwrite (s, 1, sz, fp);
        tn += sz;
        s += sz;
    }
    return tn;
}

int is_endkey (struct TableHead *th, char key) {
    int i;
    if (key == ' ')
        return 1;
    for (i = 0; i < sizeof (th->endkey); i++)
        if (key == th->endkey[i])
            return 1;
    return 0;
}

u_int convert_key32 (unsigned char *key32) {
    u_int key = 0;
    key |= *(key32);
    key |= *(key32 + 1) << 8;
    key |= *(key32 + 2) << 16;
    key |= *(key32 + 3) << 24;
    return key;
}

u_int64_t convert_key64 (unsigned char *key64) {
    u_int64_t key = 0;
    key |= (u_int64_t) * (key64);
    key |= (u_int64_t) * (key64 + 1) << 8;
    key |= (u_int64_t) * (key64 + 2) << 16;
    key |= (u_int64_t) * (key64 + 3) << 24;
    key |= (u_int64_t) * (key64 + 4) << 32;
    key |= (u_int64_t) * (key64 + 5) << 40;
    key |= (u_int64_t) * (key64 + 6) << 48;
    key |= (u_int64_t) * (key64 + 7) << 56;
    return key;
}

void bot_output (int status, struct TableHead *th) {
    if (status == 0 && th) {
        printf ("0:%d:%d%d", th->keybits, th->MaxPress, th->DefC);
        return;
    }
    printf ("%d:0:0:0", status);
}

void usage () {
    printf (
        "hime-gtab2cin usages:\n"
        "  hime-gtab2cin -i <gtab> -o <cin>\n\n"
        "    -h         Help message\n"
        "    -i         Table(gtab) filename\n"
        "    -o         Table(cin) filename\n"
        "    -b         Output information for machine\n");
}

int main (int argc, char **argv) {
    const char CIN_HEADER[] = "#\n# cin file created via hime-gtab2cin\n#\n";
    FILE *fr, *fw;
    char fname[256];
    char fname_cin[256];
    char fname_tab[256];
    struct TableHead *th;
    char *kname;
    char *keymap;
    int quick_def = 0;
    char *gtabbuf = NULL;
    long gtablen = 0;
    int key_idx, key_idx2, i, key_seq;
    QUICK_KEYS qkeys;
    int *phridx;
    char *phrbuf;
    int opt;
    int bot = 0;

    fname_cin[0] = fname_tab[0] = 0;
    while ((opt = getopt (argc, argv, "i:o:bh")) != -1) {
        switch (opt) {
        case 'i':
            if (strlen (optarg) < 256)
                strncpy (fname_tab, optarg, 256);
            break;
        case 'o':
            if (strlen (optarg) < 256)
                strncpy (fname_cin, optarg, 256);
            break;
        case 'b':
            bot = 1;
            break;
        case 'h':
        default:
            usage ();
            exit (1);
        }
    }

    if (!strlen (fname_cin) || !strlen (fname_tab)) {
        if (bot) {
            bot_output (-1, NULL);
        } else
            usage ();
        exit (1);
    }

    if ((fr = fopen (fname_tab, "rb")) == NULL) {
        if (bot) {
            bot_output (1, NULL);
            exit (1);
        } else
            p_err ("Cannot open %s\n", fname_tab);
    }

    fseek (fr, 0L, SEEK_END);
    gtablen = ftell (fr);
    rewind (fr);
    gtabbuf = malloc (gtablen);
    if (gtabbuf && (gtablen != fread (gtabbuf, 1, gtablen, fr) || gtablen <= 0)) {
        fclose (fr);
        if (bot) {
            bot_output (1, NULL);
        } else
            p_err ("Read %s fail\n", fname_tab);
    }
    fclose (fr);
    if ((fw = fopen (fname_cin, "wb")) == NULL) {
        free (gtabbuf);
        if (bot) {
            bot_output (2, NULL);
        } else
            p_err ("Cannot create");
    }

    th = (struct TableHead *) gtabbuf;
    fprintf (fw, "%s", CIN_HEADER);
    fprintf (fw, "%%gen_inp\n");
    fprintf (fw, "%%ename %s\n", fname);
    fprintf (fw, "%%cname %s\n", th->cname);
    fprintf (fw, "%%selkey ");
    if (th->selkey[sizeof (th->selkey) - 1] == 0) {
        fprintf (fw, "%s\n", th->selkey);
    } else {
        fwrite (th->selkey, 1, sizeof (th->selkey), fw);
        fprintf (fw, "%s\n", th->selkey2);
    }
    fprintf (fw, "%%dupsel %d\n", th->M_DUP_SEL);
    if (strlen (th->endkey))
        fprintf (fw, "%%endkey %s\n", th->endkey);
    fprintf (fw, "%%space_style %d\n", th->space_style);
    if (th->flag & FLAG_KEEP_KEY_CASE)
        fprintf (fw, "%%keep_key_case\n");
    if (th->flag & FLAG_GTAB_SYM_KBM)
        fprintf (fw, "%%symbol_kbm\n");
    if (th->flag & FLAG_PHRASE_AUTO_SKIP_ENDKEY)
        fprintf (fw, "%%phase_auto_skip_endkey\n");
    if (th->flag & FLAG_AUTO_SELECT_BY_PHRASE)
        fprintf (fw, "%%flag_auto_select_by_phrase\n");
    if (th->flag & FLAG_GTAB_DISP_PARTIAL_MATCH)
        fprintf (fw, "%%flag_disp_partial_match\n");
    if (th->flag & FLAG_GTAB_DISP_FULL_MATCH)
        fprintf (fw, "%%flag_disp_full_match\n");
    if (th->flag & FLAG_GTAB_VERTICAL_SELECTION)
        fprintf (fw, "%%flag_vertical_selection\n");
    if (th->flag & FLAG_GTAB_PRESS_FULL_AUTO_SEND)
        fprintf (fw, "%%flag_press_full_auto_send\n");
    if (th->flag & FLAG_GTAB_UNIQUE_AUTO_SEND)
        fprintf (fw, "%%flag_unique_auto_send\n");

    keymap = gtabbuf + sizeof (struct TableHead);
    kname = keymap + th->KeyS;
    fprintf (fw, "%%keyname begin\n");
    for (key_idx = 1; key_idx < th->KeyS; key_idx++) {
        // prevent leading #
        if (*(keymap + key_idx) == '#')
            fprintf (fw, "%c", ' ');
        fprintf (fw, "%c ", *(keymap + key_idx));
        futf8cpy_bytes (fw, (kname + CH_SZ * key_idx), CH_SZ);
        fprintf (fw, "\n");
    }
    fprintf (fw, "%%keyname end\n");

    /* check quick def */
    memset (&qkeys, 0, sizeof (qkeys));
    if (0 != memcmp (&qkeys, &th->qkeys, sizeof (qkeys)))
        quick_def = 1;
    if (quick_def) {
        fprintf (fw, "%%quick begin\n");
        for (key_idx = 0; key_idx < th->KeyS; key_idx++) {
            if (is_endkey (th, *(keymap + key_idx + 1)))
                continue;
            fprintf (fw, "%c ", *(keymap + key_idx + 1));
            for (i = 0; i < 10; i++)
                futf8cpy_bytes (fw, th->qkeys.quick1[key_idx][i], CH_SZ);
            fprintf (fw, "\n");
        }
        for (key_idx = 0; key_idx < th->KeyS; key_idx++) {
            for (key_idx2 = 0; key_idx2 < th->KeyS; key_idx2++) {
                if (is_endkey (th, *(keymap + key_idx + 1)))
                    continue;
                if (is_endkey (th, *(keymap + key_idx2 + 1)))
                    continue;
                fprintf (fw, "%c%c ", *(keymap + key_idx + 1), *(keymap + key_idx2 + 1));
                for (i = 0; i < 10; i++) {
                    if (0 == futf8cpy_bytes (fw, th->qkeys.quick2[key_idx][key_idx2][i], CH_SZ))
                        futf8cpy_bytes (fw, "□", CH_SZ);
                }
                fprintf (fw, "\n");
            }
        }
        fprintf (fw, "%%quick end\n");
    }

    fprintf (fw, "%%chardef begin\n");

    /* older gtab */
    if (th->keybits == 0) {
        if (th->MaxPress <= 5)
            th->keybits = 6;
        else
            th->keybits = 7;
    }

    if (th->keybits * th->MaxPress <= 32) {
        ITEM *item = (ITEM *) (kname + (CH_SZ * th->KeyS) + (sizeof (gtab_idx1_t) * (th->KeyS + 1)));
        u_int key;
        u_int mask = (1 << th->keybits) - 1;
        phridx = (int *) (item + th->DefC);
        phrbuf = (char *) (phridx + *phridx + 1);
        for (i = 0; i < th->DefC; i++) {
            key = convert_key32 ((unsigned char *) item->key);
            for (key_seq = 0; key_seq < th->MaxPress; key_seq++) {
                key_idx =
                    ((key >> (th->keybits * ((32 / th->keybits) - key_seq - 1))) & mask);
                /* prevent leading # */
                if (key_seq == 0 && (*(keymap + key_idx) == '#'))
                    fprintf (fw, "%c", ' ');
                fprintf (fw, "%c", *(keymap + key_idx));
            }
            fprintf (fw, " ");
            if (item->ch[0] == 0) { /* assume total phrases is less than 65535 */
                /* phrases define */
                int idx = 0, phr_len;
                char phr_str[MAX_CIN_PHR + 1];
                idx |= item->ch[0] << 16;
                idx |= item->ch[1] << 8;
                idx |= item->ch[2];
                memset (phr_str, 0, MAX_CIN_PHR + 1);
                phr_len = *(phridx + idx + 2) - *(phridx + idx + 1);
                memcpy (phr_str, phrbuf + *(phridx + idx + 1), phr_len);
                fprintf (fw, "%s\n", phr_str);
            } else {
                /* characters define */
                futf8cpy_bytes (fw, (char *) item->ch, CH_SZ);
                fprintf (fw, "\n");
            }
            item++;
        }
    } else if (th->keybits * th->MaxPress <= 64) {
        ITEM64 *item = (ITEM64 *) (kname + (CH_SZ * th->KeyS) + (sizeof (gtab_idx1_t) * (th->KeyS + 1)));
        u_int64_t key;
        u_int mask = (1L << th->keybits) - 1;
        phridx = (int *) (item + th->DefC);
        phrbuf = (char *) (phridx + *phridx + 1);
        for (i = 0; i < th->DefC; i++) {
            key = convert_key64 ((unsigned char *) item->key);
            for (key_seq = 0; key_seq < th->MaxPress; key_seq++) {
                key_idx =
                    ((key >> (th->keybits * ((64 / th->keybits) - key_seq - 1))) & mask);
                /* prevent leading # */
                if (key_seq == 0 && (*(keymap + key_idx) == '#'))
                    fprintf (fw, "%c", ' ');
                fprintf (fw, "%c", *(keymap + key_idx));
            }
            fprintf (fw, " ");
            if (item->ch[0] == 0) { /* assume total phrases is less than 65535 */
                /* phrases define */
                int idx = 0, phr_len;
                char phr_str[MAX_CIN_PHR + 1];
                idx |= item->ch[0] << 16;
                idx |= item->ch[1] << 8;
                idx |= item->ch[2];
                memset (phr_str, 0, MAX_CIN_PHR + 1);
                phr_len = *(phridx + idx + 2) - *(phridx + idx + 1);
                memcpy (phr_str, phrbuf + *(phridx + idx + 1), phr_len);
                fprintf (fw, "%s\n", phr_str);
            } else {
                /* characters define */
                futf8cpy_bytes (fw, (char *) item->ch, CH_SZ);
                fprintf (fw, "\n");
            }
            item++;
        }
    } else
        fprintf (fw, "# Unknown chardef\n");
    fprintf (fw, "%%chardef end\n");

    fprintf (fw, "#\n");
    fprintf (fw, "# Gtab version: %d\n", th->version);
    fprintf (fw, "# flags: %#x\n", th->flag);
    fprintf (fw, "# keybits: %d\n", th->keybits);
    fprintf (fw, "# MaxPress: %d\n", th->MaxPress);
    fprintf (fw, "# Defined Characters : %d\n", th->DefC);
    fprintf (fw, "#\n");
    if (bot)
        bot_output (0, th);
    else
        printf ("hime-gtab2cin done\n");
    free (gtabbuf);
    fclose (fw);
    return 0;
}
