#!/bin/env perl

# pstamp_finish.pl

use warnings;
use strict;

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

use Time::Local;
use Sys::Hostname;
use IPC::Cmd 0.36 qw( can_run run );
use File::Temp qw( tempfile );
use File::Copy;
use File::Basename qw(dirname);

use PS::IPP::Metadata::Config;
use PS::IPP::Metadata::Stats;
use PS::IPP::Metadata::List qw( parse_md_list );

use PS::IPP::Config qw( :standard );
use PS::IPP::PStamp::RequestFile qw( :standard );
use PS::IPP::PStamp::Job qw( :standard );

my ( $req_id, $req_name, $req_file, $out_dir, $product, $dbname, $dbserver, $verbose, $save_temps, $redirect_output);

# the char to the right of the bar may be used as a single - alias for the longer name
# for example --input and -i are equivalent
GetOptions(
           'req_id=s'       => \$req_id,
           'req_name=s'     => \$req_name,
           'req_file=s'     => \$req_file,
           'product=s'      => \$product,
           'out_dir=s'      => \$out_dir,
	   'dbname=s'       => \$dbname,
	   'dbserver=s'     => \$dbserver,
	   'verbose'        => \$verbose,
	   'save-temps'     => \$save_temps,
	   'redirect-output' => \$redirect_output,
) or pod2usage( 2 );

pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;

die "usage: --req_id id --req_name name --req_file file --product product --out_dir output_directory [--dbname dbname --verbose]\n"
    if !$req_id or !$req_name or !$req_file or !$product or !$out_dir;

my $ipprc = PS::IPP::Config->new(); # IPP Configuration
if ($redirect_output) {
    my $logDest = "$out_dir/psfinish.$req_id.log";
    $ipprc->redirect_output($logDest);
}

if (!$dbserver) {
    $dbserver =  metadataLookupStr($ipprc->{_siteConfig}, 'PS_DBSERVER');
}

my $missing_tools;

my $pstamptool  = can_run('pstamptool')  or (warn "Can't find pstamptool"  and $missing_tools = 1);
my $dsreg  = can_run('dsreg')  or (warn "Can't find dsreg"  and $missing_tools = 1);
my $pstamp_results = can_run('pstamp_results_file.pl') 
                            or (warn "Can't find pstamp_results_file.pl" and $missing_tools = 1);
my $pstampdump = can_run('pstampdump') or (warn "Can't find pstampdump" and $missing_tools = 1);
if ($missing_tools) {
    warn("Can't find required tools.");
    exit ($PS_EXIT_CONFIG_ERROR);
}

if ($product eq "NULL") {
    stop_request_and_exit($req_id, $PS_EXIT_PROG_ERROR);
}

my $outputDataStoreRoot = metadataLookupStr($ipprc->{_siteConfig}, 'DATA_STORE_ROOT');
exit ($PS_EXIT_CONFIG_ERROR) unless defined $outputDataStoreRoot; # lookup failure outputs a message

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

{
    # set the output fileset's name to the request name.
    my $fileset = $req_name;

    print STDERR "product: $product  REQ_NAME: $req_name $out_dir\n" if $verbose;

    if (!-e $out_dir) {
        # something must have gone wrong parsing the request
        print STDERR  "output directory $out_dir does not exist\n";

        if (!mkdir $out_dir) {
            print STDERR "cannot create output directory $out_dir";
            stop_request_and_exit($req_id, $PS_EXIT_UNKNOWN_ERROR);
        }


    } elsif (! -d $out_dir ) {
        # XXX TODO: fault the request so we pstamp_finish doesn't keep trying to process the
        # request
        print STDERR "output directory $out_dir exists but is not a directory";
        stop_request_and_exit($req_id, $PS_EXIT_UNKNOWN_ERROR);
    }

    if (! -e $req_file ) {
        print STDERR "request file $req_file is missing\n";
        stop_request_and_exit($req_id, $PS_EXIT_CONFIG_ERROR);
    }

    # this function is PS::IPP::PStamp::RequestFile::read_request_file
    my ($header, $rows) = read_request_file($req_file);

    if (!$header or !$rows) {
        # Since a request got queued, the request file must have been readable at some
        # point 
        print STDERR "failed to read request_file $req_file" ;
        stop_request_and_exit($req_id, $PS_EXIT_CONFIG_ERROR);
    }

    # at this point we need to find out what kind of request type it is and
    # split the processing depending on the request type
    # the only processing required for a detectabilty query is to build the output fileset.

    # the following is for a postage stamp request

    my ($rlf, $reglist_name) = tempfile ("$out_dir/reglist.XXXX", UNLINK => !$save_temps);
    print $rlf "results.fits|||table|\n";
    print $rlf "results.mdc|||text|\n";

    my $err_file = "parse_error.txt";
    if (-e "$out_dir/$err_file" ) {
        print $rlf "$err_file|||text|\n";
    }

    my ($tdf, $table_def_name) = tempfile ("$out_dir/tabledef.XXXX", UNLINK => !$save_temps);


    # data for the header
    print $tdf "$req_name|$req_id|\n";
    my @jobs;
    {
        my $command = "$pstamptool -listjob -req_id $req_id";
        $command   .= " -dbname $dbname" if $dbname;
        $command   .= " -dbserver $dbserver" if $dbserver;
        my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
            run(command => $command, verbose => $verbose);
        unless ($success) {
            die("Unable to perform $command error code: $error_code");
        }
        my $output = join "", @$stdout_buf;
        if (!$output) {
            if ($verbose) {
                print STDERR "Request $req_id produced no jobs.\n"
            }
            # This should not happen. A fake job should be queued
            stop_request_and_exit($req_id, $PS_EXIT_PROG_ERROR);
        } else {
            my $metadata = $mdcParser->parse($output) or die("Unable to parse metdata config doc");

            my $jobs = parse_md_list($metadata);

            @jobs = @$jobs;
        }
    }

    my $exp_info;
    my $last_exp_id = -1;
    foreach my $job (@jobs) {
        my $job_id = $job->{job_id};
        my $job_type = $job->{jobType};
        my $rownum = $job->{rownum};
        my $fault = $job->{fault};
        my $exp_id = $job->{exp_id};


        if (($fault eq $PSTAMP_DUP_REQUEST) and ($req_name eq "NULL")) {
            # this request had a duplicate request name. We can't put the results
            # on the data store since the product name is already used
            # maybe we could be more clever about this and choose a fileset name
            stop_request_and_exit($req_id, $fault);
        }
        my ($row, $req_info, $project) = get_request_info($rows, $rownum);

        my $proj_hash = resolve_project($ipprc, $project, $dbname, $dbserver);
        my $image_db = $proj_hash->{dbname};
        if (!$image_db) {
            carp("failed to find imagedb for project: $project");
            if (!$fault) {
                stop_request_and_exit($req_id, $PS_EXIT_CONFIG_ERROR);
            }
        }

        my $job_params = get_job_parameters($job);
        my $stage = "";
        if ($job_params) {
            $stage = $job_params->{stage};
        }

        if ($stage ne 'stack') {
            # get the metadata for the exposure (if any i.e. stack) 
            # returns an appropriate string if !$exp_id
            $exp_info = get_exposure_info($image_db, $exp_id);
            $last_exp_id = $exp_id;
        } else {
            my $filter = $job_params->{filter};
            $filter = "0" if !$filter;
            $exp_info = "0|0|0|$filter|0|0";
            $last_exp_id = -1;
        }

        if (($job_type eq "stamp") || ($job_type eq "get_image") || ($job_type eq "none")) {
            my $jreglist = "$out_dir/reglist$job_id";
            if (open JRL, "<$jreglist") {;
                # process the reglist file to get the list of files produced by this job
                foreach my $line (<JRL>) {
                    # add line to the requests's reglist
                    print $rlf $line;

                    chomp $line;
                    my ($img_name, undef) = split '\|', $line;

                    # add line to the table definition file
                    print $tdf "$rownum|$fault|$img_name|$job_id|";

                    # ra_deg and dec_deg are the coordinates of center of the stamp
                    # first assume that the image is compressed and check the first extension.
                    # If not found check the PHU. If that doesn't work just set them to zero.
                    # XXX do this more cleanly
                    my (undef, $ra_deg, $dec_deg) = split " ", `echo $out_dir/$img_name | fields -x 0 RA_DEG DEC_DEG`;
                    if (!defined $ra_deg) {
                        (undef, $ra_deg, $dec_deg) = split " ", `echo $out_dir/$img_name | fields RA_DEG DEC_DEG`;
                    }
                    $ra_deg = 0.0 if (!$ra_deg);
                    $dec_deg = 0.0 if (!$dec_deg);
                    print $tdf "$ra_deg|$dec_deg|";

                    print $tdf "$exp_info|";
                    print $tdf "$req_info|";
                    print $tdf "\n";
                }
                close JRL;
            } else {
                print STDERR "no reglist file for job $job_id\n" if $verbose;
                print $tdf "$rownum|$fault|0|$job_id|";
                print $tdf "0|0|";       # center of (non-existent) stamp
                print $tdf "$exp_info|";
                print $tdf "$req_info|";
                print $tdf "\n";
            }
        } else {
            # XXX do list jobs
            # we can probably arange things to use the code as above and skip the fileset registration
            print STDERR "Unknown jobType: $job_type";
            next;
        }
    }
    close $rlf;
    close $tdf;
    my $request_fault = 0;
    # make the results file
    {
        my $command = "$pstamp_results --input $table_def_name --output $out_dir/results.fits";
        my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
            run(command => $command, verbose => $verbose);
        unless ($success) {
            print STDERR "Unable to perform $command error code: $error_code\n";
            $request_fault = $error_code >> 8;
        } else {
            # dump a textual representation
            my $command = "$pstampdump $out_dir/results.fits > $out_dir/results.mdc";
            my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
                run(command => $command, verbose => $verbose);
            unless ($success) {
                print STDERR "Unable to perform $command error code: $error_code\n";
                $request_fault = $error_code >> 8;
            }
        }
    }
    if (!$request_fault) {
        # register the fileset
        my $command = "$dsreg --list $reglist_name --add $fileset --product $product --type PSRESULTS";
        $command .= " --link --datapath $out_dir --ps0 $req_id";
        $command .= " --dbname $dbname" if $dbname;

        my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
            run(command => $command, verbose => $verbose);
        unless ($success) {
            #die("Unable to perform $command error code: $error_code");
            print STDERR "Unable to perform $command error code: $error_code\n";
            $request_fault = $error_code >> 8;
            # fall through to stop the request
        }
    }
    # set the request's state to stop
    {
        my $command = "$pstamptool -updatereq -req_id $req_id -state stop -fault $request_fault";
        $command   .= " -dbname $dbname" if $dbname;
        $command   .= " -dbserver $dbserver" if $dbserver;
        my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
            run(command => $command, verbose => $verbose);
        unless ($success) {
            die("Unable to perform $command error code: $error_code");
        }
    }
    exit $request_fault;
}

sub stop_request_and_exit {
    my $req_id = shift;
    my $fault  = shift;

    my $command = "$pstamptool -updatereq -req_id $req_id -state stop -fault $fault";
    $command   .= " -dbname $dbname" if $dbname;
    $command   .= " -dbserver $dbserver" if $dbserver;

    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
        run(command => $command, verbose => $verbose);
    unless ($success) {
        die("Unable to perform $command error code: $error_code");
    }
    exit $fault;
}

sub get_request_info {
    my $rows = shift;
    my $rownum = shift;

    if ($rownum eq 0) {
        my $dummy_rowinfo = "0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|";
        return (undef, $dummy_rowinfo, "none");
    }
    my $row = $rows->{$rownum};


    # these may be set to null during processing
    my $component = $row->{COMPONENT};
    $component = "null" if !$component;
    my $tess_id = $row->{TESS_ID};
    $tess_id = "null" if !$tess_id;
    my $comment = $row->{COMMENT};
    $comment = "null" if !$comment;
    my $data_group = $row->{DATA_GROUP};
    if (!defined $data_group) {
        # XXX: backwards compatibility hook. Remove "soon".
        $data_group = $row->{LABEL};
    }
    $data_group = "null" if !$data_group;

    # This is ugly, error prone and hard to change.
    # Create a results file module and provide a list of the names (we have the data in the columns)
    my $rowinfo = "$row->{PROJECT}|$row->{JOB_TYPE}|$row->{REQ_TYPE}|$row->{IMG_TYPE}|";
    $rowinfo   .= "$row->{ID}|$tess_id|$component|$data_group|$row->{OPTION_MASK}|$row->{MJD_MIN}|$row->{MJD_MAX}|";
    $rowinfo   .= "$row->{REQFILT}|$row->{COORD_MASK}|$row->{CENTER_X}|$row->{CENTER_Y}|";
    $rowinfo   .= "$row->{WIDTH}|$row->{HEIGHT}|";
    $rowinfo   .= $comment;

    return ($row, $rowinfo, $row->{PROJECT});
}

sub get_job_parameters {
    my $job = shift;
    if (!$job->{outputBase}) {
        print "get_job_parameters: $job->{job_id} has no outputBase\n";
        return undef;
    }
    my $params_file = $job->{outputBase} . '.mdc';
    if (! -e $params_file ) {
        print "get_job_parameters: $job->{job_id} has no parameters file\n";
        return undef;
    }
    open IN, "<$params_file" or die "unable to open $params_file";
    my $data = join "", (<IN>);
    close IN;
    if (! $data ) {
        print "get_job_parameters: parameters file is empty\n";
        return undef;
    }
    my $metadata = $mdcParser->parse($data) or die("Unable to parse metdata config doc");

    my $results = parse_md_list($metadata);
    if (scalar @$results != 1) {
        print STDERR "get_job_params: failed to parse_md_list\n";
        return undef;
    }
    return $results->[0];
}

sub get_exposure_info {
    my $image_db= shift;
    my $exp_id = shift;

    if (!$exp_id or !$image_db) {
        # no exposure id just return zeros
        # XXX: we could put a value in for filter, but we don't have a good place to find it

        #"$mjd_obs|$ra_obs|$dec_obs|$filter|$exp_time|$fpa_id";
        return "0|0|0|0|0|0";
    }

    my $regtool = can_run('regtool') or (warn "Can't find regtool" and $missing_tools = 1);
    if ($missing_tools) {
        warn("Can't find required tools.");
        exit ($PS_EXIT_CONFIG_ERROR);
    }

    my $command = "$regtool -processedexp -dbname $image_db -exp_id $exp_id";

    # run the tool and parse the output
    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
                run(command => $command, verbose => $verbose);
    unless ($success) {
        # not sure if we should die here
        die "cannot get exposure information for $exp_id from image database $image_db";
    }
    my $mdcParser = PS::IPP::Metadata::Config->new; # Parser for metadata config files

    my $output = join "", @$stdout_buf;
    if (!$output) {
        print STDERR "no output returned from $command\n" if $verbose;
        return undef;
    }
    my $metadata = $mdcParser->parse($output) or die("Unable to parse metdata config doc");

    my $exposures = parse_md_list($metadata);
    my $numExp = @$exposures;

    die "unexpected number of exposures $numExp found for exp_id: $exp_id in DB: $image_db" if $numExp != 1;
    
    my $exp = $exposures->[0];

    #my $info = "$mjd_obs|$ra_obs|$dec_obs|$filter|$exp_time|$fpa_id";

    use constant RADIANS_TO_DEGREES => 90. / atan2(1, 0);
    my $ra_deg   = $exp->{ra} * RADIANS_TO_DEGREES;
    my $decl_deg = $exp->{decl} * RADIANS_TO_DEGREES;
    my $mjd_obs = dateobs_to_mjd($exp->{dateobs});

    my $info = "$mjd_obs|$ra_deg|$decl_deg|$exp->{filter}|$exp->{exp_time}|$exp->{exp_name}";
            
    return $info;
}

sub dateobs_to_mjd {
    my $dateobs = shift;

    # dateobs is in format: 1970-01-01T00:00:00

    my ($date, $time) = split "T", $dateobs;
    my ($year, $mon, $day) = split "-", $date;
    my ($hr, $min, $sec) = split ":", $time;

    my $ticks = timegm($sec, $min, $hr, $day, $mon-1, $year-1900);

    return 40587.0 + ($ticks/86400.);
}
