#!/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 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 PStamp::RequestFile qw( :standard );
use PStamp::Job qw( :standard );

my ( $req_id, $req_name, $req_file, $out_dir, $product, $dbname, $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,
	   '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);
}

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);
if ($missing_tools) {
    warn("Can't find required tools.");
    exit ($PS_EXIT_CONFIG_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;


    # Here we invoke the assumption that the output for the request is placed in the
    # fileset directory directly
#    my $out_dir = "$outputDataStoreRoot/$product/$fileset";

    # now we are assuming that the output directory is the dirname of the request file
    # XXX: put this in the database

    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($req_id, $PS_EXIT_UNKNOWN_ERROR, $dbname);
        }


    } 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($req_id, $PS_EXIT_UNKNOWN_ERROR, $dbname);
    }

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

    # this function is 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($req_id, $PS_EXIT_CONFIG_ERROR, $dbname);
    }

    # 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";

    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;
        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"
            }
            # No Jobs. 
            # XXXX Ouch. We need results for each rownum (each request specification) in the request file 
            # including those that produced no jobs.
            # for now add an entry for rownum 1 and a phony error code.
            # we've included parse_error.txt to the fileset if it exists
            #
            my $rownum = 0;
            my $fault = 42; # get a real error code
            print $tdf "$rownum|$fault|0|0|";
            print $tdf "0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|\n";
        } else {
            my $metadata = $mdcParser->parse($output) or die("Unable to parse metdata config doc");

            my $jobs = parse_md_list($metadata);

            @jobs = @$jobs;
        }
    }

    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};

        my ($row, $req_info, $project) = get_request_info($rows, $rownum);
        my $proj_hash = resolve_project($ipprc, $project, $dbname);
        my $image_db = $proj_hash->{dbname};

        # get the metadata for the exposure (if any i.e. stack) 
        # returns an appropriate string if !$exp_id
        my $exp_info = get_exposure_info($image_db, $exp_id);

        if (($job_type eq "stamp") || ($job_type eq "get_image")) {
            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
                    # XXX: parse the stamp header to find it
                    print $tdf "0.0|0.0|";

                    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|";
            }
        } 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;
        }
    }
    if (!$request_fault) {
        # register the fileset
        my $command = "$dsreg --list $reglist_name --add $fileset --product $product --type PSRESULTS";
        $command .= " --link --datapath $out_dir";
        $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;
        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 {
    my $req_id = shift;
    my $fault  = shift;
    my $dbname = shift;

    my $command = "$pstamptool -updatereq -req_id $req_id -state stop -fault $fault";
    $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");
    }
    exit $fault;
}

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

    my $row = $rows->{$rownum};

    # 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}|$row->{CLASS_ID}|$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}";

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

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

    if (!$exp_id) {
        # 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}, $exp->{exp_time});

    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;
    my $exp_time = 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);

    $ticks += $exp_time / 2.0;

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