#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <assert.h>

#include "spvm_native.h"

int32_t main(int32_t command_args_length, const char *command_args[]) {
  
  SPVM_ENV* env = SPVM_NATIVE_new_env_prepared();
  
  // Class name
  const char* class_name = "MyExe";
  
  // Class
  int32_t method_id = env->get_class_method_id(env, class_name, "main");
  
  if (method_id < 0) {
    fprintf(stderr, "The class method %s->main is not defined\n", class_name);
    return -1;
  }
  
  SPVM_VALUE* stack = env->new_stack(env);

  // Enter scope
  int32_t scope_id = env->enter_scope(env, stack);
  
  // Program name - string
  void* obj_program_name = env->new_string(env, stack, command_args[0], strlen(command_args[0]));
  
  // ARGV - string[]
  void* obj_argv = env->new_object_array(env, stack, SPVM_NATIVE_C_BASIC_TYPE_ID_STRING, command_args_length - 1);
  for (int32_t arg_index = 1; arg_index < command_args_length; arg_index++) {
    void* obj_arg = env->new_string(env, stack, command_args[arg_index], strlen(command_args[arg_index]));
    env->set_elem_object(env, stack, obj_argv, arg_index - 1, obj_arg);
  }
  
  int32_t args_stack_length = 2;
  stack[0].oval = obj_program_name;
  stack[1].oval = obj_argv;

  // Call INIT blocks
  env->call_init_blocks(env, stack);
  
  // Set command info
  {
    int32_t e;
    e = env->set_command_info_program_name(env, stack, obj_program_name);
    assert(e == 0);
    e = env->set_command_info_argv(env, stack, obj_argv);
    assert(e == 0);
  }
  
  // Run
  int32_t error = env->call_spvm_method(env, stack, method_id, args_stack_length);

  int32_t status;
  if (error) {
    env->print_stderr(env, stack, env->get_exception(env, stack));
    printf("\n");
    status = 255;
  }
  else {
    status = stack[0].ival;
  }

  // Leave scope
  env->leave_scope(env, stack, scope_id);

  env->cleanup_global_vars(env, stack);
  
  env->free_stack(env, stack);
  
  env->free_env_prepared(env);
  
  return status;
}

SPVM_ENV* SPVM_NATIVE_new_env_prepared() {
  // Class name
  const char* class_name = "MyExe";

  // Create env
  SPVM_ENV* env = SPVM_NATIVE_new_env_raw();
  
  // Create compiler
  void* compiler = env->api->compiler->new_compiler();
  
  // compiler->debug = 1;
  
  env->api->compiler->set_start_file(compiler, class_name);

  env->api->compiler->set_start_line(compiler, 0);
  
  // Add module directory
  char* module_dir = "solo/SPVM";
  env->api->compiler->add_module_dir(compiler, module_dir);

  int32_t compile_error_code = env->api->compiler->compile_spvm(compiler, class_name);
  
  if (compile_error_code != 0) {
    int32_t error_messages_length = env->api->compiler->get_error_messages_length(compiler);
    for (int32_t i = 0; i < error_messages_length; i++) {
      const char* error_message = env->api->compiler->get_error_message(compiler, i);
      fprintf(stderr, "%s\n", error_message);
    }
    exit(255);
  }

  // New runtime
  void* runtime = env->api->runtime->new_runtime(env);
  
  // Runtime allocator
  void* runtime_allocator = env->api->runtime->get_allocator(runtime);
  
  // Create SPVM 32bit codes
  int32_t* spvm_32bit_codes = env->api->compiler->create_spvm_32bit_codes(compiler, runtime_allocator);
  
  // Free compiler
  env->api->compiler->free_compiler(compiler);
  
  // Build runtime
  env->api->runtime->build(runtime, spvm_32bit_codes);
  
  // Prepare runtime
  env->api->runtime->prepare(runtime);

  // Set runtime information
  env->runtime = runtime;
  
  // Initialize env
  env->init_env(env);
  
  return env;
}
