#! /usr/bin/env perl
# -*- mode: perl; -*-

# lyx_batch.pl testname

use strict;
use warnings;
use File::Copy;
use File::Compare;
use File::Slurp qw(read_dir read_file);

sub checkPrecondition();
sub system1(@);
sub addFiles($$$);
sub mycompare($$$$);

my $builddir = "@CMAKE_BINARY_DIR@";
my $userdir = "$builddir/Testing/.lyxbatch";
my $workdir = "$builddir/autotests/out-home";

my $vsuffix = "@PROGRAM_SUFFIX@";
my $lyx_exe = "$builddir/bin/lyx$vsuffix";
my $git_exe = "@LYX_GITVERSION@";
my $qt_version = "@LYX_USE_QT@";

my $lyxsource = "@LYX_ABS_TOP_SRCDIR@";
my $data = "$lyxsource/development/batchtests";
my $test_bin = "$lyxsource/development/batchtests/bin";
my $comparepdf = "@COMPAREPDF_EXECUTABLE@";
my $perl = "@PERL_EXECUTABLE@";

# src_files := Files to be copied from lyx-source to build-dir
# check     := List of pairs of files to check
#                  created file (in build-dir)
#                  expected file (in source dir, to be compared with the created one)
# check_type: Type of check to perform, can be either:
#                  * text (default) - compares using File::Compare (i.e. full comparison)
#                  * pdf - compares using comparepdf (must be installed)
#                  * custom - compares using a custom script
# check_script: For check_type==custom, this is the script that's executed to do the comparison
# commands  := List of commands (lyx-functions) to be executed by lyx in a batch
# precondition: system commands to be executed prior to the test
# command_line: List of parameters to be used on the lyx-command-line
my %Tests = (
  save_as_test => {
    src_files => ["save_as.lyx"],
    check => [["save_as_saved.lyx"],
              ["save_as_saved2.lyx"]],
    commands => ["file-open beamer_test.lyx",
                 "buffer-write-as save_as_saved.lyx",
                 "buffer-reload dump",
                 "buffer-write-as save_as_saved2.lyx",
                 "lyx-quit"],
  },
  beamer_test => {
    src_files => ["beamer_test.lyx"],
    check => [["beamer_test.tex", "beamer_test.tex.orig"]],
    commands => ["file-open beamer_test.lyx",
                 "buffer-begin",
                 "repeat 150 outline-down",
                 "repeat 150 outline-up",
                 "buffer-export pdflatex",
                 "buffer-reload dump",
                 "lyx-quit"],
  },
  vcs_info_export => {
    precondition => {
      command => [$git_exe, "ls-files", "--error-unmatch", "vcs_info_export.lyx"],
      workdir => "$data",
    },
    src_files => ["vcs_info_export.lyx"],
    check => [["vcs_info_export.tex", "vcs_info_export.tex.orig"]],
    command_line => ["-E", "pdflatex", "vcs_info_export.tex", "$data/vcs_info_export.lyx"],
  },
  "ams-import" => {
    src_files => ["ams-import.tex"],
    check_type => 'pdf',
    check => [["ams-import.pdf", "ams-import.pdf"],
              ["ams-import.lyx"]],
    commands => ["buffer-new",
                 "buffer-import latex ams-import.tex",
                 "buffer-write",
                 "buffer-export pdf2",
                 "lyx-quit"],
  },
  "compare_test" => {
      src_files => ["old.lyx", "new.lyx"],
      check_type => 'custom',
      check_script => ["$perl","$test_bin/compare_custom.pl"],
      test_dir => "$lyxsource/development/batchtests/compare_tests/",
      check => [["diffs.lyx", "diffs.expected.lyx"]],
      commands => [
          "dialog-show compare run-blocking $workdir/old.lyx $workdir/new.lyx",
          "buffer-write-as $workdir/diffs.lyx",
          "lyx-quit"
      ],
  },
);

die("Expected argument missing") if (! defined($ARGV[0]));
my $test = $ARGV[0];
die("Invalid argument") if (! defined($Tests{$test}));

if (! -e $userdir) {
  mkdir($userdir);
}

$ENV{LANG} = "en";
$ENV{LC_ALL} = "C";
$ENV{LANGUAGE} = "en_US";

if (defined $Tests{$test}->{test_dir}) {
  my @dirs = read_dir($Tests{$test}->{test_dir}, prefix => 1);
  foreach my $dir (@dirs) {
    next unless -d $dir;
    print "--- Running tests in $dir\n";
    run_tests($dir);
  }
}
else {
  run_tests($data);
}

exit(0);


sub run_tests {
  my $test_dir = shift;

  if (-e "$test_dir/skip.test") {
    my $skip_msg = read_file("$test_dir/skip.test");
    print "TEST SKIPPED.\n$skip_msg\n";
    return;
  }

  my @expected = &addFiles($test_dir, $Tests{$test}->{check},1);

  my @created = &addFiles($workdir, $Tests{$test}->{check}, 0);

  # Copy src-files to work with
  for my $f (@{$Tests{$test}->{src_files}}) {
    copy("$test_dir/$f", "$workdir/$f") or die("Copy failed: $!");
  }
  print "Unlinking " . join(' ', @created) . "\n";
  unlink(@created);

  &checkPrecondition();
  chdir($workdir);
  my @command = ($lyx_exe, "-userdir", $userdir);
  if (defined($Tests{$test}->{command_line})) {
    push(@command, @{$Tests{$test}->{command_line}});
  }
  if (defined($Tests{$test}->{commands}->[0])) {
    if ($qt_version eq "QT5") {
      push(@command, "-platform", "offscreen");
    }
    if (defined($Tests{$test}->{commands}->[1])) { # more than one command
      push(@command, "-x", "command-sequence " . join(';', @{$Tests{$test}->{commands}}));
    }
    else {
      push(@command, "-x", $Tests{$test}->{commands}->[0]);
    }
  }

  &system1(@command);

  for (my $i = 0; defined($created[$i]); $i++) {
    die("File \"$created[$i]\" not created") if (! -e "$created[$i]");

    if (defined($expected[$i])) {
      my $res = mycompare($Tests{$test}->{check_type}, $expected[$i], $created[$i], $Tests{$test}->{check_script});

      die("Expected ($expected[$i]) and created ($created[$i]) files differ") if $res != 0;
    }
  }
}

sub checkPrecondition()
{
  return if (! defined($Tests{$test}->{precondition}));
  my $rPrecond = $Tests{$test}->{precondition};
  my @command = @{$rPrecond->{command}};
  if (defined($rPrecond->{workdir})) {
    chdir($rPrecond->{workdir});
  }
  my $result = &system1(@command);
  print "Pre-condition result = $result\n";
  die("Pre-condition error") if ($result != 0);
}

sub system1(@)
{
  my ($exe, @params) = @_;
  print "Executing:\n\t$exe '" . join("' '", @params) . "'\n";
  return(system($exe, @params));
}

# Create a list of file paths
# dir: result-dir
# rBases: List of base-names
sub addFiles($$$)
{
  my ($tdir, $rrBases, $idx) = @_;
  my $dir;
  if (defined($tdir)) {
    $dir = "$tdir/";
  }
  else {
    $dir = "";
  }
  my @result = ();
  for my $rf (@{$rrBases}) {
    my $path = undef;
    if (defined($rf) && defined($rf->[$idx])) {
      $path = "$dir$rf->[$idx]";
    }
    push(@result, $path);
  }
  return(@result);
}

sub mycompare($$$$)
{
  my ($check_type, $expected, $created, $check_script) = @_;
  my $result;

  $check_type //= 'text';

if ($check_type eq 'pdf') {
    my $cmd = $comparepdf;

    if ($cmd =~ /NOTFOUND/) {
      # no check is done due to missing executable
    }
    else {
      my @params = (
        "-ca", "-v=1", $expected, $created
      );

      my $error = "";
      if (&system1($cmd, @params) != 0) {
	if ($? == -1) {
	  $error = sprintf("failed to execute: $cmd");
	}
	elsif ($? & 127) {
	  $error = sprintf("$cmd with signal %d, %s coredump",
			   ($? & 127),  ($? & 128) ? 'with' : 'without');
	}
	else {
	  $error = sprintf("child $cmd exited with value %d", $? >> 8);
	}
      }
      die($error) if ($error ne "");
    }
    $result = 0;
  }
  elsif ($check_type eq 'custom') {
    $result = system1(@$check_script, $expected, $created);
  }
  elsif ($check_type eq 'text') {
    # defaut text comparision
    $result = compare($created, $expected);
  }
  else {
    die "Unknown check type: $check_type";
  }
  return($result);
}
