/*
 * @author  xamidev <xamidev@riseup.net>
 * @brief   PS/2 Keyboard driver
 * @license GPL-3.0-only
 */

#include "config.h"
#include <io/serial/serial.h>
#include <io/kbd/ps2.h>
#include <stdint.h>
#include <io/term/term.h>
#include <kernel.h>
#include <stddef.h>

// The key status bitfield will be used to see if ALT, CONTROL, or SHIFT is pressed
uint8_t key_status = 0b00000000;

// Keymap pointers so we can change between different layouts
unsigned char* keymap;
unsigned char* keymap_shifted;

// Keyboard buffer
char keyboard_buffer[KBD_BUFFER_MAX] = {0};
int write_index = 0;
int read_index = 0;

extern struct init_status init;

unsigned char kbdus[128] =
{
    0,  27, '1', '2', '3', '4', '5', '6', '7', '8',	/* 9 */
    '9', '0', '-', '=', '\b',	/* Backspace */
    '\t', /* Tab */
    'q', 'w', 'e', 'r',	/* 19 */
    't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n',	/* Enter key */
    CTRL,			/* 29   - Control */
    'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';',	/* 39 */
    '\'', '`',   SHIFT,		/* Left shift */
    '\\', 'z', 'x', 'c', 'v', 'b', 'n',			/* 49 */
    'm', ',', '.', '/',   SHIFT,				/* Right shift */
    '*',
    ALT,	/* Alt */
  ' ',	/* Space bar */
    0,	/* Caps lock */
    0,	/* 59 - F1 key ... > */
    0,   0,   0,   0,   0,   0,   0,   0,
    0,	/* < ... F10 */
    0,	/* 69 - Num lock*/
    0,	/* Scroll Lock */
    0,	/* Home key */
    0,	/* Up Arrow */
    0,	/* Page Up */
    '-',
    0,	/* Left Arrow */
    0,
    0,	/* Right Arrow */
    '+',
    0,	/* 79 - End key*/
    0,	/* Down Arrow */
    0,	/* Page Down */
    0,	/* Insert Key */
    0,	/* Delete Key */
    0,   0,   0,
    0,	/* F11 Key */
    0,	/* F12 Key */
    0,	/* All other keys are undefined */
};

unsigned char kbdus_shifted[128] =
{
    0,  27, '!', '@', '#', '$', '%', '^', '&', '*',   /* 9 */
    '(', ')', '_', '+', '\b',                        /* Backspace */
    '\t',                                            /* Tab */
    'Q', 'W', 'E', 'R',                               /* 19 */
    'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', '\n',     /* Enter */
    CTRL,                                            /* 29 */
    'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', /* 39 */
    '"', '~', SHIFT,                                 /* Left shift */
    '|', 'Z', 'X', 'C', 'V', 'B', 'N',                /* 49 */
    'M', '<', '>', '?', SHIFT,                        /* Right shift */
    '*',
    ALT,                                             /* Alt */
    ' ',                                             /* Space */
    0,                                               /* Caps lock */
    0, 0, 0, 0, 0, 0, 0, 0,
    0,                                               /* F10 */
    0,                                               /* Num lock */
    0,                                               /* Scroll lock */
    0, 0, 0,
    '-',
    0, 0, 0,
    '+',
    0, 0, 0,
    0, 0,
    0, 0, 0,
    0,                                               /* F11 */
    0                                                /* F12 */
};

// NOT THE REAL FR KEYMAP!!
// Some French keys have accents or weird symbols that aren't part of ASCII
// so they won't fit in 1 char. As a substitute for now, these will be
// changed to their ASCII counterparts (without accents, etc.)
unsigned char kbdfr[128] =
{
    0,  27, '&', 'e', '"', '\'', '(', '-', 'e', '_',
    'c', 'a', ')', '=', '\b',
    '\t',
    'a', 'z', 'e', 'r',
    't', 'y', 'u', 'i', 'o', 'p', '^', '$', '\n',
    CTRL,
    'q', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm',
    'u', '`', SHIFT,
    '*', 'w', 'x', 'c', 'v', 'b', 'n',
    ',', ';', ':', '!', SHIFT,
    '*',
    ALT,
    ' ',
    0,
    0, 0, 0, 0, 0, 0, 0, 0,
    0,
    0,
    0,
    0, 0, 0,
    '-',
    0, 0, 0,
    '+',
    0, 0, 0,
    0, 0,
    0, 0, 0,
    0,
    0
};

unsigned char kbdfr_shifted[128] =
{
    0,  27, '1', '2', '3', '4', '5', '6', '7', '8',
    '9', '0', '^', '+', '\b',
    '\t',
    'A', 'Z', 'E', 'R',
    'T', 'Y', 'U', 'I', 'O', 'P', '^', 'L', '\n',
    CTRL,
    'Q', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'M',
    '%', '~', SHIFT,
    'u', 'W', 'X', 'C', 'V', 'B', 'N',
    '?', '.', '/', 'S', SHIFT,
    '*',
    ALT,
    ' ',
    0,
    0, 0, 0, 0, 0, 0, 0, 0,
    0,
    0,
    0,
    0, 0, 0,
    '-',
    0, 0, 0,
    '+',
    0, 0, 0,
    0, 0,
    0, 0, 0,
    0,
    0
};

/*
 * keyboard_handler - Keyboard event handler
 *
 * Is called from the interrupt dispatcher.
 * When a key is pressed or released, we get a scancode, and
 * it is then translated to an ASCII character.
 * Left Shift, Ctrl, and Alt keys are also taken into consideration. 
 */
void keyboard_handler()
{
    unsigned char scancode = inb(0x60);

    // Key release (bit 7 set)
    if (scancode & 0x80) {
        unsigned char code = scancode & 0x7F;
        switch (code) {
            // Clear the corresponding bit if corresponding key is released
            case LEFT_SHIFT_PRESSED:
            case RIGHT_SHIFT_PRESSED:
                key_status &= ~SHIFT_PRESSED_BIT;
                break;
            case CTRL_PRESSED:
                key_status &= ~CTRL_PRESSED_BIT;
                break;
            case ALT_PRESSED:
                key_status &= ~ALT_PRESSED_BIT;
                break;
        }
        return;
    } else {
        // Key press
        switch (scancode) {
            // Set bits for corresponding special key press
            case LEFT_SHIFT_PRESSED:
            case RIGHT_SHIFT_PRESSED:
                key_status |= SHIFT_PRESSED_BIT;
                break;
            case CTRL_PRESSED:
                key_status |= CTRL_PRESSED_BIT;
                break;
            case ALT_PRESSED:
                key_status |= ALT_PRESSED_BIT;
                break;

            default:
            {
                // Avoiding buffer overflow from extended keys lol
                if (scancode < 128) {
                    // Should we get a SHIFTED char or a regular one?
                    unsigned char c = (key_status & SHIFT_PRESSED_BIT) ? keymap_shifted[scancode] : keymap[scancode];

                    if (c) {
                        if (c == '\n') {
                            internal_putc('\r', NULL);
                        }

                        internal_putc(c, NULL);
                        keyboard_putchar(c);
                    }
                }
            }
        }
    }
}

/*
 * keyboard_getchar - Get a character from keyboard
 *
 * This function reads one character from the keyboard buffer.
 * If the keyboard buffer is empty, it will block until a key
 * is pressed.
 *
 * Return:
 * <char> - character from keyboard buffer
 */
char keyboard_getchar()
{
    while (read_index == write_index); // Empty buffer

    char c = keyboard_buffer[read_index];
    read_index = (read_index+1) % KBD_BUFFER_MAX;
    return c;
}

/*
 * keyboard_putchar - Puts a character in the keyboard buffer
 * @c: character to add
 *
 * This function is used in the keyboard handler to add new
 * characters to the keyboard buffer.
 *
 * Return:
 * %-1 - keyboard buffer is full
 * %0 - operation completed successfully
 */
int keyboard_putchar(char c)
{
    if ((write_index+1) % KBD_BUFFER_MAX == read_index) {
        // Full buffer
        return -1;
   }
   
   keyboard_buffer[write_index] = c;
   write_index = (write_index+1) % KBD_BUFFER_MAX;
   return 0;
}

/*
 * keyboard_getline - Gets a line of input from keyboard
 * @output: Output string
 * @size:   Size of output string
 *
 * Read a line of characters from the keyboard, until the
 * buffer fills or a newline character is read.
 * The output string is NULL-terminated.
 *
 * Return:
 * <num> - the number of characters read
 */
int keyboard_getline(char* output, size_t size)
{
    char c;
    size_t index = 0;
    
    // Read until Enter is pressed
    while ((c = keyboard_getchar()) != 0x0A) {
        if (c == '\b') {
            if (index > 0) {
                index--;
                output[index] = '\0';
                printf(" \b");
            }
            continue;
        }

        if (index >= size-1) {
            continue;
        }
        output[index++] = c;
    }
    output[index] = '\0';
    return index;
}

/*
 * keyboard_init - Keyboard initialization
 * @layout: Desired layout
 *
 * Prepares the PS/2 keyboard to recieve input.
 */
void keyboard_init(unsigned char layout)
{
    // Here we might go and select PS/2, USB, or other... (once we implement multiple keyboard protocols)

    // Keyboard layout selection
    switch (layout) {
        case US:
            keymap = kbdus;
            keymap_shifted = kbdus_shifted;
            break;
        case FR:
            keymap = kbdfr;
            keymap_shifted = kbdfr_shifted;
            break;
        
        default:
            panic(NULL, "Unsupported keyboard layout");
            return;
    }

    // Flush keyboard buffer
    while (inb(0x64) & 1) {
        inb(0x60);
    }

    // Unmask IRQ1
    uint8_t mask = inb(0x21);
    mask &= ~(1 << 1);
    outb(0x21, mask);

    DEBUG("PS/2 Keyboard initialized");
    init.keyboard = true;
}