#include "../../../include/interrupts/interrupts.h"
#include "../../../include/io/serial.h"
#include "../../../include/interrupts/irq.h"
#include "../../../include/interrupts/idt.h"
#include "../../../include/io/ports.h"
#include "../../../include/smp/percpu.h"
#include "../../../include/apic/apic.h"

extern const int_desc_t __start_irq_handlers[];
extern const int_desc_t __stop_irq_handlers[];
static int_handler_f registered_irq_interrupts[IRQ_INTERRUPTS_COUNT]__attribute__((aligned(64)));

void irq_common_handler(struct int_frame_t* regs) {
    uint64_t vec = regs->interrupt;

    if (vec >= IRQ_INTERRUPTS_COUNT || vec < (IDT_MAX_DESCRIPTORS - IRQ_INTERRUPTS_COUNT)) {
        serial_printf("IRQ vector out of range: %d\n", vec);
        while (1)
        {
            asm volatile ("hlt");
        }
    }

    if(registered_irq_interrupts[vec]) {
        return registered_irq_interrupts[vec](regs);
    }

    serial_printf("IRQ interrupt handler\n");

    while (1)
    {
        asm volatile ("hlt");
    }
}

void setup_defined_irq_handlers(void) {
    const int_desc_t* desc;
    for (desc = __start_irq_handlers; desc < __stop_irq_handlers; desc++) {
        if(desc->vector >= IRQ_INTERRUPTS_COUNT) {
            serial_printf("Invalid IRQ vector number! Must be < %d\n", IRQ_INTERRUPTS_COUNT);
            continue;
        }
        registered_irq_interrupts[desc->vector] = desc->handler;
        serial_printf("Registered IRQ vector 0x%d\n", desc->vector);
    }
}

extern percpu_t* percpu_regions[MAX_CPUS];

DEFINE_IRQ(IPI_RESCHEDULE_VECTOR, ipi_reschedule_handler)
{
    (void)frame;

    uint32_t id = lapic_get_id();
    if (id < MAX_CPUS && percpu_regions[id] != NULL) {
        percpu_regions[id]->need_resched = true;
    }

    lapic_eoi();
}

DEFINE_IRQ(IPI_TLB_SHOOTDOWN, ipi_tlb_shootdown_handler)
{
    (void)frame;

    uint32_t id = lapic_get_id();
    tlb_shootdown_t* q = &tlb_shootdown_queue[id];

    if (q->pending) {
        for (size_t i = 0; i < q->count; i++) {
            uintptr_t addr = q->addresses[i];
            if (addr != 0) {
                asm volatile ("invlpg (%0)" :: "r"(addr) : "memory");
            }
        }

        q->pending = false;
        q->count = 0;
    }

    lapic_eoi();
}