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

#include "spvm_native.h"

#include <time.h>
#include <sstream>
#include <iomanip>
#include <errno.h>

extern "C" {

static const char* FILE_NAME = "Time/Piece.cpp";

int32_t SPVM__Time__Piece__foo(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  return 0;
}

int32_t SPVM__Time__Piece__strftime(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  void* obj_self = stack[0].oval;
  
  void* obj_format = stack[1].oval;
  
  if (!obj_format) {
    const char* format = "%a, %d %b %Y %H:%M:%S %Z";
    obj_format = env->new_string(env, stack, format, strlen(format));
  }
  
  int32_t format_length = env->length(env, stack, obj_format);
  
  if (format_length == 0) {
    return env->die_v2(env, stack, "The length of $format must be greater than 1.", __func__, FILE_NAME, __LINE__);
  }
  
  const char* format = env->get_chars(env, stack, obj_format);
  
  void* obj_tm = env->get_field_object_by_name(env, stack, obj_self, "tm", &error_id, __func__, FILE_NAME, __LINE__);
  if (error_id) { return error_id; }
  
  struct tm* st_tm = (struct tm*)env->get_pointer(env, stack, obj_tm);
  
  int32_t max_length = format_length + 160;
  
  void* obj_ret = NULL;
  
  while (1) {
    
    obj_ret = env->new_string(env, stack, NULL, max_length);
    
    char* ret = (char*)env->get_chars(env, stack, obj_ret);
    
    errno = 0;
    int32_t write_length = strftime(ret, max_length, format, st_tm);
    
    if (!(errno == 0)) {
      env->die_v2(env, stack, "[System Error]strftime failed:%s. $format is \"%s\"", __func__, FILE_NAME, __LINE__, env->strerror(env, stack, errno, 0), format);
      return SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_SYSTEM_CLASS;
    }
    
    if (write_length == 0) {
      if (max_length > 100 * format_length) {
        return env->die_v2(env, stack, "Too many memory is allocated.", __func__, FILE_NAME, __LINE__);
      }
      
      max_length *= 2;
    }
    else {
      env->shorten(env, stack, obj_ret, write_length);
      break;
    }
  }
  
  stack[0].oval = obj_ret;
  
  return 0;
}

int32_t SPVM__Time__Piece__strptime_tm(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  void* obj_string = stack[0].oval;
  
  if (!obj_string) {
    return env->die_v2(env, stack, "$string must be defined.", __func__, FILE_NAME, __LINE__);
  }
  
  const char* string = env->get_chars(env, stack, obj_string);
  
  void* obj_format = stack[1].oval;
  
  if (!obj_format) {
    return env->die_v2(env, stack, "$format must be defined.", __func__, FILE_NAME, __LINE__);
  }
  
  const char* format = env->get_chars(env, stack, obj_format);
  
  struct tm* st_tm = (struct tm*)env->new_memory_block(env, stack, sizeof(struct tm));
  
  
  std::istringstream string_stream(string);
  
  string_stream >> std::get_time(st_tm, format);
  
  if (string_stream.fail()) {
    return env->die_v2(env, stack, "std::get_time failed.", __func__, FILE_NAME, __LINE__);
  }
  
  void* obj_tm = env->new_pointer_object_by_name(env, stack, "Sys::Time::Tm", st_tm, &error_id, __func__, FILE_NAME, __LINE__);
  if (error_id) { return error_id; }
  
  stack[0].oval = obj_tm;
  
  return 0;
}

}
