#include <libk/assert.h>
#include <libk/rbtree.h>
#include <libk/std.h>
#include <libk/string.h>
#include <mm/liballoc.h>
#include <mm/pmm.h>
#include <proc/proc.h>
#include <sync/spin_lock.h>
#include <sys/debug.h>

void proc_cleanup_resources (struct proc* proc) {
  struct proc_resource* resource = NULL;

  struct rb_node_link* rnode;
  rbtree_first (&proc->resource_tree, rnode);

  while (rnode) {
    struct rb_node_link* next;
    rbtree_next (rnode, next);
    resource = rbtree_entry (rnode, struct proc_resource, proc_resource_tree_link);
    proc_drop_resource (proc, resource);
    rnode = next;
  }

  assert (proc->resource_tree == NULL);
}

void proc_drop_resource (struct proc* proc, struct proc_resource* resource) {
  DEBUG ("resource=%p, type=%d, rid=%d\n", resource, resource->type, resource->rid);

  if (atomic_fetch_sub (&resource->refs, 1) == 1) {
    spin_lock (&proc->lock);
    rbtree_delete (&proc->resource_tree, &resource->proc_resource_tree_link);
    spin_unlock (&proc->lock);

    resource->ops.cleanup (resource);
    free (resource);
  }
}

static bool proc_create_resource_mem (struct proc_resource_mem* mem,
                                      struct proc_resource_mem_init* init) {
  if (init->pages == 0)
    return false;

  uintptr_t paddr = pmm_alloc (init->pages);
  if (paddr == PMM_ALLOC_ERR)
    return false;

  mem->paddr = paddr;
  mem->pages = init->pages;

  return true;
}

static void proc_cleanup_resource_mem (struct proc_resource* resource) {
  pmm_free (resource->u.mem.paddr, resource->u.mem.pages);
}

struct proc_resource* proc_create_resource (struct proc* proc, int rid, int type, int vis,
                                            void* data) {
  /* Check if resource RID already exists */
  struct proc_resource* resource_check;
  rbtree_find (struct proc_resource, &proc->resource_tree, rid, resource_check,
               proc_resource_tree_link, rid);
  if (resource_check != NULL)
    return NULL;

  struct proc_resource* resource = malloc (sizeof (*resource));
  if (resource == NULL)
    return NULL;

  memset (resource, 0, sizeof (*resource));

  resource->lock = SPIN_LOCK_INIT;
  resource->type = type;
  resource->refs = 1;
  resource->rid = rid;
  resource->visibility = vis;

  switch (resource->type) {
  case PR_MEM: {
    struct proc_resource_mem_init* mem_init = data;
    proc_create_resource_mem (&resource->u.mem, mem_init);
    resource->ops.cleanup = &proc_cleanup_resource_mem;
    DEBUG ("resource=%p type=%d rid=%d paddr=%p, pages=%zu\n", resource, resource->type,
           resource->rid, resource->u.mem.paddr, resource->u.mem.pages);
  } break;
  default: {
    free (resource);
    return NULL;
  } break;
  }

  spin_lock (&proc->lock);
  rbtree_insert (struct proc_resource, &proc->resource_tree, &resource->proc_resource_tree_link,
                 proc_resource_tree_link, rid);
  spin_unlock (&proc->lock);

  return resource;
}
