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

// Windows 8.1+
#define _WIN32_WINNT 0x0603

#include "spvm_native.h"
#include "spvm_socket_util.h"

#include <errno.h>
#include <assert.h>

#if defined(_WIN32)
  #include <winsock2.h>
  #include <mstcpip.h>
#else
  #include <sys/un.h>
#endif

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

int32_t SPVM__Sys__Socket__htonl(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t hostlong = stack[0].ival;
  
  int32_t netlong = htonl(hostlong);
  
  stack[0].ival = netlong;
  
  return 0;
}

int32_t SPVM__Sys__Socket__htons(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int16_t hostshort = stack[0].sval;
  
  int16_t netshort = htons(hostshort);
  
  stack[0].sval = netshort;
  
  return 0;
}

int32_t SPVM__Sys__Socket__ntohl(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t netlong = stack[0].ival;
  
  int32_t hostlong = ntohl(netlong);
  
  stack[0].ival = hostlong;
  
  return 0;
}

int32_t SPVM__Sys__Socket__ntohs(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int16_t netshort = stack[0].sval;
  
  int16_t hostshort = htons(netshort);
  
  stack[0].sval = hostshort;
  
  return 0;
}

int32_t SPVM__Sys__Socket__inet_aton(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  int32_t InvalidNetworkAddress = env->get_basic_type_id_by_name(env, stack, "Sys::Socket::Error::InetInvalidNetworkAddress", &error_id, __func__, FILE_NAME, __LINE__);
  if (error_id) { return error_id; }
  
  void* obj_cp = stack[0].oval;
  
  void* obj_inp = stack[1].oval;
  
  if (!obj_cp) {
    return env->die(env, stack, "The address string $cp must be defined.", __func__, FILE_NAME, __LINE__);
  }
  
  const char* cp = env->get_chars(env, stack, obj_cp);
  
  if (!obj_inp) {
    return env->die(env, stack, "The address data structure $inp must be defined.", __func__, FILE_NAME, __LINE__);
  }
  
  struct in_addr* st_in_addr = env->get_pointer(env, stack, obj_inp);
  
#if defined(_WIN32)
  int32_t status = inet_pton(AF_INET, cp, st_in_addr);
#else
  int32_t status = inet_aton(cp, st_in_addr);
#endif

  if (status == 0) {
    env->die(env, stack, "The got address is not a valid network address.", __func__, FILE_NAME, __LINE__);
    return InvalidNetworkAddress;
  }
  else if (status == -1) {
    env->die(env, stack, "[System Error]inet_aton() failed(%d: %s).", __func__, FILE_NAME, __LINE__, spvm_socket_errno(), spvm_socket_strerror(env, stack, spvm_socket_errno(), 0));
    return SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_SYSTEM_CLASS;
  }
  
  stack[0].ival = status;
  
  return 0;
}

int32_t SPVM__Sys__Socket__inet_ntoa(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  void* obj_in = stack[0].oval;
  
  if (!obj_in) {
    return env->die(env, stack, "The address data structure $in must be defined.", __func__, FILE_NAME, __LINE__);
  }
  
  struct in_addr* in = env->get_pointer(env, stack, obj_in);
  
  char* output_address = inet_ntoa(*in);
  
  if (!output_address) {
    env->die(env, stack, "[System Error]inet_ntoa() failed(%d: %s).", __func__, FILE_NAME, __LINE__, spvm_socket_errno(), spvm_socket_strerror(env, stack, spvm_socket_errno(), 0));
    return SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_SYSTEM_CLASS;
  }
  
  void* obj_output_address;
  if (output_address) {
    obj_output_address = env->new_string(env, stack, output_address, strlen(output_address));
  }
  else {
    assert(0);
  }
  
  stack[0].oval = obj_output_address;
  
  return 0;
}

int32_t SPVM__Sys__Socket__inet_pton(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  int32_t InvalidNetworkAddress = env->get_basic_type_id_by_name(env, stack, "Sys::Socket::Error::InetInvalidNetworkAddress", &error_id, __func__, FILE_NAME, __LINE__);
  if (error_id) { return error_id; }
  
  int32_t In_addr = env->get_basic_type_id(env, stack, "Sys::Socket::In_addr");
  if (error_id) { return error_id; }
  
  int32_t In6_addr = env->get_basic_type_id(env, stack, "Sys::Socket::In6_addr");
  if (error_id) { return error_id; }
  
  int32_t af = stack[0].ival;
  
  void* obj_src = stack[1].oval;
  
  void* obj_dst = stack[2].oval;
  
  if (!(af == AF_INET || af == AF_INET6)) {
    return env->die(env, stack, "The address family $af must be AF_INET or AF_INET6.", __func__, FILE_NAME, __LINE__);
  }
  
  if (!obj_src) {
    return env->die(env, stack, "The address string $src must be defined.", __func__, FILE_NAME, __LINE__);
  }
  
  const char* src = env->get_chars(env, stack, obj_src);
  
  if (!obj_dst) {
    return env->die(env, stack, "The address data structure $dst must be defined.", __func__, FILE_NAME, __LINE__);
  }
  
  if (af == AF_INET) {
    if (!env->is_type_by_name(env, stack, obj_dst, "Sys::Socket::In_addr", 0)) {
      return env->die(env, stack, "The address data structure $dst must be the Sys::Socket::In_addr class.", __func__, FILE_NAME, __LINE__);
    }
  }
  else if (af == AF_INET6) {
    if (!env->is_type_by_name(env, stack, obj_dst, "Sys::Socket::In6_addr", 0)) {
      return env->die(env, stack, "The address data structure $dst must be the Sys::Socket::In6_addr class.", __func__, FILE_NAME, __LINE__);
    }
  }
  else {
    return env->die(env, stack, "The type of The address data structure $dst is invalid.", __func__, FILE_NAME, __LINE__);
  }
  
  void* dst = env->get_pointer(env, stack, obj_dst);
  
  int32_t status = inet_pton(af, src, dst);
  
  if (status == 0) {
    env->die(env, stack, "The got address is not a valid network address.", __func__, FILE_NAME, __LINE__);
    return InvalidNetworkAddress;
  }
  else if (status == -1) {
    env->die(env, stack, "[System Error]inet_pton() failed(%d: %s).", __func__, FILE_NAME, __LINE__, spvm_socket_errno(), spvm_socket_strerror(env, stack, spvm_socket_errno(), 0));
    return SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_SYSTEM_CLASS;
  }
  
  stack[0].ival = status;
  
  return 0;
}

int32_t SPVM__Sys__Socket__inet_ntop(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t af = stack[0].ival;
  
  void* obj_src = stack[1].oval;
  
  void* obj_dst = stack[2].oval;
  
  int32_t size = stack[3].ival;
  
  if (!(af == AF_INET || af == AF_INET6)) {
    return env->die(env, stack, "The address family $af must be AF_INET or AF_INET6.", __func__, FILE_NAME, __LINE__);
  }
  
  if (!obj_src) {
    return env->die(env, stack, "The address data structure $src must be defined.", __func__, FILE_NAME, __LINE__);
  }
  void* src = env->get_pointer(env, stack, obj_src);
  
  if (!obj_dst) {
    return env->die(env, stack, "The address string $dst must be defined.", __func__, FILE_NAME, __LINE__);
  }
  char* dst = (char*)env->get_chars(env, stack, obj_dst);
  
  const char* dst_ret = inet_ntop(af, src, dst, size);
  
  if (!dst_ret) {
    env->die(env, stack, "[System Error]inet_ntop() failed(%d: %s).", __func__, FILE_NAME, __LINE__, spvm_socket_errno(), spvm_socket_strerror(env, stack, spvm_socket_errno(), 0));
    return SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_SYSTEM_CLASS;
  }
  
  stack[0].oval = obj_dst;
  
  return 0;
}

int32_t SPVM__Sys__Socket__socket(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t domain = stack[0].ival;
  
  int32_t type = stack[1].ival;
  
  int32_t protocol = stack[2].ival;
  
  int32_t sockfd = socket(domain, type, protocol);
  
  if (sockfd == -1) {
    env->die(env, stack, "[System Error]socket() failed(%d: %s).", __func__, FILE_NAME, __LINE__, spvm_socket_errno(), spvm_socket_strerror(env, stack, spvm_socket_errno(), 0));
    return SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_SYSTEM_CLASS;
  }
  
  stack[0].ival = sockfd;
  
  return 0;
}

int32_t SPVM__Sys__Socket__connect(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t sockfd = stack[0].ival;
  
  void* obj_addr = stack[1].oval;
  
  if (!obj_addr) {
    return env->die(env, stack, "The socket address $addr must be defined.", __func__, FILE_NAME, __LINE__);
  }
  
  const struct sockaddr* addr = env->get_pointer(env, stack, obj_addr);
  
  int32_t addrlen = stack[2].ival;
  
  int32_t status = connect(sockfd, addr, addrlen);
  
  if (status == -1) {
    env->die(env, stack, "[System Error]connect() failed(%d: %s).", __func__, FILE_NAME, __LINE__, spvm_socket_errno(), spvm_socket_strerror(env, stack, spvm_socket_errno(), 0));
    return SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_SYSTEM_CLASS;
  }
  
  stack[0].ival = status;
  
  return 0;
}

int32_t SPVM__Sys__Socket__bind(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t sockfd = stack[0].ival;
  
  void* obj_addr = stack[1].oval;
  
  if (!obj_addr) {
    return env->die(env, stack, "The socket address $addr must be defined.", __func__, FILE_NAME, __LINE__);
  }
  
  const struct sockaddr* addr = env->get_pointer(env, stack, obj_addr);
  
  int32_t addrlen = stack[2].ival;
  
  int32_t status = bind(sockfd, addr, addrlen);
  
  if (status == -1) {
    env->die(env, stack, "[System Error]bind() failed(%d: %s).", __func__, FILE_NAME, __LINE__, spvm_socket_errno(), spvm_socket_strerror(env, stack, spvm_socket_errno(), 0));
    return SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_SYSTEM_CLASS;
  }
  
  stack[0].ival = status;
  
  return 0;
}

int32_t SPVM__Sys__Socket__accept(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t sockfd = stack[0].ival;
  
  void* obj_addr = stack[1].oval;
  
  int32_t* addrlen_ref = stack[2].iref;
  
  if (!obj_addr) {
    return env->die(env, stack, "The client address $addr must be defined.", __func__, FILE_NAME, __LINE__);
  }
  
  struct sockaddr* addr = env->get_pointer(env, stack, obj_addr);
  
  if (!addrlen_ref) {
    return env->die(env, stack, "The reference of the output address length $addrlen_ref must be defined.", __func__, FILE_NAME, __LINE__);
  }
  
  socklen_t sl_addrlen = *addrlen_ref;
  
  int32_t client_fd = accept(sockfd, addr, &sl_addrlen);
  
  if (client_fd == -1) {
    env->die(env, stack, "[System Error]accept() failed(%d: %s).", __func__, FILE_NAME, __LINE__, spvm_socket_errno(), spvm_socket_strerror(env, stack, spvm_socket_errno(), 0));
    return SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_SYSTEM_CLASS;
  }
  
  *addrlen_ref = sl_addrlen;
  
  stack[0].ival = client_fd;
  
  return 0;
}

int32_t SPVM__Sys__Socket__listen(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t sockfd = stack[0].ival;
  
  int32_t backlog = stack[1].ival;
  
  int32_t status = listen(sockfd, backlog);
  
  if (status == -1) {
    env->die(env, stack, "[System Error]listen() failed(%d: %s).", __func__, FILE_NAME, __LINE__, spvm_socket_errno(), spvm_socket_strerror(env, stack, spvm_socket_errno(), 0));
    return SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_SYSTEM_CLASS;
  }
  
  stack[0].ival = status;
  
  return 0;
}

int32_t SPVM__Sys__Socket__shutdown(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t sockfd = stack[0].ival;
  
  int32_t how = stack[1].ival;
  
  int32_t status = shutdown(sockfd, how);
  
  if (status == -1) {
    env->die(env, stack, "[System Error]shutdown() failed(%d: %s).", __func__, FILE_NAME, __LINE__, spvm_socket_errno(), spvm_socket_strerror(env, stack, spvm_socket_errno(), 0));
    return SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_SYSTEM_CLASS;
  }
  
  stack[0].ival = status;
  
  return 0;
}

int32_t SPVM__Sys__Socket__closesocket(SPVM_ENV* env, SPVM_VALUE* stack) {
#if !defined(_WIN32)
  env->die(env, stack, "Sys::Socket#closesocket 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
  int32_t s = stack[0].ival;
  
  int32_t status = closesocket(s);
  
  if (!(status == 0)) {
    env->die(env, stack, "[System Error]close() failed(%d: %s).", __func__, FILE_NAME, __LINE__, spvm_socket_errno(), spvm_socket_strerror(env, stack, spvm_socket_errno(), 0));
    return SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_SYSTEM_CLASS;
  }
  
  stack[0].ival = status;
  
  return 0;
#endif
}

int32_t SPVM__Sys__Socket__recv(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t sockfd = stack[0].ival;
  
  void* obj_buf = stack[1].oval;
  
  if (!obj_buf) {
    return env->die(env, stack, "The buffer $buf must be defined.", __func__, FILE_NAME, __LINE__);
  }
  
  char* buf = (char*)env->get_chars(env, stack, obj_buf);
  int32_t buf_length = env->length(env, stack, obj_buf);
  
  int32_t len = stack[2].ival;
  
  int32_t flags = stack[3].ival;
  
  int32_t buf_offset = stack[4].ival;
  if (!(len <= buf_length - buf_offset)) {
    return env->die(env, stack, "The data length $len must be less than the length of the buffer $buf minus the buffer offset $buf_offset.", __func__, FILE_NAME, __LINE__);
  }
  
  int32_t read_length = recv(sockfd, buf + buf_offset, len, flags);
  
  if (read_length == -1) {
    env->die(env, stack, "[System Error]recv() failed(%d: %s).", __func__, FILE_NAME, __LINE__, spvm_socket_errno(), spvm_socket_strerror(env, stack, spvm_socket_errno(), 0));
    return SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_SYSTEM_CLASS;
  }
  
  stack[0].ival = read_length;
  
  return 0;
}

int32_t SPVM__Sys__Socket__recvfrom(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t sockfd = stack[0].ival;
  
  void* obj_buf = stack[1].oval;
  
  int32_t len = stack[2].ival;
  
  int32_t flags = stack[3].ival;
  
  void* obj_src_addr = stack[4].oval;
  
  int32_t* addrlen_ref = stack[5].iref;
  
  int32_t buf_offset = stack[6].ival;
  
  if (!obj_buf) {
    return env->die(env, stack, "The buffer $buf must be defined.", __func__, FILE_NAME, __LINE__);
  }
  
  char* buf = (char*)env->get_chars(env, stack, obj_buf);
  int32_t buf_length = env->length(env, stack, obj_buf);
  
  struct sockaddr* src_addr = NULL;
  if (obj_src_addr) {
    src_addr = env->get_pointer(env, stack, obj_src_addr);
  }
  
  if (!addrlen_ref) {
    return env->die(env, stack, "The reference of the output address length $addrlen_ref must be defined.", __func__, FILE_NAME, __LINE__);
  }
  
  if (!(len <= buf_length - buf_offset)) {
    return env->die(env, stack, "The data length $len must be less than the length of the buffer $buf minus the buffer offset $buf_offset.", __func__, FILE_NAME, __LINE__);
  }
  
  socklen_t addrlen_ref_tmp = -1;
  int32_t read_length = recvfrom(sockfd, buf + buf_offset, len, flags, src_addr, &addrlen_ref_tmp);
  
  if (read_length == -1) {
    env->die(env, stack, "[System Error]recvfrom() failed(%d: %s).", __func__, FILE_NAME, __LINE__, spvm_socket_errno(), spvm_socket_strerror(env, stack, spvm_socket_errno(), 0));
    return SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_SYSTEM_CLASS;
  }
  
  *addrlen_ref = addrlen_ref_tmp;
  
  stack[0].ival = read_length;
  
  return 0;
}

int32_t SPVM__Sys__Socket__send(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t sockfd = stack[0].ival;
  
  void* obj_buf = stack[1].oval;
  
  int32_t len = stack[2].ival;
  
  int32_t flags = stack[3].ival;
  
  int32_t buf_offset = stack[4].ival;
  if (!obj_buf) {
    return env->die(env, stack, "The buffer $buf must be defined.", __func__, FILE_NAME, __LINE__);
  }
  
  const char* buf = env->get_chars(env, stack, obj_buf);
  int32_t buf_length = env->length(env, stack, obj_buf);
  
  if (!(len <= buf_length - buf_offset)) {
    return env->die(env, stack, "The data length $len must be less than the length of the buffer $buf minus the buffer offset $buf_offset.", __func__, FILE_NAME, __LINE__);
  }
  
  int32_t bytes_length = send(sockfd, buf + buf_offset, len, flags);
  
  if (bytes_length == -1) {
    env->die(env, stack, "[System Error]send() failed(%d: %s).", __func__, FILE_NAME, __LINE__, spvm_socket_errno(), spvm_socket_strerror(env, stack, spvm_socket_errno(), 0));
    return SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_SYSTEM_CLASS;
  }
  
  stack[0].ival = bytes_length;
  
  return 0;
}

int32_t SPVM__Sys__Socket__sendto(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t sockfd = stack[0].ival;
  
  void* obj_buf = stack[1].oval;
  
  int32_t len = stack[2].ival;
  
  int32_t flags = stack[3].ival;
  
  void* obj_addr = stack[4].oval;
  
  if (!obj_buf) {
    return env->die(env, stack, "The buffer $buf must be defined.", __func__, FILE_NAME, __LINE__);
  }
  
  const char* buf = env->get_chars(env, stack, obj_buf);
  int32_t buf_length = env->length(env, stack, obj_buf);
  
  const struct sockaddr* addr = NULL;
  
  if (obj_addr) {
    addr = env->get_pointer(env, stack, obj_addr);
  }
  
  int32_t addrlen = stack[5].ival;
  
  int32_t buf_offset = stack[6].ival;
  if (!(len <= buf_length - buf_offset)) {
    return env->die(env, stack, "The data length $len must be less than the length of the buffer $buf minus the buffer offset $buf_offset.", __func__, FILE_NAME, __LINE__);
  }
  
  int32_t bytes_length = sendto(sockfd, buf + buf_offset, len, flags, addr, addrlen);
  
  if (bytes_length == -1) {
    env->die(env, stack, "[System Error]sendto() failed(%d: %s).", __func__, FILE_NAME, __LINE__, spvm_socket_errno(), spvm_socket_strerror(env, stack, spvm_socket_errno(), 0));
    return SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_SYSTEM_CLASS;
  }
  
  stack[0].ival = bytes_length;
  
  return 0;
}

int32_t SPVM__Sys__Socket__getpeername(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t sockfd = stack[0].ival;
  
  void* obj_addr = stack[1].oval;
  
  int32_t* addrlen_ref = stack[2].iref;
  
  if (!obj_addr) {
    return env->die(env, stack, "The socket address $addr must be defined.", __func__, FILE_NAME, __LINE__);
  }
  
  struct sockaddr* addr = env->get_pointer(env, stack, obj_addr);
  
  if (!addrlen_ref) {
    return env->die(env, stack, "The reference of the output address length $addrlen_ref must be defined.", __func__, FILE_NAME, __LINE__);
  }
  
  socklen_t sl_addrlen = *addrlen_ref;
  
  int32_t status = getpeername(sockfd, addr, &sl_addrlen);
  
  if (status == -1) {
    env->die(env, stack, "[System Error]getpeername() failed(%d: %s).", __func__, FILE_NAME, __LINE__, spvm_socket_errno(), spvm_socket_strerror(env, stack, spvm_socket_errno(), 0));
    return SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_SYSTEM_CLASS;
  }
  
  *addrlen_ref = sl_addrlen;
  
  stack[0].ival = status;
  
  return 0;
}

int32_t SPVM__Sys__Socket__getsockname(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t sockfd = stack[0].ival;
  
  void* obj_addr = stack[1].oval;
  
  int32_t* addrlen_ref = stack[2].iref;
  
  if (!obj_addr) {
    return env->die(env, stack, "The socket address $addr must be defined.", __func__, FILE_NAME, __LINE__);
  }
  
  struct sockaddr* addr = env->get_pointer(env, stack, obj_addr);
  
  if (!addrlen_ref) {
    return env->die(env, stack, "The reference of the output address length $addrlen_ref must be defined.", __func__, FILE_NAME, __LINE__);
  }
  
  socklen_t sl_addrlen = *addrlen_ref;
  
  int32_t status = getsockname(sockfd, addr, &sl_addrlen);
  
  if (status == -1) {
    env->die(env, stack, "[System Error]getsockname() failed(%d: %s).", __func__, FILE_NAME, __LINE__, spvm_socket_errno(), spvm_socket_strerror(env, stack, spvm_socket_errno(), 0));
    return SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_SYSTEM_CLASS;
  }
  
  *addrlen_ref = sl_addrlen;
  
  stack[0].ival = status;
  
  return 0;
}

int32_t SPVM__Sys__Socket__getsockopt(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t sockfd = stack[0].ival;
  
  int32_t level = stack[1].ival;
  
  int32_t optname = stack[2].ival;
  
  void* obj_optval = stack[3].oval;
  
  int32_t* optlen_ref = stack[4].iref;
  
  char* optval = NULL;
  if (!obj_optval) {
    return env->die(env, stack, "The option value $optval must be defined.", __func__, FILE_NAME, __LINE__);
  }
  optval = (char*)env->get_chars(env, stack, obj_optval);
  int32_t optval_length = env->length(env, stack, obj_optval);
  
  if (!(*optlen_ref >= 0)) {
    env->die(env, stack, "The length stored at index 0 of $optlen_ref must be greater than or equal to 0.", __func__, FILE_NAME, __LINE__);
  }
  if (!(*optlen_ref <= optval_length)) {
    env->die(env, stack, "The length stored at index 0 of $optlen_ref must be less than or equal to the length of the option value $optval.", __func__, FILE_NAME, __LINE__);
  }
  
  socklen_t optlen_tmp = *optlen_ref;
  int32_t status = getsockopt(sockfd, level, optname, optval, &optlen_tmp);
  
  if (status == -1) {
    env->die(env, stack, "[System Error]getsockopt() failed(%d: %s).", __func__, FILE_NAME, __LINE__, spvm_socket_errno(), spvm_socket_strerror(env, stack, spvm_socket_errno(), 0));
    return SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_SYSTEM_CLASS;
  }
  
  *optlen_ref = optlen_tmp;
  
  stack[0].ival = status;
  
  return 0;
}

int32_t SPVM__Sys__Socket__setsockopt(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t sockfd = stack[0].ival;
  
  int32_t level = stack[1].ival;
  
  int32_t optname = stack[2].ival;
  
  void* obj_optval = stack[3].oval;
  
  socklen_t optlen = stack[4].ival;
  
  char* optval = NULL;
  if (!obj_optval) {
    return env->die(env, stack, "The option value $optval must be defined.", __func__, FILE_NAME, __LINE__);
  }
  optval = (char*)env->get_chars(env, stack, obj_optval);
  int32_t optval_length = env->length(env, stack, obj_optval);
  
  if (!(optlen >= 0)) {
    env->die(env, stack, "The length stored at index 0 of $optlen_ref must be greater than or equal to 0.", __func__, FILE_NAME, __LINE__);
  }
  if (!(optlen <= optval_length)) {
    env->die(env, stack, "The length stored at index 0 of $optlen_ref must be less than or equal to the length of the option value $optval.", __func__, FILE_NAME, __LINE__);
  }
  
  int32_t status = setsockopt(sockfd, level, optname, optval, optlen);
  
  if (status == -1) {
    env->die(env, stack, "[System Error]setsockopt() failed(%d: %s).", __func__, FILE_NAME, __LINE__, spvm_socket_errno(), spvm_socket_strerror(env, stack, spvm_socket_errno(), 0));
    return SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_SYSTEM_CLASS;
  }
  
  stack[0].ival = status;
  
  return 0;
}

int32_t SPVM__Sys__Socket__socketpair(SPVM_ENV* env, SPVM_VALUE* stack) {
#if defined(_WIN32)
  env->die(env, stack, "Sys::Socket#socketpair 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
  
  int32_t domain = stack[0].ival;
  
  int32_t type = stack[1].ival;
  
  int32_t protocol = stack[2].ival;
  
  void* obj_sv = stack[3].oval;
  
  if (!obj_sv) {
    return env->die(env, stack, "The socket pair $sv must be defined.", __func__, FILE_NAME, __LINE__);
  }
  
  int32_t* sv = env->get_elems_int(env, stack, obj_sv);
  int32_t sv_length = env->length(env, stack, obj_sv);
  
  if (!(sv_length == 2)) {
    return env->die(env, stack, "The length of the socket pair $sv must be equal to 2.", __func__, FILE_NAME, __LINE__);
  }
  
  int int_sv[2];
  int32_t status = socketpair(domain, type, protocol, int_sv);
  
  if (status == -1) {
    env->die(env, stack, "[System Error]socketpair() failed(%d: %s).", __func__, FILE_NAME, __LINE__, spvm_socket_errno(), spvm_socket_strerror(env, stack, spvm_socket_errno(), 0));
    return SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_SYSTEM_CLASS;
  }
  
  sv[0] = int_sv[0];
  sv[1] = int_sv[1];
  
  stack[0].ival = status;
  
  return 0;
#endif
}

int32_t SPVM__Sys__Socket__gai_strerror(SPVM_ENV* env, SPVM_VALUE* stack);

int32_t SPVM__Sys__Socket__getaddrinfo(SPVM_ENV* env, SPVM_VALUE* stack) {
  int32_t error_id = 0;
  
  void* obj_node = stack[0].oval;
  
  void* obj_service = stack[1].oval;
  
  void* obj_hints = stack[2].oval;
  
  void* obj_res_array = stack[3].oval;
  
  const char* node = NULL;
  if (obj_node) {
    node = env->get_chars(env, stack, obj_node);
  }
  
  const char* service = NULL;
  if (obj_service) {
    service = env->get_chars(env, stack, obj_service);
  }
  
  struct addrinfo *hints = NULL;
  if (obj_hints) {
    hints = env->get_pointer(env, stack, obj_hints);
  }
  
  if (!obj_res_array) {
    return env->die(env, stack, "The array $res_ref to store a response must be defined.", __func__, FILE_NAME, __LINE__);
  }
  int32_t res_array_length = env->length(env, stack, obj_res_array);
  if (!(res_array_length == 1)) {
    return env->die(env, stack, "The length of array $res_ref to store a response must be equal to 1.", __func__, FILE_NAME, __LINE__);
  }
  
  struct addrinfo *res = NULL;
  
  int32_t status = getaddrinfo(node, service, hints, &res);
  
  if (status == 0) {
    int32_t fields_length = 1;
    void* obj_res = env->new_pointer_object_by_name(env, stack, "Sys::Socket::AddrinfoLinkedList", res, &error_id, __func__, FILE_NAME, __LINE__);
    if (error_id) { return error_id; }
    env->set_elem_object(env, stack, obj_res_array, 0, obj_res);
  }
  else {
    stack[0].ival = status;
    SPVM__Sys__Socket__gai_strerror(env, stack);
    void* obj_gai_strerror = stack[0].oval;
    const char* ch_gai_strerror = env->get_chars(env, stack, obj_gai_strerror);
    env->die(env, stack, "[System Error]getaddrinfo() failed(%d: %s).", __func__, FILE_NAME, __LINE__, status, ch_gai_strerror);
    return SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_SYSTEM_CLASS;
  }
  
  stack[0].ival = status;
  
  return 0;
}

int32_t SPVM__Sys__Socket__getnameinfo(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  void* obj_sa = stack[0].oval;
  
  int32_t salen = stack[1].ival;
  
  void* obj_host = stack[2].oval;
  
  int32_t hostlen = stack[3].ival;
  
  void* obj_serv = stack[4].oval;
  
  int32_t servlen = stack[5].ival;
  
  int32_t flags = stack[6].ival;
  
  if (!obj_sa) {
    return env->die(env, stack, "The socket address $sa must be defined.", __func__, FILE_NAME, __LINE__);
  }
  
  const struct sockaddr* sa = env->get_pointer(env, stack, obj_sa);
  
  char* host = NULL;
  if (obj_host) {
    host = (char*)env->get_chars(env, stack, obj_host);
  }
  
  char* serv = NULL;
  if (obj_serv) {
    serv = (char*)env->get_chars(env, stack, obj_serv);
  }
  
  int32_t status = getnameinfo(sa, salen, host, hostlen, serv, servlen, flags);
  
  if (!(status == 0)) {
    stack[0].ival = status;
    SPVM__Sys__Socket__gai_strerror(env, stack);
    void* obj_gai_strerror = stack[0].oval;
    const char* ch_gai_strerror = env->get_chars(env, stack, obj_gai_strerror);
    env->die(env, stack, "[System Error]getnameinfo() failed(%d: %s).", __func__, FILE_NAME, __LINE__, status, ch_gai_strerror);
    return SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_SYSTEM_CLASS;
  }
  
  stack[0].ival = status;
  
  return 0;
}

int32_t SPVM__Sys__Socket__gai_strerror(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t errcode = stack[0].ival;
  
  const char* error_string = gai_strerror(errcode);
  
  if (error_string) {
    int32_t error_string_length = strlen(error_string);
    void* obj_error_string = env->new_string(env, stack, error_string, error_string_length);
    stack[0].oval = obj_error_string;
  }
  else {
    env->die(env, stack, "[System Error]gai_strerror() failed(%d: %s).", __func__, FILE_NAME, __LINE__, spvm_socket_errno(), spvm_socket_strerror(env, stack, spvm_socket_errno(), 0));
    return SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_SYSTEM_CLASS;
  }
  
  return 0;
}

int32_t SPVM__Sys__Socket__sockatmark(SPVM_ENV* env, SPVM_VALUE* stack) {
#if defined(_WIN32)
    env->die(env, stack, "Sys::Socket#sockatmark 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
  
  int32_t sockfd = stack[0].ival;
  
  int32_t status = sockatmark(sockfd);
  
  if (status == -1) {
    env->die(env, stack, "[System Error]sockatmark() failed(%d: %s).", __func__, FILE_NAME, __LINE__, spvm_socket_errno(), spvm_socket_strerror(env, stack, spvm_socket_errno(), 0));
    return SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_SYSTEM_CLASS;
  }
  
  stack[0].ival = status;
  
  return 0;
#endif
}

int32_t SPVM__Sys__Socket__win_set_tcp_keepalive(SPVM_ENV* env, SPVM_VALUE* stack) {
#if !defined(_WIN32)
  env->die(env, stack, "Sys::Socket#win_set_tcp_keepalive method is not supported in this system.", __func__, FILE_NAME, __LINE__);
  return SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_NOT_SUPPORTED_CLASS;
#else
  
  int32_t sockfd = stack[0].ival;
  int32_t onoff = stack[1].ival;
  int32_t keepalivetime_ms = stack[2].ival;
  int32_t keepaliveinterval_ms = stack[3].ival;

  struct tcp_keepalive settings;
  settings.onoff = (u_long)onoff;
  settings.keepalivetime = (u_long)keepalivetime_ms;
  settings.keepaliveinterval = (u_long)keepaliveinterval_ms;

  DWORD bytes_returned = 0;
  int status = WSAIoctl(
    (SOCKET)sockfd, 
    SIO_KEEPALIVE_VALS, 
    &settings, sizeof(settings), 
    NULL, 0, 
    &bytes_returned, 
    NULL, NULL
  );

  if (status == SOCKET_ERROR) {
    env->die(env, stack, "[System Error]WSAIoctl() failed(%d: %s).", __func__, FILE_NAME, __LINE__, WSAGetLastError(), spvm_socket_strerror(env, stack, WSAGetLastError(), 0));
    return SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_SYSTEM_CLASS;
  }
  
  stack[0].ival = 0;
  
  return 0;
#endif
}