#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include "intr.h"
#include "io.h"
#include "gdt.h"
#include "hal/hal.h"
#include "kprintf.h"
#include "compiler/attr.h"
#include "pic.h"
#include "pit.h"
#include "proc/proc.h"
#include "syscall/syscall.h"
#include "errors.h"
#include "ipc/pipe/pipe.h"
#include "rbuf/rbuf.h"
#include "dlmalloc/malloc.h"
#include "util/util.h"

typedef struct IntrHandler {
  struct IntrHandler *next;
  void (*fn)(void);
  int irq;
} IntrHandler;

IntrHandler *INTR_HANDLERS = NULL;

void intr_attchhandler(void (*fn)(void), int irq) {
  IntrHandler *ih = dlmalloc(sizeof(*ih));
  ih->fn = fn;
  ih->irq = irq;
  LL_APPEND(INTR_HANDLERS, ih);
}

typedef struct BackTraceFrame {
  struct BackTraceFrame *rbp;
  uint64_t rip;
} BackTraceFrame;

void backtrace(BackTraceFrame *bt) {
  kprintf("Backtrace:\n");
  for (size_t frame = 0; bt; frame++) {
    kprintf("  %zu: 0x%lx\n", frame, bt->rip);
    bt = bt->rbp;
  }
}

void hal_intr_disable(void) {
  asm volatile("cli");
}

void hal_intr_enable(void) {
  asm volatile("sti");
}

typedef struct {
  uint16_t intrlow;
  uint16_t kernelcs;
  uint8_t ist;
  uint8_t attrs;
  uint16_t intrmid;
  uint32_t intrhigh;
  uint32_t resv;
} PACKED IdtGate;

typedef struct {
  uint16_t limit;
  uint64_t base;
} PACKED Idt;

#define ENTRIES 256
ALIGNED(0x10) static IdtGate idtgates[ENTRIES] = {0};
static Idt idt = {0};

void idt_setentry(int i, uint64_t handler, uint8_t flags) {
  idtgates[i].intrlow = handler & 0xffff;
  idtgates[i].kernelcs = KCODE;
  idtgates[i].ist = 0;
  idtgates[i].attrs = flags;
  idtgates[i].intrmid = (handler >> 16) & 0xFFFF;
  idtgates[i].intrhigh = (handler >> 32) & 0xFFFFFFFF;
  idtgates[i].resv = 0;
}

void idt_init(void) {
  idt.base = (uint64_t)&idtgates;
  idt.limit = ENTRIES * sizeof(IdtGate) - 1;
  asm volatile("lidt %0" :: "m"(idt) : "memory");

  LOG("hal", "idt init\n");
}

extern void *ISR_REDIRTABLE[];

static const char *exceptions[] = {
  "#DE", "#DB", "NMI",
  "#BP", "#OF", "#BR",
  "#UD", "#NM", "#DF",
  "CSO", "#TS", "#NP",
  "#SS", "#GP", "#PF",
  "RES", "#MF", "#AC",
  "#MC", "#XM", "#VE",
  "#CP",
};

void intr_init(void) {
  #define MKINTR(N) \
    extern void intr_vec##N(void); \
    idt_setentry(N, (uint64_t)&intr_vec##N, 0x8E);

  MKINTR(0);
  MKINTR(1);
  MKINTR(2);
  MKINTR(4);
  MKINTR(5);
  MKINTR(6);
  MKINTR(7);
  MKINTR(8);
  MKINTR(9);
  MKINTR(10);
  MKINTR(11);
  MKINTR(12);
  MKINTR(13);
  MKINTR(14);
  MKINTR(15);
  MKINTR(16);
  MKINTR(17);
  MKINTR(18);
  MKINTR(19);
  MKINTR(20);
  MKINTR(21);
  MKINTR(22);
  MKINTR(23);
  MKINTR(24);
  MKINTR(25);
  MKINTR(26);
  MKINTR(27);
  MKINTR(28);
  MKINTR(29);
  MKINTR(30);
  MKINTR(31);
  MKINTR(32);
  MKINTR(33);
  MKINTR(34);
  MKINTR(35);
  MKINTR(36);
  MKINTR(37);
  MKINTR(38);
  MKINTR(39);
  MKINTR(40);
  MKINTR(41);
  MKINTR(42);
  MKINTR(43);
  MKINTR(44);
  MKINTR(45);
  MKINTR(46);
  MKINTR(47);
    
  extern void intr_vec128(void);
  idt_setentry(0x80, (uint64_t)&intr_vec128, 0xEE);

  idt_init();
}

void intr_dumpframe(IntrStackFrame *frame) {
  uint64_t cr2;
  asm volatile("mov %%cr2, %0" : "=r"(cr2));
  uint64_t cr3;
  asm volatile("mov %%cr3, %0" : "=r"(cr3));
  uint64_t cr4;
  asm volatile("mov %%cr4, %0" : "=r"(cr4));
  kprintf("rax=%016lx rcx=%016lx rdx=%016lx\n"
          "rsi=%016lx rdi=%016lx r8 =%016lx\n"
          "r9 =%016lx r10=%016lx r11=%016lx\n"
          "rip=%016lx rfl=%016lx rsp=%016lx\n"
          "cs =%016lx ss =%016lx trp=%016lx\n"
          "cr2=%016lx cr3=%016lx cr4=%016lx\n"
          "\n\n",
      frame->regs.rax, frame->regs.rcx, frame->regs.rdx,
      frame->regs.rsi, frame->regs.rdi, frame->regs.r8,
      frame->regs.r9,  frame->regs.r10, frame->regs.r11,
      frame->rip,      frame->rflags,   frame->rsp,
      frame->cs,       frame->ss,       frame->trapnum,
      cr2,             cr3,             cr4
    );
}

void hal_syscalldispatch(IntrStackFrame *frame) {
  uint64_t sysnum = frame->regs.rax;
  if (sysnum < SYSCALLS_MAX) {
    SyscallFn fn = SYSCALL_TABLE[sysnum];
    if (fn == NULL) {
      frame->regs.rax = E_BADSYSCALL;
      return;
    }
    spinlock_acquire(&PROCS.spinlock);
    uint64_t calling_proc_pid = PROCS.current->pid;
    spinlock_release(&PROCS.spinlock);
    hal_intr_enable();
    int32_t ret = fn(frame, calling_proc_pid, frame->regs.rdi, frame->regs.rsi, frame->regs.rdx,
                                              frame->regs.r10, frame->regs.r8,  frame->regs.r9);

    if (ret == E_DOSCHEDULING) {
      proc_sched((void *)frame);
    }
    frame->regs.rax = *(uint64_t *)&ret;
  }
}

void intr_eoi() {
  io_out8(PIC2_CMD, PIC_EOI);
  io_out8(PIC1_CMD, PIC_EOI);
}

void intr_handleintr(IntrStackFrame *frame) {
  if (frame->trapnum <= 31) {
    kprintf("ERROR %s, 0x%lX\n", exceptions[frame->trapnum], frame->errnum);
    intr_dumpframe(frame);
    backtrace((BackTraceFrame *)frame->regs.rbp);
    if (hal_vmm_current_cr3() != KERNEL_CR3) {
      kprintf("killed pid %ld %s\n", PROCS.current->pid, PROCS.current->name);
      proc_killself();
      proc_sched((void *)frame);
    }
    hal_hang();
  } else if (frame->trapnum >= 32 && frame->trapnum <= 47) {
    switch (frame->trapnum) {
      case INTR_IRQBASE+0:
        PIT_TICKS++;
        intr_eoi();
        proc_sched((void *)frame);
        break;
      default:
        IntrHandler *ih, *ihtmp;
        LL_FOREACH_SAFE(INTR_HANDLERS, ih, ihtmp) {
          if ((uint64_t)ih->irq == frame->trapnum) {
            ih->fn();
          }
        }
        intr_eoi();
        break;
    }
  } else if (frame->trapnum == 0x80) {
    hal_syscalldispatch(frame);
  }
}

