/*
 * Copyright (C) 2020 The HIME team, Taiwan
 * Copyright (C) 1994-2011 Edward Der-Hua Liu, Hsin-Chu, Taiwan
 *
 * 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 version 2.1
 * of the License.
 *
 * 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 <stdlib.h>

#include <sys/stat.h>

#include "hime.h"

#include "gst.h"
#include "gtab.h"
#include "pho-status.h"
#include "pho.h"
#include "win-pho.h"

extern PHO_ST poo;

extern PHO_ITEM *ch_pho;

extern PHOKBM phkbm;
gboolean b_hsu_kbm;
extern PIN_JUYIN *pin_juyin;

gboolean full_char_proc (KeySym keysym);
void hide_win_pho ();
void ClrSelArea ();

#define MAX_HASH_PHO 27
u_short hash_pho[MAX_HASH_PHO + 1];

static char typ_pho_len[] = {5, 2, 4, 3};

gboolean same_query_show_pho_win ();

gboolean typ_pho_empty () {
    return !poo.typ_pho[0] && !poo.typ_pho[1] && !poo.typ_pho[2] && !poo.typ_pho[3];
}

gboolean pho_has_input () {
    return !typ_pho_empty () || same_query_show_pho_win ();
}

phokey_t pho2key (char typ_pho[]) {
    phokey_t key = typ_pho[0];
    int i;

    if (key == BACK_QUOTE_NO)
        return (BACK_QUOTE_NO << 9) | typ_pho[1];

    for (i = 1; i < 4; i++) {
        key = typ_pho[i] | (key << typ_pho_len[i]);
    }

    return key;
}

void key_typ_pho (phokey_t phokey, u_char rtyp_pho[]) {
    rtyp_pho[3] = phokey & 7;
    phokey >>= 3;
    rtyp_pho[2] = phokey & 0xf;
    phokey >>= 4;
    rtyp_pho[1] = phokey & 0x3;
    phokey >>= 2;
    rtyp_pho[0] = phokey;
}

void mask_key_typ_pho (phokey_t *key) {
    if (poo.typ_pho[0] == BACK_QUOTE_NO)
        return;
    if (!poo.typ_pho[0])
        *key &= ~(31 << 9);
    if (!poo.typ_pho[1])
        *key &= ~(3 << 7);
    if (!poo.typ_pho[2])
        *key &= ~(15 << 3);
    if (!poo.typ_pho[3])
        *key &= ~(7);
}

#define TKBM 0
#define MIN_M_PHO 5

static void find_match_phos (u_char mtyp_pho[4], int *mcount, int newkey) {
    int vv;
    phokey_t key = pho2key (poo.typ_pho);

    mask_key_typ_pho (&key);
#if TKBM
    dbg ("-------------------- %d --", poo.typ_pho[3]);
    prph (key);
    dbg ("\n");
#endif
    for (vv = hash_pho[(int) poo.typ_pho[0]]; vv < hash_pho[(int) poo.typ_pho[0] + 1]; vv++) {
        phokey_t ttt = idx_pho[vv].key;

        if (newkey != ' ' && !poo.typ_pho[3])
            mask_key_typ_pho (&ttt);

        if (ttt > key)
            break;

        int count = 0;

        int i;
        for (i = idx_pho[vv].start; i < idx_pho[vv + 1].start; i++) {
            if (utf8_sz (pho_idx_str (i)) > 1) {
#if 0
            utf8_putchar(ch_pho[i].ch);
            dbg(" ");
#endif
                count++;
            }
        }

        if (*mcount < count) {
            *mcount = count;
            memcpy (mtyp_pho, poo.typ_pho, sizeof (poo.typ_pho));
#if TKBM
            dbg ("count %d\n", count);
#endif
            if (*mcount > MIN_M_PHO)
                break;
        }
    }
}

gboolean inph_typ_pho_pinyin (int newkey);

static int typ_pho_status () {
    return poo.typ_pho[3] ? PHO_STATUS_OK_NEW : PHO_STATUS_OK;
}

int inph_typ_pho (KeySym newkey) {
    int i;
    int insert = -1;

    if (pin_juyin) {
        return inph_typ_pho_pinyin (newkey);
    }

    if (poo.typ_pho[0] == BACK_QUOTE_NO) {
        poo.typ_pho[1] = (char) newkey;
        poo.inph[1] = newkey;
        return PHO_STATUS_OK;
    }

    int max_in_idx;
    for (max_in_idx = 3; max_in_idx >= 0 && !poo.typ_pho[max_in_idx]; max_in_idx--)
        ;

    // try insert mode first
    if (insert < 0)
        for (i = 0; i < 3; i++) {
            char num = phkbm.phokbm[(int) newkey][i].num;
            int typ = phkbm.phokbm[(int) newkey][i].typ;

            if (num && !poo.inph[typ] && typ > max_in_idx) {
                poo.inph[typ] = newkey;
                poo.typ_pho[typ] = num;
#if TKBM
                dbg ("insert typ %d\n", typ);
#endif
                insert = typ;
                break;
            }
        }

    if (insert < 0) {
        // then overwrite mode
        for (i = 0; i < 3; i++) {
            char num = phkbm.phokbm[newkey][i].num;
            int typ = phkbm.phokbm[newkey][i].typ;

            if (num) {
                poo.inph[typ] = newkey;
                poo.typ_pho[typ] = num;
                insert = typ;
                break;
            }
        }
    }

    //  dbg("newkey %c\n", newkey);

    int mcount = 0;
    u_char mtyp_pho[4];

    int a;

    for (a = 0; a < 3; a++) {
        char num = phkbm.phokbm[(int) poo.inph[0]][a].num;
        char typ = phkbm.phokbm[(int) poo.inph[0]][a].typ;

        if (typ == 3)
            continue;

        if (num) {
            if (typ == 2 && poo.typ_pho[0] && !poo.typ_pho[2])
                poo.typ_pho[0] = 0;
            poo.typ_pho[(int) typ] = num;
#if TKBM
            dbg ("%d num %d\n", a, num);
#endif
            find_match_phos (mtyp_pho, &mcount, newkey);
        }

        for (i = 0; i < 3; i++) {
            char num = phkbm.phokbm[(int) poo.inph[2]][i].num;
            char typ = phkbm.phokbm[(int) poo.inph[2]][i].typ;

            if (!num)
                break;

            if (typ != 2)
                continue;

            poo.typ_pho[(int) typ] = num;

            find_match_phos (mtyp_pho, &mcount, newkey);

            if (mcount > MIN_M_PHO) {
                return typ_pho_status ();
            }
        }

        find_match_phos (mtyp_pho, &mcount, newkey);

        if (mcount > MIN_M_PHO) {
            return typ_pho_status ();
        }
    }

    if (mcount) {
        memcpy (poo.typ_pho, mtyp_pho, sizeof (poo.typ_pho));
        return typ_pho_status ();
    }

    return PHO_STATUS_REJECT;
}

void reset_pho_structure () {
    memset (poo.typ_pho, 0, sizeof (poo.typ_pho));
    memset (poo.inph, 0, sizeof (poo.inph));
    poo.ityp3_pho = FALSE;
    poo.maxi = 0;
    poo.cpg = 0;

    if (hime_pop_up_win && !same_query_show_pho_win ())
        hide_win_pho ();
}

void clr_in_area_pho () {
    reset_pho_structure ();
    int i;
    for (i = 0; i < text_pho_N; i++)
        set_phoneme_at_index (i, "  ");
}

static void disp_in_area_pho () {
    int i;

    text_pho_N = pin_juyin ? 7 : 4;
    if (pin_juyin) {
        for (i = 0; i < text_pho_N; i++) {
            set_phoneme_at_index (i, &poo.inph[i]);
        }
    } else {
        for (i = 0; i < 4; i++) {
            if (i == 1 && poo.typ_pho[0] == BACK_QUOTE_NO) {
                set_phoneme_at_index (i, &poo.inph[1]);
            } else
                set_phoneme_at_index (i, &pho_chars[i][poo.typ_pho[i] * 3]);
        }
    }
}

static int qcmp_count (const void *aa, const void *bb) {
    PHO_ITEM *a = (PHO_ITEM *) aa;
    PHO_ITEM *b = (PHO_ITEM *) bb;

    return b->count - a->count;
}

void disp_pho_sel (char *s);

static void ClrPhoSelArea () {
    disp_pho_sel ("");
}

extern char *TableDir;
extern char phofname[128];

gboolean get_start_stop_idx (phokey_t key, int *start_i, int *stop_i) {
    int typ_pho0 = key >> 9;
    int vv = hash_pho[typ_pho0];

    while (vv < idxnum_pho) {
        if (idx_pho[vv].key >= key)
            break;
        else
            vv++;
    }

    if (vv >= idxnum_pho || idx_pho[vv].key != key)
        return FALSE;

    *start_i = idx_pho[vv].start;
    *stop_i = idx_pho[vv + 1].start;

    return TRUE;
}

// given the pho key & the utf8 char, return the idx in ch_pho
int ch_key_to_ch_pho_idx (phokey_t phkey, char *utf8) {
    int start_i, stop_i;

    get_start_stop_idx (phkey, &start_i, &stop_i);

    int i;
    for (i = start_i; i < stop_i; i++) {
        char *ch = pho_idx_str (i);
        int u8len = utf8_sz (ch);
        if (!memcmp (ch, utf8, u8len)) {
            return i;
        }
    }

    //  prph(phkey);
    //  dbg("error found   %c%c", *big5, *(big5+1));
    return -1;
}

void inc_pho_count (phokey_t key, int ch_idx) {
    int start_i, stop_i;

    if (!phonetic_char_dynamic_sequence)
        return;

    get_start_stop_idx (key, &start_i, &stop_i);

    //  dbg("start_i %d %d    %d %d\n", start_i, stop_i, poo.start_idx, poo.stop_idx);

    ch_pho[ch_idx].count++;
    //  dbg("count %d\n", ch_pho[ch_idx].count);

    qsort (&ch_pho[start_i], stop_i - start_i, sizeof (PHO_ITEM), qcmp_count);
#if 0
  int i;
  for(i=start_i; i < stop_i; i++) {
    dbg("uuuu %c%c%c %d\n", ch_pho[i].ch[0], ch_pho[i].ch[1],
      ch_pho[i].ch[2], ch_pho[i].count);
  }
#endif

    FILE *fw;

    //  dbg("phofname %s\n", phofname);
    if ((fw = fopen (phofname, "rb+")) == NULL) {
        p_err ("err %s\n", phofname);
    }

    if (fseek (fw, ch_pho_ofs + sizeof (PHO_ITEM) * start_i, SEEK_SET) < 0)
        p_err ("fseek err");
#if 1
    if (fwrite (&ch_pho[start_i], sizeof (PHO_ITEM), stop_i - start_i, fw) <= 0)
        p_err ("fwrite err");
#endif
    fclose (fw);
}

void lookup_gtab (char *ch);
gboolean is_gtab_query_mode ();
void set_gtab_target_displayed ();

#include "gtab-buf.h"

void putkey_pho (u_short key, int idx) {
    char *pho_str = pho_idx_str (idx);

    if (poo.same_pho_query_state == SAME_PHO_QUERY_pho_select && ggg.gbufN)
        insert_gbuf_nokey (pho_str);
    else
        send_text (pho_str);

    lookup_gtab (pho_str);

    inc_pho_count (key, idx);

    clr_in_area_pho ();
    ClrSelArea ();

    if (is_gtab_query_mode ())
        set_gtab_target_displayed ();
}

void recreate_win1_if_nessary ();

void load_tab_pho_file () {
    pho_load ();

    memset (poo.typ_pho, 0, sizeof (poo.typ_pho));

    u_int ttt = 0;
    int i;
    for (i = 0; i < MAX_HASH_PHO; i++) {
        if (idx_pho[ttt].key >> 9 == i)
            hash_pho[i] = ttt;
        else {
            continue;
        }

        while (ttt < idxnum_pho && idx_pho[ttt].key >> 9 == i)
            ttt++;
    }

    for (i = MAX_HASH_PHO; !hash_pho[i]; i--)
        hash_pho[i] = idxnum_pho;

    char kbmfname[MAX_HIME_STR];
    FILE *fr;

    free (pin_juyin);
    pin_juyin = NULL;

    if (!strstr (pho_kbm_name, "pinyin")) {
        text_pho_N = 4;
    } else {
        load_pin_juyin ();
    }

    if (strcmp (pho_kbm_name, "hsu"))
        b_hsu_kbm = FALSE;
    else
        b_hsu_kbm = TRUE;

    char pho_kbm_name_kbm[128];

    strcat (strcpy (pho_kbm_name_kbm, pho_kbm_name), ".kbm");
    dbg ("phokbm_name: %s\n", pho_kbm_name_kbm);

    get_sys_table_file_name (pho_kbm_name_kbm, kbmfname);

    if ((fr = fopen (kbmfname, "rb")) == NULL)
        p_err ("Cannot open %s", kbmfname);

    dbg ("kbmfname %s\n", kbmfname);

    fread (&phkbm, sizeof (phkbm), 1, fr);
    fclose (fr);
    phkbm.selkeyN = strlen (pho_selkey);

    dbg ("pho_selkey %s\n", pho_selkey);

    recreate_win1_if_nessary ();
#if 0
  for(i='A'; i <= 'z'; i++)
    dbg("%c %d %d\n", i, phkbm.phokbm[i][0].num, phkbm.phokbm[i][0].typ);
#endif
}

void show_win_pho ();

void init_tab_pho () {
    if (!ch_pho) {
        load_tab_pho_file ();
    }

    show_win_pho ();
    clr_in_area_pho ();
}

static char *pho_idx_str_markup (int ii) {
    char *pho_str = pho_idx_str (ii);
    if (!strcmp (pho_str, "<"))
        pho_str = "&lt;";
    else if (!strcmp (pho_str, ">"))
        pho_str = "&gt;";
    return pho_str;
}

gboolean shift_char_proc (KeySym key, int kbstate);
gboolean pre_punctuation (KeySym xkey);
void pho_play (phokey_t key);
void close_gtab_pho_win ();
gboolean pre_punctuation_hsu (KeySym xkey);

int feedkey_pho (KeySym xkey, int kbstate) {
    int ctyp = 0;
    static unsigned int vv, ii;
    static phokey_t key;
    char *pp = NULL;
    char kno;
    int i, j, jj = 0, kk = 0;
    char out_buffer[512];
    int out_bufferN;
    gboolean shift_is_pressed = (kbstate & ShiftMask) > 0;
    gboolean ctrl_is_pressed = (kbstate & ControlMask) > 0;

    if (ctrl_is_pressed)
        return 0;

    if (kbstate & LockMask) {
        if (xkey >= 0x7e || xkey < ' ')
            return FALSE;
        if (hime_capslock_lower)
            case_inverse (&xkey, shift_is_pressed);
        send_ascii (xkey);
        return 1;
    }

    if (xkey >= 'A' && xkey <= 'Z' && poo.typ_pho[0] != BACK_QUOTE_NO)
        xkey += 0x20;

    switch (xkey) {
    case XK_Escape:
        if (typ_pho_empty ())
            return 0;
        ClrPhoSelArea ();
        clr_in_area_pho ();
        if (is_gtab_query_mode ())
            close_gtab_pho_win ();
        return 1;
    case XK_BackSpace:
        poo.ityp3_pho = FALSE;
        for (j = 3; j >= 0; j--)
            if (poo.typ_pho[j]) {
                poo.typ_pho[j] = 0;
                if (typ_pho_empty ()) {
                    ClrSelArea ();
                    clr_in_area_pho ();
                    return 1;
                }
                break;
            }

        if (j < 0)
            return 0;

        goto llll3;
    case '<':
        if (!poo.ityp3_pho) {
            return pre_punctuation (xkey);
        }
        if (poo.cpg >= phkbm.selkeyN)
            poo.cpg -= phkbm.selkeyN;
        goto proc_state;
    case ' ':
        if (!poo.typ_pho[0] && !poo.typ_pho[1] && !poo.typ_pho[2]) {
            if (current_fullwidth_mode ())
                return full_char_proc (xkey);
            return 0;
        }

        if (!poo.ityp3_pho) {
            poo.ityp3_pho = TRUE;
            goto lll1;
        }

        ii = poo.start_idx + poo.cpg + phkbm.selkeyN;

        if (ii < poo.stop_idx) {
            poo.cpg += phkbm.selkeyN;
            dbg ("spc pool.cpg %d\n", poo.cpg);
        } else {
            if (poo.cpg) {
                poo.cpg = 0;
                ii = poo.start_idx;
            } else {
                putkey_pho (key, poo.start_idx);
                return 1;
            }
        }

        goto disp;
    default:
        if (xkey >= 127 || xkey < ' ')
            return 0;

        if (shift_is_pressed) {
            //        return shift_char_proc(xkey, kbstate);
            if (pre_punctuation (xkey))
                return 1;
            return 0;
        }

        //    dbg("poo.maxi:%d  %d\n", poo.maxi, poo.cpg);

        if ((pp = strchr (pho_selkey, xkey)) && poo.maxi && poo.ityp3_pho) {
            int c = pp - pho_selkey;

            if (c < poo.maxi) {
                putkey_pho (key, poo.start_idx + poo.cpg + c);
            }
            return 1;
        }

        if (poo.ityp3_pho && !poo.cpg) {
            //        dbg("poo.start_idx: %d\n", poo.start_idx);
            putkey_pho (key, poo.start_idx);
        }

        //      poo.cpg=0;
    }

lll1:
    inph_typ_pho (xkey);
    //  dbg("typ_pho %x %x\n", poo.typ_pho[0], poo.typ_pho[1]);

    if (hime_pop_up_win)
        show_win_pho ();

    if (poo.typ_pho[3])
        ctyp = 3;

    jj = 0;
    kk = 1;
llll2:
    if (ctyp == 3) {
        poo.ityp3_pho = TRUE; /* last key is entered */
    }
llll3:

    key = pho2key (poo.typ_pho);

#if 0
  dbg("poo.typ_pho %d %d %d %d\n", poo.typ_pho[0], poo.typ_pho[1], poo.typ_pho[2], poo.typ_pho[3]);
#endif
    if (!key) {
        return pre_punctuation_hsu (xkey);
    }

    pho_play (key);

    vv = hash_pho[(int) poo.typ_pho[0]];
    phokey_t ttt;
    ttt = 0xffff;

    while (vv < idxnum_pho) {
        ttt = idx_pho[vv].key;
        mask_key_typ_pho (&ttt);

        if (ttt >= key)
            break;
        else
            vv++;
    }

    //  dbg("vv %d %d\n", vv, idxnum_pho);

    if (ttt > key || (poo.ityp3_pho && idx_pho[vv].key != key)) {
        //    dbg("not found\n");
        while (jj < 4) {
            while (kk < 3)
                if (phkbm.phokbm[(int) poo.inph[jj]][kk].num) {

                    if (kk) {
                        ctyp = phkbm.phokbm[(int) poo.inph[jj]][kk - 1].typ;
                        poo.typ_pho[ctyp] = 0;
                    }

                    kno = phkbm.phokbm[(int) poo.inph[jj]][kk].num;
                    ctyp = phkbm.phokbm[(int) poo.inph[jj]][kk].typ;
                    poo.typ_pho[ctyp] = kno;
                    kk++;
                    goto llll2;
                } else
                    kk++;
            jj++;
            kk = 1;
        }

        bell ();
        poo.ityp3_pho = FALSE;
        poo.typ_pho[3] = 0;
        disp_in_area_pho ();
        return 1;
    }

proc_state:
    disp_in_area_pho ();
    poo.start_idx = ii = idx_pho[vv].start;
    poo.stop_idx = idx_pho[vv + 1].start;

    //  dbg("poo.start_idx: %d %d\n", poo.start_idx, poo.stop_idx);

    if (poo.typ_pho[0] == L_BRACKET_NO || poo.typ_pho[0] == R_BRACKET_NO || (poo.typ_pho[0] == BACK_QUOTE_NO && poo.typ_pho[1]))
        poo.ityp3_pho = TRUE;

    ii += poo.cpg;

    if (poo.ityp3_pho && poo.stop_idx - poo.start_idx == 1) {
        putkey_pho (key, ii);
        poo.ityp3_pho = FALSE;
        poo.maxi = 0;
        return 1;
    }

disp:
    i = 0;
    out_bufferN = 0;
    out_buffer[0] = 0;

    if (poo.ityp3_pho) {
        //    dbg("poo.cpg %d\n", poo.cpg);

        while (i < phkbm.selkeyN && ii < poo.stop_idx) {
            char tt[512];
            snprintf (tt, sizeof (tt), "<span foreground=\"%s\">%c</span>",
                      hime_sel_key_color, pho_selkey[i]);
            int ttlen = strlen (tt);
            memcpy (out_buffer + out_bufferN, tt, ttlen);
            out_bufferN += ttlen;
            //      strcat(out_buffer, tt);
            char *pho_str = pho_idx_str_markup (ii);
            int len = strlen (pho_str);
            memcpy (&out_buffer[out_bufferN], pho_str, len);
            out_bufferN += len;
            out_buffer[out_bufferN++] = ' ';

            ii++;
            i++;
        }

        char *tt = poo.cpg ? "&lt;" : " ";
        int ttlen = strlen (tt);
        memcpy (out_buffer + out_bufferN, tt, ttlen);
        out_bufferN += ttlen;

        if (ii < poo.stop_idx) {
            out_buffer[out_bufferN++] = poo.cpg ? '\\' : ' ';
            tt = "&gt;";
            ttlen = strlen (tt);
            memcpy (out_buffer + out_bufferN, tt, ttlen);
            out_bufferN += strlen (tt);
        }

        poo.maxi = i;
    } else {
        while (i < phkbm.selkeyN && ii < poo.stop_idx) {
            char *pho_str = pho_idx_str_markup (ii);
            int len = strlen (pho_str);
            memcpy (&out_buffer[out_bufferN], pho_str, len);
            out_bufferN += len;

            ii++;
            i++;
        }
        poo.maxi = i;
    }

    out_buffer[out_bufferN] = 0;
    disp_pho_sel (out_buffer);

    return 1;
}

static char typ_pho_no_to_xkey (int typ, u_char num) {
    int i, j;

    for (i = ' '; i < 127; i++)
        for (j = 0; j < 3; j++)
            if (phkbm.phokbm[i][j].typ == typ && phkbm.phokbm[i][j].num == num)
                return i;

    return 0;
}

void start_gtab_pho_query (char *utf8) {
    phokey_t phokeys[32];
    int phokeysN, i;

    phokeysN = utf8_pho_keys (utf8, phokeys);
    if (phokeysN <= 0)
        return;

    u_char rtyp_pho[4];
    memset (rtyp_pho, 0, sizeof (rtyp_pho));
    key_typ_pho (phokeys[0], rtyp_pho);

    char xkeys[4];
    memset (xkeys, 0, sizeof (xkeys));

    for (i = 0; i < 4; i++) {
        if (!rtyp_pho[i])
            continue;

        xkeys[i] = typ_pho_no_to_xkey (i, rtyp_pho[i]);
    }

    if (!xkeys[3])
        xkeys[3] = ' ';

    for (i = 0; i < 4; i++) {
        feedkey_pho (xkeys[i], 0);
    }
}

void pho_reset () {
    clr_in_area_pho ();
}

#include "im-client/hime-im-client-attr.h"
extern GtkWidget *win_pho;

int pho_get_preedit (char *str, HIME_PREEDIT_ATTR attr[], int *cursor, int *sub_comp_len) {
    *sub_comp_len = !typ_pho_empty ();
    if (is_win_pho_visible ())
        *sub_comp_len |= 2;
    *cursor = 0;
    str[0] = 0;
    return 0;
}

static PHO_ST temp_pho_st;
void pho_save_gst () {
    temp_pho_st = poo;
}

void pho_restore_gst () {
    poo = temp_pho_st;
}
