/*  This file is part of Lyos.

    Lyos 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 3 of the License, or
    (at your option) any later version.

    Lyos 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 Lyos.  If not, see <http://www.gnu.org/licenses/>. */

#include <lyos/types.h>
#include <lyos/ipc.h>
#include <lyos/const.h>
#include <errno.h>
#include <unistd.h>
#include <lyos/irqctl.h>
#include <lyos/portio.h>
#include <lyos/service.h>
#include <lyos/input.h>
#include <libinputdriver/libinputdriver.h>
#include <asm/const.h>

#include "keyboard.h"
#include "uapi/linux/input-event-codes.h"

#define KEYMAP_SIZE 512

static const unsigned short set2_keycode[KEYMAP_SIZE] = {
    0,   67,  65,  63,  61,  59,  60,  88,  0,   68,  66,  64,  62,  15,  41,
    117, 0,   56,  42,  93,  29,  16,  2,   0,   0,   0,   44,  31,  30,  17,
    3,   0,   0,   46,  45,  32,  18,  5,   4,   95,  0,   57,  47,  33,  20,
    19,  6,   183, 0,   49,  48,  35,  34,  21,  7,   184, 0,   0,   50,  36,
    22,  8,   9,   185, 0,   51,  37,  23,  24,  11,  10,  0,   0,   52,  53,
    38,  39,  25,  12,  0,   0,   89,  40,  0,   26,  13,  0,   0,   58,  54,
    28,  27,  0,   43,  0,   85,  0,   86,  91,  90,  92,  0,   14,  94,  0,
    79,  124, 75,  71,  121, 0,   0,   82,  83,  80,  76,  77,  72,  1,   69,
    87,  78,  81,  74,  55,  73,  70,  99,

    0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
    0,   217, 100, 255, 0,   97,  165, 0,   0,   156, 0,   0,   0,   0,   0,
    0,   125, 173, 114, 0,   113, 0,   0,   0,   126, 128, 0,   0,   140, 0,
    0,   0,   127, 159, 0,   115, 0,   164, 0,   0,   116, 158, 0,   172, 166,
    0,   0,   0,   142, 157, 0,   0,   0,   0,   0,   0,   0,   155, 0,   98,
    0,   0,   163, 0,   0,   226, 0,   0,   0,   0,   0,   0,   0,   0,   255,
    96,  0,   0,   0,   143, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
    107, 0,   105, 102, 0,   0,   112, 110, 111, 108, 112, 106, 103, 0,   119,
    0,   118, 109, 0,   99,  104, 119, 0,   0,   0,   0,   65,  99,
};

static const unsigned short set1_scancode[128] = {
    0,   118, 22,  30,  38,  37,  46,  54,  61,  62,  70,  69,  78,  85,  102,
    13,  21,  29,  36,  45,  44,  53,  60,  67,  68,  77,  84,  91,  90,  20,
    28,  27,  35,  43,  52,  51,  59,  66,  75,  76,  82,  14,  18,  93,  26,
    34,  33,  42,  50,  49,  58,  65,  73,  74,  89,  124, 17,  41,  88,  5,
    6,   4,   12,  3,   11,  2,   10,  1,   9,   119, 126, 108, 117, 125, 123,
    107, 115, 116, 121, 105, 114, 122, 112, 113, 127, 96,  97,  120, 7,   15,
    23,  31,  39,  47,  55,  63,  71,  79,  86,  94,  8,   16,  24,  32,  40,
    48,  56,  64,  72,  80,  87,  111, 19,  25,  57,  81,  83,  92,  95,  98,
    99,  100, 101, 103, 104, 106, 109, 110};

#define KEYCODE_NULL 255

/* static const char* name = "atkbd"; */

#define KB_DEV  0
#define AUX_DEV 1
static struct inputdriver_dev input_devs[2];

static int kb_hook_id, aux_hook_id;

static unsigned short keycode_table[KEYMAP_SIZE];

static int emul = 0;
static int release = FALSE;

static u8 aux_bytes[3];
static u8 aux_state = 0;
static int aux_counter = 0;
static int aux_available = 0;

static int kb_init();
static void keyboard_interrupt(unsigned long irq_set);
static void kbd_process(struct inputdriver_dev* dev, u8 scancode);
static void kbdaux_process(struct inputdriver_dev* dev, u8 scancode);

static void set_leds();
static void kb_wait();
static void kb_ack();
static void kb_cmd0(int cmd);
static void kb_cmd1(int cmd, int data);
static int kb_read(void);
static int scan_keyboard(u8* bp, int* is_aux);

static struct inputdriver keyboard_driver = {
    .input_interrupt = keyboard_interrupt,
};

/*****************************************************************************
 *                               main
 *****************************************************************************/
/**
 * The main loop.
 *
 *****************************************************************************/
int main()
{
    serv_register_init_fresh_callback(kb_init);
    serv_init();

    return inputdriver_start(&keyboard_driver);
}

/*****************************************************************************
 *                                keyboard_handler
 *****************************************************************************/
/**
 * <Ring 0> Handles the interrupts generated by the keyboard controller.
 *
 * @param irq The IRQ corresponding to the keyboard, unused here.
 *****************************************************************************/
static void keyboard_interrupt(unsigned long irq_set)
{
    u8 scancode;
    int is_aux;

    if (!scan_keyboard(&scancode, &is_aux)) return;

    if (!is_aux)
        kbd_process(&input_devs[KB_DEV], scancode);
    else if (aux_available)
        kbdaux_process(&input_devs[AUX_DEV], scancode);
}

static void kbd_process(struct inputdriver_dev* dev, u8 scancode)
{
    u16 keycode;
    s32 value;

    inputdriver_send_event(dev, EV_MSC, MSC_RAW, scancode);

    /* extract the release bit from scancode */
    if (emul || (scancode != 0xE0 && scancode != 0xE1)) {
        release = scancode >> 7;
        scancode &= 0x7f;
    }

    switch (scancode) {
    case 0xE0:
        emul = 1;
        return;
    }

    scancode = (scancode & 0x7f) | ((scancode & 0x80) << 1);
    if (emul == 1) scancode |= 0x80;

    if (emul && --emul) {
        return;
    }

    keycode = keycode_table[scancode];

    if (keycode != KEYCODE_NULL) {
        inputdriver_send_event(dev, EV_MSC, MSC_SCAN, scancode);
    }

    switch (keycode) {
    case KEYCODE_NULL:
        break;
    default:
        if (release) {
            value = 0;
        } else {
            value = 1;
        }

        inputdriver_send_event(dev, EV_KEY, keycode, value);
        inputdriver_sync(dev);
        break;
    }

    release = FALSE;
}

static void kbdaux_process(struct inputdriver_dev* dev, u8 scancode)
{
    static const int aux_btns[] = {BTN_LEFT, BTN_MIDDLE, BTN_RIGHT};
    u32 delta;
    int i;

    if (aux_counter == 0 && !(scancode & 0x08)) return;

    aux_bytes[aux_counter++] = scancode;

    if (aux_counter < 3) return;

    aux_counter = 0;

    for (i = 0; i < sizeof(aux_btns) / sizeof(aux_btns[0]); i++) {
        if ((aux_state ^ aux_bytes[0]) & (1 << i)) {
            aux_state ^= (1 << i);

            inputdriver_send_event(dev, EV_KEY, aux_btns[i],
                                   !!(aux_state & (1 << i)));
        }
    }

    for (i = 0; i < 2; i++) {
        delta = aux_bytes[i + 1];
        if (delta) {
            if (aux_bytes[0] & (0x10 << i)) delta |= ~0xFF;

            if (i) delta = -delta;

            inputdriver_send_event(dev, EV_REL, !i ? REL_X : REL_Y, delta);
        }
    }

    inputdriver_sync(dev);
}

static void init_keyboard(struct inputdriver_dev* dev)
{
    int i;

    inputdriver_device_init(dev, &keyboard_driver, NO_DEVICE_ID);

    dev->input_id.bustype = BUS_I8042;
    dev->input_id.vendor = 0x0001;
    dev->input_id.product = 1;
    dev->input_id.version = 1;

    SET_BIT(dev->evbit, EV_KEY);
    SET_BIT(dev->evbit, EV_MSC);

    for (i = 0; i < KEYMAP_SIZE; i++) {
        if (keycode_table[i] != KEYCODE_NULL) {
            SET_BIT(dev->keybit, keycode_table[i]);
        }
    }

    SET_BIT(dev->mscbit, MSC_SCAN);
    SET_BIT(dev->mscbit, MSC_RAW);

    inputdriver_register_device(dev);
}

static void init_aux(struct inputdriver_dev* dev)
{
    inputdriver_device_init(dev, &keyboard_driver, NO_DEVICE_ID);

    dev->input_id.bustype = BUS_I8042;
    dev->input_id.vendor = 0x0001;
    dev->input_id.product = 1;
    dev->input_id.version = 1;

    SET_BIT(dev->evbit, EV_KEY);
    SET_BIT(dev->evbit, EV_REL);

    SET_BIT(dev->relbit, REL_X);
    SET_BIT(dev->relbit, REL_Y);

    SET_BIT(dev->keybit, BTN_LEFT);
    SET_BIT(dev->keybit, BTN_MIDDLE);
    SET_BIT(dev->keybit, BTN_RIGHT);

    inputdriver_register_device(dev);
}

/*****************************************************************************
 *                                kb_init
 *****************************************************************************/
/**
 * <Ring 1> Initialize some variables and set keyboard interrupt handler.
 *
 *****************************************************************************/
static int kb_init()
{
    int i, ccb, r;
    unsigned short scancode;

    /* init keycode table */
    for (i = 0; i < 128; i++) {
        scancode = set1_scancode[i];
        keycode_table[i] = set2_keycode[scancode];
        keycode_table[i | 0x80] = set2_keycode[scancode | 0x80];
    }

    /* Disable keyboard and AUX */
    kb_cmd0(KBC_KBD_DI);
    kb_cmd0(KBC_AUX_DI);

    kb_cmd0(KBC_RD_RAM_CCB);
    ccb = kb_read();

    aux_available = !!(ccb & 0x10);

    kb_cmd0(0xAA);
    r = kb_read();
    /* if (r != 0x55) return EIO; */

    kb_hook_id = KEYBOARD_IRQ;
    irq_setpolicy(KEYBOARD_IRQ, IRQ_REENABLE, &kb_hook_id);
    irq_enable(&kb_hook_id);

    init_keyboard(&input_devs[KB_DEV]);

    /* Enable keyboard interrupt */
    ccb |= 0x1;

    if (aux_available) {
        aux_hook_id = PS_2_IRQ;
        irq_setpolicy(PS_2_IRQ, IRQ_REENABLE, &aux_hook_id);
        irq_enable(&aux_hook_id);

        init_aux(&input_devs[AUX_DEV]);

        ccb |= 0x2;
    }

    /* Enable interrupt(s) */
    kb_cmd1(KBC_WR_RAM_CCB, ccb);

    /* Re-enable keyboard */
    kb_cmd0(KBC_KBD_EN);

    if (aux_available) {
        /* Enable AUX */
        kb_cmd0(KBC_AUX_EN);
        kb_cmd1(0xD4, 0xF6);
        kb_cmd1(0xD4, 0xF4);
    }

    kb_wait();
    set_leds();

    return 0;
}

/*****************************************************************************
 *                                kb_wait
 *****************************************************************************/
/**
 * Wait until the input buffer of 8042 is empty.
 *
 *****************************************************************************/
static void kb_wait()
{
    u8 kb_stat;

    do {
        portio_inb(KB_CMD, &kb_stat);
    } while (kb_stat & 0x02);
}

/*****************************************************************************
 *                                kb_ack
 *****************************************************************************/
/**
 * Read from the keyboard controller until a KB_ACK is received.
 *
 *****************************************************************************/
static void kb_ack()
{
    u8 kb_read;

    do {
        portio_inb(KB_DATA, &kb_read);
    } while (kb_read != KB_ACK);
}

static void kb_cmd0(int cmd)
{
    kb_wait();
    portio_outb(KB_CMD, cmd);
}

static void kb_cmd1(int cmd, int data)
{
    kb_wait();
    portio_outb(KB_CMD, cmd);
    kb_wait();
    portio_outb(KB_DATA, data);
}

static int kb_read(void)
{
    u8 status, b;

    for (;;) {
        portio_inb(KB_STATUS, &status);

        if (status & KB_OUT_FULL) {
            usleep(KBC_IN_DELAY);
            portio_inb(KB_DATA, &b);

            return b;
        }
    }

    return 0;
}

static int scan_keyboard(u8* bp, int* is_aux)
{
    u8 b, status;

    if (portio_inb(KB_STATUS, &status) != OK) return FALSE;

    if (!(status & KB_OUT_FULL)) return FALSE;

    if (portio_inb(KB_DATA, &b) != OK) return FALSE;

    if (bp) *bp = b;
    if (is_aux) *is_aux = !!(status & KB_AUX_BYTE);

    return TRUE;
}

/*****************************************************************************
 *                                set_leds
 *****************************************************************************/
/**
 * Set the leds according to: caps_lock, num_lock & scroll_lock.
 *
 *****************************************************************************/
static void set_leds()
{
    u8 leds = 0;
    // u8 leds = (caps_lock << 2) | (num_lock << 1) | scroll_lock;

    kb_wait();
    portio_outb(KB_DATA, LED_CODE);
    kb_ack();

    kb_wait();
    portio_outb(KB_DATA, leds);
    kb_ack();
}
