#!/usr/bin/perl

use strict;
use 5.6.0;
use warnings;

use File::Spec;
use POSIX;
use Text::Diff;
use File::Find;
use IO::Handle;
use IPC::Open3;
use IO::File;

my $test = shift or die "No test supplied\n";

-d $test or die "No such directory $test\n";

my $args = File::Spec->catfile($test, 'args');
my $args_canonify = File::Spec->catfile($test, 'args.canonify');
my $args_compare = File::Spec->catfile($test, 'args.compare');
my $baseline = File::Spec->catfile($test, 'baseline');
my $canonical = File::Spec->catfile($test, 'canonical');
my $errors = File::Spec->catfile($test, 'errors');
my $errors_canonify = File::Spec->catfile($test, 'errors.canonify');
my $errors_compare = File::Spec->catfile($test, 'errors.compare');
my $original = File::Spec->catfile($test, 'original');
my $result = File::Spec->catfile($test, 'result');
my $result_canonify = File::Spec->catfile($test, 'result.canonify');
my $result_compare = File::Spec->catfile($test, 'result.compare');
my $source = File::Spec->catfile($test, 'source');
my $diff = File::Spec->catfile($test, 'diff');

my @sources;
if (-d $source)
  {
    find(sub {return $File::Find::prune = 1 if /^\../ or /~$/;
              push @sources, $File::Find::name if -f $_}, $source);
  }
elsif (-f $source)
  {
    push @sources, $source;
  }
else
  {
    undef $source;
  }

undef $args unless -f $args;
undef $args_canonify unless -f $args_canonify;
undef $args_compare unless -f $args_compare;
undef $baseline unless -f $baseline;
undef $canonical unless -f $canonical;
undef $errors unless -f $errors;
undef $errors_canonify unless -f $errors_canonify;
undef $errors_compare unless -f $errors_compare;
undef $original unless -f $original;
undef $result unless -f $result;
undef $result_canonify unless -f $result_canonify;
undef $result_compare unless -f $result_compare;
undef $diff unless -f $diff;

my $canonify = 0;

$canonify = 1 if $source and $canonical;
$canonify = 1 if $original and $diff and $source and not $canonical;

my $compare = 0;

$compare = 1 if ($canonical or $canonify) and $original and $diff;

my $fail = 0;

sub compare_str
  {
    my $desc = shift;
    my $expect = shift;
    my $got = shift;

    return if $expect eq $got;

    print "Differences in $desc\n";
    if (length $expect)
      {
        diff \$expect, \$got, {STYLE => 'Unified', OUTPUT => \*STDOUT};
      }
    else
      {
        print "Expected nothing, got:\n";
        print "---\n";
        print $got;
        print "---\n"
      }
    $fail = 1;
  }

sub compare_int
  {
    my $desc = shift;
    my $expect = shift;
    my $got = shift;

    return if $expect == $got;

    print "Differences in $desc: expected $expect, got $got\n";
    $fail = 1;
  }

my $canonical_data;
if ($canonify)
  {
    my $expect_result = 0;
    if ($result_canonify)
      {
        my $fh = new IO::File $result_canonify, 'r';
        $expect_result = <$fh>;
        chomp $expect_result;
      }
    elsif ($result)
      {
        my $fh = new IO::File $result, 'r';
        $expect_result = <$fh>;
        chomp $expect_result;
      }

    my $expect_errors = "";
    if ($errors_canonify)
      {
        my $fh = new IO::File $errors_canonify, 'r';
        $expect_errors .= $_ while <$fh>;
      }
    elsif ($errors)
      {
        my $fh = new IO::File $errors, 'r';
        $expect_errors .= $_ while <$fh>;
      }

    my @args;
    if ($args_canonify)
      {
        my $fh = new IO::File $args_canonify, 'r';
        while (<$fh>) {chomp; push @args, $_}
      }
    elsif ($args)
      {
        my $fh = new IO::File $args, 'r';
        while (<$fh>) {chomp; push @args, $_}
      }

    my $canonify_output = "";
    my $canonify_errors = "";
    my $canonify_result;

    {
      my ($write, $read, $err) = (new IO::Handle, new IO::Handle, new IO::Handle);
      my @cmd = ('./icheck', '--canonify',
                 $baseline ? ('--baseline', $baseline) : (),
                 @sources, @args);
      if ($ENV{TEST_VERBOSE})
        {
          print join(' ', @cmd) . "\n";
        }
      my $pid = open3($write, $read, $err, @cmd);

      close $write;

      $canonify_output .= $_ while <$read>;
      close $read;

      $canonify_errors .= $_ while <$err>;
      close $err;

      waitpid $pid, 0;
      if (WIFSIGNALED($?))
        {
          die "icheck killed by signal " . WTERMSIG($?) . "\n";
        }

      unless (WIFEXITED($?))
        {
          die "icheck died wierdly (waitpid gave $?)\n";
        }

      $canonify_result = WEXITSTATUS($?);
    }

    compare_str('canonify errors', $expect_errors, $canonify_errors);
    compare_int('canonify result', $expect_result, $canonify_result);

    if ($canonical)
      {
        $canonical_data = '';
        my $fh = new IO::File $canonical, 'r';
        $canonical_data .= $_ while <$fh>;
        compare_str('canonify output', $canonical_data, $canonify_output);
      }
    else
      {
        $canonical_data = $canonify_output;
      }

    # Now we check that the canonical form is in fact canonical -
    # canonify(canonify(x)) == canonify(x) for all x
    if ($canonical)
      {
        my ($canonify2_output, $canonify2_errors, $canonify2_result) = ("", "", undef);

        {
          my ($write, $read, $err) = (new IO::Handle, new IO::Handle, new IO::Handle);
          my @cmd = ('./icheck', '--canonify',
                     $baseline ? ('--baseline', $baseline) : (),
                     $baseline ? ($baseline) : (), $canonical);
          if ($ENV{TEST_VERBOSE})
            {
              print join(' ', @cmd) . "\n";
            }
          my $pid = open3($write, $read, $err, @cmd);

          close $write;

          $canonify2_output .= $_ while <$read>;
          close $read;

          $canonify2_errors .= $_ while <$err>;
          close $err;

          waitpid $pid, 0;
          if (WIFSIGNALED($?))
            {
              die "icheck killed by signal " . WTERMSIG($?) . "\n";
            }

          unless (WIFEXITED($?))
            {
              die "icheck died wierdly (waitpid gave $?)\n";
            }

          $canonify2_result = WEXITSTATUS($?);
        }

        # A canonical form *must* parse correctly, so this can't have errors
        compare_str('second canonify output', $canonical_data, $canonify2_output);
        compare_str('second canonify errors', '', $canonify2_errors);
        compare_int('second canonify result', 0, $canonify2_result);
      }
  }

if ($compare)
  {
    my $expect_result = 0;
    if ($result_compare)
      {
        my $fh = new IO::File $result_compare, 'r';
        $expect_result = <$fh>;
        chomp $expect_result;
      }
    elsif ($result)
      {
        my $fh = new IO::File $result, 'r';
        $expect_result = <$fh>;
        chomp $expect_result;
      }

    my $expect_errors = "";
    if ($errors_compare)
      {
        my $fh = new IO::File $errors_compare, 'r';
        $expect_errors .= $_ while <$fh>;
      }
    elsif ($errors)
      {
        my $fh = new IO::File $errors, 'r';
        $expect_errors .= $_ while <$fh>;
      }

    my @args;
    if ($args_compare)
      {
        my $fh = new IO::File $args_compare, 'r';
        while (<$fh>) {chomp; push @args, $_}
      }
    elsif ($args)
      {
        my $fh = new IO::File $args, 'r';
        while (<$fh>) {chomp; push @args, $_}
      }

    my $compare_output = "";
    my $compare_errors = "";
    my $compare_result;

    {
      my ($write, $read, $err) = (new IO::Handle, new IO::Handle, new IO::Handle);
      my @cmd = ('./icheck', '--compare', $original,
                 $canonical ? $canonical : '-',
                 @args);
      if ($ENV{TEST_VERBOSE})
        {
          print join(' ', @cmd) . "\n";
        }
      my $pid = open3($write, $read, $err, @cmd);

      if (not $canonical)
        {
          print $write $canonical_data;
        }
      close $write;

      $compare_output .= $_ while <$read>;
      close $read;

      $compare_errors .= $_ while <$err>;
      close $err;

      waitpid $pid, 0;
      if (WIFSIGNALED($?))
        {
          die "icheck killed by signal " . WTERMSIG($?) . "\n";
        }

      unless (WIFEXITED($?))
        {
          die "icheck died wierdly (waitpid gave $?)\n";
        }

      $compare_result = WEXITSTATUS($?);
    }

    compare_str('compare errors', $expect_errors, $compare_errors);
    compare_int('compare result', $expect_result, $compare_result);

    my $diff_data = '';
    my $fh = new IO::File $diff, 'r';
    $diff_data .= $_ while <$fh>;
    compare_str('compare output', $diff_data, $compare_output);
  }

exit 1 if $fail;
