// Copyright (c) 2023 Yuki Kimoto
// MIT License

#ifdef __linux__
  // Enable X/Open System Interfaces (SUSv4) functions and POSIX.1-2008 standard functions
  #define _XOPEN_SOURCE 700

  // Enable BSD and System V extensions
  #define _DEFAULT_SOURCE
#endif


#include "spvm_native.h"

static const char* FILE_NAME = "Sys/IO/Stat.c";

#include <assert.h>
#include <errno.h>
#include <sys/stat.h>
#include <time.h>

#if defined(_WIN32)
  #include "spvm_sys_windows.h"
  
  # undef S_IFLNK
  #define S_IFLNK 00120000
  
  # undef S_IFSOCK
  #define S_IFSOCK 00140000
  
  // Exactly same as Perl's one in Win32.h
  typedef DWORD Dev_t;
  
  // Exactly same as Perl's one in Win32.h
  typedef unsigned __int64 Ino_t;
  
  // This is different from Perl's ones, but it must be defined well
  typedef uint64_t Off_t;
  
  // Exactly same as Perl's one in Win32.h
  struct w32_stat {
    Dev_t st_dev;
    Ino_t st_ino;
    unsigned short st_mode;
    DWORD st_nlink;
    short st_uid;
    short st_gid;
    Dev_t st_rdev;
    Off_t st_size;
    time_t st_atime;
    time_t st_mtime;
    time_t st_ctime;
  };
  
  typedef struct w32_stat Stat_t;
#else // defined(_WIN32)
  typedef struct stat Stat_t;
#endif

#if defined(_WIN32)

// The output is the same as Perl's file_time_to_epoch in Win32.c
static time_t file_time_to_epoch(FILETIME file_time) {
  SYSTEMTIME system_time;
  struct tm st_tm = {0};
  
  time_t epoch = -1;
  
  if (!FileTimeToSystemTime(&file_time, &system_time)) {
    spvm_sys_windows_win_last_error_to_errno(EINVAL);
    goto END_OF_FUNC;
  }
  
  st_tm.tm_year = system_time.wYear - 1900;
  st_tm.tm_mon = system_time.wMonth - 1;
  st_tm.tm_mday = system_time.wDay;
  st_tm.tm_hour = system_time.wHour;
  st_tm.tm_min = system_time.wMinute;
  st_tm.tm_sec = system_time.wSecond;
  
  epoch = _mkgmtime(&st_tm);
  
  END_OF_FUNC:
  
  return epoch;
}

// The output data is the same as Perl's win32_stat_low in Win32.c.
static int32_t win_fstat_by_handle(SPVM_ENV* env, SPVM_VALUE* stack, HANDLE handle, Stat_t *st_stat) {
  
  int32_t status = -1;
  DWORD type = GetFileType(handle);
  
  switch (type) {
    case FILE_TYPE_DISK: {
      
      BY_HANDLE_FILE_INFORMATION file_info = {0};
      if (GetFileInformationByHandle(handle, &file_info)) {
        
        int32_t reparse_type = 0;
        SPVM_SYS_WINDOWS_REPARSE_DATA_BUFFER linkdata = {0};
        if (DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, NULL, 0, &linkdata, sizeof(linkdata), NULL, NULL)) {
          reparse_type = linkdata.ReparseTag;
        }
        else {
          if (GetLastError() == ERROR_NOT_A_REPARSE_POINT) {
            // Do nothing
          }
          else {
            spvm_sys_windows_win_last_error_to_errno(EINVAL);
            goto END_OF_FUNC;
          }
        }
        
        st_stat->st_dev = file_info.dwVolumeSerialNumber;
        st_stat->st_ino = file_info.nFileIndexHigh;
        st_stat->st_ino <<= 32;
        st_stat->st_ino |= file_info.nFileIndexLow;
        st_stat->st_nlink = file_info.nNumberOfLinks;
        st_stat->st_uid = 0;
        st_stat->st_gid = 0;
        /* ucrt sets this to the drive letter for
           stat(), lets not reproduce that mistake */
        st_stat->st_rdev = 0;
        st_stat->st_size = file_info.nFileSizeHigh;
        st_stat->st_size <<= 32;
        st_stat->st_size |= file_info.nFileSizeLow;
        
        st_stat->st_atime = file_time_to_epoch(file_info.ftLastAccessTime);
        st_stat->st_mtime = file_time_to_epoch(file_info.ftLastWriteTime);
        st_stat->st_ctime = file_time_to_epoch(file_info.ftCreationTime);
        
        if (reparse_type) {
          /* https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/c8e77b37-3909-4fe6-a4ea-2b9d423b1ee4
             describes all of these as WSL only, but the AF_UNIX tag
             is known to be used for AF_UNIX sockets without WSL.
          */
          st_stat->st_mode = 0;
          switch (reparse_type) {
            case IO_REPARSE_TAG_AF_UNIX: {
              st_stat->st_mode = S_IFSOCK;
              break;
            }
            case IO_REPARSE_TAG_LX_FIFO: {
              st_stat->st_mode = S_IFIFO;
              break;
            }
            case IO_REPARSE_TAG_LX_CHR: {
              st_stat->st_mode = S_IFCHR;
              break;
            }
            case IO_REPARSE_TAG_LX_BLK: {
              st_stat->st_mode = S_IFBLK;
              break;
            }
            case IO_REPARSE_TAG_SYMLINK:
            case IO_REPARSE_TAG_MOUNT_POINT:
            {
              break;
            }
            default: {
              /* Is there anything else we can do here? */
              errno = EINVAL;
              goto END_OF_FUNC;
            }
          }
        }
        
        if (st_stat->st_mode == 0) {
          if (file_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
            st_stat->st_mode = S_IFDIR | S_IREAD | S_IEXEC;
            /* duplicate the logic from the end of the old win32_stat() */
            if (!(file_info.dwFileAttributes & FILE_ATTRIBUTE_READONLY)) {
              st_stat->st_mode |= S_IWRITE;
            }
          }
          else {
            st_stat->st_mode = _S_IFREG;
            
            int32_t needed_len = GetFinalPathNameByHandleW(handle, NULL, 0, 0);
            
            if (needed_len == 0) {
              spvm_sys_windows_win_last_error_to_errno(EINVAL);
              goto END_OF_FUNC;
            }
            
            WCHAR* path_w = env->new_memory_block(env, stack, sizeof(WCHAR) * (needed_len + 1));
            
            int32_t len = GetFinalPathNameByHandleW(handle, path_w, needed_len + 1, 0);
            
            if (len) {
              if (len > 4 &&
                (_wcsicmp(path_w + len - 4, L".exe") == 0 ||
                 _wcsicmp(path_w + len - 4, L".bat") == 0 ||
                 _wcsicmp(path_w + len - 4, L".cmd") == 0 ||
                 _wcsicmp(path_w + len - 4, L".com") == 0))
              {
                st_stat->st_mode |= S_IEXEC;
              }
            }
            
            env->free_memory_block(env, stack, path_w);
            
            if (!len) {
              spvm_sys_windows_win_last_error_to_errno(EINVAL);
              goto END_OF_FUNC;
            }
            
            if (!(file_info.dwFileAttributes & FILE_ATTRIBUTE_READONLY)) {
              st_stat->st_mode |= S_IWRITE;
            }
            st_stat->st_mode |= S_IREAD;
          }
        }
      }
      else {
        spvm_sys_windows_win_last_error_to_errno(EINVAL);
        goto END_OF_FUNC;
      }
      break;
    }
    case FILE_TYPE_CHAR:
    case FILE_TYPE_PIPE:
    {
      st_stat->st_mode = (type == FILE_TYPE_CHAR) ? S_IFCHR : S_IFIFO;
      if (handle == GetStdHandle(STD_INPUT_HANDLE) ||
        handle == GetStdHandle(STD_OUTPUT_HANDLE) ||
        handle == GetStdHandle(STD_ERROR_HANDLE)) {
        st_stat->st_mode |= S_IWRITE | S_IREAD;
      }
      break;
    }
    default: {
      errno = EINVAL;
      goto END_OF_FUNC;
    }
  }
  
  /* owner == user == group */
  st_stat->st_mode |= (st_stat->st_mode & 0700) >> 3;
  st_stat->st_mode |= (st_stat->st_mode & 0700) >> 6;
  
  status = 0;
  
  END_OF_FUNC:
  
  return status;
}

static int32_t win_stat(SPVM_ENV* env, SPVM_VALUE* stack, Stat_t *st_stat) {
  
  int32_t error_id = 0;
  
  SPVM_OBJ* obj_path = stack[0].oval;
  
  const char* path = env->get_chars(env, stack, obj_path);
  
  WCHAR* path_w = spvm_sys_windows_utf8_to_win_wchar(env, stack, path, &error_id, __func__, FILE_NAME, __LINE__);
  if (error_id) {
    return error_id;
  }
  
  HANDLE handle =
      CreateFileA(path, FILE_READ_ATTRIBUTES,
                  FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
                  NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
  
  int32_t ReparseTag = 0;
  if (handle == INVALID_HANDLE_VALUE) {
    SPVM_OBJ* obj_resolved_link_text = NULL;
    {
      SPVM_OBJ* obj_link_text = NULL;
      stack[0].oval = obj_path;
      env->call_class_method_by_name(env, stack, "Sys::IO::Windows", "_follow_symlinks_to", 1, &error_id, __func__, FILE_NAME, __LINE__);
      if (error_id) {
        goto END_OF_FUNC;
      }
      obj_resolved_link_text = stack[0].oval;
    }
    const char* resolved_link_text = env->get_chars(env, stack, obj_resolved_link_text);
    
    WCHAR* resolved_link_text_w = spvm_sys_windows_utf8_to_win_wchar(env, stack, resolved_link_text, &error_id, __func__, FILE_NAME, __LINE__);
    if (error_id) {
      return error_id;
    }
    
    handle = spvm_sys_windows_CreateFileW_reparse_point_for_read(resolved_link_text_w);
    
    if (handle == INVALID_HANDLE_VALUE) {
      spvm_sys_windows_win_last_error_to_errno(EINVAL);
      error_id = SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_SYSTEM_CLASS;
      goto END_OF_FUNC;
    }
  }
  
  int32_t result = win_fstat_by_handle(env, stack, handle, st_stat);
  
  if (result == -1) {
    error_id = SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_SYSTEM_CLASS;
    goto END_OF_FUNC;
  }
  
  END_OF_FUNC:
  
  if (!(handle == INVALID_HANDLE_VALUE)) {
    CloseHandle(handle);
  }
  
  if (error_id) {
    if (errno) {
      env->die(env, stack, "[System Error]win_stat() failed(%d: %s). $path='%s'.", __func__, FILE_NAME, __LINE__, errno, env->strerror_nolen(env, stack, errno), path);
    }
    
    return error_id;
  }
  
  return 0;
}

static int32_t win_lstat(SPVM_ENV* env, SPVM_VALUE* stack, Stat_t *st_stat) {   
  
  int32_t error_id = 0;
  
  SPVM_OBJ* obj_path = stack[0].oval;
  
  const char* path = env->get_chars(env, stack, obj_path);
  
  WCHAR* path_w = spvm_sys_windows_utf8_to_win_wchar(env, stack, path, &error_id, __func__, FILE_NAME, __LINE__);
  if (error_id) {
    goto END_OF_FUNC;
  }
  
  HANDLE handle = spvm_sys_windows_CreateFileW_reparse_point_for_read(path_w);
  if (handle == INVALID_HANDLE_VALUE) {
    spvm_sys_windows_win_last_error_to_errno(EINVAL);
    error_id = SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_SYSTEM_CLASS;
    goto END_OF_FUNC;
  }
  
  int32_t result = win_fstat_by_handle(env, stack, handle, st_stat);
  
  if (result == -1) {
    error_id = SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_SYSTEM_CLASS;
    goto END_OF_FUNC;
  }
  
  int32_t is_sym = spvm_sys_windows_is_symlink_by_handle(handle);
  
  if (is_sym) {
    SPVM_OBJ* obj_link_text = NULL;
    stack[0].oval = obj_path;
    env->call_class_method_by_name(env, stack, "Sys::IO::Windows", "win_readlink", 1, &error_id, __func__, FILE_NAME, __LINE__);
    if (error_id) {
      goto END_OF_FUNC;
    }
    obj_link_text = stack[0].oval;
    
    int32_t link_text_length = env->length(env, stack, obj_link_text);
    
    st_stat->st_mode = (st_stat->st_mode & ~S_IFMT) | S_IFLNK;
    st_stat->st_size = link_text_length;
  }
  
  END_OF_FUNC:
  
  if (!(handle == INVALID_HANDLE_VALUE)) {
    CloseHandle(handle);
  }
  
  if (error_id) {
    if (errno) {
      env->die(env, stack, "[System Error]win_lstat() failed(%d: %s). $path='%s'.", __func__, FILE_NAME, __LINE__, errno, env->strerror_nolen(env, stack, errno), path);
    }
    
    return error_id;
  }
  
  return 0;
}

#endif // defined(_WIN32)

int32_t SPVM__Sys__IO__Stat__new(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  Stat_t* st_stat = env->new_memory_block(env, stack, sizeof(Stat_t));
  
  SPVM_OBJ* obj_stat = env->new_pointer_object_by_name(env, stack, "Sys::IO::Stat", st_stat, &error_id, __func__, FILE_NAME, __LINE__);
  if (error_id) { return error_id; }
  
  stack[0].oval = obj_stat;
  
  return 0;
}

int32_t SPVM__Sys__IO__Stat__DESTROY(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  SPVM_OBJ* obj_stat = stack[0].oval;
  
  Stat_t* st_stat = env->get_pointer(env, stack, obj_stat);
  
  assert(st_stat);
  
  env->free_memory_block(env, stack, st_stat);

  env->set_pointer(env, stack, obj_stat, NULL);
  
  return 0;
}

int32_t SPVM__Sys__IO__Stat__stat(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  SPVM_OBJ* obj_path = stack[0].oval;
  
  SPVM_OBJ* obj_stat = stack[1].oval;
  
  if (!obj_path) {
    return env->die(env, stack, "The path $path must be defined.", __func__, FILE_NAME, __LINE__);
  }
  const char* path = env->get_chars(env, stack, obj_path);
  
  if (!obj_stat) {
    return env->die(env, stack, "The stat object $stat must be defined.", __func__, FILE_NAME, __LINE__);
  }
  
  Stat_t* st_stat = env->get_pointer(env, stack, obj_stat);
  
#if defined(_WIN32)
  stack[0].oval = obj_path;
  error_id = win_stat(env, stack, st_stat);
  int32_t status = error_id ? -1 : 0;
#else
  int32_t status = stat(path, st_stat);
#endif
  
  if (status == -1) {
    const char* path = env->get_chars(env, stack, obj_path);
    env->die(env, stack, "[System Error]stat() failed(%d: %s). $path='%s'.", __func__, FILE_NAME, __LINE__, errno, env->strerror_nolen(env, stack, errno), path);
    return SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_SYSTEM_CLASS;
  }
  
  stack[0].ival = status;
  
  return 0;
}

int32_t SPVM__Sys__IO__Stat__lstat(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  SPVM_OBJ* obj_path = stack[0].oval;
  
  SPVM_OBJ* obj_lstat = stack[1].oval;
  
  if (!obj_path) {
    return env->die(env, stack, "The path $path must be defined.", __func__, FILE_NAME, __LINE__);
  }
  const char* path = env->get_chars(env, stack, obj_path);
  
  if (!obj_lstat) {
    return env->die(env, stack, "The stat object $lstat must be defined.", __func__, FILE_NAME, __LINE__);
  }
  
  Stat_t* st_stat = env->get_pointer(env, stack, obj_lstat);
  
#if defined(_WIN32)
  stack[0].oval = obj_path;
  error_id = win_lstat(env, stack, st_stat);
  int32_t status = error_id ? -1 : 0;
#else
  int32_t status = lstat(path, st_stat);
#endif

  if (status == -1) {
    const char* path = env->get_chars(env, stack, obj_path);
    env->die(env, stack, "[System Error]lstat() failed(%d: %s). $path='%s'.", __func__, FILE_NAME, __LINE__, errno, env->strerror_nolen(env, stack, errno), path);
    return SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_SYSTEM_CLASS;
  }
  
  stack[0].ival = status;
  
  return 0;
}

int32_t SPVM__Sys__IO__Stat__fstat(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  int32_t fd = stack[0].ival;
  
  SPVM_OBJ* obj_stat = stack[1].oval;
  
  if (!obj_stat) {
    return env->die(env, stack, "The stat object $stat must be defined.", __func__, FILE_NAME, __LINE__);
  }
  
  Stat_t* st_stat = env->get_pointer(env, stack, obj_stat);
  
  
#if defined(_WIN32)
  HANDLE handle = (HANDLE)_get_osfhandle(fd);
  int32_t status = win_fstat_by_handle(env, stack, handle, st_stat);
#else
  int32_t status = fstat(fd, st_stat);
#endif

  if (status == -1) {
    env->die(env, stack, "[System Error]fstat() failed(%d: %s).", __func__, FILE_NAME, __LINE__, errno, env->strerror_nolen(env, stack, errno));
    return SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_SYSTEM_CLASS;
  }
  
  stack[0].ival = status;
  
  return 0;
}

int32_t SPVM__Sys__IO__Stat__st_dev(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  SPVM_OBJ* obj_stat = stack[0].oval;
  
  Stat_t* st_stat = env->get_pointer(env, stack, obj_stat);
  
  stack[0].lval = st_stat->st_dev;
  
  return 0;
}

int32_t SPVM__Sys__IO__Stat__st_ino(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  SPVM_OBJ* obj_stat = stack[0].oval;
  
  Stat_t* st_stat = env->get_pointer(env, stack, obj_stat);
  
  stack[0].lval = st_stat->st_ino;
  
  return 0;
}

int32_t SPVM__Sys__IO__Stat__st_mode(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  SPVM_OBJ* obj_stat = stack[0].oval;
  
  Stat_t* st_stat = env->get_pointer(env, stack, obj_stat);
  
  stack[0].ival = st_stat->st_mode;
  
  return 0;
}

int32_t SPVM__Sys__IO__Stat__st_nlink(SPVM_ENV* env, SPVM_VALUE* stack) {
  SPVM_OBJ* obj_stat = stack[0].oval;
  
  Stat_t* st_stat = env->get_pointer(env, stack, obj_stat);
  
  stack[0].lval = st_stat->st_nlink;
  
  return 0;
}

int32_t SPVM__Sys__IO__Stat__st_size(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  SPVM_OBJ* obj_stat = stack[0].oval;
  
  Stat_t* st_stat = env->get_pointer(env, stack, obj_stat);
  
  stack[0].lval = st_stat->st_size;
  
  return 0;
}

int32_t SPVM__Sys__IO__Stat__st_blksize(SPVM_ENV* env, SPVM_VALUE* stack) {
#if defined(_WIN32)
  env->die(env, stack, "Sys::IO::Stat#st_blksize method is not supported in this system(defined(_WIN32)).", __func__, FILE_NAME, __LINE__);
  return SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_NOT_SUPPORTED_CLASS;
#else
  SPVM_OBJ* obj_stat = stack[0].oval;
  
  Stat_t* st_stat = env->get_pointer(env, stack, obj_stat);
  
  stack[0].lval = st_stat->st_blksize;
  
  return 0;
#endif
}

int32_t SPVM__Sys__IO__Stat__st_blocks(SPVM_ENV* env, SPVM_VALUE* stack) {
#if defined(_WIN32)
  env->die(env, stack, "Sys::IO::Stat#st_blocks method is not supported in this system(defined(_WIN32)).", __func__, FILE_NAME, __LINE__);
  return SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_NOT_SUPPORTED_CLASS;
#else
  SPVM_OBJ* obj_stat = stack[0].oval;
  
  Stat_t* st_stat = env->get_pointer(env, stack, obj_stat);
  
  stack[0].lval = st_stat->st_blocks;
  
  return 0;
#endif
}

int32_t SPVM__Sys__IO__Stat__st_uid(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  SPVM_OBJ* obj_stat = stack[0].oval;
  
  Stat_t* st_stat = env->get_pointer(env, stack, obj_stat);
  
  stack[0].ival = st_stat->st_uid;
  
  return 0;
}

int32_t SPVM__Sys__IO__Stat__st_gid(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  SPVM_OBJ* obj_stat = stack[0].oval;
  
  Stat_t* st_stat = env->get_pointer(env, stack, obj_stat);
  
  stack[0].ival = st_stat->st_gid;
  
  return 0;
}

int32_t SPVM__Sys__IO__Stat__st_rdev(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  SPVM_OBJ* obj_stat = stack[0].oval;
  
  Stat_t* st_stat = env->get_pointer(env, stack, obj_stat);
  
  stack[0].lval = st_stat->st_rdev;
  
  return 0;
}

int32_t SPVM__Sys__IO__Stat__st_atime(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  SPVM_OBJ* obj_stat = stack[0].oval;
  
  Stat_t* st_stat = env->get_pointer(env, stack, obj_stat);
  
#ifdef __APPLE__
  stack[0].lval = st_stat->st_atimespec.tv_sec;
#else
  stack[0].lval = st_stat->st_atime;
#endif

  return 0;
}

int32_t SPVM__Sys__IO__Stat__st_mtime(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  SPVM_OBJ* obj_stat = stack[0].oval;
  
  Stat_t* st_stat = env->get_pointer(env, stack, obj_stat);
  
#ifdef __APPLE__
  stack[0].lval = st_stat->st_mtimespec.tv_sec;
#else
  stack[0].lval = st_stat->st_mtime;
#endif
  
  return 0;
}

int32_t SPVM__Sys__IO__Stat__st_ctime(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  SPVM_OBJ* obj_stat = stack[0].oval;
  
  Stat_t* st_stat = env->get_pointer(env, stack, obj_stat);
  
#ifdef __APPLE__
  stack[0].lval = st_stat->st_ctimespec.tv_sec;
#else
  stack[0].lval = st_stat->st_ctime;
#endif
  
  return 0;
}

int32_t SPVM__Sys__IO__Stat__st_atim_tv_nsec(SPVM_ENV* env, SPVM_VALUE* stack) {
#if defined(_WIN32)
  env->die(env, stack, "Sys::IO::Stat#st_atim_tv_nsec method is not supported in this system(defined(_WIN32)).", __func__, FILE_NAME, __LINE__);
  return SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_NOT_SUPPORTED_CLASS;
#elif defined(__solaris) || defined(__sun)
  env->die(env, stack, "Sys::IO::Stat#st_atim_tv_nsec method is not supported in this system(__solaris or __sun).", __func__, FILE_NAME, __LINE__);
  return SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_NOT_SUPPORTED_CLASS;
#else
  SPVM_OBJ* obj_stat = stack[0].oval;
  
  Stat_t* st_stat = env->get_pointer(env, stack, obj_stat);

#ifdef __APPLE__
  stack[0].lval = st_stat->st_atimespec.tv_nsec;
#else
  stack[0].lval = st_stat->st_atim.tv_nsec;
#endif

  return 0;
#endif
}

int32_t SPVM__Sys__IO__Stat__st_mtim_tv_nsec(SPVM_ENV* env, SPVM_VALUE* stack) {
#if defined(_WIN32)
  env->die(env, stack, "Sys::IO::Stat#The st_mtim_tv_nsec method is not supported in this system(defined(_WIN32)).", __func__, FILE_NAME, __LINE__);
  return SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_NOT_SUPPORTED_CLASS;
#elif defined(__solaris) || defined(__sun)
  env->die(env, stack, "Sys::IO::Stat#st_mtim_tv_nsec method is not supported in this system(__solaris or __sun).", __func__, FILE_NAME, __LINE__);
  return SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_NOT_SUPPORTED_CLASS;
#else
  SPVM_OBJ* obj_stat = stack[0].oval;
  
  Stat_t* st_stat = env->get_pointer(env, stack, obj_stat);
  
#ifdef __APPLE__
  stack[0].lval = st_stat->st_mtimespec.tv_nsec;
#else
  stack[0].lval = st_stat->st_mtim.tv_nsec;
#endif
  
  return 0;
#endif
}

int32_t SPVM__Sys__IO__Stat__st_ctim_tv_nsec(SPVM_ENV* env, SPVM_VALUE* stack) {
#if defined(_WIN32)
  env->die(env, stack, "Sys::IO::Stat#st_ctim_tv_nsec method is not supported in this system(defined(_WIN32)).", __func__, FILE_NAME, __LINE__);
  return SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_NOT_SUPPORTED_CLASS;
#elif defined(__solaris) || defined(__sun)
  env->die(env, stack, "Sys::IO::Stat#st_ctim_tv_nsec method is not supported in this system(__solaris or __sun).", __func__, FILE_NAME, __LINE__);
  return SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_NOT_SUPPORTED_CLASS;
#else
  SPVM_OBJ* obj_stat = stack[0].oval;
  
  Stat_t* st_stat = env->get_pointer(env, stack, obj_stat);
  
#ifdef __APPLE__
  stack[0].lval = st_stat->st_ctimespec.tv_nsec;
#else
  stack[0].lval = st_stat->st_ctim.tv_nsec;
#endif
  
  return 0;
#endif
}

