#include <stdint.h>

#include <backtrace.h>
#include <memory.h>
#include <print.h>
#include <apic.h>
#include <ints.h>


#define IDT_SIZE	256
#define IDT_EXCEPTIONS	32

#define IDT_DPL(x)	(((unsigned)(x) & 0x3u) << 13)
#define IDT_KERNEL	IDT_DPL(0)

#define IDT_TYPE(x)	(((unsigned)(x) & 0xfu) << 8)
#define IDT_INT_GATE	IDT_TYPE(0xeu)
#define IDT_TRP_FATE	IDT_TYPE(0xfu)

#define IDT_PRESENT	(1u << 15)

#define IDT_EXCEPTION_FLAGS	(IDT_KERNEL | IDT_INT_GATE | IDT_PRESENT)
#define IDT_INTERRUPT_FLAGS	(IDT_KERNEL | IDT_INT_GATE | IDT_PRESENT)
#define IDT_SPURIOUS_FLAGS	(IDT_KERNEL | IDT_INT_GATE | IDT_PRESENT)


struct idt_desc {
	uint16_t offs0;
	uint16_t sel;
	uint16_t flags;
	uint16_t offs1;
	uint32_t offs2;
	uint32_t _reserved;
} __attribute__((packed));

struct idt_ptr {
	uint16_t limit;
	uint64_t base;
} __attribute__((packed));

struct int_desc {
	interrupt_handler_t handler;
	int busy;
};


typedef void(*int_entry_t)(void);
extern int_entry_t __raw_handler[];


static struct int_desc int_desc[IDT_SIZE - IDT_EXCEPTIONS - 1];
static exception_handler_t exc_handler[IDT_EXCEPTIONS];


static void handle_spurious(void)
{}

static void handle_exception(struct frame *frame, int exception)
{
	exception_handler_t handler = exc_handler[exception];

	if (handler) {
		handler();
		return;
	}

	/**
	 * For unknown exceptions just print backtrace and registers. So far
	 * we have only one thread of execution and so only one stack. And we
	 * know that our stack is 4096 bytes aligned, so we can easily find
	 * the bottom and the top of the stack from the current value of RSP.
	 **/
	uintptr_t rsp;

	__asm__ ("movq %%rsp, %0" : "=r"(rsp));

	const uintptr_t stack_bottom = rsp & ~((uintptr_t)PAGE_SIZE - 1);
	const uintptr_t stack_top = stack_bottom + PAGE_SIZE;


	printf("Unhandled exception %d at 0x%llx\n", exception, frame->rip);
	printf("Backtrace Begin:\n");
	backtrace(frame->rbp, stack_bottom, stack_top);
	printf("Backtrace End.\n");

	printf("RIP 0x%llx, RSP 0x%llx, RBP 0x%llx,\n"
		"R8 0x%llx, R9 0x%llx, R10 0x%llx, R11 0x%llx,\n"
		"R12 0x%llx, R13 0x%llx, R14 0x%llx, R15 0x%llx,\n"
		"RAX 0x%llx, RBX 0x%llx, RCX 0x%llx, RDX 0x%llx,\n"
		"RSI 0x%llx, RDI 0x%llx\n",
		frame->rip, frame->rsp, frame->rbp,
		frame->r8, frame->r9, frame->r10, frame->r11,
		frame->r12, frame->r13, frame->r14, frame->r15,
		frame->rax, frame->rbx, frame->rcx, frame->rdx,
		frame->rsi, frame->rdi);
	while (1);
}

static void end_of_interrupt(void)
{ local_apic_write(LOCAL_APIC_EOI, 0); }

static void handle_interrupt(int intno)
{
	/**
	 * Despite the name of the function it doesn't mark end of the interrupt
	 * it merely notfies interrupt controller that we are ready for the next
	 * interrupt. We can relatively safely notify controller right away,
	 * because we used INTERRUPT GATE entries in the IDT for interrupts
	 * which means that CPU disables interrupts (in the RFLAGS registers)
	 * before calling interrupt handler (that is the difference between
	 * INTERRUPT GATE and TRAP GATE).
	 **/
	end_of_interrupt();
	if (int_desc[intno].handler)
		int_desc[intno].handler();
}

void isr_handler(struct frame *frame)
{
	const int irq = frame->intno;

	if (irq < IDT_EXCEPTIONS) handle_exception(frame, irq);
	else if (irq == INTNO_SPURIOUS) handle_spurious();
	else handle_interrupt(irq - IDT_EXCEPTIONS);
}


static void idt_desc_setup(struct idt_desc *desc, unsigned sel, uintptr_t offs,
			unsigned flags)
{
	desc->offs0 = offs & 0xfffful;
	desc->offs1 = (offs >> 16) & 0xfffful;
	desc->offs2 = (offs >> 32) & 0xfffffffful;

	desc->sel = sel;
	desc->flags = flags;
	desc->_reserved = 0;
}

static void idtr_write(const struct idt_ptr *ptr)
{ __asm__ ("lidt %0" : : "m"(*ptr)); }


/**
 * This function prepares the IDT table and writes pointer to the IDT table
 * to the IDTR register. I provide 256 thin wrappers (generated by python
 * script in assembly) all of them call into isr_handler routine, which
 * depending on the interrupt number calls actual interrupt handler or reports
 * the error if no handler registered for the particular.
 **/
static void idt_setup(void)
{
	static struct idt_desc IDT[IDT_SIZE] __attribute__((aligned (16)));

	for (int i = 0; i != IDT_EXCEPTIONS; ++i) {
		const uintptr_t handler = (uintptr_t)__raw_handler[i];

		idt_desc_setup(&IDT[i], KERNEL_CS, handler,
					IDT_EXCEPTION_FLAGS);
	}

	for (int i = IDT_EXCEPTIONS; i != IDT_SIZE; ++i) {
		const uintptr_t handler = (uintptr_t)__raw_handler[i];

		idt_desc_setup(&IDT[i], KERNEL_CS, handler,
						IDT_INTERRUPT_FLAGS);
	}

	idt_desc_setup(&IDT[INTNO_SPURIOUS], KERNEL_CS,
				(uintptr_t)__raw_handler[INTNO_SPURIOUS],
				IDT_SPURIOUS_FLAGS);

	const struct idt_ptr ptr = {
		.limit = sizeof(IDT) - 1,
		.base = (uint64_t)IDT
	};

	idtr_write(&ptr);
}

void ints_setup(void)
{
	idt_setup();
	local_apic_setup();
}

int allocate_interrupt(void)
{
	for (int i = 0; i != sizeof(int_desc)/sizeof(int_desc[0]); ++i) {
		if (int_desc[i].busy)
			continue;
		int_desc[i].busy = 1;
		return i + IDT_EXCEPTIONS;
	}
	return -1;
}

void register_exception_handler(int intno, exception_handler_t handler)
{
	exc_handler[intno] = handler;
}

void register_interrupt_handler(int intno, interrupt_handler_t handler)
{
	int_desc[intno - IDT_EXCEPTIONS].handler = handler;
}
