#!/usr/bin/perl
# $Id: dtctl,v 1.27 2006/07/22 01:35:08 denneau Exp $

use strict;
use warnings;

use Getopt::Long;
use Pod::Usage;
use Cwd;
use File::Slurp;

use PS::MOPS::MITI;
use PS::MOPS::DC::Instance;
use PS::MOPS::DC::Field;

print STDERR "===========================================================\n";
print STDERR "= THIS IS MY-DTCTL                                        =\n";
print STDERR "===========================================================\n";

my $exitval = 1;        # process exit code; see EXIT VALUE below

my $inst;
my $instance_name = $ENV{MOPS_DBINSTANCE};
my $once;
my $finish;
my $nobuild;
my $end_mjd;
my $test;
my $help;
GetOptions(
    'instance=s' => \$instance_name,
    once => \$once,
    finish => \$finish,
    nobuild => \$nobuild,
    'end_mjd=i' => \$end_mjd,
    test => \$test,
    help => \$help
) or pod2usage(2);
pod2usage(-verbose => 3) if $help;

$inst = PS::MOPS::DC::Instance->new(DBNAME => $instance_name);
my $mops_config = $inst->getConfig;
my $mops_logger = $inst->getLogger;


sub get_fields {
    my $f;
    my @fields;

    $inst->forget_dbh;                                          # force reconnect
    my $field_i =  modcf_retrieve($inst, oldest => 1);          # fetch oldest night's worth of unprocessed fields
    return unless $field_i;      # no fields

    while ($f = $field_i->next) {
        push @fields, $f;               # add to list
    }

    return @fields;
}


sub make_makefile {
    # Create makefile; return path to this file.
    my ($ocnum, $mjd) = @_;
    my $model = $inst->dbname;

    my $findtracklets_maxv = $mops_config->{findtracklets}->{maxv_degperday} || 
        die "can't get findtracklets_maxv";
    my $findtracklets_minobs = $mops_config->{findtracklets}->{minobs} || 
        die "can't get findtracklets_minobs";
    my $findtracklets_extended = $mops_config->{findtracklets}->{extended};
        die "can't get findtracklets_extended" unless defined($findtracklets_extended);
    my $findtracklets_etime = $mops_config->{ssm}->{exposure_time_s} ||
        die "can't get findtracklets_etime";
    my $findtracklets_maxt = $mops_config->{findtracklets}->{maxt} ||
        die "can't get findtracklets_maxt";

    my $EXTENDED = $findtracklets_extended ? '--extended' : '';

    my $obscode = $mops_config->{site}->{obscode} || die "observatory code unspecified";
    my $limiting_mag = $mops_config->{site}->{limiting_mag} || die "limiting_mag code unspecified";
    my $lm1 = $limiting_mag + 1;            # cutoff mag for coarse ephem calc

    my $dir = $inst->getEnvironment('DTCTLDIR') . sprintf("/%03d/%5d", $ocnum, $mjd);
    my $file = "$dir/Makefile.$mjd";
    system("mkdir -p '$dir'") == 0 or $mops_logger->logdie("mkdir -p $dir failed: $?");

    my $objectsdir = $inst->getEnvironment('OBJECTSDIR');
    system("mkdir -p '$objectsdir'") == 0 or $mops_logger->logdie("mkdir -p $objectsdir failed: $?");
    my $ssmobjs = "$objectsdir/ssm";

    # Determine MJDs for coarse ephems.
    my ($e0, $e1, $e2) = ($mjd - 1, $mjd, $mjd + 1);

    my $MAKEFILE_TEMPLATE = <<"EOF";
# Auto-generated makefile by DTCTL
# Do not edit!


.PHONY : all
.SUFFIXES : .dets .syn .trk .pairs

# MJD that we are processing.
MJD = $mjd

# .trk = database insert of tracklets
# .syn = database insert of synthetic detections
# .fd = database insert of false detections
#all : $mjd.ins $mjd.fd
$mjd.done : $mjd.attr

$mjd.attr : $mjd.trk $mjd.fp_d
	attributeOrbits --date_mjd $mjd $mjd.fp_d
	touch $mjd.attr




# Tracklets.  Requires inserted synthetic detectiond and false detections.
$mjd.trk : $mjd.pairs
	insertTracklets --mjd $mjd $mjd.dets $mjd.pairs > $mjd.trk

$mjd.pairs : $mjd.dets
	findTracklets file $mjd.dets pairfile $mjd.pairs summaryfile $mjd.sum maxv $findtracklets_maxv minobs $findtracklets_minobs maxt $findtracklets_maxt etime $findtracklets_etime
	touch $mjd.sum      # force existing output files if empty input files
	touch $mjd.pairs
	rm $mjd.sum       # remove summary file

# Note: the .fd file is a prerequisite so that the .dets file
# is generated afterwards.
$mjd.dets : $mjd.fd
#	obsTool --detections --miti --mjd $mjd > $mjd.dets
	selectDetections $EXTENDED --miti --mjd $mjd --add_from_file=$mjd.fd > $mjd.dets



# False detections.
$mjd.fd : $mjd.syn $mjd.fields
#	insertFalseDetections --date_mjd $mjd --on_demand_file=$mjd.fd
	\@echo "*** Not inserting false detections ***"
	touch $mjd.fd

# Synthetic detections.  Requires FieldProximity files.
$mjd.syn : $mjd.fp
	insertFPDetectionsMaster --end_status=F --epoch $mjd $mjd.fp 
	touch $mjd.syn

# List of fields for the night.
$mjd.fields :
	catFields --date_mjd $mjd --radius_deg 2.68 > $mjd.fields

# Coarse ephemerides and fieldProximity for the night.
$mjd.fp : $ssmobjs $mjd.fields
	nsplit $ssmobjs | coarseMaster --epoch $mjd --obscode $obscode --delta_mjd 1.0 --limiting_mag $lm1 --out $mjd.fp

# Coarse ephems and fieldProximity for derived objects.
$mjd.fp_d : $mjd.derived $mjd.fields
	nsplit $mjd.derived | coarseMaster --epoch $mjd --obscode $obscode --delta_mjd 1.0 --limiting_mag $lm1 --out $mjd.fp_d

# Generate list of derived orbits we need to attribute.
$mjd.derived:
	catOrbits --derived > $mjd.derived



# Solar system model.  Usually generated only once, but check anyway.
$ssmobjs:
	catOrbits --ssm > $ssmobjs
EOF


    write_file($file, $MAKEFILE_TEMPLATE);
    $mops_logger->info("Created makefile $file.");

    return $dir, $file;
}


sub run_makefile {
    return if $test or $nobuild;

    my ($dir, $makefile) = @_;
    my $oldwd = getcwd;
    my $ret;                # return code

    eval {
        chdir $dir or die "can't chdir to $dir";

		#
		# False detections should already have been inserted, so 
		# delete *.fd so that processing will continue.  This probably
		# isn't the best way to do this, but will do for now.
		#

		print "**** Unlink *.fd.  Is this the right thing to do? ****\n";
		unlink <*.fd>;

        $ret = system('make', '-f', $makefile);
    };

    $mops_logger->die($@) if $@;
    chdir $oldwd;

    return $ret;
}


sub mark_fields {
    return if $test;

    my ($fref, $status) = @_;
    $mops_logger->info("Marking fields as $status.");
    foreach my $field (@$fref) {
        $field->status($status);
    }
}


sub clean_up {
    # Cleanup old files.
    $mops_logger->info("Cleaning up.");
}


my $go = 1;         # stop when this is zero
my $field_i;        # field iterator
my $mjd;
my $ocnum;
my $dir;
my $makefile;

$mops_logger->info("Starting DTCTL.");
system("./detFindFetchAndInsert");

eval {
    while ($go) {
        my @fields = get_fields();
        if (@fields) {
            $mjd = int($fields[0]->epoch);    # get MJD of fields
            $ocnum = $fields[0]->ocnum;

            last if ($end_mjd and $mjd >= $end_mjd);

            $mops_logger->info(sprintf "Fetched %d fields for MJD $mjd.", scalar @fields);

            ($dir, $makefile) = make_makefile($ocnum, $mjd);

            # Update process status.
            $0 = sprintf "DTCTL %s %03d %.6f", $inst->dbname, $ocnum, $mjd;

            if (run_makefile($dir, $makefile)) {
                $mops_logger->info("Processing failed for MJD $mjd");
                $exitval = 2;       # something happened
                last;
            }
            $exitval = 0;           # did some work
            clean_up();

            $go = 0 if ($test or $once);
        }
        else {
            # Update process status.
            $0 = sprintf "DTCTL %s (idle)", $inst->dbname;

            $mops_logger->info("No fields to process.");
            if ($test or $once or $finish) {
                $go = 0;
            }
            else {
                sleep 60;           # wait a minute
            }
        }
    }
};

if ($@) {
    $mops_logger->logwarn($@);
    $exitval = 99;
}

$mops_logger->info(sprintf "DTCTL exiting: %d", $exitval);
exit $exitval;

=pod

=head1 NAME

dtctl - Detections and Tracklets controller

=head1 SYNOPSIS

dtctl [--end_mjd MJD] [--help]

  --once : process a single night
  --finish : run until all fields processed, then quit
  --nobuild : don't execute makefiles
  --end_mjd : do not process fields dated later than MJD
  --test : test mode; don't modify DB, don't execute makefiles
  --help : show man page

=head1 DESCRIPTION

DTCTL is a first pass at the MOPS Detections and Tracklets Controller
(DTCTL).  This program will obtain metadata fields from PSMOPS and
dispatch the computation of synthetic observations and tracklets.

=head1 THEORY OF EXECUTION

DTCTL will be a persistent process.  Data will be processed by night.

=item

Obtain a earliest list of all fields for a single night.

=item

Determine which times coarse ephemerides need to be calculated.
Dispatch processing of coarse ephemerides

=item

Once all the coarse ephemerides files are generated, create a Makefile
for the night.  Process the Makefile.

=item

Repeat until date MJD has been reached or there are no fields to process.
If an ending date was not specified, wait 60 seconds.

=head1 EXIT VALUE

DTCTL exits as follows:

  0 - successfully processed some fields
  1 - successful, but nothing to do
  2 - something failed
  99 - internal failure

=cut
