#!/bin/env perl
###
### pstampparse.pl
###
###     Run the postage stamp request parser for a given request id
###

use warnings;
use strict;

use Sys::Hostname;
use Getopt::Long qw( GetOptions );
use PStamp::RequestFile qw( :standard );
use PStamp::Job qw( :standard );

my $verbose;
my $dbname;
my $req_id;
my $request_file_name;
my $mode = "list_uri";
my $out_dir;
my $product;

GetOptions(
    'file=s'    =>  \$request_file_name,
    'req_id=s'  =>  \$req_id,
    'out_dir=s' =>  \$out_dir,
    'product=s' =>  \$product,
    'mode=s'    =>  \$mode,
    'dbname=s'  =>  \$dbname,
    'verbose'   =>  \$verbose,
);

die "invalid mode '$mode'" unless ($mode eq "list_uri") or ($mode eq "list_job") or ($mode eq "queue_job");
die "file is required"     if !defined($request_file_name);

if ($mode ne "list_uri") {
    die "req_id is required"   if !$req_id;
    die "out_dir is required"  if !$out_dir;
    die "product is required"  if !$product;
}

use IPC::Cmd 0.36 qw( can_run run );

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 );
my $ipprc = PS::IPP::Config->new(); # IPP Configuration

my $missing_tools;

my $pstamptool  = can_run('pstamptool')  or (warn "Can't find pstamptool"  and $missing_tools = 1);
my $pstampdump  = can_run('pstampdump') or (warn "Can't find pstampdump" and $missing_tools = 1);
my $fields  = can_run('fields') or (warn "Can't find fields" and $missing_tools = 1);

if ($missing_tools) {
    warn("Can't find required tools.");
    exit ($PS_EXIT_CONFIG_ERROR);
}

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

#
# get the data from the extension header
#
my $fields_output;
{
    my $command = "echo $request_file_name | $fields -x 0 EXTNAME EXTVER REQ_NAME";
    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
        run(command => $command, verbose => $verbose);
    # fields doesn't return zero when it succeeds
    #unless ($success) {
    #    print STDERR @$stderr_buf;
    #}
    $fields_output = join "", @$stdout_buf;
}
my (undef, $extname, $extver, $req_name) = split " ", $fields_output;

# make sure the file contains what we are expecting

die "$request_file_name is not a PS1_PS_REQEST" if $extname ne "PS1_PS_REQUEST";
die "REQ_NAME not found in $request_file_name"  if (!$req_name);
die "wrong EXTVER $extver found in $request_file_name" if ($extver ne "1");

if ($out_dir) {
    $out_dir .= "/$req_name";
    if (! -e $out_dir ) {
        mkdir $out_dir or die "cannot create output directory $out_dir";
    } elsif (! -d $out_dir ) {
        die "output fileset directory $out_dir exists but is not a directory";
    }
}

if ($req_id) {
    my $command = "$pstamptool -updatereq -req_id $req_id  -name $req_name";
    $command .= " -outProduct $product";
    $command .= " -dbname $dbname" if $dbname;
    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
        run(command => $command, verbose => $verbose);
    unless ($success) {
        die "$command failed";
    }
}

#
# now convert the request table to an array of metadata config docs
#

my $rows;
{
    my $command = "$pstampdump $request_file_name";
    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
        run(command => $command, verbose => $verbose);
    unless ($success) {
        print STDERR @$stderr_buf;
    }
    my $table =  $mdcParser->parse(join "", @$stdout_buf) or
        die("Unable to parse metdata config doc");

    $rows = parse_md_list($table);
}

my $num_jobs = 0;
foreach my $row (@$rows) {
    my $rownum   = $row->{ROWNUM};
    my $job_type = $row->{JOB_TYPE};
    my $project  = $row->{PROJECT};
    my $req_type = $row->{REQ_TYPE};
    my $img_type = $row->{IMG_TYPE};
    my $id       = $row->{ID};
    my $class_id = $row->{CLASS_ID};
    my $filter   = $row->{REQFILT};
    my $mjd_min = $row->{MJD_MIN};
    my $mjd_max = $row->{MJD_MAX};
    my $x = $row->{CENTER_X};
    my $y = $row->{CENTER_Y};
    my $w = $row->{WIDTH};
    my $h = $row->{HEIGHT};
    my $coord_mask = $row->{COORD_MASK};
    my $option_mask= $row->{OPTION_MASK};

    $class_id = "" if ($class_id eq "null" or $class_id eq "all");
    
    # XXX: TODO: sanity check all parameters

    # XXX: TODO: We shouldn't just die in this loop.
    # If we encounter an error for a particular row
    # add a job with the proper fault code. If there is a db or config error we should probably just
    # trash the request.
    die "job_type is list_uri but mode is $mode" if ($job_type eq "list_uri") and ($mode ne "list_uri");

    my $proj_hash = resolve_project($ipprc, $project, $dbname);
    die "project $project not found\n" unless $proj_hash;

    my $image_db = $proj_hash->{dbname};
    my $camera = $proj_hash->{camera};
    my $need_magic = $proj_hash->{need_magic};

    my $roi_string;
    # XXX we're depending on other code to insure valid values for roi components
    # this is checked in and ppstamp but I should check here so that we don't get that far,
    # but not today
    if ($x && ($x ne "null") && $y && ($y ne "null") && $w && ($w ne "null") && $h && ($h ne "null")) {
        if ($coord_mask & $PSTAMP_CENTER_IN_PIXELS) {
            $roi_string = "-pixcenter $x $y";
        } else {
            $roi_string = "-skycenter $x $y";
        }
        if ($coord_mask & $PSTAMP_RANGE_IN_PIXELS) {
            $roi_string .= " -pixrange $w $h";
        } else {
            $roi_string .= " -arcrange $w $h";
        }
    }

    die "region of interest is required to make postage stamps"
        if (($job_type eq "stamp") && !defined($roi_string));

    # find the uris for this request

    if ($req_type eq "bycoord") {
        die "region of interest is required for request type bycoord" if !defined($roi_string) ;
        die "center must be specified in sky coordintes for bycoord" 
            if ($coord_mask & $PSTAMP_CENTER_IN_PIXELS);
    }

    # Call PStamp::Job's locate_images routinte to get the parameters for this request specification
    my $images = locate_images($ipprc, $image_db, $req_type, $img_type, $id, $class_id,
            $x, $y, $mjd_min, $mjd_max, $filter);

    if (!$images) {
        print STDERR "no matching images found for row $rownum\n";
        next;
    }
    if ($mode eq "list_uri") {
        foreach my $image (@$images) {
            print "$image->{image}\n";
        }
    } elsif ($job_type eq "get_image") {
        # XXX TODO: Get rid of this block and use the same code as the stamp jobs
        # XXX This doesn't work to get the mask and weight images
        my $listfile = "$out_dir/filelist";


        # map our img_type to the Data Store file types.
        my %filelist_img_types = ( "raw" => "chip", 
                                   "chip" => "chipproc",
                                   "warp" => "warp", 
                                   "stack"=>"stack",
                                   "diff" => "diff");

        my $filelist_img_type = $filelist_img_types{$img_type};

        open LISTFILE, ">$listfile"
            or die "failed to open file list: $listfile while parsing get_image request $req_id";

        foreach my $image (@$images) {
            my $class_id = $image->{class_id} ? $image->{class_id} : "";
            print LISTFILE "$image->{image}|$filelist_img_type|$class_id|\n";
        }
        close LISTFILE;
        $num_jobs++;
        my $command = "$pstamptool -addjob -req_id $req_id -job_type get_image"
                    . " -uri $listfile -outputBase $out_dir -rownum $rownum";
        $command .= " -dbname $dbname" if $dbname;
        if ($mode eq "list_job") {
            print "$command\n";
        } else {
            my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
                run(command => $command, verbose => $verbose);
            unless ($success) {
                print STDERR @$stderr_buf;
                # XXX TODO: now what? Should we mark the error state for the request?
                die "failed to queue job for request $req_id: rownum: $rownum";
            }
        }
    } else {
        my $args = $roi_string ? $roi_string : "";
        $args .= " -class_id $class_id" if $class_id;

        # sequence number for the the job for a request spec. Not to be confused with job_id
        my $job_num = 0;

        # XXX TODO: for raw and chip level images and class_id "null" if there are multiple images
        # use -list instead of -file
        foreach my $image (@$images) {
            my $uri = $image->{image};
            my $exp_id = $image->{exp_id};
            

            $job_num++;

            my $output_base = "$out_dir/${rownum}_${job_num}";

            # add astrometry file for raw and chip images if one is available
            if (($img_type eq "chip") || ($img_type eq "raw")) {
                $args .= " -astrom $image->{astrom}" if $image->{astrom};
            }

            if (($option_mask & $PSTAMP_SELECT_MASK) &&  $image->{mask} ) {
                $args .= " -mask $image->{mask}";
            }
            if (($option_mask & $PSTAMP_SELECT_WEIGHT) and $image->{weight} ) {
                $args .= " -weight $image->{weight}";
            }

            # XXX sounds like magic will be handled outside of the postage stamp server
            #if ($need_magic) {
            #    die "no magic mask available" if ! $image->{magic_mask};
            #    $args .= " -magic_mask $image->{magic_mask}" 
            #}

            # XXX: TODO: here is where we need to check whether or not the source inputs still exist
            # and if not, queue a regeneration job and set the job state appropriately.

            my $state = "run";

            $num_jobs++;
            my $command = "$pstamptool -addjob -req_id $req_id -job_type $job_type"
                . " -uri $uri -outputBase $output_base -args '$args' -rownum $rownum"
                . " -state $state";
            $command .= " -exp_id $exp_id" if $exp_id;
            $command .= " -dbname $dbname" if $dbname;

            if ($mode eq "list_job") { 
                # this is a debugging mode, just print the pstamptool that would have run
                # this is sort of like the mode -noupdate that some other tools support
                print "$command\n";
            } else {
                # mode eq "queue_job"
                my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
                    run(command => $command, verbose => $verbose);
                unless ($success) {
                    print STDERR @$stderr_buf;
                    # XXX TODO: now what? Should we mark the error state for the request?
                    # should we keep going for other uris? If so how do we report that some
                    # of the work that the request wanted isn't going to get done
                    die "failed to queue job for request $req_id";
                }
            }
        }
    }
}

if ($mode eq "queue_jobs") {
    if ($num_jobs) {
        exit 0;
    } else {
        # no jobs queued return error to get the request stopped
        # TODO: need to define meaningful error codes
        exit $PS_EXIT_UNKNOWN_ERROR;
    }
} else {
    exit 0;
}
