#include <device/debugconsole.h>
#include <device/device.h>
#include <device/partdrv.h>
#include <device/partitions.h>
#include <device/ramdrv.h>
#include <device/terminal.h>
#include <devices.h>
#include <id/id_alloc.h>
#include <libk/align.h>
#include <libk/fieldlengthof.h>
#include <libk/hash.h>
#include <libk/rbtree.h>
#include <libk/std.h>
#include <libk/string.h>
#include <limine/requests.h>
#include <lz4/lz4.h>
#include <lz4/lz4frame.h>
#include <mm/liballoc.h>
#include <status.h>
#include <sync/spin_lock.h>
#include <sys/debug.h>

#if defined(__x86_64__)
#include <device/pci.h>
#include <device/ps2_kb.h>
#endif

struct device_table {
  struct hash_node_link* device_buckets[1024];
  spin_lock_t lock;
};

static struct device_table device_table;

struct device* device_find (const char* key) {
  struct hash_node_link* found_link = NULL;
  size_t key_len = strlen_null (key);
  uint32_t hash = hash_fnv32 (key, key_len);

  spin_lock (&device_table.lock);
  hash_find (&device_table, key, key_len, hash, lengthof (device_table.device_buckets),
             device_buckets, struct device, device_table_link, key, found_link);
  spin_unlock (&device_table.lock);

  if (found_link == NULL)
    return NULL;

  return hash_entry (found_link, struct device, device_table_link);
}

struct device* device_create (const char* key, device_op_func_t* ops, size_t ops_len,
                              device_init_func_t init, device_fini_func_t fini, void* arg) {
  if (ops_len >= fieldlengthof (struct device, ops))
    return NULL;

  struct device* device = malloc (sizeof (*device));

  if (device == NULL)
    return NULL;

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

  device->init = init;
  device->fini = fini;
  memcpy (device->key, key, strlen_null (key));

  if (ops != NULL)
    memcpy (device->ops, ops, ops_len * sizeof (device_op_func_t));

  if (!device->init (device, arg)) {
    free (device);
    return NULL;
  }

  uint32_t device_hash = hash_fnv32 (device->key, strlen_null (device->key));

  spin_lock (&device_table.lock);

  hash_insert (&device_table, &device->device_table_link, device_hash,
               lengthof (device_table.device_buckets), device_buckets);

  spin_unlock (&device_table.lock);

  DEBUG ("Created device %s\n", device->key);

  return device;
}

static void debugconsole_device_init (void) {
  device_op_func_t ops[] = {
      [DEBUGCONSOLE_PUTSTR] = &debugconsole_putstr,
  };
  device_create ("DEBUGCONSOLE", ops, lengthof (ops), &debugconsole_init, &debugconsole_fini, NULL);
}

static void terminal_device_init (void) {
  device_op_func_t ops[] = {
      [TERMINAL_PUTSTR] = &terminal_putstr,
      [TERMINAL_DIMENSIONS] = &terminal_dimensions,
  };
  device_create ("TERMINAL", ops, lengthof (ops), &terminal_init, &terminal_fini, NULL);
}

static void ramdisk_device_init (void) {
  device_op_func_t ops[] = {
      [XDRV_GET_SIZE] = &ramdrv_get_size,
      [XDRV_GET_SECTOR_SIZE] = &ramdrv_get_sector_size,
      [XDRV_GET_DEVICE_TYPE] = &ramdrv_get_device_type,
      [XDRV_READ] = &ramdrv_read,
      [XDRV_WRITE] = &ramdrv_write,
  };

  struct limine_module_response* module = limine_module_request.response;

  const char* ramdisk_path = "/boot/mop3dist.tar.lz4";
  struct limine_file* file = NULL;

  for (size_t i = 0; i < module->module_count; i++) {
    file = module->modules[i];

    if (strncmp (file->path, ramdisk_path, strlen (ramdisk_path)) == 0)
      break;
  }

  const size_t unpack_buffer_size = 1024 * 1024 * 10;
  uint8_t* unpack_buffer = malloc (unpack_buffer_size);

  LZ4F_decompressionContext_t dctx;

  LZ4F_createDecompressionContext (&dctx, LZ4F_VERSION);

  size_t consumed = file->size;
  size_t produced = unpack_buffer_size;

  LZ4F_decompress (dctx, unpack_buffer, &produced, file->address, &consumed, NULL);

  struct ramdrv_init init = {
      .total_size = produced,
      .sector_size = 512,
      .buffer = unpack_buffer,
  };
  device_create ("RD0", ops, lengthof (ops), &ramdrv_init, &ramdrv_fini, &init);

  LZ4F_freeDecompressionContext (dctx);
}

static void temp_device_init (void) {
  device_op_func_t ops[] = {
      [XDRV_GET_SIZE] = &ramdrv_get_size,
      [XDRV_GET_SECTOR_SIZE] = &ramdrv_get_sector_size,
      [XDRV_GET_DEVICE_TYPE] = &ramdrv_get_device_type,
      [XDRV_READ] = &ramdrv_read,
      [XDRV_WRITE] = &ramdrv_write,
  };

  struct ramdrv_init init = {
      .total_size = 1024 * 1024 * 20,
      .sector_size = 512,
  };
  device_create ("TEMP0", ops, lengthof (ops), &ramdrv_init, &ramdrv_fini, &init);
}

#if defined(__x86_64__)
static void ps2kb_device_init (void) {
  device_op_func_t ops[] = {
      [KB_READ_KEY] = &ps2kb_read_key,
  };
  device_create ("KB", ops, lengthof (ops), &ps2kb_init, &ps2kb_fini, NULL);
}
#endif

void devices_init (void) {
  memset (&device_table, 0, sizeof (device_table));

  terminal_device_init ();
  ramdisk_device_init ();
  temp_device_init ();
  debugconsole_device_init ();

#if defined(__x86_64__)
  ps2kb_device_init ();
  pci_init ();
#endif
}
