use 5.020;
use ExtUtils::MakeMaker;

use strict;
use warnings;
use Config;
use Getopt::Long;
use FindBin;
use lib "$FindBin::Bin/lib";
use Getopt::Long 'GetOptions';
use File::Find 'find';
use File::Temp;

use SPVM::Builder::Util;
use SPVM::Builder::Util::API;
use SPVM::Builder::Config;

# If you edit yacc/spvm_yacc.y, spvm_yacc.c must be re-generated by the following command before "perl Makefile.PL"
#   yacc/bison.sh

# Check supported environment
{
  # SPVM requires a 64-bit address space (pointer size must be 8 bytes)
  # This ensures support for large memory maps required by coroutines.
  my $ptrsize = $Config{ptrsize};
  if ($ptrsize < 8) {
    warn "SPVM doesn't support 32-bit address spaces (the pointer size is $ptrsize bytes). A 64-bit environment is required.\n";
    die "OS unsupported\n";
  }
  
  # Don't support nmake
  my $make = $Config{make};
  if ($make eq 'nmake') {
    warn "SPVM doesn't support nmake. The current \"make\" is \"$make\".\n";
    die "OS unsupported\n";
  }
  
  # Don't support C++ compilers that does not support -std=c++11 option.
  {
    my $config = SPVM::Builder::Config->new_cpp;
    
    my $cc = $config->cc;
    
    my $tmp_dir = File::Temp->newdir;
    
    my $cmd = "$cc -o $tmp_dir/cpp11.o -std=c++11 t/test_files/os_support/cpp11.cpp";
    
    unless (system($cmd) == 0) {
      warn "SPVM doesn't support C++ compilers that does not support -std=c++11 option.\n";
      die "OS unsupported\n";
    }
  }
}

my @defines;
my $optimize;

GetOptions(
  "define=s" => \@defines,
  "optimize=s" => \$optimize,
  "ccflags=s" => \my @ccflags,
  "lddlflags=s" => \my @lddlflags,
  'meta' => \my $meta,
  'no-build-spvm-modules' => \my $no_build_spvm_modules,
  'no-precompile-tests' => \my $no_precompile_tests,
  'devel' => \my $devel,
);

if ($meta) {
  $no_build_spvm_modules = 1;
  $no_precompile_tests = 1;
}

my $default_config = SPVM::Builder::Util::API::create_default_config;
my $std = $default_config->std;

# Macro
@defines = map { "-D$_" } @defines;

# optimize
unless ($optimize) {
  if ($devel) {
    $optimize = "-O0 -g";
  }
  
  unless ($optimize) {
    $optimize = $default_config->optimize;
  }
}

# ccflags
my $ccflags = $Config{ccflags};

$ccflags .= " -std=$std";

$ccflags .= " " . join(' ', @{$default_config->thread_ccflags}, @ccflags);

# lddlflags
my $lddlflags = $Config{lddlflags};

$lddlflags .= " " . join(' ', @{$default_config->thread_ldflags}, @lddlflags);

# I want to print warnings, but if gcc version is different, can't suppress no needed warning message.
# so I dicide not to print warning in release version
if ($ENV{SPVM_TEST_ENABLE_WARNINGS}) {
  $ccflags .= ' -Wall -Wextra -Wno-unused-label -Wno-unused-function -Wno-unused-label -Wno-unused-parameter -Wno-unused-variable -Wno-missing-field-initializers';
}

# INC
my $inc = '-Ilib/SPVM/Builder/include';

SPVM::Builder::Util::build_precompile_header_content_c_source();

# Source files
my $spvm_core_source_file_names = SPVM::Builder::Util::get_spvm_core_source_file_names();

my @spvm_csource_files = map { "lib/SPVM/Builder/src/$_" } @$spvm_core_source_file_names;

my @csource_files = ('SPVM.c', @spvm_csource_files);

# Header files(This is only used to resolve dependencies)
my @spvm_header_files = glob 'lib/SPVM/Builder/include/*.h';
my @header_files = ('ppport.h', @spvm_header_files);

my $cpus = SPVM::Builder::Util::API::get_cpu_count();
my $jobs = $cpus + 2;

WriteMakefile(
  NAME => 'SPVM',
  VERSION_FROM => 'lib/SPVM.pm',
  LICENSE => 'mit',
  ($] >= 5.005 ?     ## Add these new keywords supported since 5.005
    (ABSTRACT_FROM => 'lib/SPVM.pm', # retrieve abstract from module
     AUTHOR => 'Yuki Kimoto<kimoto.yuki@gmail.com>') : ()),
  CCFLAGS => $ccflags,
  OPTIMIZE => $optimize,
  LDDLFLAGS => $lddlflags,
  C => [@csource_files],
  H => [@header_files],
  OBJECT => '$(O_FILES)', # link all the C files too
  INC => $inc,
  test => {TESTS => 't/*.t t/*/*.t t/*/*/*.t t/*/*/*/*.t'},
  clean => {FILES => "SPVM.o lib/SPVM/Builder/src/*.o .spvm_build t/.spvm_build t/*/.spvm_build t/03_precompile t/test_files_tmp t/*/test_files_tmp t/exe/.spvm_build"},
  DEFINE => "@defines -o \$@",
  META_MERGE => {
    'meta-spec' => { version => 2 },
    resources => {
      repository => {
        type => 'git',
        url => 'https://github.com/yuki-kimoto/SPVM.git',
        web => 'https://github.com/yuki-kimoto/SPVM',
      },
      bugtracker => {
        web => "https://github.com/yuki-kimoto/SPVM/issues",
      },
    },
    x_parallel_make_jobs => $jobs,
    x_parallel_test_jobs => $jobs,
  },
  EXE_FILES => ['script/spvm', 'script/spvmcc', 'script/spvmdist', 'script/spvmdeps'],
  PREREQ_PM => {
    'ExtUtils::CBuilder' => '0.280236',
    'Time::Piece' => '1.12',
    'JSON::PP' => '2.27105',
  },
  TEST_REQUIRES => {
    'Test::More' => '0.92',
  },
  NORECURS => 1,
);

{
  print "\n";
  
  print "# Parallel build and test command:\n";
  print "MAKEFLAGS=j$jobs make && HARNESS_OPTIONS=j$jobs make test\n\n";
  
  print "# Parallel build and test command with --output-sync=target:\n";
  print "MAKEFLAGS=j$jobs make --output-sync=target && HARNESS_OPTIONS=j$jobs make test\n\n";

  print "# Release:\n";
  print "make disttest && make dist && make install\n\n";
}

# Add Build shared library make rule
sub MY::postamble {
  
  # The content of Makefile
  my $make_rule = '';
  
  unless ($no_build_spvm_modules) {
    require SPVM::Builder::Util::API;
    
    # For loading resources
    local @INC = @INC;
    unshift @INC, 'lib';
    
    my $options = {};
    if (defined $optimize) {
      $options->{optimize} = $optimize;
    }
    $options->{dependent_files} = ['SPVM.xs'];
    
    $options->{order_only_dependent_files} = ['$(INST_DYNAMIC)'];
    
    # Test create_make_rule for backward compatibility and practical testing
    $make_rule .= SPVM::Builder::Util::API::create_make_rule_native('Fn', $options);
    $make_rule .= SPVM::Builder::Util::API::create_make_rule_precompile('Fn', $options);
    
    # Native classes (excluding Fn)
    $options->{native_classes} = [
      'Format',
      'Sync::Mutex',
      'Native',
      'Native::Compiler',
      'Native::ClassFile',
      'Native::Runtime',
      'Native::Env',
      'Native::Stack',
      'Native::BasicType',
      'Native::ClassVar',
      'Native::Field',
      'Native::Method',
      'Native::Arg',
      'Native::MethodCall',
      'Native::API',
      'Native::Constant',
    ];

    # Precompile classes (excluding Fn)
    $options->{precompile_classes} = [
      'Format',
      'Sort',
      'Packer',
    ];
    
    # Specify text files containing additional classes for practical tests
    # These files contain newline-separated class names like 'Array'
    $options->{native_classes_file} = 'native_classes.txt';
    $options->{precompile_classes_file} = 'precompile_classes.txt';
    
    # Generate a single parallel make rule for the rest
    $make_rule .= SPVM::Builder::Util::API::create_make_rule_parallel($options);
    
  }
  
  unless ($no_precompile_tests) {
    # Create precompile tests
    my @test_files;
    find(
      sub {
        if (-f $File::Find::name) {
          my $rel_path = $File::Find::name;
          $rel_path =~ s|^\Q$FindBin::Bin/||;
          
          # Slip hidden files
          return if $rel_path =~ /[\/\\]\./;

          push @test_files, $rel_path;
        }
      },
      "$FindBin::Bin/t/02_vm"
    );
    my $test_files_str = join(' ', @test_files);
    
    my $time_stamp_file = "$FindBin::Bin/t/03_precompile/time_stamp.txt";
    
    $make_rule .= "dynamic :: $time_stamp_file\n";
    $make_rule .= "\t\$(NOECHO) \$(NOOP)\n\n";

    $make_rule .= "$time_stamp_file :: $test_files_str\n";
    $make_rule .= "\tperl t/utils/copy_vm_to_precompile.pl\n";
  }
  
  return $make_rule;
}

1;
