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

#include "spvm_native.h"

#include <assert.h>

static const char* FILE_NAME = "Runtime.c";

int32_t SPVM__Runtime__DESTROY(SPVM_ENV* env, SPVM_VALUE* stack) {
  (void)env;
  (void)stack;
  
  int32_t e = 0;
  
  void* obj_self = stack[0].oval;
  
  void* runtime = env->get_pointer(env, stack, obj_self);
  
  env->api->runtime->free_object(runtime);
  
  return 0;
}

int32_t SPVM__Runtime__set_native_method_address(SPVM_ENV* env, SPVM_VALUE* stack) {
  (void)env;
  (void)stack;
  
  int32_t e = 0;
  
  void* obj_self = stack[0].oval;
  
  void* obj_class_name = stack[1].oval;
  const char* class_name = env->get_chars(env, stack, obj_class_name);

  void* obj_method_name = stack[2].oval;
  const char* method_name = env->get_chars(env, stack, obj_method_name);
  
  void* obj_address = stack[3].oval;

  void* runtime = env->get_pointer(env, stack, obj_self);
  
  // Method id
  int32_t method_id = env->api->runtime->get_method_id_by_name(runtime, class_name, method_name);
  
  // Native address
  void* address = env->get_pointer(env, stack, obj_address);
  
  env->api->runtime->set_native_method_address(runtime, method_id, address);

  return 0;
}

int32_t SPVM__Runtime__get_native_method_address(SPVM_ENV* env, SPVM_VALUE* stack) {
  (void)env;
  (void)stack;
  
  int32_t e = 0;

  void* obj_self = stack[0].oval;
  void* obj_class_name = stack[1].oval;
  void* obj_method_name = stack[2].oval;

  void* runtime = env->get_pointer(env, stack, obj_self);
  
  // Class name
  const char* class_name = env->get_chars(env, stack, obj_class_name);

  // Method name
  const char* method_name = env->get_chars(env, stack, obj_method_name);
  
  // Method id
  int32_t method_id = env->api->runtime->get_method_id_by_name(runtime, class_name, method_name);
  
  void* address = env->api->runtime->get_native_method_address(runtime, method_id);
  
  // Native address
  void* obj_address = env->new_pointer_object_by_name(env, stack, "Native::Address", address, &e, __func__, FILE_NAME, __LINE__);
  
  stack[0].oval = obj_address;
  
  return 0;
}

int32_t SPVM__Runtime__get_method_is_class_method(SPVM_ENV* env, SPVM_VALUE* stack) {
  (void)env;
  (void)stack;
  
  int32_t e = 0;

  void* obj_self = stack[0].oval;
  void* obj_class_name = stack[1].oval;
  void* obj_method_name = stack[2].oval;

  void* runtime = env->get_pointer(env, stack, obj_self);
  
  // Class name
  const char* class_name = env->get_chars(env, stack, obj_class_name);

  // Method name
  const char* method_name = env->get_chars(env, stack, obj_method_name);
  
  // Method id
  int32_t method_id = env->api->runtime->get_method_id_by_name(runtime, class_name, method_name);
  
  int32_t is_class_method = env->api->runtime->get_method_is_class_method(runtime, method_id);
  
  stack[0].ival = is_class_method;
  
  return 0;
}


int32_t SPVM__Runtime__get_precompile_method_address(SPVM_ENV* env, SPVM_VALUE* stack) {
  (void)env;
  (void)stack;
  
  int32_t e = 0;

  void* obj_self = stack[0].oval;
  void* obj_class_name = stack[1].oval;
  void* obj_method_name = stack[2].oval;

  void* runtime = env->get_pointer(env, stack, obj_self);
  
  // Class name
  const char* class_name = env->get_chars(env, stack, obj_class_name);

  // Method name
  const char* method_name = env->get_chars(env, stack, obj_method_name);
  
  // Method id
  int32_t method_id = env->api->runtime->get_method_id_by_name(runtime, class_name, method_name);
  
  void* address = env->api->runtime->get_precompile_method_address(runtime, method_id);
  
  // Native address
  void* obj_address = env->new_pointer_object_by_name(env, stack, "Native::Address", address, &e, __func__, FILE_NAME, __LINE__);
  
  stack[0].oval = obj_address;
  
  return 0;
}

int32_t SPVM__Runtime__set_precompile_method_address(SPVM_ENV* env, SPVM_VALUE* stack) {
  (void)env;
  (void)stack;
  
  int32_t e = 0;
  
  void* obj_self = stack[0].oval;
  
  void* obj_class_name = stack[1].oval;
  const char* class_name = env->get_chars(env, stack, obj_class_name);

  void* obj_method_name = stack[2].oval;
  const char* method_name = env->get_chars(env, stack, obj_method_name);
  
  void* obj_address = stack[3].oval;

  void* runtime = env->get_pointer(env, stack, obj_self);
  
  // Method id
  int32_t method_id = env->api->runtime->get_method_id_by_name(runtime, class_name, method_name);
  
  // Native address
  void* address = env->get_pointer(env, stack, obj_address);
  
  env->api->runtime->set_precompile_method_address(runtime, method_id, address);

  return 0;
}

int32_t SPVM__Runtime__build_precompile_class_source(SPVM_ENV* env, SPVM_VALUE* stack) {
  (void)env;
  (void)stack;
  
  int32_t e = 0;
  
  void* obj_self = stack[0].oval;

  void* obj_class_name = stack[1].oval;
  const char* class_name = env->get_chars(env, stack, obj_class_name);

  void* runtime = env->get_pointer(env, stack, obj_self);

  // New allocator
  void* allocator = env->api->allocator->new_object();
  
  // New string buffer
  void* string_buffer = env->api->string_buffer->new_object(allocator, 0);

  void* precompile = env->api->precompile->new_object();
  
  env->api->precompile->set_runtime(precompile, runtime);
  
  env->api->precompile->build_class_source(precompile, string_buffer, class_name);
  
  env->api->precompile->free_object(precompile);

  const char* string_buffer_value = env->api->string_buffer->get_value(string_buffer);
  int32_t string_buffer_length = env->api->string_buffer->get_length(string_buffer);
  void* obj_precompile_class_source = env->new_string(env, stack, string_buffer_value, string_buffer_length);
  
  // Free string buffer
  env->api->string_buffer->free_object(string_buffer);

  // Free allocator
  env->api->allocator->free_object(allocator);

  stack[0].oval = obj_precompile_class_source;
  
  return 0;
}

int32_t SPVM__Runtime__build_precompile_method_source(SPVM_ENV* env, SPVM_VALUE* stack) {
  (void)env;
  (void)stack;
  
  int32_t e = 0;
  
  void* obj_self = stack[0].oval;

  void* obj_class_name = stack[1].oval;
  const char* class_name = env->get_chars(env, stack, obj_class_name);

  void* obj_method_name = stack[2].oval;
  const char* method_name = env->get_chars(env, stack, obj_method_name);

  void* runtime = env->get_pointer(env, stack, obj_self);

  // New allocator
  void* allocator = env->api->allocator->new_object();
  
  // New string buffer
  void* string_buffer = env->api->string_buffer->new_object(allocator, 0);

  void* precompile = env->api->precompile->new_object();
  
  env->api->precompile->set_runtime(precompile, runtime);
  
  env->api->precompile->build_method_source(precompile, string_buffer, class_name, method_name);
  
  env->api->precompile->free_object(precompile);

  const char* string_buffer_value = env->api->string_buffer->get_value(string_buffer);
  int32_t string_buffer_length = env->api->string_buffer->get_length(string_buffer);
  void* obj_precompile_method_source = env->new_string(env, stack, string_buffer_value, string_buffer_length);

  // Free string buffer
  env->api->string_buffer->free_object(string_buffer);

  // Free allocator
  env->api->allocator->free_object(allocator);

  stack[0].oval = obj_precompile_method_source;
  
  return 0;
}

int32_t SPVM__Runtime__get_runtime_codes(SPVM_ENV* env, SPVM_VALUE* stack) {
  (void)env;
  (void)stack;
  
  int32_t e = 0;

  void* obj_self = stack[0].oval;

  void* runtime = env->get_pointer(env, stack, obj_self);
  
  // SPVM 32bit codes
  int32_t* runtime_runtime_codes = env->api->runtime->get_runtime_codes(runtime);
  int32_t runtime_codes_length = env->api->runtime->get_runtime_codes_length(runtime);
  
  void* obj_runtime_codes = env->new_int_array(env, stack, runtime_codes_length);
  int32_t* runtime_codes = env->get_elems_int(env, stack, obj_runtime_codes);
  memcpy(runtime_codes, runtime_runtime_codes, sizeof(int32_t) * runtime_codes_length);
  
  stack[0].oval = obj_runtime_codes;
  
  return 0;
}

int32_t SPVM__Runtime__get_classes_length(SPVM_ENV* env, SPVM_VALUE* stack) {
  (void)env;
  (void)stack;
  
  int32_t e = 0;

  void* obj_self = stack[0].oval;

  void* runtime = env->get_pointer(env, stack, obj_self);
  
  int32_t classes_length = env->api->runtime->get_classes_length(runtime);
  
  stack[0].ival = classes_length;
  
  return 0;
}

int32_t SPVM__Runtime__get_class_names(SPVM_ENV* env, SPVM_VALUE* stack) {
  (void)env;
  (void)stack;
  
  int32_t e = 0;

  void* obj_self = stack[0].oval;

  void* runtime = env->get_pointer(env, stack, obj_self);
  
  int32_t classes_length = env->api->runtime->get_classes_length(runtime);
  
  void* obj_class_names = env->new_string_array(env, stack, classes_length);
  for (int32_t class_id = 0; class_id < classes_length; class_id++) {
    const char* class_name = env->api->runtime->get_name(runtime, env->api->runtime->get_class_name_id(runtime, class_id));
    void* obj_class_name = env->new_string_nolen(env, stack, class_name);
    env->set_elem_object(env, stack, obj_class_names, class_id, obj_class_name);
  }
  
  stack[0].oval = obj_class_names;
  
  return 0;
}


int32_t SPVM__Runtime__get_module_file(SPVM_ENV* env, SPVM_VALUE* stack) {
  (void)env;
  (void)stack;
  
  int32_t e = 0;
  
  void* obj_self = stack[0].oval;
  
  void* obj_class_name = stack[1].oval;
  const char* class_name = env->get_chars(env, stack, obj_class_name);
  
  void* runtime = env->get_pointer(env, stack, obj_self);
  
  // Copy class load path to builder
  int32_t class_id = env->api->runtime->get_class_id_by_name(runtime, class_name);
  const char* module_file;
  void* sv_module_file;

  void* obj_module_file = NULL;
  if (class_id >= 0) {
    int32_t module_rel_file_id = env->api->runtime->get_class_module_rel_file_id(runtime, class_id);
    int32_t module_dir_id = env->api->runtime->get_class_module_dir_id(runtime, class_id);
    const char* module_dir = NULL;
    const char* module_dir_sep;
    if (module_dir_id >= 0) {
      module_dir_sep = "/";
      module_dir = env->api->runtime->get_constant_string_value(runtime, module_dir_id, NULL);
    }
    else {
      module_dir_sep = "";
      module_dir = "";
    }
    const char* module_rel_file = env->api->runtime->get_constant_string_value(runtime, module_rel_file_id, NULL);
    
    int32_t module_file_length = strlen(module_dir) + strlen(module_dir_sep) + strlen(module_rel_file);
    obj_module_file = env->new_string(env, stack, NULL, module_file_length);
    char* module_file = (char*)env->get_chars(env, stack, obj_module_file);
    memcpy(module_file, module_dir, strlen(module_dir));
    memcpy(module_file + strlen(module_dir), module_dir_sep, strlen(module_dir_sep));
    memcpy(module_file + strlen(module_dir) + strlen(module_dir_sep), module_rel_file, strlen(module_rel_file));
  }
  
  stack[0].oval = obj_module_file;
  
  return 0;
}

int32_t SPVM__Runtime__get_parent_class_name(SPVM_ENV* env, SPVM_VALUE* stack) {
  (void)env;
  (void)stack;
  
  int32_t e = 0;
  
  void* obj_self = stack[0].oval;
  
  void* obj_class_name = stack[1].oval;
  const char* class_name = env->get_chars(env, stack, obj_class_name);
  
  void* runtime = env->get_pointer(env, stack, obj_self);
  
  int32_t class_id = env->api->runtime->get_class_id_by_name(runtime, class_name);
  int32_t parent_class_id = env->api->runtime->get_class_parent_class_id(runtime, class_id);
  
  void* obj_parent_class_name = NULL;
  if (parent_class_id >= 0) {
    int32_t parent_class_name_id = env->api->runtime->get_class_name_id(runtime, parent_class_id);
    const char* parent_class_name = env->api->runtime->get_name(runtime, parent_class_name_id);
    obj_parent_class_name = env->new_string_nolen(env, stack, parent_class_name);
  }
  
  stack[0].oval = obj_parent_class_name;
  
  return 0;
}

int32_t SPVM__Runtime__get_anon_class_names(SPVM_ENV* env, SPVM_VALUE* stack) {
  (void)env;
  (void)stack;
  
  int32_t e = 0;

  void* obj_self = stack[0].oval;

  void* obj_class_name = stack[1].oval;
  const char* class_name = env->get_chars(env, stack, obj_class_name);

  void* runtime = env->get_pointer(env, stack, obj_self);
  
  int32_t class_id = env->api->runtime->get_class_id_by_name(runtime, class_name);

  int32_t methods_length = env->api->runtime->get_class_methods_length(runtime, class_id);
  
  int32_t anon_classes_length = 0;
  for (int32_t method_index = 0; method_index < methods_length; method_index++) {
    int32_t method_id = env->api->runtime->get_method_id_by_index(runtime, class_id, method_index);
    int32_t is_anon_method = env->api->runtime->get_method_is_anon(runtime, method_id);
    if (is_anon_method) {
      anon_classes_length++;
    }
  }
  
  void* obj_anon_class_names = env->new_string_array(env, stack, anon_classes_length);
  int32_t anon_class_index = 0;
  for (int32_t method_index = 0; method_index < methods_length; method_index++) {
    int32_t method_id = env->api->runtime->get_method_id_by_index(runtime, class_id, method_index);
    int32_t is_anon_method = env->api->runtime->get_method_is_anon(runtime, method_id);
    if (is_anon_method) {
      int32_t anon_class_id = env->api->runtime->get_method_class_id(runtime, method_id);
      const char* anon_class_name = env->api->runtime->get_name(runtime, env->api->runtime->get_class_name_id(runtime, anon_class_id));
      void* obj_anon_class_name = env->new_string_nolen(env, stack, anon_class_name);
      env->set_elem_object(env, stack, obj_anon_class_names, anon_class_index, obj_anon_class_name);
      anon_class_index++;
    }
  }
  
  stack[0].oval = obj_anon_class_names;
  
  return 0;
}

int32_t SPVM__Runtime___get_method_names(SPVM_ENV* env, SPVM_VALUE* stack) {
  (void)env;
  (void)stack;
  
  int32_t e = 0;

  void* obj_self = stack[0].oval;

  void* obj_class_name = stack[1].oval;
  const char* class_name = env->get_chars(env, stack, obj_class_name);
  int32_t native_flag = stack[2].ival;
  int32_t precompile_flag = stack[3].ival;
  int32_t enum_flag = stack[4].ival;
  
  void* runtime = env->get_pointer(env, stack, obj_self);
  
  int32_t class_id = env->api->runtime->get_class_id_by_name(runtime, class_name);

  int32_t methods_length = env->api->runtime->get_class_methods_length(runtime, class_id);
  
  int32_t match_methodes_length = 0;
  for (int32_t method_index = 0; method_index < methods_length; method_index++) {
    int32_t method_id = env->api->runtime->get_method_id_by_index(runtime, class_id, method_index);
    int32_t match = 0;
    if (native_flag) {
      if (env->api->runtime->get_method_is_native(runtime, method_id)) {
        match = 1;
      }
    }
    else if (precompile_flag) {
      if (env->api->runtime->get_method_is_precompile(runtime, method_id)) {
        match = 1;
      }
    }
    else if (enum_flag) {
      if (env->api->runtime->get_method_is_enum(runtime, method_id)) {
        match = 1;
      }
    }
    else {
      match = 1;
    }

    if (match) {
      match_methodes_length++;
    }
  }
  
  void* obj_method_names = env->new_string_array(env, stack, match_methodes_length);
  int32_t match_method_index = 0;
  for (int32_t method_index = 0; method_index < methods_length; method_index++) {
    int32_t method_id = env->api->runtime->get_method_id_by_index(runtime, class_id, method_index);
    int32_t match = 0;
    if (native_flag) {
      if (env->api->runtime->get_method_is_native(runtime, method_id)) {
        match = 1;
      }
    }
    else if (precompile_flag) {
      if (env->api->runtime->get_method_is_precompile(runtime, method_id)) {
        match = 1;
      }
    }
    else if (enum_flag) {
      if (env->api->runtime->get_method_is_enum(runtime, method_id)) {
        match = 1;
      }
    }
    else {
      match = 1;
    }
    
    if (match) {
      const char* method_name = env->api->runtime->get_name(runtime, env->api->runtime->get_method_name_id(runtime, method_id));
      void* obj_method_name = env->new_string_nolen(env, stack, method_name);
      env->set_elem_object(env, stack, obj_method_names, match_method_index, obj_method_name);
      match_method_index++;
    }
  }
  
  stack[0].oval = obj_method_names;
  
  return 0;
}

int32_t SPVM__Runtime__build_env(SPVM_ENV* env, SPVM_VALUE* stack) {
  (void)env;
  (void)stack;
  
  int32_t e = 0;
  
  void* obj_runtime = stack[0].oval;
  
  void* runtime = env->get_pointer(env, stack, obj_runtime);
  
  SPVM_ENV* my_env = env->new_env_raw(env);
  
  // Set runtime information
  my_env->runtime = runtime;
  
  // Initialize env
  my_env->init_env(my_env);
  
  void* obj_self= env->new_pointer_object_by_name(env, stack, "Env", my_env, &e, __func__, FILE_NAME, __LINE__);
  if (e) { return e; }
  
  env->set_field_object_by_name(env, stack, obj_self, "runtime", obj_runtime, &e, __func__, FILE_NAME, __LINE__);
  if (e) { return e; }
  
  stack[0].oval = obj_self;
  
  return 0;
}