#!/usr/bin/perl

=head1 NAME

hist_sub - Ingest historical PS1 observations into the submission table

=head1 SYNOPSIS

hist_sub --submitter=NAME --disposition=D [options] < OBSFILE

hist_sub --desig=DESIG --tracklet_id=TID --submitter=NAME --disposition=D [options]

  OBSFILE : MPC-formatted observations
  --submitter=YOU : your name/initials
  --desig=DESIG : set designation (usually only with --tracklet_id)
  --disposition=D : set this disposition for the designation, D in P/Q/S/T/O/C
  --digest=F : set digest score (if known)
  --discovery : set as discovery
  --tracklet_id=TID : set tracklet ID (if known)
  --mpc_desig=DESIG : assign MPC designation
  --user_dbname=NAME : override DB name (for outreach)
  --debug : dump data, don't insert anything
  --help : show this manpage

=head1 DESCRIPTION

Ingest historical or externally submitted (outreach usually) PS1
observations in MPC format.

Observations are read, ganged into designations, checked for previous
submission, then inserted into the DB.

=head1 DISPOSITION VALUES

You must specify one of the following of the following disposition values
for the designation:

C : confirmed NEO discovery

S : submitted NEO, awaiting confirmation on NEOCP

K : submitted as new NEO, but known

D : submitted NEO, downgraded

T : standard submission (all other submitted cases)

O : outreach (usually IASC) object

J : rejected by MPC, problem with observations

=cut


use strict;
use warnings;
use Data::Dumper;
use Getopt::Long;
use Pod::Usage;
use Mail::Send;
use Astro::Time;

# Just a stub for quick debugging.
use PS::MOPS::Lib qw(:all);
use PS::MOPS::MPC;
use PS::MOPS::DC::Instance;
use PS::MOPS::DC::Field;
use PS::MOPS::DC::Tracklet;
use PS::MOPS::DC::DerivedObject;


# Detection statuses.
our $SUBMISSION_PENDING_NEO = 'P';
our $SUBMISSION_PENDING_STD = 'Q';
our $SUBMISSION_NEO = 'S';
our $SUBMISSION_STD = 'T';
our $SUBMISSION_OUTREACH = 'O';

# Start program here.
my $instance_name;
my $submitter;
my $desig;
my $mpc_desig;
my $disposition;
my $digest;
my $discovery;
my $tracklet_id;
my $user_dbname;    # user-override DB name (for outreach)
my $debug;
my $nosend;
my $help;
GetOptions(
    'instance=s' => \$instance_name,
    'submitter=s' => \$submitter,
    'tracklet_id=s' => \$tracklet_id,
    'disposition=s' => \$disposition,
    'desig=s' => \$desig,
    'digest=f' => \$digest,
    'mpc_desig=s' => \$mpc_desig,
    'dbname=s' => \$user_dbname,
    debug => \$debug,
    nosend => \$nosend,
    help => \$help,
) or pod2usage(2);
pod2usage(-verbose => 3) if $help;
pod2usage(-msg => 'Disposition is required.') unless $disposition;
pod2usage(-msg => 'Submitter is required.') unless $submitter;


my $inst = PS::MOPS::DC::Instance->new(DBNAME => $instance_name);
my $dbname = $inst->dbname;
my $logger = $inst->getLogger();
my $dbh = $inst->dbh;
my $lookup_sth = $dbh->prepare(<<"SQL") or die $dbh->errstr;
/* args are P1.dec, P2.dec, P1.dec, P2.dec, P1.ra, P2.ra */
select desig 
from export.mpc_sub 
where fpa_id=?
and abs(
    degrees(
        /* spherical distance from tracklet to target field center */
        acos(least(1.0, /* handle slight roundoff error causing arg > 1.0 */
            sin(radians(export.mpc_sub.dec_deg)) * sin(radians(?))
            + cos(radians(export.mpc_sub.dec_deg)) * cos(radians(?)) * cos(radians(export.mpc_sub.ra_deg - ?))
        ))
    )
) < 0.000555 /* 2 arcsec */
SQL


my $insert_sth = $dbh->prepare(<<"SQL") or die $dbh->errstr;
insert into export.mpc_sub
values (
    ?, /* seq_num */
    null, /* batch_num; will write later */
    ?, /* fpa_id */
    ?, /* epoch_mjd */
    ?, /* ra_deg */
    ?, /* dec_deg */
    ?, /* filter_id */
    ?, /* mag */
    ?, /* obscode */
    ?, /* desig */
    ?, /* digest */
    ?, /* spatial_idx */
    ?, /* survey_mode */
    ?, /* dbname */
    ?, /* det_id */
    ?, /* tracklet_id */
    ?, /* derivedobject_id */
    ?, /* submitter */
    current_timestamp(),
    ?, /* disposition */
    ?, /* discovery */
    ? /* mpc_desig */,
    ? /* revised */
)
SQL


# Select submission data for a particular designation.
my $desig_sth = $dbh->prepare(<<"SQL") or die $dbh->errstr;
select desig, epoch_mjd, ra_deg, dec_deg, filter_id, mag, obscode, dbname, tracklet_id, derivedobject_id, digest
from export.mpc_sub
where desig=?
SQL


# Select unsubmitted designations.
my $pending_sth = $dbh->prepare(<<"SQL") or die $dbh->errstr;
select distinct(desig)
from export.mpc_sub
where disposition=?
SQL

# Update submitted observations.
my $update_sth = $dbh->prepare(<<"SQL") or die $dbh->errstr;
update export.mpc_sub
set batch_num=?, disposition=?
where desig=?
SQL


my %already_inserted;
my %trk_to_insert;


# First read all the MPC-formatted detections and arrange into 
# designations (tracklets).
my $tracklets;

if ($tracklet_id) {
    $tracklets = read_tracklet($desig, $tracklet_id);   # read from DB
}
else {
    $tracklets = read_mpc();        # read all from STDIN
}
while (my ($desig, $detref) = each %{$tracklets}) {
    # 1. Check that none of the tracklet's detections have been submitted.
    # 2. Set up insert data.
    # 3. Insert into table & retrieve designation.
    # 4. Submit via email.

    # This is a tracklet; fetch it and add the detections.
    my $can_insert = 1;
    foreach my $det (@{$detref}) {
        # Look up det in DB.
        my $found_desig;
        if ($found_desig = lookup_det($det)) {
            emit(sprintf "%s already submitted as %s/%s.\n", $desig, $dbname, $found_desig);
            $can_insert = 0;
            last;
        }
    }

    if ($can_insert) {
        $trk_to_insert{$desig} = $detref;
    }
    else {
        $already_inserted{$desig} = $detref;
    }
}

if ($debug) {
    # Sanity check.
    foreach my $desig (keys %trk_to_insert) {
        print Dumper(\%trk_to_insert);
    }
    exit;
}

# Standard eval for DB transaction.
$dbh->begin_work() or die $dbh->errstr;
eval {
    foreach my $desig (keys %trk_to_insert) {
        insert_tracklet($desig, $trk_to_insert{$desig});
        print "Inserted $desig.\n";
    }
};
if ($@) {
    $dbh->rollback();
    die $@;
}
$dbh->commit() or die $dbh->errstr;
exit;


sub get_seq_num {
    # Return a new seq_num for a provisional designation.
    my ($dbh) = @_;

    $dbh->do('insert into export.desig_seq values(null)');
    my $id = $dbh->{'mysql_insertid'};
    die "Got bogus insert ID trying to get submission seq num" unless $id;
}


sub insert_tracklet {
    # Insert a tracklet into the submission table.  We will convert the 
    # sequence number to a provisional designation.
    my ($desig, $detsref) = @_;

    foreach my $det (@{$detsref}) {
        my $fpa_id = $det->{fpa_id};
        $insert_sth->execute(
            undef,
            $fpa_id,
            $det->{epoch_mjd},
            $det->{ra},
            $det->{dec},
            $det->{filter_id},
            $det->{mag},
            $det->{obscode},
            $desig,
            $digest,                        # digest
            0,  # spatial idx
            $det->{survey_mode},            # survey mode
            ($user_dbname || $dbname),      # dbname
            $det->{det_id},                 # det ID (can be undef)
            $tracklet_id,                   # tracklet ID (can be undef)
            undef,                          # derivedobject ID
            $submitter,
            $disposition,
            $discovery,
            $mpc_desig,
            undef,                          # revised
        ) or die $insert_sth->errstr;
    }

    return $desig;
}


sub emit {
    print @_;
}


sub read_mpc {
    my $line;
    my %desigs;
    while ($line = <STDIN>) {
        chomp $line;
        next if !$line or $line =~ /^[A-Z]/ or $line =~ /^\s+$/ or $line =~ /---/;
        my $foo = PS::MOPS::MPC::mpc_split($line);
        my $desig = $foo->{DESIGNATION} or die "didn't get designation from $line";
        die "Not a PS1 observation: $foo->{OBSERVATORY}\n" unless $foo->{OBSERVATORY} eq 'F51';

        my $f = lookup_field($foo->{DATE})
            or die "can't find matching field for $foo->{DATE} in $dbname\n";
        my $det = {
            desig => $foo->{DESIGNATION},
            epoch_mjd => $f->{epoch_mjd},
            fpa_id => $f->{fpa_id},
            survey_mode => $f->{survey_mode},
            ra => str2deg($foo->{RA}, 'H'),
            dec => str2deg($foo->{DEC}, 'D'),
            mag => $foo->{MAG},
            filter_id => $foo->{BAND},
            obscode => $foo->{OBSERVATORY},
        };
        push @{$desigs{$desig}}, $det;
    }

    return \%desigs;
}


sub lookup_det {
    my ($det) = @_;
    $lookup_sth->execute(
        $det->{fpa_id}, $det->{dec}, $det->{dec}, $det->{ra}
    ) or die $lookup_sth->errstr;
    my ($near) = $lookup_sth->fetchrow_array();
    return $near;
}


sub lookup_field {
    # Given an MPC date string, return an FPA id for the nearest PS1 
    # exposure within +/- 1 sec.  Date format is
    # 2011 01 23.23452
    my ($date) = @_;
    $date =~ s/^\s+|\s+$//g;    # strip
    my ($y, $m, $d) = split /\s+/, $date;
    my $df = ($d - int($d));
    $d = int($d);
    my $mjd = cal2mjd($d, $m, $y, $df);
    my $field_i = modcf_retrieve($inst, 
        begin_mjd => $mjd - 5/100_100,
        end_mjd => $mjd + 5/100_100,
    );
    if ($field_i) {
        my $field = $field_i->next;
        return  {
            epoch_mjd => $field->epoch,
            fpa_id => $field->fpaId,
            survey_mode => $field->surveyMode,
        };
    }
    else {
        return undef;
    }
}


sub read_tracklet {
    my ($desig, $tid) = @_;
    my $tdata = {};
    my $trk = modct_retrieve($inst, trackletId => $tid) or die "can't retrieve tracklet ID $tid\n";
    foreach my $det (sort {$a->epoch <=> $b->epoch} @{$trk->detections}) {
        my $field = modcf_retrieve($inst, fieldId => $det->fieldId) or die "can't retrieve field ID $det->fieldId\n"; 

        push @{$tdata->{$desig}}, {
            desig => $desig,
            det_id => $det->detId,
            epoch_mjd => $det->epoch,
            fpa_id => $field->fpaId,
            survey_mode => $field->{surveyMode},
            ra => $det->ra,
            dec => $det->dec,
            mag => $det->mag,
            filter_id => $det->filter,
            obscode => $det->obscode,
        };
    }

    return $tdata;
}
