#!/usr/bin/env perl

use strict;
use warnings;
use diagnostics;

use FindBin;

# runner.pl is executed from the build dir
use lib "$FindBin::Bin/../../scripts/Modules";
use lib "$FindBin::Bin/Modules";

use Filters;
use Util;

use feature 'say';

my $remote_root       = '/home/lab/smchurla/Documents/failnix';
my $remote_db_conf    = "$remote_root/db.conf";
my $remote_builds_dir = "$remote_root/builds";
my $remote_runner     = "$remote_root/scripts/runner.pl";

my $db_user   = "smchurla";
my $db_prefix = "$db_user";

my $fail_server_port   = '22941';
my $resultbrowser_port = '22941';
my $fail_bin           = "$remote_root/fail/bin";
my $fail_share         = "$remote_root/fail/share";
my $bochs_runner       = "$fail_bin/bochs-experiment-runner.py";
my $fail_trace         = "$fail_bin/fail-x86-tracing";
my $fail_dump          = "$fail_bin/dump-trace";
my $fail_import        = "$fail_bin/import-trace";
my $fail_prune         = "$fail_bin/prune-trace";
my $fail_server        = "$fail_bin/generic-experiment-server";
my $fail_inject        = "$fail_bin/generic-experiment-client";
my $result_browser     = "$fail_bin/resultbrowser.py";

sub trace {
    my ($experiment) = @_;

    # Util::notify("Tracing $experiment...");

    my $trace_command = join " ", (
        "$bochs_runner",
        "-V $fail_share/vgabios.bin",
        "-b $fail_share/BIOS-bochs-latest",
        "-1",
        "-f $fail_trace",
        "-e $remote_builds_dir/$experiment/system.elf",
        "-i $remote_builds_dir/$experiment/system.iso",
        "--",
        "-Wf,--start-symbol=fail_start_trace",
        "-Wf,--save-symbol=fail_start_trace",
        "-Wf,--end-symbol=fail_stop_trace",
        "-Wf,--state-file=$remote_builds_dir/$experiment/state",
        "-Wf,--trace-file=$remote_builds_dir/$experiment/trace.pb",
        "-Wf,--elf-file=$remote_builds_dir/$experiment/system.elf",

        # "-Wf,--full-trace",
        # "-Wf,--check-bounds",
        ">$remote_builds_dir/$experiment/1_trace.log 2>&1"
    );
    say "Trace command: $trace_command";

    system($trace_command);
}

sub import_trace {
    my ($experiment) = @_;

    # Util::notify("Importing $experiment trace...");

    # Benchmark: ip
    my $import_ip_command = join " ", (
        "$fail_import",
        "--database-option-file $remote_db_conf",
        "-t $remote_builds_dir/$experiment/trace.pb",
        "-i RegisterImporter",
        "-e $remote_builds_dir/$experiment/system.elf",
        "-v $experiment",
        "-b ip",
        "--no-gp",    # Don't inject general purpose registers
        "--ip",       # Inject instruction pointer
        ">$remote_builds_dir/$experiment/2_import_ip.log 2>&1"
    );
    say "Import IP command: $import_ip_command";
    system($import_ip_command);

    # Benchmark: mem
    my $import_mem_command = join " ",
      (
        "$fail_import",
        "--database-option-file $remote_db_conf",
        "-t $remote_builds_dir/$experiment/trace.pb",
        "-i MemoryImporter",
        "-e $remote_builds_dir/$experiment/system.elf",
        "-v $experiment",
        "-b mem",
        ">$remote_builds_dir/$experiment/2_import_mem.log 2>&1"
      );
    say "Import MEM command: $import_mem_command";
    system($import_mem_command);

    # Benchmark: regs
    my $import_regs_command = join " ", (
        "$fail_import",
        "--database-option-file $remote_db_conf",
        "-t $remote_builds_dir/$experiment/trace.pb",
        "-i RegisterImporter",
        "-e $remote_builds_dir/$experiment/system.elf",
        "-v $experiment",
        "-b regs",
        "--flags",    # Inject flags register
        ">$remote_builds_dir/$experiment/2_import_regs.log 2>&1"
    );
    say "Import REGS command: $import_regs_command";
    system($import_regs_command);

    # Import fulltrace for VisualFAIL
    # system(
    #     join " ",
    #     (
    #         "$fail_import",
    #         "--database-option-file $remote_db_conf",
    #         "-t $remote_builds_dir/$experiment/trace.pb",
    #         "-i FullTraceImporter",
    #         "-e $remote_builds_dir/$experiment/system.elf",
    #         "-v $experiment",
    #     )
    # );

    # Import disassembly/sources
    my $import_ip_asm_command = join " ", (
        "$fail_import",
        "--database-option-file $remote_db_conf",
        "-t $remote_builds_dir/$experiment/trace.pb",
        "-i ElfImporter",
        "-e $remote_builds_dir/$experiment/system.elf",
        "-v $experiment",
        "-b ip",
        "--objdump objdump",

        # "--sources",
        ">$remote_builds_dir/$experiment/2_import_ip_asm.log 2>&1"
    );
    say "Import IP Asm command: $import_ip_asm_command";
    system($import_ip_asm_command);

    my $import_mem_asm_command = join " ", (
        "$fail_import",
        "--database-option-file $remote_db_conf",
        "-t $remote_builds_dir/$experiment/trace.pb",
        "-i ElfImporter",
        "-e $remote_builds_dir/$experiment/system.elf",
        "-v $experiment",
        "-b mem",
        "--objdump objdump",

        # "--sources",
        ">$remote_builds_dir/$experiment/2_import_mem_asm.log 2>&1"
    );
    say "Import Mem Asm command: $import_mem_asm_command";
    system($import_mem_asm_command);

    my $import_regs_asm_command = join " ", (
        "$fail_import",
        "--database-option-file $remote_db_conf",
        "-t $remote_builds_dir/$experiment/trace.pb",
        "-i ElfImporter",
        "-e $remote_builds_dir/$experiment/system.elf",
        "-v $experiment",
        "-b regs",
        "--objdump objdump",

        # "--sources",
        ">$remote_builds_dir/$experiment/2_import_regs_asm.log 2>&1"
    );
    say "Import Regs Asm command: $import_regs_asm_command";
    system($import_regs_asm_command);

    my $prune_command = join " ", (
        "$fail_prune",    "--database-option-file $remote_db_conf",
        "-v $experiment", "-b %%",

        # '-p NoPruner',
        '-p BasicPruner',
        "--overwrite", ">$remote_builds_dir/$experiment/2_prune.log 2>&1"
    );
    say "Prune command: $prune_command";
    system($prune_command);
}

sub inject {
    my ($experiment) = @_;

    my $count = Util::cpu_count();

    Util::notify("Injecting $experiment using $count cores...");

    my $server_command = join " ", (
        "$fail_server",

        # "--port $fail_server_port",
        "--database-option-file $remote_db_conf", "-v $experiment",
        "-b %",                                   "--inject-single-bit",
        "--inject-registers",
        ">$remote_builds_dir/$experiment/3_server.log 2>&1"
    );
    say "Server command: $server_command";

    # Read catch flags written by build.pl into the experiment directory
    my $runner_flags_file = "$remote_builds_dir/$experiment/runner_flags";
    my @catch_flags       = ();
    if ( -e $runner_flags_file ) {
        open( my $rfh, '<', $runner_flags_file )
          or die "Cannot open $runner_flags_file: $!";
        @catch_flags = map { chomp; "-Wf,$_" } grep { /\S/ } <$rfh>;
        close($rfh);
    }

    my $client_command = join " ", (
        "nice $bochs_runner",
        "-V $fail_share/vgabios.bin",
        "-b $fail_share/BIOS-bochs-latest",
        "-f $fail_inject",
        "-e $remote_builds_dir/$experiment/system.elf",
        "-i $remote_builds_dir/$experiment/system.iso",
        "-j $count",
        "--",

        # "-Wf,--server-port=$fail_server_port",
        "-Wf,--state-dir=$remote_builds_dir/$experiment/state",
        "-Wf,--trap",

        @catch_flags,

        "-Wf,--timeout=500000",
        "-Wf,--ok-marker=fail_marker_positive",
        "-Wf,--fail-marker=fail_marker_negative",
        "-Wf,--detected-marker=fail_marker_detected",

        # Called in WAMR exception handler (disable for C compilation target!)
        # "-Wf,--group1-marker=fail_marker_group1",

        ">/dev/null 2>&1"

        # ">$remote_builds_dir/$experiment/4_client.log 2>&1"
    );
    say "Client command: $client_command";

    say "Forking...";
    my $pid = fork();
    die "fork failed: $!" unless defined $pid;

    if ( $pid == 0 ) {

        # child -> server
        say "Running server in child process...";
        exec($server_command) == 0 or die "exec server failed: $!";
    }

    # parent -> client
    say "Waiting for server...";
    sleep(10);

    say "Running client with $count cores in parent process";
    system($client_command) == 0 or die "client failed: $?";

    say "Killing server with pid $pid...";
    kill 'TERM', $pid;
    waitpid( $pid, 0 );
}

sub results {
    my ($experiment) = @_;

    my @queries =
      map { s/\.pm//gr } Util::find_files("$remote_root/scripts/Queries");

    # Filters are combined into one
    my @configs = Filters::get_default_configs();

    for my $query (@queries) {
        Util::execute_query( $experiment, $query,
            $remote_db_conf, $remote_builds_dir, 1, @configs );
    }
}

# Run single experiment passed as argument
my $experiment = $ARGV[0] or die "Usage: runner.pl <experiment>\n";
Util::rewrite_file( $remote_db_conf, "database=",
    "database=${db_prefix}_$experiment\n" );

say "Killing generic-experiment-server process if it's running...";
system( "pkill", "-u", $db_user, "-f", "generic-experiment-server" );
sleep(10);

trace($experiment);
import_trace($experiment);
inject($experiment);
results($experiment);

Util::notify("Finished experiment $experiment");
