// Zig version: 0.4.0

const gdt = @import("gdt.zig");
const arch = @import("arch.zig");
const log = @import("../../log.zig");

const NUMBER_OF_ENTRIES: u16 = 256;
const TABLE_SIZE: u16 = @sizeOf(IdtEntry) * NUMBER_OF_ENTRIES - 1;

// The different gate types
const TASK_GATE_32BIT: u4 = 0x5;
const INTERRUPT_GATE_16BIT: u4 = 0x6;
const TRAP_GATE_16BIT: u4 = 0x7;
const INTERRUPT_GATE_32BIT: u4 = 0xE;
const TRAP_GATE_32BIT: u4 = 0xF;

// Privilege levels
const PRIVILEGE_RING_0: u2 = 0x0;
const PRIVILEGE_RING_1: u2 = 0x1;
const PRIVILEGE_RING_2: u2 = 0x2;
const PRIVILEGE_RING_3: u2 = 0x3;

/// The structure that contains all the information that each IDT entry needs.
const IdtEntry = packed struct {
    /// The lower 16 bits of the base address of the interrupt handler offset.
    base_low: u16,

    /// The code segment in the GDT which the handlers will be held.
    selector: u16,

    /// Must be zero, unused.
    zero: u8,

    /// The IDT gate type.
    gate_type: u4,

    /// Must be 0 for interrupt and trap gates.
    storage_segment: u1,

    /// The minimum ring level that the calling code must have to run the handler. So user code may not be able to run some interrupts.
    privilege: u2,

    /// Whether the IDT entry is present.
    present: u1,

    /// The upper 16 bits of the base address of the interrupt handler offset.
    base_high: u16,
};

/// The IDT pointer structure that contains the pointer to the beginning of the IDT and the number
/// of the table (minus 1). Used to load the IST with LIDT instruction.
pub const IdtPtr = packed struct {
    /// The total size of the IDT (minus 1) in bytes.
    limit: u16,

    /// The base address where the IDT is located.
    base: *IdtEntry,
};

/// The IDT entry table of NUMBER_OF_ENTRIES entries.
var idt: [NUMBER_OF_ENTRIES]IdtEntry = []IdtEntry{makeEntry(0, 0, 0, 0, 0)} ** NUMBER_OF_ENTRIES;

/// The IDT pointer that the CPU is loaded with that contains the base address of the IDT and the
/// size.
const idt_ptr: IdtPtr = IdtPtr {
    .limit = TABLE_SIZE,
    .base = &idt[0],
};

///
/// Make a IDT entry.
///
/// Arguments:
///     IN base: u32     - The pointer to the interrupt handler.
///     IN selector: u16 - The segment the interrupt is in. This will usually be the
///                        kernels code segment.
///     IN gate_type: u4 - The type of interrupt.
///     IN privilege: u2 - What privilege to call the interrupt in. This will usually be
///                        the kernel ring level 0.
///     IN present: u1   - Whether a interrupt handler is present to be called..
///
/// Return:
///     A new IDT entry.
///
fn makeEntry(base: u32, selector: u16, gate_type: u4, privilege: u2, present: u1) IdtEntry {
    return IdtEntry {
        .base_low = @truncate(u16, base),
        .selector = selector,
        .zero = 0,
        .gate_type = gate_type,
        .storage_segment = 0,
        .privilege = privilege,
        .present = present,
        .base_high = @truncate(u16, base >> 16),
    };
}

///
/// Open a interrupt gate with a given index and a handler to call.
///
/// Arguments:
///     IN index: u8             - The interrupt number to close.
///     IN base: extern fn()void - The function handler for the interrupt.
///
pub fn openInterruptGate(index: u8, base: extern fn()void) void {
    idt[index] = makeEntry(@ptrToInt(base), gdt.KERNEL_CODE_OFFSET, INTERRUPT_GATE_32BIT, PRIVILEGE_RING_0, 1);
}

///
/// Close a interrupt gate with a given index
///
/// Arguments:
///     IN index: u8 - The interrupt number to close.
///
pub fn closeInterruptGate(index: u8) void {
    idt[index] = makeEntry(0, 0, 0, 0, 0);
}

///
/// Initialise the Interrupt descriptor table
///
pub fn init() void {
    log.logInfo("Init idt\n");
    arch.lidt(&idt_ptr);
}