#include <stddef.h>
#include <limine.h>
#include "pmm.h"
#include "kprintf.h"
#include "bitmap/bitmap.h"
#include "bootinfo/bootinfo.h"
#include "spinlock/spinlock.h"
#include "util/util.h"
#include "hal/hal.h"

PhysMem PHYS_MEM;

void pmm_init(void) {
  spinlock_init(&PHYS_MEM.spinlock);

  BitMap *bm = &PHYS_MEM.self;

  bm->nblocks = _DIV_ROUNDUP(BOOT_INFO.memmap_total, BITMAP_BLOCK_SIZE);
  bm->nbytes = _DIV_ROUNDUP(bm->nblocks, 8);

  struct limine_memmap_entry *memmap_ent = NULL;

  for (size_t i = 0; i < BOOT_INFO.memmap_entrycount; i++) {
    struct limine_memmap_entry *entry = BOOT_INFO.memmap_entries[i];
    if (entry->type != LIMINE_MEMMAP_USABLE || entry->length < bm->nbytes) {
      continue;
    }
    memmap_ent = entry;
    break;
  }

  if (!memmap_ent) {
    ERR("pmm", "required memory: {%lx}\n", bm->nbytes);
    hal_hang();
  }

  size_t physbegin = memmap_ent->base;
  bm->map = (uint8_t *)(physbegin + BOOT_INFO.hhdm_off);

  hal_memset(bm->map, 0xff, bm->nbytes);
  for (size_t i = 0; i < BOOT_INFO.memmap_entrycount; i++) {
    struct limine_memmap_entry *entry = BOOT_INFO.memmap_entries[i];
    if (entry == memmap_ent) {
      continue;
    }
    // mark usable as 0 and unusable as 1
    bitmap_markregion(bm, (void *)entry->base, entry->length, entry->type != LIMINE_MEMMAP_USABLE);
  }

  bitmap_markregion(bm, (void *)physbegin, bm->nbytes, 1);
  bm->alloc_blocks = 0;

  LOG("pmm", "phys bitmap init 0x%lx, size = 0x%lx\n", physbegin, bm->nbytes);
}

void *pmm_alloc(size_t pages) {
  spinlock_acquire(&PHYS_MEM.spinlock);
  uintptr_t phys = (uintptr_t)bitmap_alloc(&PHYS_MEM.self, pages);
  spinlock_release(&PHYS_MEM.spinlock);

  if (!phys) {
    ERR("pmm", "phys memory ran out\n");
    return NULL;
  }
  return (void *)phys;
}

void pmm_free(uintptr_t ptr, size_t pages) {
  spinlock_acquire(&PHYS_MEM.spinlock);
  bitmap_markregion(&PHYS_MEM.self, (void *)ptr, pages * BITMAP_BLOCK_SIZE, 0);
  spinlock_release(&PHYS_MEM.spinlock);
}
