#include <device/device.h>
#include <fs/fat1.h>
#include <fs/fatfs.h>
#include <fs/tarfs.h>
#include <fs/vfs.h>
#include <fs_types.h>
#include <id/id_alloc.h>
#include <libk/fieldsizeof.h>
#include <libk/hash.h>
#include <libk/lengthof.h>
#include <libk/std.h>
#include <libk/string.h>
#include <mm/liballoc.h>
#include <proc/proc.h>
#include <proc/procgroup.h>
#include <proc/reschedule.h>
#include <proc/suspension_q.h>
#include <status.h>
#include <sync/spin_lock.h>
#include <sys/debug.h>

static struct vfs_volume_table volume_table;

static struct vfs_volume* vfs_find_volume (const char* volume) {
  struct hash_node_link* found_link = NULL;
  size_t volume_len = strlen_null (volume);
  uint32_t hash = hash_fnv32 (volume, volume_len);

  spin_lock (&volume_table.lock);
  hash_find (&volume_table, volume, volume_len, hash, lengthof (volume_table.volume_buckets),
             volume_buckets, struct vfs_volume, volume_table_link, key, found_link);
  spin_unlock (&volume_table.lock);

  if (found_link == NULL)
    return NULL;

  return hash_entry (found_link, struct vfs_volume, volume_table_link);
}

int vfs_create_volume (const char* key, int fs_type, struct device* back_device, bool format) {
  if (strlen_null (key) > VOLUME_MAX)
    return -ST_OOB_ERROR;

  struct vfs_volume* volume = malloc (sizeof (*volume));

  if (volume == NULL)
    return -ST_OOM_ERROR;

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

  memcpy (volume->key, key, strlen_null (key));
  volume->fs_type = fs_type;
  volume->back_device = back_device;
  volume->lock = SPIN_LOCK_INIT;

  switch (volume->fs_type) {
  case FS_TARFS:
    volume->driver_ops.mount = &tarfs_mount;
    volume->driver_ops.format = &tarfs_format;
    volume->driver_ops.describe = &tarfs_describe;
    volume->driver_ops.read_file = &tarfs_read_file;
    volume->driver_ops.write_file = &tarfs_write_file;
    volume->driver_ops.read_dir_entry = &tarfs_read_dir_entry;
    volume->driver_ops.create_file = &tarfs_create_file;
    volume->driver_ops.create_dir = &tarfs_create_dir;
    volume->driver_ops.remove = &tarfs_remove;
    break;
  case FS_FAT16:
    volume->driver_ops.mount = &fatfs_mount;
    volume->driver_ops.format = &fatfs16_format;
    volume->driver_ops.describe = &fatfs_describe;
    volume->driver_ops.read_file = &fatfs_read_file;
    volume->driver_ops.write_file = &fatfs_write_file;
    volume->driver_ops.read_dir_entry = &fatfs_read_dir_entry;
    volume->driver_ops.create_file = &fatfs_create_file;
    volume->driver_ops.create_dir = &fatfs_create_dir;
    volume->driver_ops.remove = &fatfs_remove;
    break;
  case FS_FAT32:
    volume->driver_ops.mount = &fatfs_mount;
    volume->driver_ops.format = &fatfs32_format;
    volume->driver_ops.describe = &fatfs_describe;
    volume->driver_ops.read_file = &fatfs_read_file;
    volume->driver_ops.write_file = &fatfs_write_file;
    volume->driver_ops.read_dir_entry = &fatfs_read_dir_entry;
    volume->driver_ops.create_file = &fatfs_create_file;
    volume->driver_ops.create_dir = &fatfs_create_dir;
    volume->driver_ops.remove = &fatfs_remove;
    break;
  default:
    free (volume);
    return -ST_MOUNT_ERROR;
  }

  int ret = volume->driver_ops.mount (volume, format);

  if (ret < 0) {
    free (volume);
    return ret;
  }

  uint32_t mp_hash = hash_fnv32 (volume->key, strlen_null (volume->key));

  spin_lock (&volume_table.lock);

  hash_insert (&volume_table, &volume->volume_table_link, mp_hash,
               lengthof (volume_table.volume_buckets), volume_buckets);

  spin_unlock (&volume_table.lock);

  return ST_OK;
}

int vfs_volume_open (struct proc* proc, const char* volume_name, struct reschedule_ctx* rctx) {
  struct vfs_volume* volume = vfs_find_volume (volume_name);

  if (volume == NULL)
    return -ST_NOT_FOUND;

  spin_lock (&volume->lock);

  if (!volume->locked) {
    volume->locked = true;
    volume->owner = proc;
    spin_unlock (&volume->lock);
    return ST_OK;
  } else {
    if (proc == VFS_KERNEL) {
      spin_unlock (&volume->lock);
      return -ST_TRY_AGAIN;
    } else {
      proc_sq_suspend (proc, &volume->sq, &volume->lock, rctx);
      return ST_OK;
    }
  }
}

int vfs_volume_close (struct proc* proc, const char* volume_name, struct reschedule_ctx* rctx) {
  struct vfs_volume* volume = vfs_find_volume (volume_name);

  if (volume == NULL)
    return -ST_NOT_FOUND;

  spin_lock (&volume->lock);

  if (volume->locked && volume->owner != proc) {
    spin_unlock (&volume->lock);
    return -ST_PERMISSION_ERROR;
  }

  spin_lock (&volume->sq.lock);

  struct list_node_link* node = volume->sq.proc_list;

  if (node) {
    struct proc_sq_entry* sq_entry = list_entry (node, struct proc_sq_entry, sq_link);
    struct proc* resumed_proc = sq_entry->proc;

    volume->owner = resumed_proc;
    volume->locked = true;

    spin_unlock (&volume->sq.lock);
    spin_unlock (&volume->lock);

    proc_sq_resume (resumed_proc, sq_entry, rctx);
    return ST_OK;
  }

  volume->locked = false;
  volume->owner = NULL;

  spin_unlock (&volume->sq.lock);
  spin_unlock (&volume->lock);

  return ST_OK;
}

int vfs_format (struct proc* proc, const char* volume_name) {
  struct vfs_volume* volume = vfs_find_volume (volume_name);

  if (volume == NULL)
    return -ST_NOT_FOUND;

  spin_lock (&volume->lock);

  if (volume->locked && volume->owner != proc) {
    spin_unlock (&volume->lock);
    return -ST_PERMISSION_ERROR;
  }

  spin_unlock (&volume->lock);

  return volume->driver_ops.format (volume);
}

int vfs_read_file (struct proc* proc, const char* volume_name, const char* path, uint8_t* buffer,
                   size_t off, size_t size) {
  struct vfs_volume* volume = vfs_find_volume (volume_name);

  if (volume == NULL)
    return -ST_NOT_FOUND;

  spin_lock (&volume->lock);

  if (volume->locked && volume->owner != proc) {
    spin_unlock (&volume->lock);
    return -ST_PERMISSION_ERROR;
  }

  spin_unlock (&volume->lock);

  return volume->driver_ops.read_file (volume, path, buffer, off, size);
}

int vfs_write_file (struct proc* proc, const char* volume_name, const char* path, uint8_t* buffer,
                    size_t off, size_t size, uint32_t flags) {
  struct vfs_volume* volume = vfs_find_volume (volume_name);

  if (volume == NULL)
    return -ST_NOT_FOUND;

  spin_lock (&volume->lock);

  if (volume->locked && volume->owner != proc) {
    spin_unlock (&volume->lock);
    return -ST_PERMISSION_ERROR;
  }

  spin_unlock (&volume->lock);

  return volume->driver_ops.write_file (volume, path, buffer, off, size, flags);
}

int vfs_create_file (struct proc* proc, const char* volume_name, const char* path) {
  struct vfs_volume* volume = vfs_find_volume (volume_name);

  if (volume == NULL)
    return -ST_NOT_FOUND;

  spin_lock (&volume->lock);

  if (volume->locked && volume->owner != proc) {
    spin_unlock (&volume->lock);
    return -ST_PERMISSION_ERROR;
  }

  spin_unlock (&volume->lock);

  return volume->driver_ops.create_file (volume, path);
}

int vfs_describe (struct proc* proc, const char* volume_name, const char* path, struct desc* desc) {
  struct vfs_volume* volume = vfs_find_volume (volume_name);

  if (volume == NULL)
    return -ST_NOT_FOUND;

  spin_lock (&volume->lock);

  if (volume->locked && volume->owner != proc) {
    spin_unlock (&volume->lock);
    return -ST_PERMISSION_ERROR;
  }

  spin_unlock (&volume->lock);

  return volume->driver_ops.describe (volume, path, desc);
}

int vfs_read_dir_entry (struct proc* proc, const char* volume_name, const char* path,
                        struct dir_entry* entry, size_t entry_num) {
  struct vfs_volume* volume = vfs_find_volume (volume_name);

  if (volume == NULL)
    return -ST_NOT_FOUND;

  spin_lock (&volume->lock);

  if (volume->locked && volume->owner != proc) {
    spin_unlock (&volume->lock);
    return -ST_PERMISSION_ERROR;
  }

  spin_unlock (&volume->lock);

  return volume->driver_ops.read_dir_entry (volume, path, entry, entry_num);
}

int vfs_create_dir (struct proc* proc, const char* volume_name, const char* path) {
  struct vfs_volume* volume = vfs_find_volume (volume_name);

  if (volume == NULL)
    return -ST_NOT_FOUND;

  spin_lock (&volume->lock);

  if (volume->locked && volume->owner != proc) {
    spin_unlock (&volume->lock);
    return -ST_PERMISSION_ERROR;
  }

  spin_unlock (&volume->lock);

  return volume->driver_ops.create_dir (volume, path);
}

int vfs_remove (struct proc* proc, const char* volume_name, const char* path) {
  struct vfs_volume* volume = vfs_find_volume (volume_name);

  if (volume == NULL)
    return -ST_NOT_FOUND;

  spin_lock (&volume->lock);

  if (volume->locked && volume->owner != proc) {
    spin_unlock (&volume->lock);
    return -ST_PERMISSION_ERROR;
  }

  spin_unlock (&volume->lock);

  return volume->driver_ops.remove (volume, path);
}

void vfs_init (void) {
  memset (&volume_table, 0, sizeof (volume_table));

  volume_table.lock = SPIN_LOCK_INIT;
}

void vfs_translate (size_t fs_block, size_t fs_block_count, size_t fs_block_size,
                    size_t device_sector_size, size_t* out_phys_sector, size_t* out_sector_count) {
  size_t ratio = fs_block_size / device_sector_size;

  if (out_phys_sector != NULL)
    *out_phys_sector = fs_block * ratio;

  if (out_sector_count != NULL)
    *out_sector_count = fs_block_count * ratio;
}
