#!/usr/bin/perl -w

package ippToPsps::Batch;

use warnings;
use strict;
use XML::LibXML;
use PS::IPP::Config 1.01 qw( :standard );
use IPC::Cmd 0.36 qw( can_run run );
use File::Basename;
use File::Temp qw(tempfile);


###########################################################################
#
# Constructor
#
###########################################################################
sub existing {

    my $class = shift;
    my $self = {
        _camera => shift,
        _gpc1Db => shift,
        _batchId => shift,
        _ippToPspsDb => shift,
        _baseDir => shift,
        _verbose => shift,
        _save_temps => shift,
    };

    my $batches;
    $self->{_ippToPspsDb}->getBatch($self->{_batchId}, \$batches);
    my $batch;
    foreach $batch ( @{$batches} ) {
        my ($timestamp, 
                $type,
                $expId, 
                $batchId, 
                $surveyType, 
                $deleted,
                $dvoDb, 
                $processed, 
                $onDatastore,
                $loadedToOdm,
                $loadFailed,
                $mergeWorthy, 
                $merged) = @{$batch};

        $self->{_timestamp} = $timestamp; 
        $self->{_type} = $type; 
        $self->{_expId} = $expId; 
        $self->{_batchId} = $batchId; 
        $self->{_surveyType} = $surveyType; 
        $self->{_deleted} = $deleted; 
        $self->{_dvoDb} = $dvoDb; 
        $self->{_processed} = $processed; 
        $self->{_onDatastore} = $onDatastore; 
        $self->{_loadedToOdm} = $loadedToOdm; 
        $self->{_mergeWorthy} = $mergeWorthy; 
        $self->{_merged} = $merged;
    }

    bless $self, $class;

    if (!defined $self->{_baseDir}) {$self->{_baseDir} = "./";}
    $self->{_parentDir} = $self->{_dvoDb};

    $self->init();
    return $self;
}

###########################################################################
#
# Constructor
#
# This is the superclass of all different types of Batches, eg
# - detections (DetectionBatch)
# - init (InitBatch)
# - etc
#
###########################################################################
sub create {

    my $class = shift;
    my $self = {
        _type => shift,        # batch type, IN,P2,ST...
            _camera => shift,
        _gpc1Db => shift,
        _ippToPspsDb => shift,
        _baseDir => shift,        # output path
            _datastore => shift,
        _expId => shift,
        _expName => shift,
        _dvoDb => shift,
        _dvoPath => shift,
        _distGroup => shift,
        _dontTarball => shift,
        _verbose => shift,
        _save_temps => shift,
    };

    bless $self, $class;

    # break up the DVO Db path, if provided
    if (defined $self->{_dvoPath} && defined $self->{_dvoDb}) {

        $self->{_parentDir} = $self->{_dvoDb};
    }
    else {

        $self->{_parentDir} = ".";
        $self->{_dvoDb} = "NULL";
    }

    # create a new batch entry in the database
    $self->{_batchId} = $self->{_ippToPspsDb}->createNewBatch(
            $self->{_expId}, 
            $self->{_distGroup}, 
            $self->{_type}, 
            $self->{_dvoDb}, 
            (defined $self->{_datastore}) ? $self->{_datastore}->getProduct() : "NONE"
            );

    # use IPP dist_group to get PSPS survey type
    $self->{_surveyType} = $self->getSurveyTypeFromDistGroup();

    # set up batch output paths
    $self->init();

    # make directories
    unless(-d $self->{_typeDir}) {mkdir($self->{_typeDir}, 0777);}
    unless(-d $self->{_subDir}) {mkdir($self->{_subDir}, 0777);}
    mkdir($self->{_batchDir}, 0777);

    return $self;
}

########################################################################################
# 
# Sets to program and checks we can run it
# 
########################################################################################
sub setProgram {
    my ($self, $program) = @_;

    # can we run ippToPsps?
    $self->{program} = can_run($program) or
        (warn "Can't find '$program' program" and exit($PS_EXIT_CONFIG_ERROR));
}

########################################################################################
# 
# Sets names and paths
# 
########################################################################################
sub init {
    my ($self) = @_;

    # get ipp config stuff
    if (defined $self->{_camera}) {

        $self->{_ipprc} = PS::IPP::Config->new($self->{_camera}) or
            (warn "Can't get camera configuration" and exit($PS_EXIT_CONFIG_ERROR));
    }

    $self->{_name} =  sprintf("B%08d", $self->{_batchId});
    $self->{_filename} = "$self->{_name}.tar.gz"; 
    $self->{_typeDir} = "$self->{_baseDir}/$self->{_type}";
    $self->{_subDir} = "$self->{_typeDir}/$self->{_parentDir}";
    $self->{_batchDir} = "$self->{_subDir}/$self->{_name}";
    $self->{_tarball} = "$self->{_subDir}/$self->{_filename}";

    #print "name     $self->{_name}\n";
    #print "filename $self->{_filename}\n";
    #print "subDir   $self->{_subDir}\n";
    #print "batchDir $self->{_batchDir}\n";
    #print "tarball  $self->{_tarball}\n";

}

#######################################################################################
# 
# Generates a PSPS-suitable survey 'type' from the IPP distribution group
#
#######################################################################################
sub getSurveyTypeFromDistGroup {
    my ($self) = @_;

    if ($self->{_distGroup} =~ m/^MD([0-1][0-9])$/i) {return "MD$1";}
    if ($self->{_distGroup} =~ m/^M31$/i) {return "M31";}
    if ($self->{_distGroup} =~ m/^sts$/i) {return "STS1";}
    if ($self->{_distGroup} =~ m/^SweetSpot$/i) {return "SSS";}
    if ($self->{_distGroup} =~ m/^(3PI)|(ThreePi)|(SAS)$/i) {return "3PI";}

    print "* Unknown distribution group: '$self->{_distGroup}'\n";
    return $self->{_distGroup};
}

#######################################################################################
#
# runs the ippToPsps program
#
#######################################################################################
sub runProgram {
    my ($self, $input, $resultsFilePath) = @_;

    my $thisFunction = (caller(0))[3];
    print "* ERROR: '$thisFunction' not implemented\n";
}


########################################################################################
# 
# Returns batch ID
# 
########################################################################################
sub getId {
    my ($self) = @_;
    return $self->{_batchId};
}

########################################################################################
# 
# Returns batch name
# 
########################################################################################
sub getName {
    my ($self) = @_;
    return $self->{_name};
}

########################################################################################
# 
# Returns name of tarred and zipped batch file
# 
########################################################################################
sub getFileName {
    my ($self) = @_;
    return $self->{_filename};
}

########################################################################################
# 
# Returns full path of type dir
# 
########################################################################################
sub getTypeDir {
    my ($self) = @_;
    return $self->{_typeDir};
}

########################################################################################
# 
# Returns full path of sub dir
# 
########################################################################################
sub getSubDir {
    my ($self) = @_;
    return $self->{_subDir};
}

########################################################################################
# 
# Returns full path of batch dir
# 
########################################################################################
sub getBatchDir {
    my ($self) = @_;
    return $self->{_batchDir};
}

########################################################################################
# 
# Returns file name of tarball 
# 
########################################################################################
sub getFilename {
    my ($self) = @_;
    return $self->{_filename};
}

########################################################################################
# 
# Returns full path of tarball 
# 
########################################################################################
sub getTarball {
    my ($self) = @_;
    return $self->{_tarball};
}

########################################################################################
# 
# Deletes a batch tar.gz file
# 
########################################################################################
sub deleteFile {
    my ($self) = @_;

    if (! -e $self->{_tarball}) {return 0;}

    my $command = "rm " . $self->{_tarball};
    my ($success, $error_code, $full_buf, $stdout_buf, $stderr_buf) =
        run(command => $command, verbose => $self->{_verbose});
    if (!$success) {
        print "* Unable to run: $command\n";
        return 0;
    }

    return 1;
}

########################################################################################
# 
# Deletes batch dir 
# 
########################################################################################
sub deleteDir {
    my ($self) = @_;

    if (! -e $self->{_batchDir}) {return 0;}
    my $command = "rm -r " . $self->{_batchDir};

    my ($success, $error_code, $full_buf, $stdout_buf, $stderr_buf) =
        run(command => $command, verbose => $self->{_verbose});
    if (!$success) {
        print "* Unable to run: $command\n";
        return 0;
    }

    return 1;
}

#######################################################################################
#
# Writes the manifest file
#
#######################################################################################
sub writeManifest {
    my ($self, $filename, $minObjId, $maxObjId) = @_;

    use XML::Writer;
    use Digest::MD5::File qw( file_md5_hex );

    # sort out paths
    my $outputPath = "$self->{_batchDir}/BatchManifest.xml";
    my $output = new IO::File(">$outputPath");

    # create a timestamp
    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime(time);
    my $timeStamp = sprintf "%4d-%02d-%02d %02d:%02d:%02d", $year+1900,$mon+1,$mday,$hour,$min,$sec;

    # create XML file
    my $writer = new XML::Writer(OUTPUT => $output, DATA_MODE => 1, DATA_INDENT=>2);
    $writer->xmlDecl('UTF-8');

    # get md5sum of file
    my $md5sum  = file_md5_hex("$self->{_batchDir}/$filename");

    # get size of file in bytes
    my $filesize = -s "$self->{_batchDir}/$filename";

    # write manifest element TODO needs to be in subclass
    if($self->{_type} eq 'P2') {

        my $pspsSurvey = undef;
        if ($self->{_surveyType} =~ m/^MD[0-1][0-9]$/i) {$pspsSurvey = "MDF";}
        else {$pspsSurvey = $self->{_surveyType};}

        $writer->startTag('manifest',
                "name" => "$self->{_name}",
                "type" => "$self->{_type}",
                "survey" => $pspsSurvey,
                "timestamp" => "$timeStamp",
                "minObjId" => $minObjId,
                "maxObjId" => $maxObjId);
    }
    else {

        print "'$self->{_name}' '$self->{_type}' '$timeStamp'\n";

        $writer->startTag('manifest',
                "name" => "$self->{_name}",
                "type" => "$self->{_type}",
                "timestamp" => "$timeStamp");
    }
    # write the tag for this batch
    $writer->startTag("file",
            "name" => $filename,
            "bytes" => $filesize,
            "md5" => $md5sum);

    $writer->endTag();
    $writer->endTag();
    $writer->end();

    return 1;
}

#######################################################################################
#
# tar 'n zip a batch TODO handle errors
#
#######################################################################################
sub createTarball {
    my ($self) = @_;

    my $tar = $self->{_name}.".tar";
    my $tarball = "$tar.gz";
    my $batchTar = "$self->{_batchDir}/$tar";

    # tar
    my $command = "tar -cvf $batchTar -C $self->{_subDir} $self->{_name}";
    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
        run(command => $command, verbose => $self->{_verbose});
    if (!$success) { print "* Unable to tar up contents of dir: ". $self->{_batchDir}. "\n" and return 0; }

    # zip
    $command = "gzip -c $batchTar  > $self->{_tarball}";
    ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
        run(command => $command, verbose => $self->{_verbose});
    if (!$success) { print "* Unable to gzip: $batchTar\n" and return 0; }

    # remove tar file
    $command = "rm $batchTar";
    ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
        run(command => $command, verbose => $self->{_verbose});
    if (!$success) { print "* Unable to remove: $batchTar\n" };

    # remove batch dir
    $command = "rm -r $self->{_batchDir}";
    ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
        run(command => $command, verbose => $self->{_verbose});
    if (!$success) { print "* Unable to remove: $self->{_name}\n"};

    return 1;
}

#######################################################################################
#
# Abstract method to be inherited by subclasses
#
#######################################################################################
sub process {
    my ($self) = @_;

    print "* Preparing batch '$self->{_name}' for '$self->{_type}'\n";

    # create temporary input file for file list
    my ($resultsFile, $resultsFilePath) = tempfile( "/tmp/ippToPsps_results.XXXX", UNLINK => !$self->{_save_temps});

    # get list of input files
    my ($inputFile, $inputPath) = tempfile( "/tmp/ippToPsps_inputFileList.XXXX", UNLINK => !$self->{_save_temps});

    my $success = 0;
    if ($self->createInputFileList($inputFile)) {

        # run IppToPsps program TODO should not need
        $success = $self->runProgram($inputPath, $resultsFilePath);

        if ($success) {

            # TODO type-specific processing
            $self->parseResults($resultsFilePath);

            # tar n' zip
            if (!$self->{_dontTarball} && $self->createTarball()) {

                # and publish
                if (defined $self->{_datastore} &&
                        $self->{_datastore}->register($self->{_name}, $self->{_subDir}, $self->{_filename}, "IPP_PSPS", "tgz")) {
                    $self->{_ippToPspsDb}->setPublished($self->{_batchId}, $self->{_expId}, 1);
                }
            }
        }
    }

    close $resultsFile;
    close $inputFile;

    if (!$self->{_save_temps}) {unlink $resultsFile};
    if (!$self->{_save_temps}) {unlink $inputFile};

    return $success;
}

#######################################################################################
# 
# Abstract method to be inherited by subclasses
# Creates an input file list for ippToPsps 
#
#######################################################################################
sub createInputFileList {
    my ($self) = @_;
    my $thisFunction = (caller(0))[3];
    print "* ERROR: '$thisFunction' not implemented\n";
}

#######################################################################################
#
# Abstract method to be inherited by subclasses
# Deals with the results
#
#######################################################################################
sub parseResults {
    my ($self) = @_;

    my $thisFunction = (caller(0))[3];
    print "* ERROR: '$thisFunction' not implemented\n";
}

1;
