/**
 * @brief Keyboard reading functions.
 *
 * Abstracts away the differences between our EFI and BIOS
 * environments to provide consistent scancode feedback for
 * the menus and command line editor.
 *
 * For EFI, we use the WaitForKey and ReadKeyStroke interfaces.
 *
 * For BIOS, we have a bad PS/2 driver, which should be fine if
 * you're booting with BIOS?
 *
 * @copyright
 * This file is part of ToaruOS and is released under the terms
 * of the NCSA / University of Illinois License - see LICENSE.md
 * Copyright (C) 2021 K. Lange
 */
#include "kbd.h"
#include "util.h"
#include "text.h"

#ifdef EFI_PLATFORM
#include <efi.h>
extern EFI_SYSTEM_TABLE *ST;

#define KBD_SCAN_DOWN  0x50
#define KBD_SCAN_UP    0x48
#define KBD_SCAN_LEFT  0x4B
#define KBD_SCAN_RIGHT 0x4D
#define KBD_SCAN_ENTER 0x1C
#define KBD_SCAN_1     2
#define KBD_SCAN_9     10

int read_scancode(int timeout) {
	EFI_INPUT_KEY Key;
	unsigned long int index;
	if (timeout) {
		EFI_EVENT events[] = {ST->ConIn->WaitForKey, 0};
		uefi_call_wrapper(ST->BootServices->CreateEvent, 5, EVT_TIMER, 0, NULL, NULL, &events[1]);
		uefi_call_wrapper(ST->BootServices->SetTimer, 3, events[1], TimerRelative, 10000000UL);
		uefi_call_wrapper(ST->BootServices->WaitForEvent, 3, 2, events, &index);
	} else {
		uefi_call_wrapper(ST->BootServices->WaitForEvent, 3, 1, &ST->ConIn->WaitForKey, &index);
	}
	EFI_STATUS result = uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, &Key);

	if (result == EFI_NOT_READY) return -1;
	switch (Key.ScanCode) {
		case 0:
			switch (Key.UnicodeChar) {
				case L'\r':
					return KBD_SCAN_ENTER;
				case L'1':
				case L'2':
				case L'3':
				case L'4':
				case L'5':
				case L'6':
				case L'7':
				case L'8':
				case L'9':
					return Key.UnicodeChar - L'1' + KBD_SCAN_1;
				case L'e':
					return 0x12;
				default:
					return 0xFF;
			}
			break;
		case 0x01: return KBD_SCAN_UP;
		case 0x02: return KBD_SCAN_DOWN;
		case 0x03: return KBD_SCAN_RIGHT;
		case 0x04: return KBD_SCAN_LEFT;
		default:
			return 0xFF;
	}
}

int read_key(int * c) {
	EFI_INPUT_KEY Key;
	unsigned long int index;
	uefi_call_wrapper(ST->BootServices->WaitForEvent, 3, 1, &ST->ConIn->WaitForKey, &index);
	uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, &Key);

	if (Key.ScanCode == 0) {
		*c = Key.UnicodeChar;
		if (*c == '\r') *c = '\n';
		return 0;
	}

	switch (Key.ScanCode) {
		case 0x03: return 3;
		case 0x04: return 2;
		case 0x09: return 4;
		case 0x0a: return 5;
		case 0x17: *c = 27; return 0;
	}

	return 1;
}

#else

int read_cmos_seconds(void) {
	outportb(0x70,0);
	return inportb(0x71);
}

static char kbd_us[128] = {
	0, 27, '1','2','3','4','5','6','7','8','9','0',
	'-','=','\b', '\t', 'q','w','e','r','t','y','u','i','o','p','[',']','\n',
	0, 'a','s','d','f','g','h','j','k','l',';','\'', '`',
	0, '\\','z','x','c','v','b','n','m',',','.','/',
	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, 0, 0, 0, 0,
};

static char kbd_us_l2[128] = {
	0, 27, '!','@','#','$','%','^','&','*','(',')',
	'_','+','\b', '\t', 'Q','W','E','R','T','Y','U','I','O','P','{','}','\n',
	0, 'A','S','D','F','G','H','J','K','L',':','"', '~',
	0, '|','Z','X','C','V','B','N','M','<','>','?',
	0, '*', 0, '\037', 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, 0, 0,
};

int read_key(int * c) {
	static int shift_state = 0;

	int sc = read_scancode(0);

	if (sc == 0xe0) {
		*c = 0xe0;
		return 1;
	}

	if (*c == 0xe0) {
		*c = 0;
		switch (sc) {
			/* Keft left and right */
			case 0x4B:
				return shift_state ? 4 : 2;
			case 0x4D:
				return shift_state ? 5 : 3;
		}
		return 1;
	}

	switch (sc) {
		/* Shift down */
		case 0x2A:
		case 0x36:
			shift_state = 1;
			return 1;
		/* Shift up */
		case 0xAA:
		case 0xB6:
			shift_state = 0;
			return 1;
	}

	if (!(sc & 0x80)) {
		*c = shift_state ? kbd_us_l2[sc] : kbd_us[sc];
		return *c == 0;
	}

	return 1;
}

extern int do_bios_call(unsigned int function, unsigned int arg1);

int kbd_status(void) {
	int result = do_bios_call(4,0x11);
	return (result & 0xFF) == 0;
}

int read_scancode(int timeout) {
	if (timeout) {
		int start_s = read_cmos_seconds();
		while (kbd_status()) {
			int now_s = read_cmos_seconds();
			if (now_s != start_s) return -1;
		}
	}
	int result = do_bios_call(4,0);
	return result >> 8;
}
#endif
