package PS::MOPS::JPLORB;

use 5.008;
use strict;
use warnings;

use base qw(Exporter);
our @EXPORT = qw(
    jplorb_evaluateOrbit
    jplorb_evaluateAttribution
);
our $VERSION = '0.01';

use Carp;
use Cwd;
use Params::Validate;
use FileHandle;
use File::Temp;
use File::Path;
use File::Slurp;
use PS::MOPS::DC::Orbit;  # Wait until Orbit.pm rebuilt for derived objects
use PS::MOPS::DC::Instance;


my $instance_name = $ENV{'MOPS_DBINSTANCE'} || die "can't get instance name.";
my $inst = PS::MOPS::DC::Instance->new(DBNAME => $instance_name);
my $mops_config = $inst->getConfig;
our $JPLORB_EXE = $mops_config->{lodctl}->{diffcor_program} || 'mopsdiffcor';
#croak "can't locate $JPLORB_EXE" unless -x $JPLORB_EXE;

$ENV{CAET_DATA} = "$ENV{MOPS_HOME}/data/caet_data" unless $ENV{CAET_DATA};


sub _flatten_detections {
    # Return the list of detections specified with duplicates (by detId) removed.
    my @in_detections = @_;
    my @out_detections;
    my %chk;                # keep track of dups here
    my $id;

    foreach my $det (@in_detections) {
        $id = $det->detId;
        if (!$chk{$id}) {
            push @out_detections, $det;
            $chk{$id} = 1;      # mark as seen
        }
    }
    return @out_detections;
}


sub _flatten_tracklets {
    # Return the list of tracklets specified with duplicates (by trackletId) removed.
    my @in_tracklets = @_;
    my @out_tracklets;
    my %chk;                # keep track of dups here
    my $id;

    foreach my $trk (@in_tracklets) {
        $id = $trk->trackletId;
        if (!$chk{$id}) {
            push @out_tracklets, $trk;
            $chk{$id} = 1;      # mark as seen
        }
    }
    return @out_tracklets;
}


sub jplorb_evaluateOrbit {
    my $inst = shift;
    my %args = validate(@_, {
        orbit => 1,
        detections => 1,
        covariance => 0,
    });

    my $orb = $args{orbit};
    my @detections = sort {
        $a->epoch <=> $b->epoch
    } _flatten_detections @{$args{detections}};  # new detections, sorted by epoch

    # Need to write out IOD and detections files, then read
    # output file.

    my $TMPDIR = File::Temp::tempdir('/tmp/jplorbXXXXXX', CLEANUP => 1);
    my $IODFILE = "$TMPDIR/iod";
    my $DETFILE = "$TMPDIR/det";
    my $OUTFILE = "$TMPDIR/out";

    # Write out IOD.
    my $iodfh = new FileHandle ">$IODFILE" or die "can't open IODFILE $IODFILE";
    my $old_oid = $orb->orbitId;
    die "need objectName attached to orbit" unless $orb->objectName;

    # Need to wire objectName into orbitId so differential correct can associate
    # the orbit with track.
    $orb->orbitId($orb->objectName);        # hard-wire objectName => orbitId
    print $iodfh $orb->serialize(), "\n";
    $orb->orbitId($old_oid);                # restore it
    close $iodfh;

    # Write out detections.
    my $detfh = new FileHandle ">$DETFILE" or die "can't open DETFILE $DETFILE";
    foreach my $det (@detections) {
        print $detfh join(" ",
            $orb->objectName,
            $det->epoch,
            $det->ra,
            $det->dec,
            $det->mag,
            $det->obscode) . "\n";
    }
    close $detfh;

    # note: JPLORB DIVA lib noise redirected to /dev/null
    my $options = $args{covariance} ? '--covariance' : '';
    my %results;
#    my $cmd = "$JPLORB_EXE $options $IODFILE $DETFILE $OUTFILE > /dev/null 2>&1";
    my $cmd = "$JPLORB_EXE $options $IODFILE $DETFILE $OUTFILE";
    print "cmd=$cmd\n";
    my $ret = system $cmd;
    carp "cmd failed: $? : $cmd" unless $ret == 0;

#    print "Enter to continue.\n";
#    my $input=<STDIN>;


    # Result from JPLORB_EXE should be
    # q_au e i_deg node_deg arg_peri_deg time_peri_mjd mag epoch object_name resid conv_code
    my $stuff = read_file($OUTFILE);
    my $obj;

    if ($stuff) {
        $obj = modco_deserialize($inst, $stuff);
    }
    # cleanup $TMPDIR
    rmtree($TMPDIR);
    return $obj;
}



sub jplorb_evaluateAttribution {
    my ($inst, $dobj, $tracklet) = @_;
    my $obj;
    my $cmd;

    my $oldwd = getcwd;         # current working directory
    eval {
        # Need to write out .sum export for milani_iod.  This is lame; should be a high-level interface for this.
        my $TMPDIR = File::Temp::tempdir('/tmp/jplorbXXXXXX', CLEANUP => 1);
        chdir $TMPDIR or die "can't chdir to $TMPDIR";

#        my $IODDIR = "stdin";
        my $OUTFILE = "out";
#        my $DETFILE = "dets";

        # Write out IOD DX export.
#        my $oid = 'A000000';
        my $lineno = 0;
        my @tracklets = sort { 
            $a->trackletId <=> $b->trackletId 
        } _flatten_tracklets ($dobj->fetchTracklets(), $tracklet);    # sorted list of tracklet IDs
        my $oid = join('=', $dobj->objectName, $tracklet->trackletId); 
        my $ROOT = $oid;
        my $SUMFILE = "$ROOT.sum";
        my $fh = new FileHandle ">$SUMFILE";
        my $det;

        foreach my $trk (@tracklets) {
            foreach (@{$trk->detections}) {
                print $fh join(" ", $oid, $trk->trackletId, $lineno++), "\n";
            }
        }
        close $fh;

        # Old hard-wired dxlt2iod stuff.
#        $cmd = sprintf "dxlt2iodreq --instance=%s --twopass --tracklets --oid=%s -", $inst->dbname, $oid;
#        $cmd = sprintf "dxlt2iodreq --instance=%s --tracklets --oid=%s -", $inst->dbname, $oid;
#        my $fh = new FileHandle "|$cmd" or die "can't open filehandle for command: $cmd";
#        print $fh $_->trackletId, "\n" foreach @tracklets;
#        close $fh;

        # Write out detections for JPL code in MITI format (blech).
        my $TRACKSFILE = "$ROOT.tracks";
        my $tracks_fh = new FileHandle ">$TRACKSFILE";
        foreach $det ($dobj->fetchDetections()) {
            printf $tracks_fh "%s %.10f %.10f %.10f %.10f %s\n",
                $oid,
                $det->epoch,
                $det->ra,
                $det->dec,
                $det->mag,
                $det->obscode;
        }
        # Add detections we want to test.
        foreach $det (@{$tracklet->detections}) {
            printf $tracks_fh "%s %.10f %.10f %.10f %.10f %s\n",
                $oid,
                $det->epoch,
                $det->ra,
                $det->dec,
                $det->mag,
                $det->obscode;
        }
        close $tracks_fh;

        # note: JPLORB DIVA lib noise redirected to /dev/null
        my $iod_program = $mops_config->{lodctl}->{iod_program} or die "can't get iod_program from config";
        my $fallback_iod = $mops_config->{lodctl}->{fallback_iod};
        my $fallback_iod_str = $fallback_iod ? "--fallback_iod $fallback_iod" : '';
        # XXX 1000 => yuck
#        $cmd = "$iod_program $IODDIR | $JPLORB_EXE --covariance $fallback_iod_str --threshold_arcseconds 1000 - $DETFILE $OUTFILE > /dev/null 2>&1";
        $cmd = "$iod_program $TRACKSFILE | $JPLORB_EXE --covariance $fallback_iod_str --threshold_arcseconds 1000 - $TRACKSFILE $OUTFILE > /dev/null 2>&1";

        my $ret = system $cmd;
        die "cmd failed: $? : $cmd" unless $ret == 0;

        # Result from JPLORB_EXE should be
        # q_au e i_deg node_deg arg_peri_deg time_peri_mjd mag epoch object_name resid conv_code
        my $stuff = read_file($OUTFILE);

        if ($stuff) {
            $obj = modco_deserialize($inst, $stuff);
        }
    };
    croak $@ if $@;
    chdir $oldwd;           # restore working directory

    return $obj;
}


1;
__END__

=head1 NAME

PS::MOPS::JPLORB - Perl extension for performing a differential correction
on an orbit and detections using JPL's Rapid Comet and Asteroid Orbit
Determination Process software.

=head1 SYNOPSIS

  use PS::MOPS::JPLORB qw(all);
  $results = jplorb_evaluateOrbit(
    orbit => $orbit,
    detections => @detections
  );

=head1 DESCRIPTION

Adds a list of detections to an already existing PSMOPS orbit's detections
and performs a differential correction on the orbit.  The return value
is a HASHREF containing:

  conv_code : JPL od_converge() convergence code
  residual_arcsec : residual of differentially-corrected orbit, in arcseconds
  q_au
  e
  i_deg
  node_deg
  arg_peri_deg
  time_peri
  epoch : new orbital elements

=head1 SEE ALSO

PS::MOPS::JPLEPH

=head1 AUTHOR

Larry Denneau, E<lt>denneau@ifa.hawaii.edu<gt>

=head1 COPYRIGHT AND LICENSE

Copyright 2005 Institute for Astronomy, University of Hawaii

=cut
