/*
 * SPDX-FileCopyrightText: 2008~2010 dragchan <zgchan317@gmail.com>
 * SPDX-FileCopyrightText: 2021~2021 CSSlayer <wengxt@gmail.com>
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 *
 */

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcitx-utils/utf8.h>
#include <linux/input.h>
#include <linux/kd.h>
#include <linux/keyboard.h>
#include <sys/ioctl.h>
#include "input_key.h"

static char key_down[NR_KEYS];
static unsigned char shift_down[NR_SHIFT];
static short shift_state;
static char lock_state;
static char cr_with_lf, applic_keypad, cursor_esco;
static int npadch;

void init_keycode_state() {
    npadch = -1;
    shift_state = 0;
    memset(key_down, 0, sizeof(char) * NR_KEYS);
    memset(shift_down, 0, sizeof(char) * NR_SHIFT);
    ioctl(STDIN_FILENO, KDGKBLED, &lock_state);
}

void update_term_mode(char crlf, char appkey, char curo) {
    cr_with_lf = crlf;
    applic_keypad = appkey;
    cursor_esco = curo;
}

unsigned short keycode_to_keysym(unsigned short keycode, char down) {
    if (keycode >= NR_KEYS)
        return K_HOLE;

    char rep = (down && key_down[keycode]);
    key_down[keycode] = down;

    struct kbentry ke;
    ke.kb_table = shift_state;
    ke.kb_index = keycode;

    if (ioctl(STDIN_FILENO, KDGKBENT, &ke) == -1)
        return K_HOLE;

    if (KTYP(ke.kb_value) == KT_LETTER && (lock_state & K_CAPSLOCK)) {
        ke.kb_table = shift_state ^ (1 << KG_SHIFT);
        if (ioctl(STDIN_FILENO, KDGKBENT, &ke) == -1)
            return K_HOLE;
    }

    if (ke.kb_value == K_HOLE || ke.kb_value == K_NOSUCHMAP)
        return K_HOLE;

    unsigned value = KVAL(ke.kb_value);

    switch (KTYP(ke.kb_value)) {
    case KT_SPEC:
        switch (ke.kb_value) {
        case K_NUM:
            if (applic_keypad)
                break;
        case K_BARENUMLOCK:
        case K_CAPS:
        case K_CAPSON:
            if (down && !rep) {
                if (value == KVAL(K_NUM) || value == KVAL(K_BARENUMLOCK))
                    lock_state ^= K_NUMLOCK;
                else if (value == KVAL(K_CAPS))
                    lock_state ^= K_CAPSLOCK;
                else if (value == KVAL(K_CAPSON))
                    lock_state |= K_CAPSLOCK;

                ioctl(STDIN_FILENO, KDSKBLED, lock_state);
            }
            break;

        default:
            break;
        }
        break;

    case KT_SHIFT:
        if (value >= NR_SHIFT || rep)
            break;

        if (value == KVAL(K_CAPSSHIFT)) {
            value = KVAL(K_SHIFT);

            if (down && (lock_state & K_CAPSLOCK)) {
                lock_state &= ~K_CAPSLOCK;
                ioctl(STDIN_FILENO, KDSKBLED, lock_state);
            }
        }

        if (down)
            shift_down[value]++;
        else if (shift_down[value])
            shift_down[value]--;

        if (shift_down[value])
            shift_state |= (1 << value);
        else
            shift_state &= ~(1 << value);

        break;

    case KT_LATIN:
    case KT_LETTER:
    case KT_FN:
    case KT_PAD:
    case KT_CONS:
    case KT_CUR:
    case KT_META:
    case KT_ASCII:
        break;

    default:
        printf("not support!\n");
        break;
    }

    return ke.kb_value;
}

unsigned short keypad_keysym_redirect(unsigned short keysym) {
    if (applic_keypad || KTYP(keysym) != KT_PAD || KVAL(keysym) >= NR_PAD)
        return keysym;

#define KL(val) K(KT_LATIN, val)
    static const unsigned short num_map[] = {
        KL('0'), KL('1'), KL('2'), KL('3'), KL('4'), KL('5'), KL('6'),
        KL('7'), KL('8'), KL('9'), KL('+'), KL('-'), KL('*'), KL('/'),
        K_ENTER, KL(','), KL('.'), KL('?'), KL('('), KL(')'), KL('#')};

    static const unsigned short fn_map[] = {
        K_INSERT, K_SELECT, K_DOWN,   K_PGDN,  K_LEFT,  K_P5,    K_RIGHT,
        K_FIND,   K_UP,     K_PGUP,   KL('+'), KL('-'), KL('*'), KL('/'),
        K_ENTER,  K_REMOVE, K_REMOVE, KL('?'), KL('('), KL(')'), KL('#')};

    if (lock_state & K_NUMLOCK)
        return num_map[keysym - K_P0];
    return fn_map[keysym - K_P0];
}

char *keysym_to_term_string(unsigned short keysym, char down) {
    static struct kbsentry kse;
    char *buf = (char *)kse.kb_string;
    *buf = 0;

    if (KTYP(keysym) != KT_SHIFT && !down)
        return buf;

    keysym = keypad_keysym_redirect(keysym);
    unsigned index = 0, value = KVAL(keysym);

    switch (KTYP(keysym)) {
    case KT_LATIN:
    case KT_LETTER:
        if (value < KVAL(AC_START) || value > KVAL(AC_END))
            index = fcitx_ucs4_to_utf8(value, buf);
        break;

    case KT_FN:
        kse.kb_func = value;
        ioctl(STDIN_FILENO, KDGKBSENT, &kse);
        index = strlen(buf);
        break;

    case KT_SPEC:
        if (keysym == K_ENTER) {
            buf[index++] = '\r';
            if (cr_with_lf)
                buf[index++] = '\n';
        } else if (keysym == K_NUM && applic_keypad) {
            buf[index++] = '\e';
            buf[index++] = 'O';
            buf[index++] = 'P';
        }
        break;

    case KT_PAD:
        if (applic_keypad && !shift_down[KG_SHIFT]) {
            if (value < NR_PAD) {
                static const char app_map[] = "pqrstuvwxylSRQMnnmPQS";

                buf[index++] = '\e';
                buf[index++] = 'O';
                buf[index++] = app_map[value];
            }
        } else if (keysym == K_P5 && !(lock_state & K_NUMLOCK)) {
            buf[index++] = '\e';
            buf[index++] = (applic_keypad ? 'O' : '[');
            buf[index++] = 'G';
        }
        break;

    case KT_CUR:
        if (value < 4) {
            static const char cur_chars[] = "BDCA";

            buf[index++] = '\e';
            buf[index++] = (cursor_esco ? 'O' : '[');
            buf[index++] = cur_chars[value];
        }
        break;

    case KT_META: {
        long flag;
        ioctl(STDIN_FILENO, KDGKBMETA, &flag);

        if (flag == K_METABIT) {
            buf[index++] = 0x80 | value;
        } else {
            buf[index++] = '\e';
            buf[index++] = value;
        }
        break;
    }

    case KT_SHIFT:
        if (!down && npadch != -1) {
            index = fcitx_ucs4_to_utf8(npadch, buf);
            npadch = -1;
        }
        break;

    case KT_ASCII:
        if (value < NR_ASCII) {
            int base = 10;

            if (value >= KVAL(K_HEX0)) {
                base = 16;
                value -= KVAL(K_HEX0);
            }

            if (npadch == -1)
                npadch = value;
            else
                npadch = npadch * base + value;
        }
        break;

    default:
        break;
    }

    buf[index] = 0;
    return buf;
}
