use 5.008_007;
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 SPVM::Builder::Util;

# 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 only support 64bit integer Perl
  my $ivsize = $Config{ivsize};
  if ($ivsize < 8) {
    warn "SPVM doesn't support the Perl which \"ivsize\" is lower than 8. The current \"ivsize\" is $ivsize.\n";
    die "OS unsupported\n";
  }
  
  # Currently SPVM doesn't support Alpine Linux
  my $myuname = $Config{myuname};
  if ($myuname =~ /\balpine\b/i) {
    warn "Currently SPVM doesn't support Alpine Linux because segmentation fault occurs due to incompatibility with glibc. If you want to use SPVM on Alpine Linux, please join SVPM project to fix the problems. The current \"myuname\" is \"$myuname\".\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";
  }
}

my @defines;
my $optimize;

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

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

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

# OPTIMIZE
unless ($optimize) {
  $optimize = '-O3';
}

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

# SPVM is compiled as C99, but some platforms can't compile XS which contains perl.h with pure C99(-std=c99). I select -std=gnu99.
$ccflags .= ' -std=gnu99';

# Fix compile warnings
{
  my $compiler_name;
  my $compiler_major_version;
  my $compiler_minor_version;
  
  my $config_gcc_version = $Config{gccversion};
  
  if ($config_gcc_version =~ /clang-(\d{2})(\d{2})\b/) {
    $compiler_name = 'clang';
    $compiler_major_version = $1;
    $compiler_minor_version = $2;
  }
  
  if (defined $compiler_name) {
    if ($compiler_name eq 'clang') {
      if ($compiler_major_version >= 12) {
        $ccflags .= ' -Wno-compound-token-split-by-macro';
      }
    }
  }
}

# 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';

# 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);

WriteMakefile(
    NAME              => 'SPVM',
    VERSION_FROM      => 'lib/SPVM.pm', # finds $VERSION
    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,
    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'},
    clean => {FILES => "SPVM.o lib/SPVM/Builder/src/*.o .spvm_build t/.spvm_build t/02_vm/.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",
            },            
        },
    },
    EXE_FILES => ['script/spvmcc', 'script/spvmdist'],
    PREREQ_PM => {
        'ExtUtils::CBuilder' => '0.280236',
        'Time::Piece' => '1.12'
    },
    TEST_REQUIRES => {
        'Test::More' => '0.92',
    },
    NORECURS => 1,
);

# 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;
    
    # Build native
    $make_rule .= SPVM::Builder::Util::API::create_make_rule_native('Fn');
    $make_rule .= SPVM::Builder::Util::API::create_make_rule_native('Array');
    $make_rule .= SPVM::Builder::Util::API::create_make_rule_native('Hash');
    $make_rule .= SPVM::Builder::Util::API::create_make_rule_native('Format');
    $make_rule .= SPVM::Builder::Util::API::create_make_rule_native('Time');
    $make_rule .= SPVM::Builder::Util::API::create_make_rule_native('Time::Info');
    
    $make_rule .= SPVM::Builder::Util::API::create_make_rule_native('Compiler');
    $make_rule .= SPVM::Builder::Util::API::create_make_rule_native('Runtime');
    $make_rule .= SPVM::Builder::Util::API::create_make_rule_native('Env');
    $make_rule .= SPVM::Builder::Util::API::create_make_rule_native('Stack');
    
    # Build precompile
    $make_rule .= SPVM::Builder::Util::API::create_make_rule_precompile('Fn');
    $make_rule .= SPVM::Builder::Util::API::create_make_rule_precompile('Array');
    $make_rule .= SPVM::Builder::Util::API::create_make_rule_precompile('Format');
    $make_rule .= SPVM::Builder::Util::API::create_make_rule_precompile('Hash');
    $make_rule .= SPVM::Builder::Util::API::create_make_rule_precompile('Sort');
  }
  
  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;