package SPVM::Builder::Exe;

use strict;
use warnings;
use Carp 'confess';
use Pod::Usage 'pod2usage';
use Config;

use SPVM::Builder;
use SPVM::Builder::CC;
use SPVM::Builder::Util;
use SPVM::Builder::Config::Exe;
use SPVM::Builder::Runtime;

use File::Spec;
use File::Find 'find';

use Getopt::Long 'GetOptions';

use ExtUtils::CBuilder;
use File::Copy 'copy', 'move';
use File::Path 'mkpath';
use DynaLoader;
use Scalar::Util 'weaken';

use File::Basename 'dirname', 'basename';

# Fields
sub builder {
  my $self = shift;
  if (@_) {
    $self->{builder} = $_[0];
    return $self;
  }
  else {
    return $self->{builder};
  }
}

sub module_dirs {
  my $self = shift;
  if (@_) {
    $self->builder->module_dirs($_[0]);
    return $self;
  }
  else {
    return $self->builder->module_dirs;
  }
}

sub class_name {
  my $self = shift;
  if (@_) {
    $self->{class_name} = $_[0];
    return $self;
  }
  else {
    return $self->{class_name};
  }
}

sub output_file {
  my $self = shift;
  if (@_) {
    $self->{output_file} = $_[0];
    return $self;
  }
  else {
    return $self->{output_file};
  }
}

sub quiet {
  my $self = shift;
  if (@_) {
    $self->{quiet} = $_[0];
    return $self;
  }
  else {
    return $self->{quiet};
  }
}

sub force {
  my $self = shift;
  if (@_) {
    $self->{force} = $_[0];
    return $self;
  }
  else {
    return $self->{force};
  }
}

sub config {
  my $self = shift;
  if (@_) {
    $self->{config} = $_[0];
    return $self;
  }
  else {
    return $self->{config};
  }
}

sub config_file {
  my $self = shift;
  if (@_) {
    $self->{config_file} = $_[0];
    return $self;
  }
  else {
    return $self->{config_file};
  }
}

sub runtime {
  my $self = shift;
  if (@_) {
    $self->{runtime} = $_[0];
    return $self;
  }
  else {
    return $self->{runtime};
  }
}

# Methods
sub new {
  my $class = shift;
  
  my $self = {@_};
  
  # Target class name
  my $class_name = $self->{class_name};
  unless (defined $class_name) {
    confess "Class name not specified";
  }
  
  # Excutable file name
  my $output_file = $self->{output_file};
  unless (defined $output_file) {
    $output_file = $class_name;
    $output_file =~ s/::/__/g;
    $self->{output_file} = $output_file;
  }
  
  # Build directory
  my $build_dir = delete $self->{build_dir};
  
  unless (defined $build_dir) {
    $build_dir = '.spvm_build';
  }

  # Module searching directries
  my $module_dirs = delete $self->{module_dirs};
  unless (defined $module_dirs) {
    $module_dirs = [];
  }
  
  # New SPVM::Builder object
  my $builder = SPVM::Builder->new(
    build_dir => $build_dir,
    module_dirs => $module_dirs
  );
  
  # Config file
  my $config_file = $self->{config_file};
  
  # Config
  my $config;
  if (defined $config_file) {
    $config = SPVM::Builder::Config::Exe->load_config($config_file);
    unless ($config->output_type eq 'exe') {
      confess "Config file \"$config_file\" is not the config to create the executable file";
    }
  }
  else {
    $config = SPVM::Builder::Config::Exe->new_gnu99(file_optional => 1);
  }
  $self->{config} = $config;
  
  $self->{builder} = $builder;
  
  return bless $self, $class;
}

sub get_dependent_resources {
  my ($self) = @_;
  
  my $config = $self->config;
  
  unless ($self->{finish_compile}) {
    $self->compile;
  }
  
  my $dependent_resources = [];
  
  my $builder = $self->builder;

  my $build_dir = $self->builder->build_dir;
  
  # Compiler for native module
  my $builder_cc_native = SPVM::Builder::CC->new(
    global_before_compile => $config->global_before_compile,
    build_dir => $build_dir,
    quiet => $self->quiet,
    force => $self->force,
  );
  
  my $class_names = $self->runtime->get_class_names;
  my $class_names_without_anon = [grep { $_ !~ /::anon::/ } @$class_names];
  my $all_object_files = [];
  for my $class_name (@$class_names_without_anon) {

    my $perl_class_name = "SPVM::$class_name";
    
    my $native_method_names = $self->runtime->get_method_names($class_name, 'native');
    if (@$native_method_names) {
      my $native_module_file = $self->runtime->get_module_file($class_name);
      my $native_dir = $native_module_file;
      
      $native_dir =~ s/\.spvm$//;
      $native_dir .= 'native';
      my $input_dir = SPVM::Builder::Util::remove_class_part_from_file($native_module_file, $perl_class_name);
      my $build_object_dir = SPVM::Builder::Util::create_build_object_path($self->builder->build_dir);
      mkpath $build_object_dir;

      # Module file
      my $module_file = $self->runtime->get_module_file($class_name);
      unless (defined $module_file) {
        my $config_file = SPVM::Builder::Util::get_config_file_from_class_name($class_name);
        if ($config_file) {
          $module_file = $config_file;
          $module_file =~ s/\.config$/\.spvm/;
        }
        else {
          confess "\"$module_file\" module is not loaded";
        }
      }
      my $config = $builder_cc_native->create_native_config_from_module_file($module_file);
      
      my $resource_names = $config->get_resource_names;
      for my $resource_name (@$resource_names) {
        my $resource = $config->get_resource($resource_name);
        
        my $resource_info = {
          class_name => $class_name,
          resource => $resource
        };
        
        push @$dependent_resources, $resource_info;
      }
    }
  }
  
  return $dependent_resources;
}

sub get_dependent_resource_lines {
  my ($self) = @_;
  
  my $dependent_resources = $self->get_dependent_resources;
  
  my @lines;
  for my $dependent_resource (@$dependent_resources) {
    my $class_name = $dependent_resource->{class_name};
    my $resource = $dependent_resource->{resource};
    my $resource_class_name = $resource->class_name;
    my $resource_mode = $resource->mode;
    my $resource_args = $resource->args || [];
    
    my $line = qq({class_name:"$class_name",resource_class_name:"$resource_class_name",resource_mode:);
    if (defined $resource_mode) {
      $line .= qq("$resource_mode");
    }
    else {
      $line .= 'undefined';
    }
    $line .= ",resource_args:[";
    
    $line .= join(",", map { qq("$_") } @$resource_args);
    
    $line .= "]}";
    
    push @lines, $line;
  }
  
  return \@lines;
}

sub compile {
  my ($self) = @_;

  # Builder
  my $builder = $self->builder;
  
  # Class name
  my $class_name = $self->{class_name};
  
  # Compile SPVM
  my $compiler = SPVM::Builder::Compiler->new(
    module_dirs => $builder->module_dirs
  );
  my $success = $compiler->compile($class_name, __FILE__, __LINE__);
  unless ($success) {
    $compiler->print_error_messages(*STDERR);
    exit(255);
  }
  my $runtime = $compiler->build_runtime;
  $self->runtime($runtime);
  
  $self->{finish_compile} = 1;
}

sub build_exe_file {
  my ($self) = @_;
  
  # Builder
  my $builder = $self->builder;
  
  # Class name
  my $class_name = $self->{class_name};
  
  # Build runtime
  unless ($self->{finish_compile}) {
    $self->compile;
  }
  
  # Config file
  my $module_file = $self->runtime->get_module_file($class_name);

  # Object files
  my $object_files = [];

  # Compile SPVM core source files
  my $spvm_core_objects = $self->compile_core_sources;
  push @$object_files, @$spvm_core_objects;
  
  my $config = $self->config;
  
  my $no_precompile = $config->no_precompile;

  unless ($no_precompile) {
    # Create precompile C source_files
    $self->build_class_sources;
    
    # Compile precompile C source_files
    my $precompile_object_files = $self->compile_precompile_sources;
    push @$object_files, @$precompile_object_files;
  }

  # Compile native source files
  my $native_object_files = $self->compile_native_sources;
  push @$object_files, @$native_object_files;
  
  # Create bootstrap C source
  $self->create_bootstrap_source;

  # Compile bootstrap C source
  my $bootstrap_object_file = $self->compile_bootstrap_source;
  push @$object_files, $bootstrap_object_file;
  
  # Build directory
  my $build_dir = $self->builder->build_dir;
  mkpath $build_dir;

  # Link and generate executable file
  my $cc_linker = SPVM::Builder::CC->new(
    global_before_compile => $config->global_before_compile,
    build_dir => $build_dir,
    quiet => $self->quiet,
    force => $self->force,
  );
  my $options = {
    output_file => $self->{output_file},
    config => $self->config,
    category => 'native',
  };

  $cc_linker->link($class_name, $object_files, $options);
}

sub create_source_file {
  my ($self, $options) = @_;
  
  # Config
  my $config = $self->config;
  
  my $input_files = $options->{input_files};
  my $output_file = $options->{output_file};
  my $create_cb = $options->{create_cb};
  
  my $config_dependent_files = $config->dependent_files;
  my $need_generate_input_files = [@$input_files, @$config_dependent_files];
  my $need_generate = SPVM::Builder::Util::need_generate({
    force => $self->force || $config->force,
    output_file => $output_file,
    input_files => $need_generate_input_files,
  });
  
  if ($need_generate) {
    $create_cb->();
  }
}

sub compile_source_file {
  my ($self, $options) = @_;

  # Config
  my $config = $self->config;
  
  my $options_ccflags = $options->{ccflags};
  $options_ccflags = [] unless defined $options_ccflags;
  
  if (@$options_ccflags) {
    $config->add_ccflags(@$options_ccflags);
  }

  my $source_file = $options->{source_file};
  my $output_file = $options->{output_file};
  my $depend_files = $options->{depend_files};
  unless ($depend_files) {
    $depend_files = [];
  }
  
  my $config_dependent_files = $config->dependent_files;
  my $need_generate_input_files = [$source_file, @$depend_files, @$config_dependent_files];
  my $need_generate = SPVM::Builder::Util::need_generate({
    force => $self->force || $config->force,
    output_file => $output_file,
    input_files => $need_generate_input_files,
  });


  my $builder = $self->builder;

  # Build directory
  my $build_dir = $self->builder->build_dir;
  
  # Compile command
  my $builder_cc = SPVM::Builder::CC->new(
    global_before_compile => $config->global_before_compile,
    build_dir => $build_dir,
    quiet => $self->quiet,
    force => $self->force,
  );
  my $compile_info = $builder_cc->create_compile_command_info({config => $config, output_file => $output_file, source_file => $source_file});
    
  if ($need_generate) {
    $builder_cc->compile_single($compile_info, $config);
  }
  
  my $compile_info_cc = $compile_info->{cc};
  my $compile_info_ccflags = $compile_info->{ccflags};
  my $object_file_info = SPVM::Builder::ObjectFileInfo->new(
    compile_info => $compile_info,
  );
  
  return $object_file_info;
}

sub create_bootstrap_header_source {
  my ($self) = @_;

  # Config
  my $config = $self->config;

  # Builder
  my $builder = $self->builder;

  # Class name
  my $class_name = $self->class_name;

  # Class names
  my $class_names = $self->runtime->get_class_names;
  
  my $class_names_without_anon = [grep { $_ !~ /::anon::/ } @$class_names];

  my $source = '';

  $source .= <<'EOS';

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

// Only used for _setmode function and _O_BINARY
#include <fcntl.h>

#include "spvm_native.h"

EOS
  
  my $no_precompile = $config->no_precompile;
  
  unless ($no_precompile) {
    $source .= "// precompile functions declaration\n";
    for my $class_name (@$class_names) {
      my $precompile_method_names = $self->runtime->get_method_names($class_name, 'precompile');
      for my $method_name (@$precompile_method_names) {
        my $class_cname = $class_name;
        $class_cname =~ s/::/__/g;
        $source .= <<"EOS";
int32_t SPVMPRECOMPILE__${class_cname}__$method_name(SPVM_ENV* env, SPVM_VALUE* stack);
EOS
      }
    }

    $source .= "static int32_t* SPVM_BOOTSTRAP_create_bootstrap_set_precompile_method_addresses(SPVM_ENV* env);\n";

    $source .= <<"EOS";
static int32_t* SPVM_BOOTSTRAP_set_precompile_method_address(SPVM_ENV* env, const char* class_name, const char* method_name, void* precompile_address) {
  int32_t method_id = env->api->runtime->get_method_id_by_name(env->runtime, class_name, method_name);
  env->api->runtime->set_precompile_method_address(env->runtime, method_id, precompile_address);
}
EOS
  }
  
  $source .= "// native functions declaration\n";
  for my $class_cname (@$class_names_without_anon) {
    my $native_method_names = $self->runtime->get_method_names($class_cname, 'native');
    for my $method_name (@$native_method_names) {
      my $class_cname = $class_cname;
      $class_cname =~ s/::/__/g;
      $source .= <<"EOS";
int32_t SPVM__${class_cname}__$method_name(SPVM_ENV* env, SPVM_VALUE* stack);
EOS
    }
  }

  $source .= "static int32_t* SPVM_BOOTSTRAP_create_bootstrap_set_native_method_addresses(SPVM_ENV* env);\n";

  $source .= "static int32_t* SPVM_BOOTSTRAP_get_runtime_codes();\n";

  $source .= <<"EOS";
static int32_t* SPVM_BOOTSTRAP_set_native_method_address(SPVM_ENV* env, const char* class_name, const char* method_name, void* native_address) {
  int32_t method_id = env->api->runtime->get_method_id_by_name(env->runtime, class_name, method_name);
  env->api->runtime->set_native_method_address(env->runtime, method_id, native_address);
}
EOS

  return $source;
}

sub create_bootstrap_main_func_source {
  my ($self) = @_;

  # Builder
  my $builder = $self->builder;

  # Class name
  my $class_name = $self->class_name;

  # Class names
  my $class_names = $self->runtime->get_class_names;
  my $class_names_without_anon = [grep { $_ !~ /::anon::/ } @$class_names];

  my $source = '';

  $source .= <<"EOS";

int32_t main(int32_t command_args_length, const char *command_args[]) {

  // Binary mode in all systems
#ifdef _WIN32
  _setmode(fileno(stdout), _O_BINARY);
  _setmode(fileno(stderr), _O_BINARY);
  _setmode(fileno(stdin), _O_BINARY);
#endif
  
  SPVM_ENV* env = SPVM_NATIVE_new_env_prepared();
  
  // Set the program name and the command line arguments
  {
    SPVM_VALUE* my_stack = env->new_stack(env);
    
    // Enter scope
    int32_t scope_id = env->enter_scope(env, my_stack);
    
    // Program name - string
    void* obj_program_name = env->new_string(env, my_stack, command_args[0], strlen(command_args[0]));
    
    // ARGV - string[]
    void* obj_argv = env->new_object_array(env, my_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, my_stack, command_args[arg_index], strlen(command_args[arg_index]));
      env->set_elem_object(env, my_stack, obj_argv, arg_index - 1, obj_arg);
    }
    
    // Base time
    int64_t base_time = time(NULL);
    
    // Set command info
    {
      int32_t e;
      e = env->set_command_info_program_name(env, obj_program_name);
      assert(e == 0);
      e = env->set_command_info_argv(env, obj_argv);
      assert(e == 0);
      e = env->set_command_info_base_time(env, base_time);
      assert(e == 0);
    }
    // Leave scope
    env->leave_scope(env, my_stack, scope_id);
    
    env->free_stack(env, my_stack);
  }

  // Call INIT blocks
  env->call_init_blocks(env);
  
  SPVM_VALUE* stack = env->new_stack(env);
  
  // Class name
  const char* class_name = "$class_name";
  
  // Class
  int32_t method_id = env->get_class_method_id(env, stack, class_name, "main");
  
  if (method_id < 0) {
    fprintf(stderr, "The class method %s->main is not defined\\n", class_name);
    return -1;
  }
  
  // Run
  int32_t args_stack_length = 0;
  int32_t error = env->call_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;
  }
  
  // Free stack
  env->free_stack(env, stack);
  
  // Cleanup global vars
  env->cleanup_global_vars(env);
  
  // Free env
  env->free_env_prepared(env);
  
  return status;
}
EOS

  return $source;
}

sub create_bootstrap_get_runtime_codes_func_source {
  my ($self) = @_;

  # Builder
  my $builder = $self->builder;

  my $runtime_codes = SPVM::Builder::Runtime->get_runtime_codes($self->runtime);
  my $runtime_codes_length = @$runtime_codes;
  my $source = '';
  
  my $runtime_codes_str = join(",", @$runtime_codes);

  $source .= "static int32_t SPVM_BOOTSTRAP_runtime_codes[$runtime_codes_length] = {$runtime_codes_str};\n";

  $source .= <<"EOS";
static int32_t* SPVM_BOOTSTRAP_get_runtime_codes() {
  return SPVM_BOOTSTRAP_runtime_codes;
}
EOS
  
  return $source;
}

sub create_bootstrap_new_env_prepared_func_source {
  my ($self) = @_;

  # Config
  my $config = $self->config;

  # Builder
  my $builder = $self->builder;

  # Class name
  my $class_name = $self->class_name;

  # Class names
  my $class_names = $self->runtime->get_class_names;
  my $class_names_without_anon = [grep { $_ !~ /::anon::/ } @$class_names];

  my $source = '';
  
  my $no_precompile = $config->no_precompile;
  
  my $set_precompile_method_addresses_source = '';
  unless ($no_precompile) {
    $set_precompile_method_addresses_source = "SPVM_BOOTSTRAP_create_bootstrap_set_precompile_method_addresses(env);";
  }
  $source .= <<"EOS";
SPVM_ENV* SPVM_NATIVE_new_env_prepared() {
EOS

  $source .= <<"EOS";

  // Create env
  SPVM_ENV* env = SPVM_NATIVE_new_env_raw();

  // New runtime
  void* runtime = env->api->runtime->new_object(env);
  
  // Runtime allocator
  void* runtime_allocator = env->api->runtime->get_allocator(runtime);
  
  // Create SPVM 32bit codes
  int32_t* runtime_codes = SPVM_BOOTSTRAP_get_runtime_codes();
  
  // Build runtime
  env->api->runtime->build(runtime, runtime_codes);

  // Set runtime information
  env->runtime = runtime;

  // Initialize env
  env->init_env(env);

  // Set precompile method addresses
  $set_precompile_method_addresses_source
  
  // Set native method addresses
  SPVM_BOOTSTRAP_create_bootstrap_set_native_method_addresses(env);
  
  return env;
}
EOS
  
  return $source;
}

sub create_bootstrap_set_precompile_method_addresses_func_source {
  my ($self) = @_;

  # Builder
  my $builder = $self->builder;

  # Class names
  my $class_names = $self->runtime->get_class_names;

  my $source = '';

  $source .= "static int32_t* SPVM_BOOTSTRAP_create_bootstrap_set_precompile_method_addresses(SPVM_ENV* env){\n";

  for my $class_name (@$class_names) {
    my $class_cname = $class_name;
    $class_cname =~ s/::/__/g;
    
    my $precompile_method_names = $self->runtime->get_method_names($class_name, 'precompile');
    
    for my $precompile_method_name (@$precompile_method_names) {
      $source .= <<"EOS";
  SPVM_BOOTSTRAP_set_precompile_method_address(env, "$class_name", "$precompile_method_name", &SPVMPRECOMPILE__${class_cname}__$precompile_method_name);
EOS
    }
  }

  $source .= "}\n";
  
  return $source;
}

sub create_bootstrap_set_native_method_addresses_func_source {
  my ($self) = @_;

  # Builder
  my $builder = $self->builder;

  # Class names
  my $class_names = $self->runtime->get_class_names;

  my $source = '';

  $source .= "static int32_t* SPVM_BOOTSTRAP_create_bootstrap_set_native_method_addresses(SPVM_ENV* env){\n";

  for my $class_name (@$class_names) {
    my $class_cname = $class_name;
    $class_cname =~ s/::/__/g;
    
    my $native_method_names = $self->runtime->get_method_names($class_name, 'native');
    
    for my $native_method_name (@$native_method_names) {
      $source .= <<"EOS";
  SPVM_BOOTSTRAP_set_native_method_address(env, "$class_name", "$native_method_name", &SPVM__${class_cname}__$native_method_name);
EOS
    }
  }

  $source .= "}\n";
  
  return $source;
}

sub create_bootstrap_source {
  my ($self) = @_;
  
  # Config
  my $config = $self->config;

  # Builder
  my $builder = $self->builder;
  
  # Class name
  my $class_name = $self->class_name;
  
  # Class names
  my $class_names = $self->runtime->get_class_names;
  my $class_names_without_anon = [grep { $_ !~ /::anon::/ } @$class_names];
  
  # Module files - Input
  my $module_files = [];
  for my $class_name (@$class_names_without_anon) {
    my $module_file = $self->runtime->get_module_file($class_name);
    push @$module_files, $module_file;
  }
  
  # Source file - Output
  my $build_src_dir = SPVM::Builder::Util::create_build_src_path($self->builder->build_dir);
  my $target_perl_class_name = "SPVM::$class_name";
  my $bootstrap_base = $target_perl_class_name;
  $bootstrap_base =~ s|::|/|g;
  my $bootstrap_source_file = "$build_src_dir/$bootstrap_base.boot.c";
  
  my $no_precompile = $config->no_precompile;

  # Source creating callback
  my $create_cb = sub {
    
    my $bootstrap_source = '';
    
    # Header
    $bootstrap_source .= $self->create_bootstrap_header_source;
    
    # main function
    $bootstrap_source .= $self->create_bootstrap_main_func_source;

    # SPVM_NATIVE_new_env_prepared function
    $bootstrap_source .= $self->create_bootstrap_new_env_prepared_func_source;

    # Set precompile method addresses function
    unless ($no_precompile) {
      $bootstrap_source .= $self->create_bootstrap_set_precompile_method_addresses_func_source;
    }

    # Set native method addresses function
    $bootstrap_source .= $self->create_bootstrap_set_native_method_addresses_func_source;

    # get_runtime_codes function
    $bootstrap_source .= $self->create_bootstrap_get_runtime_codes_func_source;

    # Build source directory
    my $build_src_dir = SPVM::Builder::Util::create_build_src_path($self->builder->build_dir);
    mkpath $build_src_dir;
    mkpath dirname $bootstrap_source_file;
    
    open my $bootstrap_source_fh, '>', $bootstrap_source_file
      or die "Can't open file $bootstrap_source_file:$!";

    print $bootstrap_source_fh $bootstrap_source;
  };
  
  # Create source file
  $self->create_source_file({
    input_files => [@$module_files, __FILE__],
    output_file => $bootstrap_source_file,
    create_cb => $create_cb,
  });
}

sub compile_bootstrap_source {
  my ($self) = @_;
  
  # Target class name
  my $class_name = $self->class_name;
  
  my $target_perl_class_name = "SPVM::$class_name";
  
  # Compile source files
  my $class_name_rel_file = SPVM::Builder::Util::convert_class_name_to_rel_file($target_perl_class_name);
  my $object_file = SPVM::Builder::Util::create_build_object_path($self->builder->build_dir, "$class_name_rel_file.boot.o");
  my $source_file = SPVM::Builder::Util::create_build_src_path($self->builder->build_dir, "$class_name_rel_file.boot.c");
  
  # Create directory for object file output
  mkdir dirname $object_file;
  
  # Compile
  my $object_file_info = $self->compile_source_file({source_file => $source_file, output_file => $object_file});
  
  return $object_file_info;
}

sub compile_core_sources {
  my ($self) = @_;

  # Config
  my $config = $self->config;

  # SPVM src directory
  my $builder_src_dir = $config->builder_src_dir;

  # SPVM runtime source files
  my $no_compiler_api = $config->no_compiler_api;
  my $spvm_runtime_src_base_names;
  if ($no_compiler_api) {
    $spvm_runtime_src_base_names = SPVM::Builder::Util::get_spvm_core_common_source_file_names();
  }
  else {
    $spvm_runtime_src_base_names = SPVM::Builder::Util::get_spvm_core_source_file_names();
  }
  my @spvm_core_source_files = map { "$builder_src_dir/$_" } @$spvm_runtime_src_base_names;

  # Object dir
  my $output_dir = SPVM::Builder::Util::create_build_object_path($self->builder->build_dir);
  mkpath $output_dir;
  
  # Compile source files
  my $object_file_infos = [];
  for my $src_file (@spvm_core_source_files) {
    # Object file
    my $object_file = "$output_dir/" . basename($src_file);
    $object_file =~ s/\.c$//;
    $object_file .= '.o';

    my $no_compiler_api = $config->no_compiler_api;
    my $ccflags = [];
    if ($no_compiler_api) {
      push @$ccflags, '-DSPVM_NO_COMPILER_API';
    }
    
    my $object_file_info = $self->compile_source_file({
      ccflags => $ccflags,
      source_file => $src_file,
      output_file => $object_file,
    });
    push @$object_file_infos, $object_file_info;
  }
  
  return $object_file_infos;
}

sub build_class_sources {
  my ($self) = @_;

  my $config = $self->config;

  my $builder = $self->builder;

  # Build directory
  my $build_dir = $self->builder->build_dir;
  mkpath $build_dir;

  # Build precompile classes
  my $builder_cc_precompile = SPVM::Builder::CC->new(
    global_before_compile => $config->global_before_compile,
    build_dir => $build_dir,
    quiet => $self->quiet,
    force => $self->force,
  );

  my $class_names = $self->runtime->get_class_names;
  my $class_names_without_anon = [grep { $_ !~ /::anon::/ } @$class_names];
  for my $class_name (@$class_names_without_anon) {
    my $precompile_method_names = $self->runtime->get_method_names($class_name, 'precompile');
    if (@$precompile_method_names) {
      
      my $build_src_dir = SPVM::Builder::Util::create_build_src_path($self->builder->build_dir);
      mkpath $build_src_dir;
      my $module_file = $self->runtime->get_module_file($class_name);
      my $precompile_source = $self->runtime->build_precompile_class_source($class_name);
      $builder_cc_precompile->build_precompile_class_source_file(
        $class_name,
        {
          output_dir => $build_src_dir,
          precompile_source => $precompile_source,
          module_file => $module_file,
        }
      );
    }
  }
}

sub compile_precompile_sources {
  my ($self) = @_;

  my $config = $self->config;
  
  # Builer
  my $builder = $self->builder;
  
  # Build directory
  my $build_dir = $self->builder->build_dir;
  
  # Build precompile classes
  my $builder_cc_precompile = SPVM::Builder::CC->new(
    global_before_compile => $config->global_before_compile,
    build_dir => $build_dir,
    quiet => $self->quiet,
    force => $self->force,
  );
  
  my $class_names = $self->runtime->get_class_names;
  my $class_names_without_anon = [grep { $_ !~ /::anon::/ } @$class_names];
  my $object_files = [];
  for my $class_name (@$class_names_without_anon) {
    my $precompile_method_names = $self->runtime->get_method_names($class_name, 'precompile');
    if (@$precompile_method_names) {
      my $build_src_dir = SPVM::Builder::Util::create_build_src_path($self->builder->build_dir);
      mkpath $build_src_dir;
      
      my $build_object_dir = SPVM::Builder::Util::create_build_object_path($self->builder->build_dir);
      mkpath $build_object_dir;
      
      my $config = SPVM::Builder::Config->new_gnu99(file_optional => 1);
      my $precompile_object_files = $builder_cc_precompile->compile(
        $class_name,
        {
          input_dir => $build_src_dir,
          output_dir => $build_object_dir,
          config => $config,
          category => 'precompile',
        }
      );
      push @$object_files, @$precompile_object_files;
    }
  }
  
  return $object_files;
}

sub compile_native_sources {
  my ($self) = @_;

  my $config = $self->config;
  
  my $builder = $self->builder;

  # Build directory
  my $build_dir = $self->builder->build_dir;
  mkpath $build_dir;

  # Compiler for native module
  my $builder_cc_native = SPVM::Builder::CC->new(
    global_before_compile => $config->global_before_compile,
    build_dir => $build_dir,
    quiet => $self->quiet,
    force => $self->force,
  );
  
  my $class_names = $self->runtime->get_class_names;
  my $class_names_without_anon = [grep { $_ !~ /::anon::/ } @$class_names];
  my $all_object_files = [];
  for my $class_name (@$class_names_without_anon) {

    my $perl_class_name = "SPVM::$class_name";
    
    my $native_method_names = $self->runtime->get_method_names($class_name, 'native');
    if (@$native_method_names) {
      my $native_module_file = $self->runtime->get_module_file($class_name);
      my $native_dir = $native_module_file;
      
      $native_dir =~ s/\.spvm$//;
      $native_dir .= 'native';
      my $input_dir = SPVM::Builder::Util::remove_class_part_from_file($native_module_file, $perl_class_name);
      my $build_object_dir = SPVM::Builder::Util::create_build_object_path($self->builder->build_dir);
      mkpath $build_object_dir;

      # Module file
      my $module_file = $self->runtime->get_module_file($class_name);
      unless (defined $module_file) {
        my $config_file = SPVM::Builder::Util::get_config_file_from_class_name($class_name);
        if ($config_file) {
          $module_file = $config_file;
          $module_file =~ s/\.config$/\.spvm/;
        }
        else {
          confess "\"$module_file\" module is not loaded";
        }
      }
      my $config = $builder_cc_native->create_native_config_from_module_file($module_file);
      
      my $include_dirs = [];
      my $exe_config = $self->config;
      my $resource_names = $exe_config->get_resource_names;
      for my $resource_name (@$resource_names) {
        my $resource = $exe_config->get_resource($resource_name);
        my $resource_include_dir = $resource->config->own_include_dir;
        push @$include_dirs, $resource_include_dir;
      }
      
      my $object_files = $builder_cc_native->compile(
        $class_name,
        {
          input_dir => $input_dir,
          output_dir => $build_object_dir,
          config => $config,
          category => 'native',
          no_use_resource => 1,
          include_dirs => $include_dirs,
        }
      );
      push @$all_object_files, @$object_files;
    }
  }
  
  return $all_object_files;
}

1;

=head1 Name

SPVM::Builder::Exe - Creating Executable File

=head1 Copyright & License

Copyright (c) 2023 Yuki Kimoto

MIT License