#include <stdlib.h>
#include <wchar.h>
#include <stdio.h>
#include <ctype.h>
#include <kernel/kernel.h>
#include <kernel/sys.h>
#include <kernel/thread.h>
#include <kernel/handle.h>
#include <errno.h>

#include <kernel/debug.h>

#include <kernel/driver.h>
#include "keyboard.h"
#include "british.h"

/*!
 *  \ingroup	drivers
 *  \defgroup	keyboard    AT/PS2 keyboard
 *  @{
 */

typedef struct Keyboard Keyboard;
struct Keyboard
{
	device_t dev;
	dword keys;
	wchar_t compose;
	dword *write, *read;
	dword buffer[128];
	bool isps2;
	word port, ctrl;
};

#define SET(var, bit, val) \
	{ \
		if (val) \
			var |= bit; \
		else \
			var &= ~bit; \
	}

void kbdHwWrite(Keyboard* ctx, word port, byte data)
{
	dword timeout;
	byte stat;

	for (timeout = 500000L; timeout != 0; timeout--)
	{
		stat = in(ctx->ctrl);

		if ((stat & 0x02) == 0)
			break;
	}

	if (timeout != 0)
		out(port, data);
}

byte kbdHwRead(Keyboard* ctx)
{
	unsigned long Timeout;
	byte Stat, Data;

	for (Timeout = 50000L; Timeout != 0; Timeout--)
	{
		Stat = in(ctx->ctrl);

		/* loop until 8042 output buffer full */
		if ((Stat & 0x01) != 0)
		{
			Data = in(ctx->port);

			/* loop if parity error or receive timeout */
			if((Stat & 0xC0) == 0)
				return Data;
		}
	}

	return -1;
}

byte kbdHwWriteRead(Keyboard* ctx, word port, byte data, const char* expect)
{
	int RetVal;

	kbdHwWrite(ctx, port, data);
	for (; *expect; expect++)
	{
		RetVal = kbdHwRead(ctx);
		if ((byte) *expect != RetVal)
		{
			TRACE2("[keyboard] error: expected 0x%x, got 0x%x\n",
				(byte) *expect, RetVal);
			return RetVal;
		}
	}

	return 0;
}

dword kbdScancodeToKey(Keyboard* ctx, byte scancode)
{
	static int keypad[RAW_NUM0 - RAW_NUM7 + 1] =
	{
		7, 8, 9, -1,
		4, 5, 6, -1,
		1, 2, 3,
		0
	};

	bool down = (scancode & 0x80) == 0;
	byte code = scancode & ~0x80;
	dword key = 0;
	byte temp;
	
	switch (code)
	{
	case RAW_LEFT_CTRL:
	case RAW_RIGHT_CTRL:
		SET(ctx->keys, KBD_BUCKY_CTRL, down);
		break;

	case RAW_LEFT_ALT:
	//case RAW_RIGHT_ALT:
		if (down && (ctx->keys & KBD_BUCKY_ALT) == 0)
			ctx->compose = 0;
		else if (!down && (ctx->keys & KBD_BUCKY_ALT))
			key = ctx->compose;

		SET(ctx->keys, KBD_BUCKY_ALT, down);
		break;

	case RAW_LEFT_SHIFT:
	case RAW_RIGHT_SHIFT:
		SET(ctx->keys, KBD_BUCKY_SHIFT, down);
		break;

	case RAW_NUM_LOCK:
		if (!down)
		{
			ctx->keys ^= KBD_BUCKY_NUM;
			goto leds;
		}
		break;

	case RAW_CAPS_LOCK:
		if (!down)
		{
			ctx->keys ^= KBD_BUCKY_CAPS;
			goto leds;
		}
		break;

	case RAW_SCROLL_LOCK:
		if (!down)
		{
			ctx->keys ^= KBD_BUCKY_SCRL;
			goto leds;
		}
		break;

leds:
		kbdHwWrite(ctx, ctx->port, KEYB_SET_LEDS);
		temp = 0;
		if (ctx->keys & KBD_BUCKY_SCRL)
			temp |= 1;
		if (ctx->keys & KBD_BUCKY_NUM)
			temp |= 2;
		if (ctx->keys & KBD_BUCKY_CAPS)
			temp |= 4;
		kbdHwWrite(ctx, ctx->port, temp);
		break;

	default:
		if (down)
		{
			if (code >= RAW_NUM7 && 
				code <= RAW_NUM0 &&
				code != 0x4a &&
				code != 0x4e)
			{
				if (ctx->keys & KBD_BUCKY_ALT &&
					keypad[code - RAW_NUM7] != -1)
				{
					ctx->compose *= 10;
					ctx->compose += keypad[code - RAW_NUM7];
					//wprintf(L"pad = %d compose = %d\n", 
						//keypad[code - RAW_NUM7], ctx->compose);
				}
				else if (ctx->keys & KBD_BUCKY_NUM)
					key = '0' + keypad[code - RAW_NUM7];
				else
					key = keys[code];
			}
			else if (ctx->keys & KBD_BUCKY_SHIFT)
				key = keys_shift[code];
			else
				key = keys[code];

			if (ctx->keys & KBD_BUCKY_CAPS)
			{
				if (iswupper(key))
					key = towlower(key);
				else if (iswlower(key))
					key = towupper(key);
			}
		}
	}
	
	return key | ctx->keys;
}

size_t kbdRead(Keyboard* ctx, void* buffer, size_t length)
{
	dword ch, *buf = (dword*) buffer;
	size_t read = 0;

	while (length > 0)
	{
		if (ctx->read == ctx->write)
			return read;
		
		ch = *ctx->read;
		ctx->read++;
		if (ctx->read >= ctx->buffer + countof(ctx->buffer))
			ctx->read = ctx->buffer;

		*buf = ch;
		buf++;
		length -= sizeof(dword);
		read += sizeof(dword);
	}

	return read;
}

void kbdDoRequests(Keyboard* ctx)
{
	request_t *req, *next;
	size_t read;
	dword key;

	while (ctx->dev.req_first &&
		(read = kbdRead(ctx, &key, sizeof(key))))
	{
		for (req = ctx->dev.req_first; req; req = next)
		{
			/*wprintf(L"read = %d req = %p ", read, req);
			wprintf(L"buffer = %p size = %d\n", req->params.read.buffer, 
				req->params.read.length);

			_cputws(L"Writing buffer...");*/
			((dword*) req->params.read.buffer)[req->params.read.length / sizeof(dword)] = key;
			//_cputws(L"done\nUpdating length...");
			req->params.read.length += read;
			
			next = req->next;
			if (req->params.read.length >= req->user_length)
				devFinishRequest(&ctx->dev, req);
			//else
				//_cputws(L"more to do\n");
		}
	}
}

void kbdKey(Keyboard* ctx, byte scancode)
{
	dword key;

	if (ctx->write >= ctx->read)
	{
		key = kbdScancodeToKey(ctx, scancode);

		if (key == (KBD_BUCKY_CTRL | KBD_BUCKY_ALT | KEY_DEL))
			keShutdown(SHUTDOWN_REBOOT);
		else if (key == KEY_F11)
			if (dbgInvoke(thrCurrent(), thrContext(thrCurrent()), 0))
				return;
		
		if (key)
		{
			*ctx->write = key;
			ctx->write++;

			if (ctx->write >= ctx->buffer + countof(ctx->buffer))
				ctx->write = ctx->buffer;

			//wprintf(L"read = %d, write = %d\n", ctx->read - ctx->buffer, ctx->write - ctx->buffer);
			//Read(&key, sizeof(key));
			//wprintf(L"%c", key);
		}

		kbdDoRequests(ctx);
	}
}

bool kbdRequest(device_t* dev, request_t* req)
{
	Keyboard* ctx = (Keyboard*) dev;

	TRACE2("[%c%c]", req->code / 256, req->code % 256);
	switch (req->code)
	{
	case DEV_READ:
		devStartRequest(dev, req);
		req->params.read.length = 0;

		kbdDoRequests(ctx);
		return true;

	case DEV_OPEN:
	case DEV_CLOSE:
		hndSignal(req->event, true);
		return true;

	case DEV_ISR:
		kbdKey(ctx, kbdHwRead(ctx));
		return true;

	case CHR_GETSIZE:
		*((size_t*) req->params.buffered.buffer) = ctx->write - ctx->read;
		hndSignal(req->event, true);
		return true;
	}

	req->result = ENOTIMPL;
	return false;
}

Keyboard* INIT_CODE kbdInit(driver_t* drv, device_config_t *cfg)
{
	Keyboard* ctx;
	dword i;
	word port, ctrl;
	device_t* kdebug;
	byte status;

	ctx = (Keyboard*) hndAlloc(sizeof(Keyboard), NULL);

	if (cfg)
		i = devFindResource(cfg, dresIo, 0);
	else
		i = -1;

	if (i == (dword) -1)
		port = KEYB_PORT;
	else
		port = cfg->resources[i].u.io.base;

	if (cfg)
		i = devFindResource(cfg, dresIo, 1);
	else
		i = -1;

	if (i == (dword) -1)
		ctrl = KEYB_CTRL;
	else
		ctrl = cfg->resources[i].u.io.base;

	ctx->dev.driver = drv;
	ctx->dev.request = kbdRequest;
	ctx->dev.config = cfg;
	ctx->dev.req_first = ctx->dev.req_last = NULL;
	ctx->keys = 0;
	ctx->write = ctx->read = ctx->buffer;
	ctx->port = port;
	ctx->ctrl = ctrl;

	TRACE2("keyboard: port = %x ctrl = %x\n", port, ctrl);

	/*
	 * The kernel debugger may have registered our IRQ1, which would make a 
	 *	mess of keyboard initialization.
	 */
	kdebug = devOpen(L"debugger", NULL);
	devRegisterIrq(kdebug, 1, false);
	devClose(kdebug);

#if 0
	out(port, 0xff); /*reset keyboard   */ 
    do {
        status = in(ctrl);
    } while (status & 0x02);

    out(ctrl, 0xAA); /*selftest   */
    in(port);
    out(ctrl, 0xAB); /*interface selftest   */
    in(port);

    out(ctrl, 0xAE); /*enable keyboard   */

    out(port, 0xff); /*reset keyboard   */

    in(port);
    in(port);

    out(ctrl, 0xAD); /*disable keyboard   */

    out(ctrl, 0x60);
    out(port, 0x01 | 0x04 | 0x20 | 0x40);

    out(ctrl, 0xAE); /*enable keyboard   */
#else
	/* Reset keyboard and disable scanning until further down */
	TRACE0("Disable...");
	kbdHwWriteRead(ctx, ctx->port, KEYB_RESET_DISABLE, KEYB_ACK);

	/* Set keyboard controller flags: 
	   interrupts for keyboard & aux port
	   SYS bit (?) */
	TRACE0("done\nFlags...");
	kbdHwWrite(ctx, ctx->ctrl, KCTRL_WRITE_CMD_BYTE);
	kbdHwWrite(ctx, ctx->port, KCTRL_IRQ1 | KCTRL_SYS | KCTRL_IRQ12);

	TRACE0("done\nIdentify...");
	kbdHwWriteRead(ctx, ctx->port, KEYB_IDENTIFY, KEYB_ACK);
	if (kbdHwRead(ctx) == 0xAB &&
		kbdHwRead(ctx) == 0x83)
	{
		TRACE0("[keyboard] PS/2 keyboard detected\n");

		/* Program desired scancode set, expect 0xFA (ACK)... */
		kbdHwWriteRead(ctx, ctx->port, KEYB_SET_SCANCODE_SET, KEYB_ACK);

		/* ...send scancode set value, expect 0xFA (ACK) */
		kbdHwWriteRead(ctx, ctx->port, 1, KEYB_ACK);

		/* make all keys typematic (auto-repeat) and make-break. This may work
		   only with scancode set 3, I'm not sure. Expect 0xFA (ACK) */
		kbdHwWriteRead(ctx, ctx->port, KEYB_ALL_TYPM_MAKE_BRK, KEYB_ACK);

		ctx->isps2 = true;
	}
	else
	{
		/* Argh... bloody bochs */
		TRACE0("[keyboard] AT keyboard detected\n");
		ctx->isps2 = false;
	}

	/* Set typematic delay as short as possible;
	   Repeat as fast as possible, expect ACK... */
	TRACE0("Typematic...");
	kbdHwWriteRead(ctx, ctx->port, KEYB_SET_TYPEMATIC, KEYB_ACK);

	/* ...typematic control byte (0 corresponds to MODE CON RATE=30 DELAY=1),
	   expect 0xFA (ACK) */
	kbdHwWriteRead(ctx, ctx->port, 0, KEYB_ACK);

	/* Enable keyboard, expect 0xFA (ACK) */
	TRACE0("done\nEnable...");
	kbdHwWriteRead(ctx, ctx->port, KEYB_ENABLE, KEYB_ACK);
#endif

	TRACE0("done\nIRQ...");
	devRegisterIrq(&ctx->dev, 1, true);
	TRACE0("done\n");
	return ctx;
}

device_t* kbdAddDevice(driver_t* drv, const wchar_t* name, 
					   device_config_t* cfg)
{
	return (device_t*) kbdInit(drv, cfg);
}

bool STDCALL INIT_CODE drvInit(driver_t* drv)
{
	drv->add_device = kbdAddDevice;
	//devRegister(L"keyboard", kbdAddDevice(drv, L"keyboard", NULL), NULL);
	return true;
}

//@}