#include "wasi_serdes.h"
#include "wasi_types.h"

void uvwasi_serdes_write_uint64_t(void* ptr,
                                  size_t offset,
                                  uint64_t value) {
  uvwasi_serdes_write_uint32_t(ptr, offset, (uint32_t) value);
  uvwasi_serdes_write_uint32_t(ptr, offset + 4, value >> 32);
}

void uvwasi_serdes_write_uint32_t(void* ptr,
                                  size_t offset,
                                  uint32_t value) {
  uvwasi_serdes_write_uint16_t(ptr, offset, (uint16_t) value);
  uvwasi_serdes_write_uint16_t(ptr, offset + 2, value >> 16);
}

void uvwasi_serdes_write_uint16_t(void* ptr,
                                  size_t offset,
                                  uint16_t value) {
  uvwasi_serdes_write_uint8_t(ptr, offset, (uint8_t) value);
  uvwasi_serdes_write_uint8_t(ptr, offset + 1, value >> 8);
}

void uvwasi_serdes_write_uint8_t(void* ptr,
                                 size_t offset,
                                 uint8_t value) {
  ((uint8_t*) ptr)[offset] = value;
}

uint64_t uvwasi_serdes_read_uint64_t(const void* ptr, size_t offset) {
  uint64_t low = uvwasi_serdes_read_uint32_t(ptr, offset);
  uint64_t high = uvwasi_serdes_read_uint32_t(ptr, offset + 4);
  return low | (high << 32);
}

uint32_t uvwasi_serdes_read_uint32_t(const void* ptr, size_t offset) {
  uint32_t low = uvwasi_serdes_read_uint16_t(ptr, offset);
  uint32_t high = uvwasi_serdes_read_uint16_t(ptr, offset + 2);
  return low | (high << 16);
}

uint16_t uvwasi_serdes_read_uint16_t(const void* ptr, size_t offset) {
  uint16_t low = uvwasi_serdes_read_uint8_t(ptr, offset);
  uint16_t high = uvwasi_serdes_read_uint8_t(ptr, offset + 1);
  return low | (high << 8);
}

uint8_t uvwasi_serdes_read_uint8_t(const void* ptr,  size_t offset) {
  return ((const uint8_t*) ptr)[offset];
}

#define TYPE_SWITCH switch (value->type)

#define ALL_TYPES(STRUCT, FIELD, ALIAS)                                       \
                                                                              \
  ALIAS(advice_t,        uint8_t)                                             \
  ALIAS(clockid_t,       uint32_t)                                            \
  ALIAS(device_t,        uint64_t)                                            \
  ALIAS(dircookie_t,     uint64_t)                                            \
  ALIAS(errno_t,         uint16_t)                                            \
  ALIAS(eventrwflags_t,  uint16_t)                                            \
  ALIAS(eventtype_t,     uint8_t)                                             \
  ALIAS(exitcode_t,      uint32_t)                                            \
  ALIAS(fd_t,            uint32_t)                                            \
  ALIAS(fdflags_t,       uint16_t)                                            \
  ALIAS(filesize_t,      uint64_t)                                            \
  ALIAS(filetype_t,      uint8_t)                                             \
  ALIAS(fstflags_t,      uint16_t)                                            \
  ALIAS(inode_t,         uint64_t)                                            \
  ALIAS(linkcount_t,     uint64_t)                                            \
  ALIAS(lookupflags_t,   uint32_t)                                            \
  ALIAS(oflags_t,        uint16_t)                                            \
  ALIAS(preopentype_t,   uint8_t)                                             \
  ALIAS(riflags_t,       uint16_t)                                            \
  ALIAS(rights_t,        uint64_t)                                            \
  ALIAS(roflags_t,       uint16_t)                                            \
  ALIAS(sdflags_t,       uint8_t)                                             \
  ALIAS(siflags_t,       uint16_t)                                            \
  ALIAS(signal_t,        uint8_t)                                             \
  ALIAS(size_t,          uint32_t)                                            \
  ALIAS(subclockflags_t, uint16_t)                                            \
  ALIAS(timestamp_t,     uint64_t)                                            \
  ALIAS(userdata_t,      uint64_t)                                            \
  ALIAS(whence_t,        uint8_t)                                             \
                                                                              \
  STRUCT(fdstat_t) {                                                          \
    FIELD( 0, filetype_t, fs_filetype);                                       \
    FIELD( 2, fdflags_t,  fs_flags);                                          \
    FIELD( 8, rights_t,   fs_rights_base);                                    \
    FIELD(16, rights_t,   fs_rights_inheriting);                              \
  }                                                                           \
                                                                              \
  STRUCT(filestat_t) {                                                        \
    FIELD( 0, device_t,    st_dev);                                           \
    FIELD( 8, inode_t,     st_ino);                                           \
    FIELD(16, filetype_t,  st_filetype);                                      \
    FIELD(24, linkcount_t, st_nlink);                                         \
    FIELD(32, filesize_t,  st_size);                                          \
    FIELD(40, timestamp_t, st_atim);                                          \
    FIELD(48, timestamp_t, st_mtim);                                          \
    FIELD(56, timestamp_t, st_ctim);                                          \
  }                                                                           \
                                                                              \
  STRUCT(prestat_t) {                                                         \
    FIELD(0, preopentype_t, pr_type);                                         \
    FIELD(4, uint32_t,      u.dir.pr_name_len);                               \
  }                                                                           \
                                                                              \
  STRUCT(event_t) {                                                           \
    FIELD( 0, userdata_t,  userdata);                                         \
    FIELD( 8, errno_t,     error);                                            \
    FIELD(10, eventtype_t, type);                                             \
    TYPE_SWITCH {                                                             \
      case UVWASI_EVENTTYPE_FD_READ:                                          \
      case UVWASI_EVENTTYPE_FD_WRITE:                                         \
        FIELD(16, filesize_t,     u.fd_readwrite.nbytes);                     \
        FIELD(24, eventrwflags_t, u.fd_readwrite.flags);                      \
    }                                                                         \
  }                                                                           \
                                                                              \
  STRUCT(subscription_t) {                                                    \
    FIELD(0, userdata_t,  userdata);                                          \
    FIELD(8, eventtype_t, type);                                              \
    TYPE_SWITCH {                                                             \
      case UVWASI_EVENTTYPE_CLOCK:                                            \
        FIELD(16, clockid_t,       u.clock.clock_id);                         \
        FIELD(24, timestamp_t,     u.clock.timeout);                          \
        FIELD(32, timestamp_t,     u.clock.precision);                        \
        FIELD(40, subclockflags_t, u.clock.flags);                            \
        break;                                                                \
      case UVWASI_EVENTTYPE_FD_READ:                                          \
      case UVWASI_EVENTTYPE_FD_WRITE:                                         \
        FIELD(16, fd_t, u.fd_readwrite.fd);                                   \
    }                                                                         \
  }                                                                           \

#define WRITE_STRUCT(name)                                                    \
  void uvwasi_serdes_write_##name(void* ptr,                                  \
                                  size_t offset,                              \
                                  const uvwasi_##name* value)                 \

#define READ_STRUCT(name)                                                     \
  void uvwasi_serdes_read_##name(const void* ptr,                             \
                                 size_t offset,                               \
                                 uvwasi_##name* value)                        \

#define WRITE_FIELD(field_offset, type, field)                                \
  do {                                                                        \
    uvwasi_serdes_write_##type(ptr, offset + field_offset, value->field);     \
  } while (0)                                                                 \

#define READ_FIELD(field_offset, type, field)                                 \
  do {                                                                        \
    value->field = uvwasi_serdes_read_##type(ptr, offset + field_offset);     \
  } while (0)                                                                 \

#define WRITE_ALIAS(new_name, old_name)                                       \
  void uvwasi_serdes_write_##new_name(void* ptr,                              \
                                      size_t offset,                          \
                                      uvwasi_##new_name value) {              \
    uvwasi_serdes_write_##old_name(ptr, offset, value);                       \
  }                                                                           \

#define READ_ALIAS(new_name, old_name)                                        \
  uvwasi_##new_name uvwasi_serdes_read_##new_name(const void* ptr,            \
                                                  size_t offset) {            \
    return uvwasi_serdes_read_##old_name(ptr, offset);                        \
  }                                                                           \

ALL_TYPES(WRITE_STRUCT, WRITE_FIELD, WRITE_ALIAS)
ALL_TYPES(READ_STRUCT, READ_FIELD, READ_ALIAS)


uvwasi_errno_t uvwasi_serdes_read_ciovec_t(const void* ptr,
                                           size_t end,
                                           size_t offset,
                                           uvwasi_ciovec_t* value) {
  uint32_t buf_ptr;

  buf_ptr = uvwasi_serdes_read_uint32_t(ptr, offset);
  value->buf_len = uvwasi_serdes_read_size_t(ptr, offset + 4);

  if (!uvwasi_serdes_check_bounds(buf_ptr, end, value->buf_len))
    return UVWASI_EOVERFLOW;

  value->buf = ((uint8_t*) ptr + buf_ptr);
  return UVWASI_ESUCCESS;
}


uvwasi_errno_t uvwasi_serdes_read_iovec_t(const void* ptr,
                                          size_t end,
                                          size_t offset,
                                          uvwasi_iovec_t* value) {
  uint32_t buf_ptr;

  buf_ptr = uvwasi_serdes_read_uint32_t(ptr, offset);
  value->buf_len = uvwasi_serdes_read_size_t(ptr, offset + 4);

  if (!uvwasi_serdes_check_bounds(buf_ptr, end, value->buf_len))
    return UVWASI_EOVERFLOW;

  value->buf = ((uint8_t*) ptr + buf_ptr);
  return UVWASI_ESUCCESS;
}


uvwasi_errno_t uvwasi_serdes_readv_ciovec_t(const void* ptr,
                                            size_t end,
                                            size_t offset,
                                            uvwasi_ciovec_t* iovs,
                                            uvwasi_size_t iovs_len) {
  uvwasi_errno_t err;
  uvwasi_size_t i;

  for (i = 0; i < iovs_len; i++) {
    err = uvwasi_serdes_read_ciovec_t(ptr, end, offset, &iovs[i]);
    if (err != UVWASI_ESUCCESS)
      return err;
    offset += UVWASI_SERDES_SIZE_ciovec_t;
  }

  return UVWASI_ESUCCESS;
}


uvwasi_errno_t uvwasi_serdes_readv_iovec_t(const void* ptr,
                                           size_t end,
                                           size_t offset,
                                           uvwasi_iovec_t* iovs,
                                           uvwasi_size_t iovs_len) {
  uvwasi_errno_t err;
  uvwasi_size_t i;

  for (i = 0; i < iovs_len; i++) {
    err = uvwasi_serdes_read_iovec_t(ptr, end, offset, &iovs[i]);
    if (err != UVWASI_ESUCCESS)
      return err;
    offset += UVWASI_SERDES_SIZE_iovec_t;
  }

  return UVWASI_ESUCCESS;
}


int uvwasi_serdes_check_bounds(size_t offset, size_t end, size_t size) {
  return end > offset && size <= (end - offset);
}


int uvwasi_serdes_check_array_bounds(size_t offset,
                                     size_t end,
                                     size_t size,
                                     size_t count) {
  return end > offset &&
         ((count * size) / size == count) &&
         (count * size <= end - offset);
}
