#!/usr/bin/env perl

# program to define a magic run
# The magictool mode will not queue a run until all diffs from an exposure are
# successfully completed. There may also be issues with multiple warp runs
# for a single exposure.
# This program selects the diffs based on the value for warp_id if provided

use Carp;
use warnings;
use strict;

## report the program and machine
use Sys::Hostname;
my $host = hostname();

use vars qw( $VERSION );
$VERSION = '0.01';

use IPC::Cmd 0.36 qw( can_run run );
use File::Temp qw( tempfile );
use PS::IPP::Metadata::Config;
use PS::IPP::Metadata::List qw( parse_md_list );

use PS::IPP::Config 1.01 qw( :standard );

use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
use Pod::Usage qw( pod2usage );

# Look for programs we need
my $missing_tools;
my $magictool = can_run('magictool') or (warn "Can't find magictool" and $missing_tools = 1);
my $difftool  = can_run('difftool') or (warn "Can't find difftool" and $missing_tools = 1);
my $warptool  = can_run('warptool') or (warn "Can't find warptool" and $missing_tools = 1);
if ($missing_tools) {
    warn("Can't find required tools.");
    exit($PS_EXIT_CONFIG_ERROR);
}

# Parse the command-line arguments
my ($exp_id, $warp_id, $min_diff_id, $label, $workdir, $dbname, $save_temps, $verbose);
GetOptions(
           'exp_id=s'        => \$exp_id,     # exposure identifier
           'warp_id=s'       => \$warp_id,    # warp identifier
           'min_diff_id=s'   => \$min_diff_id, # minimum diff id to consider
           'label=s'         => \$label,      # label for images of interest
           'dbname=s'        => \$dbname,     # Database name
           'workdir=s'       => \$workdir,    # workdir
           'save-temps'      => \$save_temps, # Save temporary files?
           'verbose'         => \$verbose,    # Print stuff?
           ) or pod2usage( 2 );

pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
pod2usage( -msg => "Required options: --warp_id --workdir",
           -exitval => 3) unless
    defined $workdir and
    defined $warp_id;

my $ipprc = PS::IPP::Config->new() or my_die("Unable to set up", $PS_EXIT_CONFIG_ERROR); # IPP configuration

my $mdcParser = PS::IPP::Metadata::Config->new; # Parser for metadata config files

if (!$exp_id) {
    my $command = "$warptool -warped -warp_id $warp_id -limit 1";
    $command .= " -dbname $dbname" if defined $dbname;
    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
        run(command => $command, verbose => $verbose);
    unless ($success) {
        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
        &my_die("Unable to perform warptool -warped error_code: $error_code", $error_code);
    }

    my $warptool_output = join "", @$stdout_buf;

    my $warpskyfiles = parse_md_fast($mdcParser, $warptool_output);
    &my_die("Unable to parse metadata list", $PS_EXIT_UNKNOWN_ERROR) unless $warpskyfiles;

    my $warped = $warpskyfiles->[0];
    $exp_id = $warped->{exp_id};
    &my_die("failed to get exp_id for warp: $warp_id", $PS_EXIT_UNKNOWN_ERROR) unless $exp_id;
}

### Get a list of inputs
my $inputs;                     # List of inputs
{
    my $command = "$difftool -diffskyfile -exp_id $exp_id";
    $command .= " -dbname $dbname" if defined $dbname;
    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
        run(command => $command, verbose => $verbose);
    unless ($success) {
        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
        &my_die("Unable to perform difftool -diffskyfile error_code: $error_code", $error_code);
    }

    my $difftool_output = join "", @$stdout_buf;

    $inputs = parse_md_fast($mdcParser, $difftool_output);
    &my_die("Unable to parse metadata list", $PS_EXIT_UNKNOWN_ERROR) unless $inputs;
}

my $num_skyfiles = @$inputs;
if (! $num_skyfiles) {
    carp("failed to find any diffskyfiles for exposure $exp_id");
    exit(1);
}

print "$num_skyfiles skyfiles found for $exp_id\n" if $verbose;

my $diff_id_for_run;
if ($warp_id or $min_diff_id) {
    # filter the inputs
    my $i = 0;
    while ($i < scalar @$inputs) {
        my $keep = 1;
        my $sf = $inputs->[$i];

        if ($warp_id) {
            if ($warp_id ne $sf->{warp1}) {;
                $keep = 0;
            }
        }
        if ($min_diff_id) {
            if ($sf->{diff_id} < $min_diff_id) {
                $keep = 0;
            }
        }
        if ( $keep) {
            $i++;
            $diff_id_for_run = $sf->{diff_id} if !$diff_id_for_run;
        } else {
            # remove it
            splice @$inputs, $i, 1;
        }
    }
    $num_skyfiles = $i;
    print "$num_skyfiles skyfiles accepted for $exp_id\n" if $verbose;
}

if (!$num_skyfiles) {
    # should I return an error here?
    &my_die("no skyfiles matching criteria found for exp_id $exp_id", 0);
}

my $magic_id;
{
    my $command = "$magictool -definerun -exp_id $exp_id -workdir $workdir -simple";
    $command .= " -diff_id $diff_id_for_run" if defined $diff_id_for_run;
    $command .= " -label $label" if defined $label;
    $command .= " -dbname $dbname" if defined $dbname;

    print "$command\n";

    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
        run(command => $command, verbose => $verbose);
    unless ($success) {
        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
        &my_die("Unable to perform difftool -diffskyfile: $error_code", $error_code);
    }

    my $buffer = join "", @$stdout_buf;
    ($magic_id, undef) = split " ", $buffer;
}

&my_die("failed to retrieve new magic run information", $PS_EXIT_CONFIG_ERROR) if !$magic_id;

### This is left over from when diffs were composed of a single skycell
### When we're not too busy, it should be deleted in favour of:
### magicRun JOIN diffSkyfile USING(diff_id) WHERE diffSkyfile.fault = 0 AND diffSkyfile.quality = 0
foreach my $diff_skyfile (@$inputs) {
    my $skycell_id = $diff_skyfile->{skycell_id};
    if ($diff_skyfile->{fault} > 0) {
        print "skipping faulted diffSkyfile: $skycell_id\n" if $verbose;
        next;
    }
    my $diff_id = $diff_skyfile->{diff_id};
    my $command = "$magictool -addinputskyfile";
    $command .= " -magic_id $magic_id";
    $command .= " -node $skycell_id";
    $command .= " -dbname $dbname" if $dbname;

    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
        run(command => $command, verbose => $verbose);
    unless ($success) {
        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
        &my_die("Unable to perform magictool -addinputskyfile for magicRun: $magic_id skycell_id: $skycell_id: $error_code", $error_code);
    }
}

# set the new magicRun to state 'run'
{
    my $command = "$magictool -updaterun -magic_id $magic_id -state run";
    $command .= " -dbname $dbname" if $dbname;

    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
        run(command => $command, verbose => $verbose);
    unless ($success) {
        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
        &my_die("Unable to perform magictool -updaterun for magicRun: $magic_id: $error_code", $error_code);
    }
}

sub my_die
{
    my $msg = shift;            # Warning message on die
    my $exit_code = shift;      # Exit code to add

    carp($msg);
    exit $exit_code;
}

sub parse_md_fast {
    my $mdcParser = shift;
    my $input = shift;
    my $output = ();

    my @whole = split /\n/, $input;
    my @single = ();

    my $n;
    while ( ($n = @whole) > 0) {
        my $value = shift @whole;
        push @single, $value;
        if ($value =~ /^\s*END\s*$/) {
            push @single, "\n";

            my $list = parse_md_list( $mdcParser->parse( join("\n", @single ) ) ) or
                print STDERR "Unable to parse metdata config doc" and return undef;
            push @$output, $list->[0];

            @single = ();
        }
    }
    return $output;
}

__END__
