// Copyright (c) 2015-2017 Nuxi, https://nuxi.nl/
//
// SPDX-License-Identifier: BSD-2-Clause

#ifndef SYS_STAT_STAT_IMPL_H
#define SYS_STAT_STAT_IMPL_H

#include <common/time.h>

#include <sys/stat.h>

#include <assert.h>
#include <wasi/api.h>
#include <stdbool.h>

static_assert(S_ISBLK(S_IFBLK), "Value mismatch");
static_assert(S_ISCHR(S_IFCHR), "Value mismatch");
static_assert(S_ISDIR(S_IFDIR), "Value mismatch");
static_assert(S_ISFIFO(S_IFIFO), "Value mismatch");
static_assert(S_ISLNK(S_IFLNK), "Value mismatch");
static_assert(S_ISREG(S_IFREG), "Value mismatch");
static_assert(S_ISSOCK(S_IFSOCK), "Value mismatch");

static inline void to_public_stat(const __wasi_filestat_t *in,
                                  struct stat *out) {
  // Ensure that we don't truncate any values.
#ifdef __wasilibc_unmodified_upstream
  static_assert(sizeof(in->st_dev) == sizeof(out->st_dev), "Size mismatch");
  static_assert(sizeof(in->st_ino) == sizeof(out->st_ino), "Size mismatch");
  static_assert(sizeof(in->st_filetype) == sizeof(out->__st_filetype),
                "Size mismatch");
#else
  static_assert(sizeof(in->dev) == sizeof(out->st_dev), "Size mismatch");
  static_assert(sizeof(in->ino) == sizeof(out->st_ino), "Size mismatch");
  /*
   * The non-standard __st_filetype field appears to only be used for shared
   * memory, which we don't currently support.
   */
#endif
#ifdef __wasilibc_unmodified_upstream
  static_assert(sizeof(in->st_nlink) == sizeof(out->st_nlink), "Size mismatch");
  static_assert(sizeof(in->st_size) == sizeof(out->st_size), "Size mismatch");
#else
  /* nlink_t is 64-bit on wasm32, following the x32 ABI. */
  static_assert(sizeof(in->nlink) <= sizeof(out->st_nlink), "Size shortfall");
  static_assert(sizeof(in->size) == sizeof(out->st_size), "Size mismatch");
#endif

  *out = (struct stat){
#ifdef __wasilibc_unmodified_upstream
#define COPY_FIELD(field) .field = in->field
      COPY_FIELD(st_dev),
      COPY_FIELD(st_ino),
      .__st_filetype = in->st_filetype,
      COPY_FIELD(st_nlink),
      COPY_FIELD(st_size),
#undef COPY_FIELD
#define COPY_TIMESPEC(field) .field = timestamp_to_timespec(in->field)
      COPY_TIMESPEC(st_atim),
      COPY_TIMESPEC(st_mtim),
      COPY_TIMESPEC(st_ctim),
#undef COPY_TIMESPEC
#else
      .st_dev = in->dev,
      .st_ino = in->ino,
      .st_nlink = in->nlink,
      .st_size = in->size,
      .st_atim = timestamp_to_timespec(in->atim),
      .st_mtim = timestamp_to_timespec(in->mtim),
      .st_ctim = timestamp_to_timespec(in->ctim),
#endif
  };

  // Convert file type to legacy types encoded in st_mode.
#ifdef __wasilibc_unmodified_upstream
  switch (in->st_filetype) {
#else
  switch (in->filetype) {
#endif
    case __WASI_FILETYPE_BLOCK_DEVICE:
      out->st_mode |= S_IFBLK;
      break;
    case __WASI_FILETYPE_CHARACTER_DEVICE:
      out->st_mode |= S_IFCHR;
      break;
    case __WASI_FILETYPE_DIRECTORY:
      out->st_mode |= S_IFDIR;
      break;
    case __WASI_FILETYPE_REGULAR_FILE:
      out->st_mode |= S_IFREG;
      break;
    case __WASI_FILETYPE_SOCKET_DGRAM:
    case __WASI_FILETYPE_SOCKET_STREAM:
      out->st_mode |= S_IFSOCK;
      break;
    case __WASI_FILETYPE_SYMBOLIC_LINK:
      out->st_mode |= S_IFLNK;
      break;
  }
}

static inline bool utimens_get_timestamps(const struct timespec *times,
#ifdef __wasilibc_unmodified_upstream // fstat
                                          __wasi_filestat_t *fs,
                                          __wasi_fsflags_t *flags) {
#else
                                          __wasi_timestamp_t *st_atim,
                                          __wasi_timestamp_t *st_mtim,
                                          __wasi_fstflags_t *flags) {
#endif
  if (times == NULL) {
    // Update both timestamps.
#ifdef __wasilibc_unmodified_upstream // fstat
    *flags = __WASI_FILESTAT_ATIM_NOW | __WASI_FILESTAT_MTIM_NOW;
#else
    *flags = __WASI_FSTFLAGS_ATIM_NOW | __WASI_FSTFLAGS_MTIM_NOW;
#endif
  } else {
    // Set individual timestamps.
    *flags = 0;
    switch (times[0].tv_nsec) {
      case UTIME_NOW:
#ifdef __wasilibc_unmodified_upstream // fstat
        *flags |= __WASI_FILESTAT_ATIM_NOW;
#else
        *flags |= __WASI_FSTFLAGS_ATIM_NOW;
#endif
        break;
      case UTIME_OMIT:
        break;
      default:
#ifdef __wasilibc_unmodified_upstream // fstat
        *flags |= __WASI_FILESTAT_ATIM;
        if (!timespec_to_timestamp_exact(&times[0], &fs->st_atim))
#else
        *flags |= __WASI_FSTFLAGS_ATIM;
        if (!timespec_to_timestamp_exact(&times[0], st_atim))
#endif
          return false;
        break;
    }

    switch (times[1].tv_nsec) {
      case UTIME_NOW:
#ifdef __wasilibc_unmodified_upstream // fstat
        *flags |= __WASI_FILESTAT_MTIM_NOW;
#else
        *flags |= __WASI_FSTFLAGS_MTIM_NOW;
#endif
        break;
      case UTIME_OMIT:
        break;
      default:
#ifdef __wasilibc_unmodified_upstream // fstat
        *flags |= __WASI_FILESTAT_MTIM;
        if (!timespec_to_timestamp_exact(&times[1], &fs->st_mtim))
#else
        *flags |= __WASI_FSTFLAGS_MTIM;
        if (!timespec_to_timestamp_exact(&times[1], st_mtim))
#endif
          return false;
        break;
    }
  }
  return true;
}

#endif
