#!/usr/bin/env perl

use strict;
use warnings;
use diagnostics;

use FindBin;
use lib "$FindBin::Bin/Modules";

use Util;
use TUI;

use POSIX qw(strftime);
use feature 'say';

my $local_root            = '/home/christoph/Notes/TU/MastersThesis/FailNix';
my $local_builds_dir      = "$local_root/builds";
my $local_experiments_dir = "$local_root/targets/wasm-module";
my $compile_pl            = "$local_root/scripts/compile.pl";

sub compile {
    my ( $module, $target, $mode ) = @_;
    say "Running: WAMR_USE_AOT_IN_TEXT=$ENV{WAMR_USE_AOT_IN_TEXT}",
      " WAMR_USE_MMAP_IN_TEXT=$ENV{WAMR_USE_MMAP_IN_TEXT}",
      " WAMR_USE_XIP=$ENV{WAMR_USE_XIP}",
      " WAMR_USE_ALLOCATOR=$ENV{WAMR_USE_ALLOCATOR}",
      " WAMR_USE_GLOBAL_HEAP_IN_TEXT=$ENV{WAMR_USE_GLOBAL_HEAP_IN_TEXT}",
      " WAMR_USE_RUNTIME_POOL_IN_TEXT=$ENV{WAMR_USE_RUNTIME_POOL_IN_TEXT}",
      " WAMR_USE_LINEAR_POOL_IN_TEXT=$ENV{WAMR_USE_LINEAR_POOL_IN_TEXT}",
      " compile.pl $module $target $mode";

    system(
        "perl $compile_pl $module $target $mode > $local_root/build.log 2>&1")
      == 0
      or die "Build failed\n";
    sleep(1);
}

# Find and select experiments
my @experiments = map { s/\.cpp//r } Util::find_files($local_experiments_dir);
my @selected_experiments =
  TUI::select_from_list( "Select Experiments to Build", 1, @experiments );
die "No experiment selected" unless @selected_experiments;

# Select targets
my @targets = ( "fail", "linux", "linux-baremetal" );
my @selected_targets =
  TUI::select_from_list( "Select Target Platforms", 1, @targets );
die "No target selected" unless @selected_targets;

# Select modes
my @modes = ( "c", "aot", "interp" );
my @selected_modes =
  TUI::select_from_list( "Select Execution Modes", 1, @modes );
die "No mode selected" unless @selected_modes;

# ========================================================================================= #
# Select WAMR allocator variant
# ========================================================================================= #

my @allocator_variants = (
    "Pool allocator (Alloc_With_Pool)",
    "Allocator with usage (Alloc_With_Allocator)",
);
my $selected_allocator_variant = $allocator_variants[1];
if ( grep { $_ eq "aot" or $_ eq "interp" } @selected_modes ) {
    ($selected_allocator_variant) =
      TUI::select_from_list( "Select WAMR Allocator Variant",
        0, @allocator_variants );
    die "No allocator variant selected" unless $selected_allocator_variant;
}
local $ENV{WAMR_USE_ALLOCATOR} =
  ( $selected_allocator_variant eq $allocator_variants[1] )
  ? "true"
  : "false";

# ========================================================================================= #
# Select XIP variant
# ========================================================================================= #

my @xip_variants = ( "Compile AOT with --xip", "Compile AOT without --xip" );
my $selected_xip_variant = $xip_variants[0];
if ( grep { $_ eq "aot" } @selected_modes ) {
    ($selected_xip_variant) =
      TUI::select_from_list( "Select WAMRC XIP Variant", 0, @xip_variants );
    die "No XIP variant selected" unless $selected_xip_variant;
}
local $ENV{WAMR_USE_XIP} =
  ( $selected_xip_variant eq $xip_variants[0] )
  ? "true"
  : "false";

# ========================================================================================= #
# Select .text.wamr_mmap variant
# ========================================================================================= #

my @mmap_variants = (
    "Place mmap_space in .text.wamr_mmap",
    "Let the linker decide where mmap_space is located"
);
my $selected_mmap_variant = $mmap_variants[1];
if ( grep { $_ eq "aot" or $_ eq "interp" } @selected_modes ) {
    ($selected_mmap_variant) =
      TUI::select_from_list( "Select WAMR Mmap.Text Variant",
        0, @mmap_variants );
    die "No variant selected" unless $selected_mmap_variant;
}
local $ENV{WAMR_USE_MMAP_IN_TEXT} =
  ( $selected_mmap_variant eq $mmap_variants[0] )
  ? "true"
  : "false";

# ========================================================================================= #
# Select .text.wamr_aot variant
# ========================================================================================= #

my @aot_section_variants = (
    "Place AOT array in .text.wamr_aot",
    "Let the linker decide where the AOT array is located",
);
my $selected_aot_variant = $aot_section_variants[1];
if ( grep { $_ eq "aot" } @selected_modes ) {
    ($selected_aot_variant) =
      TUI::select_from_list( "Select WAMR Array.Text Variant",
        0, @aot_section_variants );
    die "No AOT section variant selected" unless $selected_aot_variant;
}
local $ENV{WAMR_USE_AOT_IN_TEXT} =
  ( $selected_aot_variant eq $aot_section_variants[0] )
  ? "true"
  : "false";

# ========================================================================================= #
# Select .text.wamr_global_heap variant
# ========================================================================================= #

my @global_heap_variants = (
    "Place pool allocator's global_heap in .text.wamr_global_heap",
    "Let the linker decide where global_heap is located"
);
my $selected_global_heap_variant = $global_heap_variants[1];
if ( grep { $_ eq "aot" or $_ eq "interp" } @selected_modes
    and $selected_allocator_variant eq $allocator_variants[0] )
{
    ($selected_global_heap_variant) =
      TUI::select_from_list( "Select WAMR Global Heap Variant",
        0, @global_heap_variants );
    die "No global heap variant selected" unless $selected_global_heap_variant;
}
local $ENV{WAMR_USE_GLOBAL_HEAP_IN_TEXT} =
  ( $selected_global_heap_variant eq $global_heap_variants[0] )
  ? "true"
  : "false";

# ========================================================================================= #
# Select .text.wamr_runtime_pool variant
# ========================================================================================= #

my @runtime_pool_variants = (
    "Place usage allocator's runtime_pool in .text.wamr_runtime_pool",
    "Let the linker decide where runtime_pool is located"
);
my $selected_runtime_pool_variant = $runtime_pool_variants[1];
if ( grep { $_ eq "aot" or $_ eq "interp" } @selected_modes
    and $selected_allocator_variant eq $allocator_variants[1] )
{
    ($selected_runtime_pool_variant) =
      TUI::select_from_list( "Select WAMR Runtime Pool Variant",
        0, @runtime_pool_variants );
    die "No runtime pool variant selected"
      unless $selected_runtime_pool_variant;
}
local $ENV{WAMR_USE_RUNTIME_POOL_IN_TEXT} =
  ( $selected_runtime_pool_variant eq $runtime_pool_variants[0] )
  ? "true"
  : "false";

# ========================================================================================= #
# Select .text.wamr_linear_pool variant
# ========================================================================================= #

my @linear_pool_variants = (
    "Place usage allocator's linear_pool in .text.wamr_linear_pool",
    "Let the linker decide where linear_pool is located"
);
my $selected_linear_pool_variant = $linear_pool_variants[1];
if ( grep { $_ eq "aot" or $_ eq "interp" } @selected_modes
    and $selected_allocator_variant eq $allocator_variants[1] )
{
    ($selected_linear_pool_variant) =
      TUI::select_from_list( "Select WAMR Linear Pool Variant",
        0, @linear_pool_variants );
    die "No linear pool variant selected" unless $selected_linear_pool_variant;
}
local $ENV{WAMR_USE_LINEAR_POOL_IN_TEXT} =
  ( $selected_linear_pool_variant eq $linear_pool_variants[0] )
  ? "true"
  : "false";

# ========================================================================================= #
# Select FAIL catch flags (written to runner_flags in each build dir)
# ========================================================================================= #

# NOTE: The runner will prefix "-Wf," to each flag
my %catch_flag_map = (
    "--catch-outer"     => "--catch-outerspace",
    "--catch-text"      => "--catch-write-textsegment",
    "--wamr-exceptions" => "--group1-marker=fail_marker_group1",
);
my @selected_catch_flags;
if ( grep { $_ eq "fail" } @selected_targets ) {
    @selected_catch_flags =
      TUI::select_from_list( "Select FAIL Flags", 1,
        sort keys %catch_flag_map );
}

# ========================================================================================= #
# Build everything
# ========================================================================================= #

system( "mkdir", "-p", "$local_builds_dir" );
foreach my $experiment (@selected_experiments) {
    foreach my $target (@selected_targets) {
        foreach my $mode (@selected_modes) {

            # Remove --wamr-exceptions for C builds, it is WAMR-specific
            my @build_catch_flags =
              $mode eq "c"
              ? grep { $_ ne "--wamr-exceptions" } @selected_catch_flags
              : @selected_catch_flags;

            my $allocator_info = "";
            if ( $mode eq "aot" || $mode eq "interp" ) {
                $allocator_info =
                  $selected_allocator_variant eq $allocator_variants[0]
                  ? "alloc_pool"
                  : "alloc_usage";
            }
            my $xip_info =
              (      $mode eq "aot"
                  && $selected_xip_variant
                  && $selected_xip_variant eq $xip_variants[0] )
              ? "xip"
              : "";
            my $aot_info =
              (      $mode eq "aot"
                  && $selected_aot_variant
                  && $selected_aot_variant eq $aot_section_variants[0] )
              ? "wamr_aot"
              : "";
            my $mmap_info =
              ( ( $mode eq "aot" || $mode eq "interp" )
                  && $selected_mmap_variant eq $mmap_variants[0] )
              ? "wamr_mmap"
              : "";
            my $global_heap_info =
              (      ( $mode eq "aot" || $mode eq "interp" )
                  && $selected_global_heap_variant
                  && $selected_global_heap_variant eq $global_heap_variants[0] )
              ? "wamr_global_heap"
              : "";
            my $runtime_pool_info =
              (      ( $mode eq "aot" || $mode eq "interp" )
                  && $selected_runtime_pool_variant
                  && $selected_runtime_pool_variant eq
                  $runtime_pool_variants[0] )
              ? "wamr_runtime_pool"
              : "";
            my $linear_pool_info =
              (      ( $mode eq "aot" || $mode eq "interp" )
                  && $selected_linear_pool_variant
                  && $selected_linear_pool_variant eq $linear_pool_variants[0] )
              ? "wamr_linear_pool"
              : "";
            my $flags_info = join " ", @build_catch_flags;

            my $info_str = join " ",
              grep { length } (
                $mode,             $aot_info,         $mmap_info,
                $allocator_info,   $global_heap_info, $runtime_pool_info,
                $linear_pool_info, $xip_info,         $flags_info
              );

            # Build experiment
            my $date = strftime( "%m-%d_%H-%M-%S", localtime );
            compile( $experiment, $target, $mode );

            # Write extra info for the menu
            system("echo '$info_str' > $local_root/build-$experiment/0.info");

            # Write runner_flags so runner.pl knows which FAIL flags to use
            my $runner_flags_path =
              "$local_root/build-$experiment/runner_flags";
            open( my $fhandle, '>', $runner_flags_path )
              or die "Cannot write $runner_flags_path: $!";
            print $fhandle "$catch_flag_map{$_}\n" for @build_catch_flags;
            close($fhandle);

            system( "mv", "$local_root/build.log",
                "$local_root/build-$experiment/build.log" );

            system(
                join " ",
                (
                    "mv",
                    "$local_root/build-$experiment",
                    "$local_builds_dir/${date}_${experiment}_${mode}_${target}",
                )
            );
        }
    }
}
