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

#include "spvm_native.h"

#include <assert.h>

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

int32_t SPVM__Native__Runtime__build_precompile_class_source(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  void* obj_self = stack[0].oval;
  
  void* obj_basic_type = stack[1].oval;
  
  void* self = env->get_pointer(env, stack, obj_self);
  
  void* basic_type = env->get_pointer(env, stack, obj_basic_type);
  
  void* allocator = env->api->allocator->new_instance();
  
  void* string_buffer = env->api->string_buffer->new_instance(allocator, 0);
  
  env->api->runtime->build_precompile_class_source(self, string_buffer, basic_type);
  
  const char* string_buffer_value = env->api->string_buffer->get_string(string_buffer);
  int32_t string_buffer_length = env->api->string_buffer->get_length(string_buffer);
  void* obj_precompile_source = env->new_string(env, stack, string_buffer_value, string_buffer_length);
  
  env->api->string_buffer->free_instance(string_buffer);
  
  env->api->allocator->free_instance(allocator);
  
  stack[0].oval = obj_precompile_source;
  
  return 0;
}

int32_t SPVM__Native__Runtime__build_precompile_method_source(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  void* obj_self = stack[0].oval;
  
  void* obj_method = stack[1].oval;
  
  void* self = env->get_pointer(env, stack, obj_self);
  
  void* method = env->get_pointer(env, stack, obj_method);
  
  void* allocator = env->api->allocator->new_instance();
  
  void* string_buffer = env->api->string_buffer->new_instance(allocator, 0);
  
  env->api->runtime->build_precompile_method_source(self, string_buffer, method);
  
  const char* string_buffer_value = env->api->string_buffer->get_string(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);
  
  env->api->string_buffer->free_instance(string_buffer);
  
  env->api->allocator->free_instance(allocator);
  
  stack[0].oval = obj_precompile_method_source;
  
  return 0;
}

int32_t SPVM__Native__Runtime__set_native_method_address(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  void* obj_self = stack[0].oval;
  
  void* obj_basic_type_name = stack[1].oval;
  const char* basic_type_name = env->get_chars(env, stack, obj_basic_type_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* self = env->get_pointer(env, stack, obj_self);
  
  // Method id
  void* basic_type = env->api->runtime->get_basic_type_by_name(self, basic_type_name);
  void* method = env->api->basic_type->get_method_by_name(self, basic_type, method_name);
  
  // Native address
  void* address = env->get_pointer(env, stack, obj_address);
  
  env->api->method->set_native_address(self, method, address);

  return 0;
}

int32_t SPVM__Native__Runtime__get_native_method_address(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;

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

  void* self = env->get_pointer(env, stack, obj_self);
  
  // Basic type name
  const char* basic_type_name = env->get_chars(env, stack, obj_basic_type_name);

  // Method name
  const char* method_name = env->get_chars(env, stack, obj_method_name);
  
  // Method id
  void* basic_type = env->api->runtime->get_basic_type_by_name(self, basic_type_name);
  void* method = env->api->basic_type->get_method_by_name(self, basic_type, method_name);
  
  void* address = env->api->method->get_native_address(self, method);
  
  // Native address
  void* obj_address = env->new_pointer_object_by_name(env, stack, "Native::Address", address, &error_id, __func__, FILE_NAME, __LINE__);
  
  stack[0].oval = obj_address;
  
  return 0;
}

int32_t SPVM__Native__Runtime__get_method_is_class_method(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;

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

  void* self = env->get_pointer(env, stack, obj_self);
  
  // Basic type name
  const char* basic_type_name = env->get_chars(env, stack, obj_basic_type_name);

  // Method name
  const char* method_name = env->get_chars(env, stack, obj_method_name);
  
  // Method id
  void* basic_type = env->api->runtime->get_basic_type_by_name(self, basic_type_name);
  void* method = env->api->basic_type->get_method_by_name(self, basic_type, method_name);
  
  int32_t is_class_method = env->api->method->is_class_method(self, method);
  
  stack[0].ival = is_class_method;
  
  return 0;
}


int32_t SPVM__Native__Runtime__get_precompile_method_address(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;

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

  void* self = env->get_pointer(env, stack, obj_self);
  
  // Basic type name
  const char* basic_type_name = env->get_chars(env, stack, obj_basic_type_name);

  // Method name
  const char* method_name = env->get_chars(env, stack, obj_method_name);
  
  // Method id
  void* basic_type = env->api->runtime->get_basic_type_by_name(self, basic_type_name);
  void* method = env->api->basic_type->get_method_by_name(self, basic_type, method_name);
  
  void* address = env->api->method->get_precompile_address(self, method);
  
  // Native address
  void* obj_address = env->new_pointer_object_by_name(env, stack, "Native::Address", address, &error_id, __func__, FILE_NAME, __LINE__);
  
  stack[0].oval = obj_address;
  
  return 0;
}

int32_t SPVM__Native__Runtime__set_precompile_method_address(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  void* obj_self = stack[0].oval;
  
  void* obj_basic_type_name = stack[1].oval;
  const char* basic_type_name = env->get_chars(env, stack, obj_basic_type_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* self = env->get_pointer(env, stack, obj_self);
  
  // Method id
  void* basic_type = env->api->runtime->get_basic_type_by_name(self, basic_type_name);
  void* method = env->api->basic_type->get_method_by_name(self, basic_type, method_name);
  
  // Native address
  void* address = env->get_pointer(env, stack, obj_address);
  
  env->api->method->set_precompile_address(self, method, address);

  return 0;
}

int32_t SPVM__Native__Runtime__get_basic_types_length(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  void* obj_self = stack[0].oval;
  
  void* self = env->get_pointer(env, stack, obj_self);
  
  int32_t basic_types_length = env->api->runtime->get_basic_types_length(self);
  
  stack[0].ival = basic_types_length;
  
  return 0;
}

int32_t SPVM__Native__Runtime__get_basic_type_by_id(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  void* obj_self = stack[0].oval;
  
  int32_t index = stack[1].ival;
  
  void* self = env->get_pointer(env, stack, obj_self);
  
  void* basic_type = env->api->runtime->get_basic_type_by_id(self, index);
  
  if (!basic_type) {
    return env->die(env, stack, "The basic type cannot be found.", __func__, FILE_NAME, __LINE__);
  }
  
  void* obj_address_basic_type = env->new_pointer_object_by_name(env, stack, "Address", basic_type, &error_id, __func__, FILE_NAME, __LINE__);
  if (error_id) { return error_id; }
  stack[0].oval = obj_address_basic_type;
  env->call_class_method_by_name(env, stack, "Native::BasicType", "new_with_pointer", 1, &error_id, __func__, FILE_NAME, __LINE__);
  if (error_id) { return error_id; }
  void* obj_basic_type = stack[0].oval;
  env->set_no_free(env, stack, obj_basic_type, 1);
  
  env->set_field_object_by_name(env, stack, obj_basic_type, "runtime", obj_self, &error_id, __func__, FILE_NAME, __LINE__);
  if (error_id) { return error_id; }
  
  stack[0].oval = obj_basic_type;
  
  return 0;
}

int32_t SPVM__Native__Runtime__get_basic_type_by_name(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  void* obj_self = stack[0].oval;
  
  void* obj_name = stack[1].oval;
  
  if (!obj_name) {
    return env->die(env, stack, "The basic type cannot be found.", __func__, FILE_NAME, __LINE__);
  }
  
  const char* name = env->get_chars(env, stack, obj_name);
  
  void* self = env->get_pointer(env, stack, obj_self);
  
  void* basic_type = env->api->runtime->get_basic_type_by_name(self, name);
  
  if (!basic_type) {
    return env->die(env, stack, "The basic type cannot be found.", __func__, FILE_NAME, __LINE__);
  }
  
  void* obj_address_basic_type = env->new_pointer_object_by_name(env, stack, "Address", basic_type, &error_id, __func__, FILE_NAME, __LINE__);
  if (error_id) { return error_id; }
  stack[0].oval = obj_address_basic_type;
  env->call_class_method_by_name(env, stack, "Native::BasicType", "new_with_pointer", 1, &error_id, __func__, FILE_NAME, __LINE__);
  if (error_id) { return error_id; }
  void* obj_basic_type = stack[0].oval;
  env->set_no_free(env, stack, obj_basic_type, 1);
  
  env->set_field_object_by_name(env, stack, obj_basic_type, "runtime", obj_self, &error_id, __func__, FILE_NAME, __LINE__);
  if (error_id) { return error_id; }
  
  stack[0].oval = obj_basic_type;
  
  return 0;
}

int32_t SPVM__Native__Runtime__get_basic_type_parent_name(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  void* obj_self = stack[0].oval;
  
  void* obj_basic_type_name = stack[1].oval;
  const char* basic_type_name = env->get_chars(env, stack, obj_basic_type_name);
  
  void* self = env->get_pointer(env, stack, obj_self);
  
  void* basic_type = env->api->runtime->get_basic_type_by_name(self, basic_type_name);
  void* parent_basic_type = env->api->basic_type->get_parent(self, basic_type);
  
  void* obj_parent_basic_type_name = NULL;
  if (parent_basic_type) {
    const char* parent_basic_type_name = env->api->basic_type->get_name(self, parent_basic_type);
    obj_parent_basic_type_name = env->new_string_nolen(env, stack, parent_basic_type_name);
  }
  
  stack[0].oval = obj_parent_basic_type_name;
  
  return 0;
}

int32_t SPVM__Native__Runtime___get_method_names(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  void* obj_self = stack[0].oval;
  
  void* obj_basic_type_name = stack[1].oval;
  const char* basic_type_name = env->get_chars(env, stack, obj_basic_type_name);
  int32_t native_flag = stack[2].ival;
  int32_t precompile_flag = stack[3].ival;
  int32_t enum_flag = stack[4].ival;
  
  void* self = env->get_pointer(env, stack, obj_self);
  
  void* basic_type = env->api->runtime->get_basic_type_by_name(self, basic_type_name);
  
  int32_t methods_length = env->api->basic_type->get_methods_length(self, basic_type);
  
  int32_t match_methodes_length = 0;
  for (int32_t method_index = 0; method_index < methods_length; method_index++) {
    void* method = env->api->basic_type->get_method_by_index(self, basic_type, method_index);
    int32_t match = 0;
    if (native_flag) {
      if (env->api->method->is_native(self, method)) {
        match = 1;
      }
    }
    else if (precompile_flag) {
      if (env->api->method->is_precompile(self, method)) {
        match = 1;
      }
    }
    else if (enum_flag) {
      if (env->api->method->is_enum(self, method)) {
        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++) {
    void* method = env->api->basic_type->get_method_by_index(self, basic_type, method_index);
    int32_t match = 0;
    if (native_flag) {
      if (env->api->method->is_native(self, method)) {
        match = 1;
      }
    }
    else if (precompile_flag) {
      if (env->api->method->is_precompile(self, method)) {
        match = 1;
      }
    }
    else if (enum_flag) {
      if (env->api->method->is_enum(self, method)) {
        match = 1;
      }
    }
    else {
      match = 1;
    }
    
    if (match) {
      const char* method_name = env->api->method->get_name(self, method);
      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__Native__Runtime__get_compiler(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  void* obj_self = stack[0].oval;
  
  void* obj_basic_type = stack[1].oval;
  
  void* self = env->get_pointer(env, stack, obj_self);
  
  void* obj_compiler = env->get_field_object_by_name(env, stack, obj_self, "compiler", &error_id, __func__, FILE_NAME, __LINE__);
  if (error_id) { return error_id; }
  
  stack[0].oval = obj_compiler;
  
  return 0;
}