#!/usr/bin/env perl
#
# DataStore fileset registration
#
# Does not enforce validity of metadata items.
#

use strict;
use warnings;

use Getopt::Long qw( GetOptions );
use Pod::Usage qw( pod2usage );
use Digest::MD5::File qw( file_md5_hex ); 
use DBI;
use PS::IPP::Metadata::Config;
use PS::IPP::Metadata::Stats;
use PS::IPP::Metadata::List qw( parse_md_list );
use File::Copy;

use PS::IPP::Config qw($PS_EXIT_SUCCESS
		       $PS_EXIT_UNKNOWN_ERROR
		       $PS_EXIT_SYS_ERROR
		       $PS_EXIT_CONFIG_ERROR
		       $PS_EXIT_PROG_ERROR
		       $PS_EXIT_DATA_ERROR
		       $PS_EXIT_TIMEOUT_ERROR
		       metadataLookupStr
		       metadataLookupBool
		       caturi
		       );

my $add;
my $del;

my $ds_dir;
my $product;
my $fileset;

### XXX: these aren't currently used
### fill in some bogus values here
my $type = 'OBJECT';
my $ra = '00:00:00.00';
my $dec = '00:00:00.00';
my $equinox = 2000;
my $etime = 30.0;
my $filter = 'z';
my $airmass = 1.2;

my $date;

#
# parse args
#

# XXX: need to make argument parsing file type specific
GetOptions(
    'add'               => \$add,
    'del'               => \$del,
    'dsdir|ds=s'        => \$ds_dir,
    'type|t=s'          => \$type,
    'ra|r=s'            => \$ra,
    'dec|d=s'           => \$dec,
    'equinox|eq=f'      => \$equinox,
    'etime|ex|e=f'      => \$etime,
    'filter|f=s'        => \$filter,
	'date=s'        => \$date,
    'airmass|a=f'       => \$airmass,
) or pod2usage(2);

my $err = "";

$err .= "Must specify a product and a fileset.\n"
    unless @ARGV == 2;

# will exit if neither or both -add and -del were specified
$err .= "Must specify either --add or --del.\n"
    unless $add xor $del;

my $ipprc =  PS::IPP::Config->new(); # IPP Configuration
my $siteConfig = $ipprc->{_siteConfig};
if (!$ds_dir) {
    $ds_dir = metadataLookupStr($ipprc->{_siteConfig}, 'PSTAMP_DATA_STORE_ROOT');
    if (!$ds_dir) {
        die("data store root not defined");
    }
}


if (!stat("$ds_dir/index.txt")) {
    $err .= "Datastore not found at '$ds_dir'.\n"
}

# bail if any of the above args were improper
show_usage($err)
    if ($err);

$product = shift;

show_usage("Invalid product '$product'.")
    unless defined stat("$ds_dir/$product/index.txt");

$fileset = shift;


#
# run
#

my $dbserver = metadataLookupStr($siteConfig, 'DBSERVER');
my $dbname   = metadataLookupStr($siteConfig, 'DBNAME');
my $dbuser   = metadataLookupStr($siteConfig, 'DBUSER');
my $dbpass   = metadataLookupStr($siteConfig, 'DBPASSWORD');
exit ($PS_EXIT_CONFIG_ERROR) unless defined $dbserver and $dbname and $dbuser and $dbpass;

my $dsn = "DBI:mysql:host=$dbserver;database=$dbname";

my $dbh = DBI->connect($dsn, $dbuser, $dbpass) or die "Cannot connect to server\n";

my $index_script_name = "$ds_dir/$product/$fileset/index.txt";

if ($del) {
    #
    # delete fileset
    #
    my $stmt = $dbh->prepare("SELECT prod_id, last_fs FROM dsProduct WHERE prod_name = \'$product\'");
    $stmt->execute();
    my $prod_row = $stmt->fetchrow_hashref();
    my $prod_id = $prod_row->{prod_id};
    my $old_last_fs = $prod_row->{last_fs};

    if (!$prod_id) {
        die("product $product not found");
    }
    $stmt = $dbh->prepare("SELECT fileset_id,fileset_name FROM dsFileset WHERE fileset_name = '$fileset'" .
                        " AND prod_id = $prod_id");

    $stmt->execute();
    my $fs_row = $stmt->fetchrow_hashref();

    if (!$fs_row) {
        print "Fileset '$fileset' not found under $product.\n";
        exit 1;
    }

    # zap the index script
    unlink("$index_script_name");

    my $fileset_id = $fs_row->{fileset_id};

    $dbh->do("DELETE from dsFile where fileset_id = $fileset_id");
    $dbh->do("DELETE from dsFileset where fileset_id = $fileset_id");

    if ($old_last_fs eq $fs_row->{fileset_name}) {
        # print STDERR "deleting most recent fileset in $product\n";

        $stmt = $dbh->prepare("SELECT fileset_name from dsFileset " .
                    "WHERE prod_id = $prod_id ORDER BY reg_time DESC LIMIT 1");
        $stmt->execute();
        my $new_last_fs;
        my $new_newest = $stmt->fetchrow_hashref();
        if ($new_newest) {
            $new_last_fs = $new_newest->{fileset_name};
        } else {
            $new_last_fs = "none";
        }
        $dbh->do("UPDATE dsProduct SET last_fs = \'$new_last_fs\' WHERE prod_id = $prod_id");
    }
    exit 0;

} else {
    #
    # adding a new fileset
    #
    my $stmt = $dbh->prepare("SELECT prod_id FROM dsProduct WHERE prod_name = \'$product\'");
    $stmt->execute();
    my $row = $stmt->fetchrow_hashref();
    my $prod_id = $row->{prod_id};

    if (!$prod_id) {
        die("product $product not found");
    }
    my $count = $dbh->do("SELECT fileset_id FROM dsFileset WHERE fileset_name = '$fileset'" .
                        " AND prod_id = $prod_id");

    if ($count != 0E0) {
        print "Fileset '$fileset' already exists under $product.\n";
        exit 1;
    }

    # Read file data from STDIN

    my $lineno = 0;

    my @files;
    while (<STDIN>) {
        $lineno++;
        
        my $filename;
        my $filetype;
        my $type_col_0;
        if (/([A-Za-z0-9-_.]+)\s+([A-Za-z0-9-_.]+)\s+([A-Za-z0-9-_.]+)/) {
            $filename = $1;
            $filetype = $2;
            $type_col_0 = $3;
        } else {
            unless (/([A-Za-z0-9-_.]+)\s+([A-Za-z0-9-_.]+)\s+/) {
                print STDERR "Line $lineno: ignored malformed input line.\n";
                next;
            }
            $filename = $1;
            $filetype = $2;
        }

        my @finfo = stat("$ds_dir/$product/$fileset/$filename");

        unless (@finfo) {
            die ("Line $lineno: referenced file "
                ."($ds_dir/$product/$fileset/$1) does not exist.\n");
        }
        
        # Get MD5 sum
        my $md5 = file_md5_hex("$ds_dir/$product/$fileset/$filename");

        my $hashref = { 
            'file' => $filename,
            'bytes' => $finfo[7],
            'md5sum'    => $md5,
            'type'      => $filetype,
            'type_col_0'=> $type_col_0
        };

        push @files, $hashref;
    }


    if (@files == 0) {
        die("empty filelist");
    }

    # create the cgi script index file by making a copy of its parent's
    if (!copy("$ds_dir/$product/index.txt", $index_script_name)) {
        die("failed to copy index script to fileset");
    }

    eval {
        $dbh->{RaiseError} = 1; # raise exception if error occurs
        $dbh->{PrintError} = 0;
        $dbh->{AutoCommit} = 0;

        $count = $dbh->do("INSERT into dsFileset" .
                " (prod_id, fileset_name, reg_time, type)" .
                " VALUES($prod_id, \'$fileset\', UTC_TIMESTAMP(), 'PSRESULTS')");
        if ($count == 0E0) {
            die("failed to insert $fileset");
        }

        my $fileset_id = $dbh->last_insert_id(undef, undef, undef, undef);

        # print STDERR "Inserted fileset_id $fileset_id\n";

        foreach my $ref (@files) {
            my $query;
            if ($ref->{type_col_0}) {
                $query = "INSERT INTO dsFile" .
                    " (fileset_id, file_id, file_name, bytes, md5sum, type, type_col_0)" .
                    " VALUES($fileset_id, 0, \'$ref->{file}\', $ref->{bytes}," .
                             " \'$ref->{md5sum}\', \'$ref->{type}\', \'$ref->{type_col_0}\')";
            } else {
                $query = "INSERT INTO dsFile" .
                    " (fileset_id, file_id, file_name, bytes, md5sum, type)" .
                    " VALUES($fileset_id, 0, \'$ref->{file}\', $ref->{bytes}," .
                             " \'$ref->{md5sum}\', \'$ref->{type}\')";
            }
            $count = $dbh->do($query);

            if ($count == 0E0) {
                $dbh->rollback();
                die("failed to insert $ref->{file} into $fileset");
            }
        }

        $count = $dbh->do("UPDATE dsProduct SET last_update = UTC_TIMESTAMP(), last_fs = \'$fileset\'"
            . " WHERE prod_id = $prod_id");
        if ($count == 0E0) {
            $dbh->rollback();
            die("failed to update dsProduct $prod_id");
        }

        $dbh->commit();
    };
    if ($@) { # an error occured
        print STDERR "transactionfailed, rolling back error was:\n$@\n";
        eval {$dbh->rollback();};
        unlink($index_script_name);
        exit 1;
    }

    # print "Added $fileset to $product.\n";

    exit 0;
}

sub show_usage {
    my $str = shift;

    chomp $str;

    my @tmp = split(/\//, $0);

    pod2usage(
        -msg => 
"usage: $tmp[$#tmp] [--add|--del] product fileset [options]

Commands:

    --del        Remove a fileset.
    --add        Add a new fileset with the given metadata options.
                File information will be read per-line from STDIN, in the form:
                    fileID type [chipname]
                These entries are read until EOF is sent.
Options:
    
    --dsdir      Specify the path to the Datastore
    
$str",
        -exitval => 2
    );

    if (0) {
     my $saveString = "(The following metadata is not used yet)

    --date       ISO8601 date string. (File timestamp used if not specified.)
    --type       Fileset type.
    --ra         Telescope pointing RA.
    --dec        Telescope pointing Dec.
    --equinox    Equinox.
    --etime      Exposure time.
    --filter     Filter used.
    --airmass    Air mass. ";   
    }
}

# get the current UTC time in iso8601 format
# this is used if no time parameter is specified
sub get_iso8601() {
    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
        gmtime(time);

    return sprintf("%4d-%02d-%02dT%02d:%02d:%02dZ",
        $year+1900, $mon+1, $mday, $hour, $min, $sec);
}
