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

#include "spvm_native.h"

#include "spvm_api.h"
#include "spvm_op.h"
#include "spvm_compiler.h"
#include "spvm_hash.h"
#include "spvm_list.h"
#include "spvm_package.h"
#include "spvm_sub.h"
#include "spvm_basic_type.h"

// module source get functions declaration
const char* SPMODSRC__SPVM__Byte__get_module_source();
const char* SPMODSRC__SPVM__Short__get_module_source();
const char* SPMODSRC__SPVM__Int__get_module_source();
const char* SPMODSRC__SPVM__Long__get_module_source();
const char* SPMODSRC__SPVM__Float__get_module_source();
const char* SPMODSRC__SPVM__Double__get_module_source();
const char* SPMODSRC__MyExe__get_module_source();
const char* SPMODSRC__MyExe__Precompile__get_module_source();
const char* SPMODSRC__TestCase__NativeAPI2__get_module_source();
// precompile functions declaration
int32_t SPPRECOMPILE__MyExe__Precompile__anon__15__13__(SPVM_ENV* env, SPVM_VALUE* stack);
int32_t SPPRECOMPILE__MyExe__Precompile__anon__22__13__(SPVM_ENV* env, SPVM_VALUE* stack);
int32_t SPPRECOMPILE__MyExe__Precompile__sum(SPVM_ENV* env, SPVM_VALUE* stack);
int32_t SPPRECOMPILE__MyExe__Precompile__anon_sub_sum(SPVM_ENV* env, SPVM_VALUE* stack);
int32_t SPPRECOMPILE__MyExe__Precompile__anon_sub0(SPVM_ENV* env, SPVM_VALUE* stack);
int32_t SPPRECOMPILE__MyExe__Precompile__anon_sub1(SPVM_ENV* env, SPVM_VALUE* stack);
// native functions declaration
int32_t SPNATIVE__TestCase__NativeAPI2__mul(SPVM_ENV* env, SPVM_VALUE* stack);
int32_t SPNATIVE__TestCase__NativeAPI2__src_foo(SPVM_ENV* env, SPVM_VALUE* stack);
int32_t SPNATIVE__TestCase__NativeAPI2__src_bar(SPVM_ENV* env, SPVM_VALUE* stack);

int32_t main(int32_t argc, const char *argv[]) {
  // Package name
  const char* package_name = "MyExe";
  
  // Create compiler
  SPVM_COMPILER* compiler = SPVM_COMPILER_new();
  compiler->no_directry_module_search = 1;

  // Create use op for entry point package
  SPVM_OP* op_name_start = SPVM_OP_new_op_name(compiler, package_name, package_name, 0);
  SPVM_OP* op_type_start = SPVM_OP_build_basic_type(compiler, op_name_start);
  SPVM_OP* op_use_start = SPVM_OP_new_op(compiler, SPVM_OP_C_ID_USE, package_name, 0);
  SPVM_OP_build_use(compiler, op_use_start, op_type_start, NULL, 0);
  SPVM_LIST_push(compiler->op_use_stack, op_use_start);
  
  // Set module sources
  {
    const char* module_source = SPMODSRC__SPVM__Byte__get_module_source();
    SPVM_HASH_insert(compiler->module_source_symtable, "SPVM::Byte", strlen("SPVM::Byte"), (void*)module_source);
  }
  {
    const char* module_source = SPMODSRC__SPVM__Short__get_module_source();
    SPVM_HASH_insert(compiler->module_source_symtable, "SPVM::Short", strlen("SPVM::Short"), (void*)module_source);
  }
  {
    const char* module_source = SPMODSRC__SPVM__Int__get_module_source();
    SPVM_HASH_insert(compiler->module_source_symtable, "SPVM::Int", strlen("SPVM::Int"), (void*)module_source);
  }
  {
    const char* module_source = SPMODSRC__SPVM__Long__get_module_source();
    SPVM_HASH_insert(compiler->module_source_symtable, "SPVM::Long", strlen("SPVM::Long"), (void*)module_source);
  }
  {
    const char* module_source = SPMODSRC__SPVM__Float__get_module_source();
    SPVM_HASH_insert(compiler->module_source_symtable, "SPVM::Float", strlen("SPVM::Float"), (void*)module_source);
  }
  {
    const char* module_source = SPMODSRC__SPVM__Double__get_module_source();
    SPVM_HASH_insert(compiler->module_source_symtable, "SPVM::Double", strlen("SPVM::Double"), (void*)module_source);
  }
  {
    const char* module_source = SPMODSRC__MyExe__get_module_source();
    SPVM_HASH_insert(compiler->module_source_symtable, "MyExe", strlen("MyExe"), (void*)module_source);
  }
  {
    const char* module_source = SPMODSRC__MyExe__Precompile__get_module_source();
    SPVM_HASH_insert(compiler->module_source_symtable, "MyExe::Precompile", strlen("MyExe::Precompile"), (void*)module_source);
  }
  {
    const char* module_source = SPMODSRC__TestCase__NativeAPI2__get_module_source();
    SPVM_HASH_insert(compiler->module_source_symtable, "TestCase::NativeAPI2", strlen("TestCase::NativeAPI2"), (void*)module_source);
  }


  SPVM_COMPILER_compile(compiler);

  if (compiler->error_count > 0) {
    exit(1);
  }
  { 
    const char* package_name = "MyExe::Precompile::anon::15::13";
    const char* sub_name = "";
    SPVM_BASIC_TYPE* basic_type = SPVM_HASH_fetch(compiler->basic_type_symtable, package_name, strlen(package_name));
    assert(basic_type);
    SPVM_PACKAGE* package = basic_type->package;
    assert(package);
    SPVM_SUB* sub = SPVM_HASH_fetch(package->sub_symtable, sub_name, strlen(sub_name));
    assert(sub);
    sub->precompile_address = SPPRECOMPILE__MyExe__Precompile__anon__15__13__;
  }
  { 
    const char* package_name = "MyExe::Precompile::anon::22::13";
    const char* sub_name = "";
    SPVM_BASIC_TYPE* basic_type = SPVM_HASH_fetch(compiler->basic_type_symtable, package_name, strlen(package_name));
    assert(basic_type);
    SPVM_PACKAGE* package = basic_type->package;
    assert(package);
    SPVM_SUB* sub = SPVM_HASH_fetch(package->sub_symtable, sub_name, strlen(sub_name));
    assert(sub);
    sub->precompile_address = SPPRECOMPILE__MyExe__Precompile__anon__22__13__;
  }
  { 
    const char* package_name = "MyExe::Precompile";
    const char* sub_name = "sum";
    SPVM_BASIC_TYPE* basic_type = SPVM_HASH_fetch(compiler->basic_type_symtable, package_name, strlen(package_name));
    assert(basic_type);
    SPVM_PACKAGE* package = basic_type->package;
    assert(package);
    SPVM_SUB* sub = SPVM_HASH_fetch(package->sub_symtable, sub_name, strlen(sub_name));
    assert(sub);
    sub->precompile_address = SPPRECOMPILE__MyExe__Precompile__sum;
  }
  { 
    const char* package_name = "MyExe::Precompile";
    const char* sub_name = "anon_sub_sum";
    SPVM_BASIC_TYPE* basic_type = SPVM_HASH_fetch(compiler->basic_type_symtable, package_name, strlen(package_name));
    assert(basic_type);
    SPVM_PACKAGE* package = basic_type->package;
    assert(package);
    SPVM_SUB* sub = SPVM_HASH_fetch(package->sub_symtable, sub_name, strlen(sub_name));
    assert(sub);
    sub->precompile_address = SPPRECOMPILE__MyExe__Precompile__anon_sub_sum;
  }
  { 
    const char* package_name = "MyExe::Precompile";
    const char* sub_name = "anon_sub0";
    SPVM_BASIC_TYPE* basic_type = SPVM_HASH_fetch(compiler->basic_type_symtable, package_name, strlen(package_name));
    assert(basic_type);
    SPVM_PACKAGE* package = basic_type->package;
    assert(package);
    SPVM_SUB* sub = SPVM_HASH_fetch(package->sub_symtable, sub_name, strlen(sub_name));
    assert(sub);
    sub->precompile_address = SPPRECOMPILE__MyExe__Precompile__anon_sub0;
  }
  { 
    const char* package_name = "MyExe::Precompile";
    const char* sub_name = "anon_sub1";
    SPVM_BASIC_TYPE* basic_type = SPVM_HASH_fetch(compiler->basic_type_symtable, package_name, strlen(package_name));
    assert(basic_type);
    SPVM_PACKAGE* package = basic_type->package;
    assert(package);
    SPVM_SUB* sub = SPVM_HASH_fetch(package->sub_symtable, sub_name, strlen(sub_name));
    assert(sub);
    sub->precompile_address = SPPRECOMPILE__MyExe__Precompile__anon_sub1;
  }
  { 
    const char* package_name = "TestCase::NativeAPI2";
    const char* sub_name = "mul";
    SPVM_BASIC_TYPE* basic_type = SPVM_HASH_fetch(compiler->basic_type_symtable, package_name, strlen(package_name));
    assert(basic_type);
    SPVM_PACKAGE* package = basic_type->package;
    assert(package);
    SPVM_SUB* sub = SPVM_HASH_fetch(package->sub_symtable, sub_name, strlen(sub_name));
    assert(sub);
    sub->native_address = SPNATIVE__TestCase__NativeAPI2__mul;
  }
  { 
    const char* package_name = "TestCase::NativeAPI2";
    const char* sub_name = "src_foo";
    SPVM_BASIC_TYPE* basic_type = SPVM_HASH_fetch(compiler->basic_type_symtable, package_name, strlen(package_name));
    assert(basic_type);
    SPVM_PACKAGE* package = basic_type->package;
    assert(package);
    SPVM_SUB* sub = SPVM_HASH_fetch(package->sub_symtable, sub_name, strlen(sub_name));
    assert(sub);
    sub->native_address = SPNATIVE__TestCase__NativeAPI2__src_foo;
  }
  { 
    const char* package_name = "TestCase::NativeAPI2";
    const char* sub_name = "src_bar";
    SPVM_BASIC_TYPE* basic_type = SPVM_HASH_fetch(compiler->basic_type_symtable, package_name, strlen(package_name));
    assert(basic_type);
    SPVM_PACKAGE* package = basic_type->package;
    assert(package);
    SPVM_SUB* sub = SPVM_HASH_fetch(package->sub_symtable, sub_name, strlen(sub_name));
    assert(sub);
    sub->native_address = SPNATIVE__TestCase__NativeAPI2__src_bar;
  }
    
  // Create env
  SPVM_ENV* env = SPVM_API_create_env(compiler);
  
  // Call begin blocks
  SPVM_API_call_begin_blocks(env);

  // Package
  int32_t sub_id = SPVM_API_get_sub_id(env, package_name, "main", "int(string[])");
  
  if (sub_id < 0) {
    return -1;
  }
  
  // Enter scope
  int32_t scope_id = env->enter_scope(env);
  
  // new byte[][args_length] object
  int32_t arg_type_basic_id = env->get_basic_type_id(env, "byte");
  void* cmd_args_obj = env->new_muldim_array(env, arg_type_basic_id, 1, argc);
  
  // Set command line arguments
  for (int32_t arg_index = 0; arg_index < argc; arg_index++) {
    void* cmd_arg_obj = env->new_string(env, argv[arg_index], strlen(argv[arg_index]));
    env->set_elem_object(env, cmd_args_obj, arg_index, cmd_arg_obj);
  }
  
  SPVM_VALUE stack[255];
  stack[0].oval = cmd_args_obj;
  
  // Run
  int32_t exception_flag = env->call_sub(env, sub_id, stack);
  
  int32_t status;
  if (exception_flag) {
    SPVM_API_print(env, env->exception_object);
    printf("\n");
    status = 255;
  }
  else {
    status = stack[0].ival;
  }
  
  // Leave scope
  env->leave_scope(env, scope_id);
  
  SPVM_API_free_env(env);

  // Free compiler
  SPVM_COMPILER_free(compiler);
  
  return status;
}