#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <uv.h>

#include "../include/fs-ext.h"

#include "platform.h"

int
fs_ext_try_lock(uv_os_fd_t fd, uint64_t offset, size_t length, fs_ext_lock_type_t type) {
  int err = fs_ext__try_lock(fd, offset, length, type);

  if (err == UV_EACCES || err == UV_EAGAIN || err == UV_EBUSY) {
    err = UV_EAGAIN;
  }

  return err;
}

static void
fs_ext__lock_close(uv_handle_t *handle) {
  fs_ext_lock_t *r = (fs_ext_lock_t *) handle->data;

  if (r->cb) r->cb(r, r->result);
}

static void
fs_ext__lock_after_work(uv_async_t *signal) {
  int err;

  fs_ext_lock_t *r = (fs_ext_lock_t *) signal->data;

  err = uv_thread_join(&r->thread);
  assert(err == 0);

  uv_close((uv_handle_t *) signal, fs_ext__lock_close);
}

static void
fs_ext__lock_work(void *data) {
  int err;

  fs_ext_lock_t *r = (fs_ext_lock_t *) data;

  r->result = fs_ext__wait_for_lock(r->fd, r->offset, r->length, r->type);

  err = uv_async_send(&r->signal);
  assert(err == 0);
}

int
fs_ext_wait_for_lock(uv_loop_t *loop, fs_ext_lock_t *req, uv_os_fd_t fd, uint64_t offset, size_t length, fs_ext_lock_type_t type, fs_ext_lock_cb cb) {
  int err;

  req->fd = fd;
  req->offset = offset;
  req->length = length;
  req->type = type;
  req->cb = cb;
  req->signal.data = (void *) req;

  err = uv_async_init(loop, &req->signal, fs_ext__lock_after_work);
  assert(err == 0);

  err = uv_thread_create(&req->thread, fs_ext__lock_work, (void *) req);
  assert(err == 0);

  return 0;
}

int
fs_ext_try_downgrade_lock(uv_os_fd_t fd, uint64_t offset, size_t length) {
  int err = fs_ext__try_downgrade_lock(fd, offset, length);

  if (err == UV_EACCES || err == UV_EAGAIN || err == UV_EBUSY) {
    err = UV_EAGAIN;
  }

  return err;
}

static void
fs_ext__downgrade_lock_work(void *data) {
  int err;

  fs_ext_lock_t *r = (fs_ext_lock_t *) data;

  r->result = fs_ext__wait_for_downgrade_lock(r->fd, r->offset, r->length);

  err = uv_async_send(&r->signal);
  assert(err == 0);
}

int
fs_ext_wait_for_downgrade_lock(uv_loop_t *loop, fs_ext_lock_t *req, uv_os_fd_t fd, uint64_t offset, size_t length, fs_ext_lock_cb cb) {
  int err;

  req->fd = fd;
  req->offset = offset;
  req->length = length;
  req->type = FS_EXT_RDLOCK;
  req->cb = cb;
  req->signal.data = (void *) req;

  err = uv_async_init(loop, &req->signal, fs_ext__lock_after_work);
  assert(err == 0);

  err = uv_thread_create(&req->thread, fs_ext__downgrade_lock_work, (void *) req);
  assert(err == 0);

  return 0;
}

static void
fs_ext__upgrade_lock_work(void *data) {
  int err;

  fs_ext_lock_t *r = (fs_ext_lock_t *) data;

  r->result = fs_ext__wait_for_upgrade_lock(r->fd, r->offset, r->length);

  err = uv_async_send(&r->signal);
  assert(err == 0);
}

int
fs_ext_try_upgrade_lock(uv_os_fd_t fd, uint64_t offset, size_t length) {
  int err = fs_ext__try_upgrade_lock(fd, offset, length);

  if (err == UV_EACCES || err == UV_EAGAIN || err == UV_EBUSY) {
    err = UV_EAGAIN;
  }

  return err;
}

int
fs_ext_wait_for_upgrade_lock(uv_loop_t *loop, fs_ext_lock_t *req, uv_os_fd_t fd, uint64_t offset, size_t length, fs_ext_lock_cb cb) {
  int err;

  req->fd = fd;
  req->offset = offset;
  req->length = length;
  req->type = FS_EXT_WRLOCK;
  req->cb = cb;
  req->signal.data = (void *) req;

  err = uv_async_init(loop, &req->signal, fs_ext__lock_after_work);
  assert(err == 0);

  err = uv_thread_create(&req->thread, fs_ext__upgrade_lock_work, (void *) req);
  assert(err == 0);

  return 0;
}

int
fs_ext_unlock(uv_os_fd_t fd, uint64_t offset, size_t length) {
  return fs_ext__unlock(fd, offset, length);
}

static void
fs_ext__trim_work(uv_work_t *req) {
  fs_ext_trim_t *r = (fs_ext_trim_t *) req->data;

  r->result = fs_ext__trim(r->fd, r->offset, r->length);
}

static void
fs_ext__trim_after_work(uv_work_t *req, int status) {
  fs_ext_trim_t *r = (fs_ext_trim_t *) req->data;

  if (r->cb) r->cb(r, r->result);
}

int
fs_ext_trim(uv_loop_t *loop, fs_ext_trim_t *req, uv_os_fd_t fd, uint64_t offset, size_t length, fs_ext_trim_cb cb) {
  req->fd = fd;
  req->offset = offset;
  req->length = length;
  req->cb = cb;
  req->req.data = (void *) req;

  return uv_queue_work(loop, &req->req, fs_ext__trim_work, fs_ext__trim_after_work);
}

static void
fs_ext__sparse_work(uv_work_t *req) {
  fs_ext_sparse_t *r = (fs_ext_sparse_t *) req->data;

  r->result = fs_ext__sparse(r->fd);
}

static void
fs_ext__sparse_after_work(uv_work_t *req, int status) {
  fs_ext_sparse_t *r = (fs_ext_sparse_t *) req->data;

  if (r->cb) r->cb(r, r->result);
}

int
fs_ext_sparse(uv_loop_t *loop, fs_ext_sparse_t *req, uv_os_fd_t fd, fs_ext_sparse_cb cb) {
  req->fd = fd;
  req->cb = cb;
  req->req.data = (void *) req;

  return uv_queue_work(loop, &req->req, fs_ext__sparse_work, fs_ext__sparse_after_work);
}

static void
fs_ext__swap_work(uv_work_t *req) {
  fs_ext_swap_t *r = (fs_ext_swap_t *) req->data;

  r->result = fs_ext__swap(r->from, r->to);
}

static void
fs_ext__swap_after_work(uv_work_t *req, int status) {
  fs_ext_swap_t *r = (fs_ext_swap_t *) req->data;

  char *from = r->from;
  char *to = r->to;

  if (r->cb) r->cb(r, r->result);

  free(from);
  free(to);
}

int
fs_ext_swap(uv_loop_t *loop, fs_ext_swap_t *req, const char *from, const char *to, fs_ext_swap_cb cb) {
  int err;

  req->from = strdup(from);
  req->to = strdup(to);
  req->cb = cb;
  req->req.data = (void *) req;

  err = uv_queue_work(loop, &req->req, fs_ext__swap_work, fs_ext__swap_after_work);

  if (err < 0) {
    free(req->from);
    free(req->to);

    return err;
  }

  return 0;
}

static void
fs_ext__get_attr_work(uv_work_t *req) {
  fs_ext_get_attr_t *r = (fs_ext_get_attr_t *) req->data;

  r->result = fs_ext__get_attr(r->fd, r->name, &r->value);
}

static void
fs_ext__get_attr_after_work(uv_work_t *req, int status) {
  fs_ext_get_attr_t *r = (fs_ext_get_attr_t *) req->data;

  uv_buf_t value = r->value;
  char *name = r->name;

  if (r->cb) r->cb(r, r->result, &value);

  free(value.base);
  free(name);
}

int
fs_ext_get_attr(uv_loop_t *loop, fs_ext_get_attr_t *req, uv_os_fd_t fd, const char *name, fs_ext_get_attr_cb cb) {
  int err;

  req->fd = fd;
  req->name = strdup(name);
  req->value.base = NULL;
  req->value.len = 0;
  req->cb = cb;
  req->req.data = (void *) req;

  err = uv_queue_work(loop, &req->req, fs_ext__get_attr_work, fs_ext__get_attr_after_work);

  if (err < 0) {
    free(req->name);

    return err;
  }

  return 0;
}

static void
fs_ext__set_attr_work(uv_work_t *req) {
  fs_ext_set_attr_t *r = (fs_ext_set_attr_t *) req->data;

  r->result = fs_ext__set_attr(r->fd, r->name, &r->value);
}

static void
fs_ext__set_attr_after_work(uv_work_t *req, int status) {
  fs_ext_set_attr_t *r = (fs_ext_set_attr_t *) req->data;

  char *name = r->name;

  if (r->cb) r->cb(r, r->result);

  free(name);
}

int
fs_ext_set_attr(uv_loop_t *loop, fs_ext_set_attr_t *req, uv_os_fd_t fd, const char *name, const uv_buf_t *value, fs_ext_set_attr_cb cb) {
  int err;

  req->fd = fd;
  req->name = strdup(name);
  req->value = *value;
  req->cb = cb;
  req->req.data = (void *) req;

  err = uv_queue_work(loop, &req->req, fs_ext__set_attr_work, fs_ext__set_attr_after_work);

  if (err < 0) {
    free(req->name);

    return err;
  }

  return 0;
}

static void
fs_ext__remove_attr_work(uv_work_t *req) {
  fs_ext_remove_attr_t *r = (fs_ext_remove_attr_t *) req->data;

  r->result = fs_ext__remove_attr(r->fd, r->name);
}

static void
fs_ext__remove_attr_after_work(uv_work_t *req, int status) {
  fs_ext_remove_attr_t *r = (fs_ext_remove_attr_t *) req->data;

  char *name = r->name;

  if (r->cb) r->cb(r, r->result);

  free(name);
}

int
fs_ext_remove_attr(uv_loop_t *loop, fs_ext_remove_attr_t *req, uv_os_fd_t fd, const char *name, fs_ext_remove_attr_cb cb) {
  int err;

  req->fd = fd;
  req->name = strdup(name);
  req->cb = cb;
  req->req.data = (void *) req;

  err = uv_queue_work(loop, &req->req, fs_ext__remove_attr_work, fs_ext__remove_attr_after_work);

  if (err < 0) {
    free(req->name);

    return err;
  }

  return 0;
}

static void
fs_ext__list_attrs_work(uv_work_t *req) {
  fs_ext_list_attrs_t *r = (fs_ext_list_attrs_t *) req->data;

  r->result = fs_ext__list_attrs(r->fd, &r->names, &r->length);
}

static void
fs_ext__list_attrs_after_work(uv_work_t *req, int status) {
  fs_ext_list_attrs_t *r = (fs_ext_list_attrs_t *) req->data;

  const char **names = (const char **) r->names;

  if (r->cb) r->cb(r, r->result, names, r->length);

  free(names);
}

int
fs_ext_list_attrs(uv_loop_t *loop, fs_ext_list_attrs_t *req, uv_os_fd_t fd, fs_ext_list_attrs_cb cb) {
  req->fd = fd;
  req->cb = cb;
  req->req.data = (void *) req;

  return uv_queue_work(loop, &req->req, fs_ext__list_attrs_work, fs_ext__list_attrs_after_work);
}
