// MIT License, Copyright (c) 2020 Marvin Borner
// TODO: Remove some magic numbers

#include <cpu.h>
#include <def.h>
#include <interrupts.h>
#include <mem.h>
#include <print.h>
#include <serial.h>

/**
 * IDT
 */

void idt_set_gate(u8 num, u32 base, u16 sel, u8 flags)
{
	// Specify the interrupt routine's base address
	idt[num].base_low = (u16)(base & 0xFFFF);
	idt[num].base_high = (u16)((base >> 16) & 0xFFFF);

	// Set selector/segment of IDT entry
	idt[num].sel = sel;
	idt[num].always0 = 0;
	idt[num].flags = (u8)(flags | 0x60);
}

// Install IDT
void idt_install()
{
	// Set IDT pointer and limit
	idt_ptr.limit = (sizeof(struct idt_entry) * 256) - 1;
	idt_ptr.base = &idt;

	// Clear IDT by setting memory cells to 0
	memset(&idt, 0, sizeof(struct idt_entry) * 256);

	__asm__ volatile("lidt %0" : : "m"(idt_ptr));
}

/**
 * IRQ
 */

void (*irq_routines[16])(struct regs *) = { 0 };

// Install IRQ handler
void irq_install_handler(int irq, void (*handler)(struct regs *r))
{
	irq_routines[irq] = handler;
}

// Remove IRQ handler
void irq_uninstall_handler(int irq)
{
	irq_routines[irq] = 0;
}

// Remap the IRQ table
void irq_remap()
{
	outb(0x20, 0x11);
	outb(0xA0, 0x11);
	outb(0x21, 0x20);
	outb(0xA1, 0x28);
	outb(0x21, 0x04);
	outb(0xA1, 0x02);
	outb(0x21, 0x01);
	outb(0xA1, 0x01);
	outb(0x21, 0x00);
	outb(0xA1, 0x00);
}

// Handle IRQ ISRs
void irq_handler(struct regs *r)
{
	void (*handler)(struct regs * r);

	// Execute custom handler if exists
	handler = irq_routines[r->int_no - 32];
	if (handler)
		handler(r);

	// Send EOI to second (slave) PIC
	if (r->int_no >= 40)
		outb(0xA0, 0x20);

	// Send EOI to master PIC
	outb(0x20, 0x20);
}

// Map ISRs to the correct entries in the IDT
void irq_install()
{
	irq_remap();

	idt_set_gate(32, (u32)irq0, 0x08, 0x8E);
	idt_set_gate(33, (u32)irq1, 0x08, 0x8E);
	idt_set_gate(34, (u32)irq2, 0x08, 0x8E);
	idt_set_gate(35, (u32)irq3, 0x08, 0x8E);
	idt_set_gate(36, (u32)irq4, 0x08, 0x8E);
	idt_set_gate(37, (u32)irq5, 0x08, 0x8E);
	idt_set_gate(38, (u32)irq6, 0x08, 0x8E);
	idt_set_gate(39, (u32)irq7, 0x08, 0x8E);

	idt_set_gate(40, (u32)irq8, 0x08, 0x8E);
	idt_set_gate(41, (u32)irq9, 0x08, 0x8E);
	idt_set_gate(42, (u32)irq10, 0x08, 0x8E);
	idt_set_gate(43, (u32)irq11, 0x08, 0x8E);
	idt_set_gate(44, (u32)irq12, 0x08, 0x8E);
	idt_set_gate(45, (u32)irq13, 0x08, 0x8E);
	idt_set_gate(46, (u32)irq14, 0x08, 0x8E);
	idt_set_gate(47, (u32)irq15, 0x08, 0x8E);
}

/**
 * ISR
 */

void (*isr_routines[256])(struct regs *) = { 0 };

const char *isr_exceptions[32] = { "Division By Zero",
				   "Debug",
				   "Non Maskable Interrupt",
				   "Breakpoint",
				   "Into Detected Overflow",
				   "Out of Bounds",
				   "Invalid Opcode",
				   "No Coprocessor",

				   "Double Fault",
				   "Coprocessor Segment Overrun",
				   "Bad TSS",
				   "Segment Not Present",
				   "Stack Fault",
				   "General Protection Fault",
				   "Page Fault",
				   "Unknown Interrupt",

				   "Coprocessor Fault",
				   "Alignment Check",
				   "Machine Check",
				   "Reserved",
				   "Reserved",
				   "Reserved",
				   "Reserved",
				   "Reserved",

				   "Reserved",
				   "Reserved",
				   "Reserved",
				   "Reserved",
				   "Reserved",
				   "Reserved",
				   "Reserved",
				   "Reserved" };

void isr_install_handler(int isr, void (*handler)(struct regs *r))
{
	isr_routines[isr] = handler;
}

void isr_uninstall_handler(int isr)
{
	isr_routines[isr] = 0;
}

void isr_handler(struct regs *r)
{
	void (*handler)(struct regs * r);

	// Execute fault handler if exists
	handler = isr_routines[r->int_no];
	if (handler) {
		handler(r);
	} else if (r->int_no <= 32) {
		cli();
		printf("\n%s Exception, halting!\n", isr_exceptions[r->int_no]);
		printf("Error code: %d\n", r->err_code);
		while (1) {
		};
	}
}

void isr_install()
{
	idt_set_gate(0, (u32)isr0, 0x08, 0x8E);
	idt_set_gate(1, (u32)isr1, 0x08, 0x8E);
	idt_set_gate(2, (u32)isr2, 0x08, 0x8E);
	idt_set_gate(3, (u32)isr3, 0x08, 0x8E);
	idt_set_gate(4, (u32)isr4, 0x08, 0x8E);
	idt_set_gate(5, (u32)isr5, 0x08, 0x8E);
	idt_set_gate(6, (u32)isr6, 0x08, 0x8E);
	idt_set_gate(7, (u32)isr7, 0x08, 0x8E);

	idt_set_gate(8, (u32)isr8, 0x08, 0x8E);
	idt_set_gate(9, (u32)isr9, 0x08, 0x8E);
	idt_set_gate(10, (u32)isr10, 0x08, 0x8E);
	idt_set_gate(11, (u32)isr11, 0x08, 0x8E);
	idt_set_gate(12, (u32)isr12, 0x08, 0x8E);
	idt_set_gate(13, (u32)isr13, 0x08, 0x8E);
	idt_set_gate(14, (u32)isr14, 0x08, 0x8E);
	idt_set_gate(15, (u32)isr15, 0x08, 0x8E);

	idt_set_gate(16, (u32)isr16, 0x08, 0x8E);
	idt_set_gate(17, (u32)isr17, 0x08, 0x8E);
	idt_set_gate(18, (u32)isr18, 0x08, 0x8E);
	idt_set_gate(19, (u32)isr19, 0x08, 0x8E);
	idt_set_gate(20, (u32)isr20, 0x08, 0x8E);
	idt_set_gate(21, (u32)isr21, 0x08, 0x8E);
	idt_set_gate(22, (u32)isr22, 0x08, 0x8E);
	idt_set_gate(23, (u32)isr23, 0x08, 0x8E);

	idt_set_gate(24, (u32)isr24, 0x08, 0x8E);
	idt_set_gate(25, (u32)isr25, 0x08, 0x8E);
	idt_set_gate(26, (u32)isr26, 0x08, 0x8E);
	idt_set_gate(27, (u32)isr27, 0x08, 0x8E);
	idt_set_gate(28, (u32)isr28, 0x08, 0x8E);
	idt_set_gate(29, (u32)isr29, 0x08, 0x8E);
	idt_set_gate(30, (u32)isr30, 0x08, 0x8E);
	idt_set_gate(31, (u32)isr31, 0x08, 0x8E);
}

/**
 * Combined
 */
void interrupts_install()
{
	idt_install();
	isr_install();
	irq_install();
}