#!/usr/bin/env perl

# Copyright (C) Yichun Zhang. All rights reserved.
# Licensed under the standard 2-clause BSD license.

use v5.10.1;
use strict;
use warnings;

use Getopt::Std qw( getopts );
use Cwd qw( cwd );
use File::Find;

my %opts;
getopts("hvj:", \%opts) or usage(1);

if ($opts{h}) {
    usage(0);
}

my $jobs = $opts{j} // 1;
$jobs += 0;

my $luajit_prefix = shift or
    die "no luajit installation prefix specified.\n";

my $valgrind = $opts{v};

my $luajit = shift;
my $cc = shift // 'gcc';
my $cxx = shift // 'g++';

if (!defined $luajit) {
    $luajit = glob "$luajit_prefix/bin/luajit-2.1*";
}

if (!$luajit || !-x $luajit) {
    die "cannot find the luajit binary under $luajit_prefix/bin/";
}

my $cwd = cwd();

if ($valgrind) {
    $luajit = "valgrind --gen-suppressions=all --error-exitcode=2 --num-callers=100 --leak-check=full --show-possibly-lost=no --suppressions=$cwd/valgrind.suppress -q $luajit";
}

my $luajit_inc = "$luajit_prefix/include/luajit-2.1";

my $failures = 0;

sub shell {
    my ($cmd, $test) = @_;
    if (system("@_") != 0) {
        if ($test) {
            warn "\e[31mFailed test when running @_: $?\e[0m\n";
            $failures++;
        } else {
            die "cannot run command @_: $?\n";
        }
    }
}

my @tasks;

sub wanted {
    return unless -f $_ && /\.lua$/;
    return if $_ eq 'ffi_arith_int64.lua';
    push @tasks, [$File::Find::dir, $_];
}

shell "cd test/clib && rm -f ctest && $cc -Wall -O -g -o ctest -fpic -shared -I $luajit_inc ctest.c";
shell "cd test/clib && rm -f cpptest && $cxx -Wall -O -g -o cpptest -fpic -shared -I $luajit_inc cpptest.cpp";

$ENV{LUA_CPATH} = "$cwd/test/clib/?;;";

my $cmd = "pkg-config --cflags --libs gtk+-2.0";
my $cdefs = `$cmd`;
if ($? != 0) {
    die "failed to run command $cmd: $?";
}
chomp $cdefs;
$ENV{CDEFS} = $cdefs;
#warn "CDEFS=$cdefs";

if (@ARGV) {
    for my $test_file (@ARGV) {
        my ($dir, $fname);
        if ($test_file =~ m{(.*)/(.*)}) {
            ($dir, $fname) = ($1, $2);;
        } else {
            $dir = '.';
            $fname = $test_file;
        }
        push @tasks, [$dir, $fname];
    }

} else {
    find({ wanted => \&wanted }, 'test');
}

my $each = @tasks / $jobs;
if ($each < 1) {
    $each = 1;
    $jobs = scalar @tasks;
}

if ($jobs > 1) {
    require Parallel::ForkManager;

    my $pm = new Parallel::ForkManager($jobs);

    my $fname = "FAILS.txt";
    open my $out, ">$fname"
        or die "failed to open $fname for writing; $!\n";
    close $out;

    for (my $group = 0; $group < $jobs; $group++) {
        my $idx = $group * $each;

        $pm->start and next; # do the fork

        my $prev_dir;
        for (my $i = 0; $i < $each; $i++) {
            my $task = $tasks[$idx + $i];
            my ($dir, $file) = @$task;
            warn "=== $dir/$file\n";
            if (!defined $prev_dir || $dir ne $prev_dir) {
                chdir "$cwd/$dir" or die "chdir $cwd/$dir failed: $!";
                $prev_dir = $dir;
            }

            shell("$luajit $file", 1);
        }

        open my $out, ">>$cwd/$fname"
            or die "failed to open $fname for appending: $!\n";
        print $out "$failures\n";
        close $out;

        $pm->finish; # do the exit in the child process
    }

    $pm->wait_all_children;

    #warn "HERE: $failures";

    open my $in, "$cwd/$fname"
        or die "failed to open $fname for reading; $!\n";
    while (<$in>) {
        #warn "got: $_";
        $failures += $_ + 0;
    }
    close $in;

} else {
    my $prev_dir;
    for my $task (@tasks) {
        my ($dir, $file) = @$task;
        warn "=== $dir/$file\n";
        if (!defined $prev_dir || $dir ne $prev_dir) {
            chdir "$cwd/$dir" or die "chdir $cwd/$dir failed: $!";
            $prev_dir = $dir;
        }
        shell("$luajit $file", 1);
    }
}

report($failures);

sub report {
    my $failures = shift;

    if ($failures) {
        print "\e[31m$failures tests failed.\e[0m\n";
        exit 1;
    } else {
        print "\e[32mAll tests successful.\e[0m\n";
    }
}

sub usage {
    my $rc = shift;
    my $msg = <<_EOC_;
Usage: $0 [-h] [-v] [-j JOBS] LUAJIT-PREFIX [TEST-FILE...]\n";

Options:
    -h      Print this help.
    -j N    Run tests in N parallel jobs, utilizing multiple
            CPU cores.
    -v      Use Valgrind to run the tests.
_EOC_
    if ($rc == 0) {
        print $msg;
        exit 0;
    }
    print STDERR $msg;
    exit $rc;
}
