#include <stdint.h>
#include "syscall.h"
#include "mman.h"
#include "spinlock/spinlock.h"
#include "proc/proc.h"
#include "hal/hal.h"
#include "pmm/pmm.h"
#include "util/util.h"
#include "errors.h"
#include "sysdefs/mman.h"
#include "bootinfo/bootinfo.h"
#include "dlmalloc/malloc.h"
#include "kprintf.h"

int32_t SYSCALL5(sys_mman_map, addr1, size1, prot1, flags1, out1) {
  uint8_t *addr = (uint8_t *)addr1;
  size_t size = size1;
  uint64_t prot = prot1;
  uint64_t flags = flags1;
  uint8_t **out = (uint8_t **)out1;

  if (size == 0 || (size % HAL_PAGE_SIZE != 0)) {
    if (out != NULL) {
      *out = NULL;
    }
    return E_INVALIDARGUMENT;
  }

  size_t pages = _DIV_ROUNDUP(size, HAL_PAGE_SIZE);
  uint8_t *phys = (uint8_t *)pmm_alloc(pages);
  hal_memset(VIRT(phys), 0, pages * HAL_PAGE_SIZE);
  
  spinlock_acquire(&PROCS.spinlock);
  Proc *proc = NULL;
  LL_FINDPROP(PROCS.procs, proc, pid, _caller_pid);
  spinlock_release(&PROCS.spinlock);

  uint8_t *virt = NULL;
  if ((flags & MMAN_MAP_F_FIXED) && addr != NULL) {
    if ((uintptr_t)addr % HAL_PAGE_SIZE != 0) {
      if (out != NULL) {
        *out = NULL;
      }
      pmm_free((uintptr_t)phys, pages);
      return E_INVALIDARGUMENT;
    }

    virt = addr;
  } else {
    virt = (uint8_t *)proc->mman_map_base;
    proc->mman_map_base += pages * HAL_PAGE_SIZE;
  }

  uint64_t pflags = HAL_PG_USER | HAL_PG_PRESENT;
  if (prot & MMAN_MAP_PF_RW) {
    pflags |= HAL_PG_RW;
  }

  hal_vmm_map_range(proc->platformdata.cr3, virt, phys, pages * HAL_PAGE_SIZE, pflags);
  VasRange *range = dlmalloc(sizeof(*range));
  range->virtstart = virt;
  range->physstart = phys;
  range->size = pages * HAL_PAGE_SIZE;
  range->pgflags = pflags;
  LL_APPEND(proc->vas, range);

  if (out != NULL) {
    *out = virt;
  }

  return E_OK;
}

int32_t SYSCALL1(sys_mman_unmap, addr1) {
  uint8_t *addr = (uint8_t *)addr1;

  if (addr == NULL) {
    return E_INVALIDARGUMENT;
  }

  spinlock_acquire(&PROCS.spinlock);
  Proc *proc = NULL;
  LL_FINDPROP(PROCS.procs, proc, pid, _caller_pid);
  spinlock_release(&PROCS.spinlock);

  VasRange *tofree = NULL;

  VasRange *vas, *vastmp;
  LL_FOREACH_SAFE(proc->vas, vas, vastmp) {
    if (vas->virtstart == addr) {
      tofree = vas;
      break;
    }
  }

  if (tofree == NULL) {
    return E_INVALIDARGUMENT;
  }

  hal_vmm_unmap_range(proc->platformdata.cr3, tofree->virtstart, tofree->physstart, tofree->size);
  LL_REMOVE(proc->vas, tofree);
  pmm_free((uintptr_t)tofree->physstart, tofree->size / HAL_PAGE_SIZE);
  dlfree(tofree);
  return E_OK;
}
