#define _GNU_SOURCE
#include <search.h>
#include "platform.h"

#define USER_SPACE RANGE(0x40000000, 0xc0000000)

typedef struct PageMap {
  void *va;
  void *pa;
  struct PageMap *next;
  int prot;
  int is_mapped;
  char key[32];  // used for hsearch_r()
} PageMap;

typedef struct VMHead {
  PageMap *head;
  struct hsearch_data hash;
  int nr_page;
} VMHead;

#define list_foreach(p, head) \
  for (p = (PageMap *)(head); p != NULL; p = p->next)

extern int __am_pgsize;
static int vme_enable = 0;
static void* (*pgalloc)(int) = NULL;
static void (*pgfree)(void *) = NULL;

bool vme_init(void* (*pgalloc_f)(int), void (*pgfree_f)(void*)) {
  pgalloc = pgalloc_f;
  pgfree = pgfree_f;
  vme_enable = 1;
  return true;
}

void protect(AddrSpace *as) {
  assert(as != NULL);
  VMHead *h = pgalloc(__am_pgsize); // used as head of the list
  assert(h != NULL);
  memset(h, 0, sizeof(*h));
  int max_pg = (USER_SPACE.end - USER_SPACE.start) / __am_pgsize;
  int ret = hcreate_r(max_pg, &h->hash);
  assert(ret != 0);

  as->ptr = h;
  as->pgsize = __am_pgsize;
  as->area = USER_SPACE;
}

void unprotect(AddrSpace *as) {
}

void __am_switch(Context *c) {
  if (!vme_enable) return;

  VMHead *head = c->vm_head;
  VMHead *now_head = thiscpu->vm_head;
  if (head == now_head) goto end;

  PageMap *pp;
  if (now_head != NULL) {
    // munmap all mappings
    list_foreach(pp, now_head->head) {
      if (pp->is_mapped) {
        __am_pmem_unmap(pp->va);
        pp->is_mapped = false;
      }
    }
  }

  if (head != NULL) {
    // mmap all mappings
    list_foreach(pp, head->head) {
      assert(IN_RANGE(pp->va, USER_SPACE));
      __am_pmem_map(pp->va, pp->pa, pp->prot);
      pp->is_mapped = true;
    }
  }

end:
  thiscpu->vm_head = head;
}

void map(AddrSpace *as, void *va, void *pa, int prot) {
  assert(IN_RANGE(va, USER_SPACE));
  assert((uintptr_t)va % __am_pgsize == 0);
  assert((uintptr_t)pa % __am_pgsize == 0);
  assert(as != NULL);
  PageMap *pp = NULL;
  VMHead *vm_head = as->ptr;
  assert(vm_head != NULL);
  char buf[32];
  snprintf(buf, 32, "%x", va);
  ENTRY item = { .key = buf };
  ENTRY *item_find;
  hsearch_r(item, FIND, &item_find, &vm_head->hash);
  if (item_find == NULL) {
    pp = pgalloc(__am_pgsize); // this will waste memory, any better idea?
    snprintf(pp->key, 32, "%x", va);
    item.key = pp->key;
    item.data = pp;
    int ret = hsearch_r(item, ENTER, &item_find, &vm_head->hash);
    assert(ret != 0);
    vm_head->nr_page ++;
  } else {
    pp = item_find->data;
  }
  pp->va = va;
  pp->pa = pa;
  pp->prot = prot;
  pp->is_mapped = false;
  pp->next = vm_head->head;
  vm_head->head = pp;

  if (vm_head == thiscpu->vm_head) {
    // enforce the map immediately
    __am_pmem_map(pp->va, pp->pa, pp->prot);
    pp->is_mapped = true;
  }
}

Context* ucontext(AddrSpace *as, Area kstack, void *entry) {
  Context *c = (Context*)kstack.end - 1;

  __am_get_example_uc(c);
  c->uc.uc_mcontext.gregs[REG_RIP] = (uintptr_t)entry;
  c->uc.uc_mcontext.gregs[REG_RSP] = (uintptr_t)USER_SPACE.end;

  int ret = sigemptyset(&(c->uc.uc_sigmask)); // enable interrupt
  assert(ret == 0);
  c->vm_head = as->ptr;

  c->ksp = (uintptr_t)kstack.end;

  return c;
}

int __am_in_userspace(void *addr) {
  return vme_enable && thiscpu->vm_head != NULL && IN_RANGE(addr, USER_SPACE);
}
