#include "kbd.h"
#include "process.h"
#include "scheduler.h"
#include "common.h"

#include <lib/tasks.h>
#include <lib/print.h>

#include <stddef.h>

#define KBD_BUFFER_SIZE 256

static spinlock_t kbd_lock = {0};
static process_t* kbd_waiting_task = NULL;

static char kbd_buffer[KBD_BUFFER_SIZE];
static int kbd_head = 0;
static int kbd_tail = 0;

static void lock_kbd() {
    disable_interrupts();
    spin_lock(&kbd_lock);
}

static void unlock_kbd() {
    spin_unlock(&kbd_lock);
    enable_interrupts();
}

static void kbd_set_waiting(process_t* task) {
    lock_kbd();
    if (kbd_head == kbd_tail) {
        kbd_waiting_task = task;
    }
    unlock_kbd();
}

static char kbd_pop() {
    char c = 0;
    lock_kbd();
    if (kbd_head != kbd_tail) {
        c = kbd_buffer[kbd_tail];
        kbd_tail = (kbd_tail + 1) % KBD_BUFFER_SIZE;
    }
    unlock_kbd();
    return c;
}

void kbd_push(char c) {
    spin_lock(&kbd_lock);
    // push to buffer first, very important
    int next_head = (kbd_head + 1) % KBD_BUFFER_SIZE;
    if (next_head != kbd_tail) {
        kbd_buffer[kbd_head] = c;
        kbd_head = next_head;
    }

    if (kbd_waiting_task != NULL) {
        wake(kbd_waiting_task);
        kbd_waiting_task = NULL;
    }
    spin_unlock(&kbd_lock);
}

int kbd_ready() {
    int ready = 0;
    lock_kbd();
    ready = (kbd_head != kbd_tail);
    unlock_kbd();
    return ready;
}

void kbd_wait() {
    process_t* task = current_task();
    while (!kbd_ready()) {
        task->state = STATE_BLOCKED;
        kbd_set_waiting(task);
        yield();
    }
}

uint32_t kbd_read(struct vfs_node* node, uint32_t offset, uint32_t size, uint8_t* buffer) {
    if (!kbd_ready()) return -1;
    buffer[0] = kbd_pop();
    return 1;
}