#!/usr/bin/env perl

use warnings;
use strict;

## report the program and machine
use Sys::Hostname;
my $host = hostname();
print "Starting script $0 on $host\n\n";

use DateTime;
my $mjd_start = DateTime->now->mjd;   # MJD of starting script

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

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

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 $pubtool = can_run('pubtool') or (warn "Can't find pubtool" and $missing_tools = 1);
my $camtool = can_run('camtool') or (warn "Can't find camtool" and $missing_tools = 1);
my $difftool = can_run('difftool') or (warn "Can't find difftool" and $missing_tools = 1);
my $ppMops = can_run('ppMops') or (warn "Can't find ppMops" and $missing_tools = 1);
my $dsreg = can_run('dsreg') or (warn "Can't find dsreg" and $missing_tools = 1);
if ($missing_tools) {
    warn("Can't find required tools.");
    exit($PS_EXIT_CONFIG_ERROR);
}

# Parse the command-line arguments
my ( $pub_id, $camera, $stage, $stage_id, $fileset, $format, $product, $workdir );
my ( $dbname, $verbose, $no_update, $no_op, $save_temps, $redirect );

GetOptions(
    'pub_id=s'          => \$pub_id, # Publish identifier
    'camera=s'          => \$camera, # Camera name
    'stage=s'           => \$stage,       # Stage of interest
    'stage_id=s'        => \$stage_id,    # Stage identifier
    'product=s'         => \$product,     # Datastore product name
    'fileset=s'         => \$fileset,     # Fileset name
    'workdir=s'         => \$workdir,     # Working directory
    'dbname=s'          => \$dbname,    # Database name
    'verbose'           => \$verbose,   # Print to stdout
    'no-update'         => \$no_update, # Don't update the database?
    'no-op'             => \$no_op, # Don't do any operations
    'save-temps'        => \$save_temps, # Save temporary files?
    'redirect-output'   => \$redirect,   # Redirect output to log file?
    ) or pod2usage( 2 );

pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
pod2usage( -msg => "Required options: --pub_id --camera --stage --stage_id --product --workdir",
           -exitval => $PS_EXIT_CONFIG_ERROR) unless
    defined $pub_id and
    defined $camera and
    defined $product and
    defined $stage and
    defined $stage_id and
    defined $workdir;

my $outroot = "$workdir/$product.$pub_id"; # Output root name

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

$ipprc->outroot_prepare( $outroot );
my $logDest = "$outroot.log";
$ipprc->redirect_output($logDest) or &my_die( "Unable to redirect output", $pub_id, $PS_EXIT_SYS_ERROR ) if $redirect;

my $mdcParser = PS::IPP::Metadata::Config->new;

my ($dsFile, $dsFileName) = tempfile("/tmp/publish.$pub_id.ds.XXXX", UNLINK => !$save_temps );

if ($stage eq 'camera') {
    my $command =  "camtool -processedexp -cam_id $stage_id";
    $command .= " -dbname $dbname" if defined $dbname;
    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
        run(command => $command, verbose => $verbose);
    &my_die( "Unable to retrieve filename", $pub_id, $PS_EXIT_SYS_ERROR) unless $success;

    my $metadata = $mdcParser->parse(join "", @$stdout_buf) or
        &my_die("Unable to parse metadata config", $pub_id, $PS_EXIT_PROG_ERROR);

    my $components = parse_md_list($metadata) or
        &my_die("Unable to parse metadata list", $pub_id, $PS_EXIT_PROG_ERROR);

    &my_die("More than one entry for cam_id $stage_id", $pub_id, $PS_EXIT_PROG_ERROR) if scalar @$components > 1;

    my $comp = $$components[0]; # Component of interest
    my $path_base = $comp->{path_base}; # Base name for file
    my $file = $ipprc->filename( "PSASTRO.OUTPUT", $path_base );
    $file = $ipprc->file_resolve($file);
    my $cam_id = $comp->{cam_id};
    my $name = "cam_$cam_id";
    print $dsFile "$file|||$product|$name|\n";
} elsif ($stage eq 'diff') {
    my $command =  "difftool -diffskyfile -diff_id $stage_id";
    $command .= " -dbname $dbname" if defined $dbname;
    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
        run(command => $command, verbose => $verbose);
    &my_die( "Unable to retrieve filename", $pub_id, $PS_EXIT_SYS_ERROR) unless $success;

    my $metadata = $mdcParser->parse(join "", @$stdout_buf) or
        &my_die("Unable to parse metadata config", $pub_id, $PS_EXIT_PROG_ERROR);

    my $components = parse_md_list($metadata) or
        &my_die("Unable to parse metadata list", $pub_id, $PS_EXIT_PROG_ERROR);

    my ($mopsPositiveFile, $mopsPositiveFileName) = tempfile("/tmp/publish.$pub_id.mops.pos.XXXX", UNLINK => !$save_temps ) if $product eq 'IPP-MOPS';
    my ($mopsNegativeFile, $mopsNegativeFileName) = tempfile("/tmp/publish.$pub_id.mops.neg.XXXX", UNLINK => !$save_temps ) if $product eq 'IPP-MOPS';

    my %positive;               # Data for positive diff detections
    my %negative;               # Data for negative diff detections
    foreach my $comp ( @$components ) {
        my $path_base = $comp->{path_base}; # Base name for file
        next if defined $comp->{quality} and $comp->{quality} > 0;

        (carp "Bad zpt_obs or exp_time for component" and next) if not defined $comp->{zpt_obs} or not defined $comp->{exp_time};
        my $zp = $comp->{zpt_obs} + 2.5 * log($comp->{exp_time}) / log(10);
        my $astrom = sqrt($comp->{sigma_ra_1}**2 + $comp->{sigma_dec_1}**2);

        my $skycell_id = $comp->{skycell_id};
        my $filename = $ipprc->filename( "PPSUB.OUTPUT.SOURCES", $path_base );
        $filename = $ipprc->file_resolve($filename);

        my $data = { zp => $zp,
                     zp_err => $comp->{zpt_stdev},
                     astrom => sqrt($comp->{sigma_ra_1}**2 + $comp->{sigma_dec_1}**2),
                     exp_name => $comp->{exp_name_1},
                     exp_id => $comp->{exp_id_1},
                     chip_id => $comp->{chip_id_1},
                     cam_id => $comp->{cam_id_1},
                     fake_id => $comp->{fake_id_1},
                     warp_id => $comp->{warp1},
                     diff_id => $comp->{diff_id},
                     direction => 1,
        };

        diff_check(\%positive, $data, "positive");

        if ($product eq 'IPP-MOPS') {
            print $mopsPositiveFile "$filename\n";
        } else {
            print $dsFile "$filename|||$product|${skycell_id}.pos|\n";
        }

        # Negative direction
        if (defined $comp->{bothways} and $comp->{bothways}) {
            my $filename = $ipprc->filename( "PPSUB.INVERSE.SOURCES", $path_base );
            $filename = $ipprc->file_resolve($filename);

            my $data = { zp => $zp,
                         zp_err => $comp->{zpt_stdev},
                         astrom => sqrt($comp->{sigma_ra_2}**2 + $comp->{sigma_dec_2}**2),
                         exp_name => $comp->{exp_name_2},
                         exp_id => $comp->{exp_id_2},
                         chip_id => $comp->{chip_id_2},
                         cam_id => $comp->{cam_id_2},
                         fake_id => $comp->{fake_id_2},
                         warp_id => $comp->{warp2},
                         diff_id => $comp->{diff_id},
                         direction => 0,
            };

            diff_check(\%negative, $data, "negative");

            if ($product eq 'IPP-MOPS') {
                print $mopsNegativeFile "$filename\n";
            } else {
                print $dsFile "$filename|||$product|${skycell_id}.neg|\n";
            }
        }
    }

    close $mopsPositiveFile;
    close $mopsNegativeFile;

    if ($product eq 'IPP-MOPS') {
        if (scalar keys %positive > 0) {
            my $output = mops_combine(\%positive, "$outroot.pos.mops", $mopsPositiveFileName);
            print $dsFile "$output|||$product|positive|\n";
        }
        if (scalar keys %negative > 0) {
            my $output = mops_combine(\%negative, "$outroot.neg.mops", $mopsNegativeFileName);
            print $dsFile "$output|||$product|negative|\n";
        }
    }
}

close $dsFile;

unless ($no_update) {
    my $command = "$dsreg --add $stage.$stage_id --copy --abspath --product $product --type $product --list $dsFileName";

    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
        run(command => $command, verbose => $verbose);
    &my_die( "Unable to register with data store", $pub_id, $PS_EXIT_SYS_ERROR) unless $success;
}

unless ($no_update) {
    my $command = "$pubtool -add -pub_id $pub_id -path_base $outroot";
    $command .= (" -dtime_script " . ((DateTime->now->mjd - $mjd_start) * 86400));
    $command .= " -hostname $host" if defined $host;

    $command .= " -dbname $dbname" if defined $dbname;

    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
        run(command => $command, verbose => $verbose);
    &my_die( "Unable to register with data store", $pub_id, $PS_EXIT_SYS_ERROR) unless $success;
}



### Pau.

# Check inputs for a diff
sub diff_check
{
    my $data = shift;           # Data hash
    my $comp = shift;           # Component data
    my $name = shift;           # Name of component

    $data->{zp} = $comp->{zp} unless defined $data->{zp};
    $data->{zp_err} = $comp->{zp_err} unless defined $data->{zp_err};
    $data->{astrom} = $comp->{astrom} unless defined $data->{astrom};
    $data->{exp_name} = $comp->{exp_name} unless defined $data->{exp_name};
    $data->{exp_id} = $comp->{exp_id} unless defined $data->{exp_id};
    $data->{chip_id} = $comp->{chip_id} unless defined $data->{chip_id};
    $data->{cam_id} = $comp->{cam_id} unless defined $data->{cam_id};
    $data->{fake_id} = $comp->{fake_id} unless defined $data->{fake_id};
    $data->{warp_id} = $comp->{warp_id} unless defined $data->{warp_id};
    $data->{diff_id} = $comp->{diff_id} unless defined $data->{diff_id};
    $data->{direction} = $comp->{direction} unless defined $data->{direction};

    &my_die("zp value for $name doesn't match", $pub_id, $PS_EXIT_SYS_ERROR) if defined $data->{zp} and $comp->{zp} != $data->{zp};
    &my_die("zp_err value for $name doesn't match", $pub_id, $PS_EXIT_SYS_ERROR) if defined $data->{zp_err} and $comp->{zp_err} != $data->{zp_err};
    &my_die("astrom value for $name doesn't match", $pub_id, $PS_EXIT_SYS_ERROR) if defined $data->{astrom} and $comp->{astrom} != $data->{astrom};
    &my_die("exp_name value for $name doesn't match", $pub_id, $PS_EXIT_SYS_ERROR) if defined $data->{exp_name} and $comp->{exp_name} ne $data->{exp_name};
    &my_die("exp_id value for $name doesn't match", $pub_id, $PS_EXIT_SYS_ERROR) if defined $data->{exp_id} and $comp->{exp_id} != $data->{exp_id};
    &my_die("chip_id value for $name doesn't match", $pub_id, $PS_EXIT_SYS_ERROR) if defined $data->{chip_id} and $comp->{chip_id} != $data->{chip_id};
    &my_die("cam_id value for $name doesn't match", $pub_id, $PS_EXIT_SYS_ERROR) if defined $data->{cam_id} and $comp->{cam_id} != $data->{cam_id};
    &my_die("fake_id value for $name doesn't match", $pub_id, $PS_EXIT_SYS_ERROR) if defined $data->{fake_id} and $comp->{fake_id} != $data->{fake_id};
    &my_die("warp_id value for $name doesn't match", $pub_id, $PS_EXIT_SYS_ERROR) if defined $data->{warp_id} and $comp->{warp_id} != $data->{warp_id};
    &my_die("diff_id value for $name doesn't match", $pub_id, $PS_EXIT_SYS_ERROR) if defined $data->{diff_id} and $comp->{diff_id} != $data->{diff_id};
    &my_die("direction value for $name doesn't match", $pub_id, $PS_EXIT_SYS_ERROR) if defined $data->{direction} and $comp->{direction} != $data->{direction};

    return 1;
}

# Combine multiple files for MOPS
sub mops_combine
{
    my $data = shift;           # Data
    my $output = shift;         # Output name
    my $input = shift;          # Input name

    $output = $ipprc->file_resolve( $output, 'create' ) or
        &my_die( "Unable to resolve output file $output", $pub_id, $PS_EXIT_SYS_ERROR);

    my $command = "$ppMops $input $output";
    $command .= " -exp_name " . $data->{exp_name} if defined $data->{exp_name};
    $command .= " -exp_id " . $data->{exp_id} if defined $data->{exp_id};
    $command .= " -chip_id " . $data->{chip_id} if defined $data->{chip_id};
    $command .= " -cam_id " . $data->{cam_id} if defined $data->{cam_id};
    $command .= " -fake_id " . $data->{fake_id} if defined $data->{fake_id};
    $command .= " -warp_id " . $data->{warp_id} if defined $data->{warp_id};
    $command .= " -diff_id " . $data->{diff_id} if defined $data->{diff_id};
    $command .= " -inverse" if defined $data->{direction} and $data->{direction} == 0;
    $command .= " -zp " . $data->{zp} if defined $data->{zp};
    $command .= " -zp_error " . $data->{zp_err} if defined $data->{zp_err};
    $command .= " -astrom_rms " . $data->{astrom} if defined $data->{astrom};

    unless ($no_op) {
        my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
            run(command => $command, verbose => $verbose);
        &my_die( "Unable to translate", $pub_id, $PS_EXIT_SYS_ERROR) unless $success;
        &my_die( "Unable to find translated file $output", $pub_id, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists( $output );
    } else {
        print "Not running: $command\n";
    }

    return $output;
}


sub my_die
{
    my $msg = shift;            # Exit message
    my $pub_id = shift;         # Publish run identifier
    my $fault = shift;          # Fault code

    $fault = $PS_EXIT_PROG_ERROR unless defined $fault;

    carp($msg);
    if (defined $pub_id and not $no_update) {
        my $command = "$pubtool -add";
        $command .= " -pub_id $pub_id";
        $command .= " -path_base $outroot";
        $command .= (" -dtime_script " . ((DateTime->now->mjd - $mjd_start) * 86400));
        $command .= " -hostname $host" if defined $host;
        $command .= " -fault $fault";
        $command .= " -dbname $dbname" if defined $dbname;

        my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
            run(command => $command, verbose => $verbose);
    }
    exit $fault;
}


__END__
