#!/usr/bin/env perl 

use Carp;
use warnings;
use strict;

use IPC::Cmd 0.36 qw( can_run run );
use PS::IPP::Metadata::Config;
use PS::IPP::Metadata::List qw( parse_md_list );
use PS::IPP::Config 1.01 qw( :standard );

use File::Basename;
use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
use Pod::Usage qw( pod2usage );

# Hard coded values
my $DMZ_HOST = 'wtrw';
my $SEC_HOST = 'mu-fe';
my $IPP_PATH = '/turquoise/usr/projects/cosmo/mswarren/ipp/';
my $remote_root  = '/scratch3/watersc1/';

# tools
my $missing_tools;
my $ssh        = can_run('ssh')  or (warn "Can't find ssh" and $missing_tools = 1);
my $scp        = can_run('scp')  or (warn "Can't find scp" and $missing_tools = 1);
my $remotetool = can_run('echo') or (warn "Can't find remotetool" and $missing_tools = 1);

if ($missing_tools) {
    warn("Can't find required tools.");
    exit($PS_EXIT_CONFIG_ERROR);
}


# Options
my ($remote_id,$path_base,$policy,$poll,$job_id,$dbname,$verbose,$no_update,$camera);

GetOptions(
    'remote_id=s'   => \$remote_id,
    'job_id=s'      => \$job_id,
    'path_base=s'   => \$path_base,
    'policy=s'      => \$policy,
    'poll'          => \$poll,
    'camera=s'      => \$camera,
    'dbname=s'      => \$dbname,
    'verbose'       => \$verbose,
    'no_update'     => \$no_update,
    ) or pod2usage( 2 );

pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
pod2usage( -msg => "Required options: --remote_id --path_base", -exitval => 3) unless
    defined($path_base) and
    defined($remote_id);

my $ipprc = PS::IPP::Config->new( $camera ) or my_die( "Unable to set up", $remote_id);

my $mdcParser = PS::IPP::Metadata::Config->new; # Parser for metadata config files                                    

# Phase 1: See if we can actually do anything.
# If the link is down, there's no benefit in trying to do anything else.
&check_ssh_connection();

print "passed authentication challenge.\n";

# These are operations we only need to do on the first call, not on subsequent poll operations.
unless ($poll) {
# Phase 2: Ensure files are in place:
# Copy command files
    my @files = ();
    
    my $uri_command = $path_base . ".cmd";
    my $uri_transfer= $path_base . ".transfer";
    my $uri_check   = $path_base . ".check";
    my $uri_config  = $path_base . ".config";

    my $disk_command = $ipprc->file_resolve($uri_command);
    my $disk_transfer= $ipprc->file_resolve($uri_transfer);
    my $disk_check   = $ipprc->file_resolve($uri_check);
    my $disk_config  = $ipprc->file_resolve($uri_config);
    
    scp_put($disk_command,uri_local_to_remote($uri_command));
    scp_put($disk_transfer,uri_local_to_remote($uri_transfer));
    scp_put($disk_check,uri_local_to_remote($uri_check));
    scp_put($disk_config,uri_local_to_remote($uri_config));
    
# Run check command
    my (undef,$remote_check) = uri_convert($uri_check);
    my $ssh_check_stdout = ssh_exec_command("${remote_root}/sc_validate_files.pl --check_list $remote_check");
    
# Parse stdout for files to transfer.
    if ($#{ $ssh_check_stdout } != -1) { # There were files not found.
	# Someone was missing.  Use the check and transfer lists to identify the local version, and send it.
	# What was missing?
	my %missing_files = ();
	foreach my $l (split /\n/, (join '', @$ssh_check_stdout)) {
	    print "Missing!: $l\n";
	    $missing_files{$l} = 1;
	}
	
	# Do we have that file?
	my @check_files = ();
	open(CHECK,$disk_check);
	while(<CHECK>) {
	    chomp;
	    push @check_files, $_;
	}
	close(CHECK);
	
	my @trans_files = ();
	open(TRANS,$disk_transfer);
	while(<TRANS>) {
	    chomp;
	    push @trans_files, $_;
	}
	close(TRANS);
	
	if ($#trans_files != $#check_files) { &my_die("The transfer file list and check file list are different sizes",
						      $remote_id,$PS_EXIT_DATA_ERROR); }
	
	for (my $i = 0; $i <= $#check_files; $i++) {
	    if (exists($missing_files{$check_files[$i]})) {
		if ($trans_files[$i] ne "REMOTE_ONLY") {
		    scp_put($trans_files[$i],"$check_files[$i]");
		}
		else {
		    &my_die("There is a file that should be there, but the file isn't there, and I don't have a copy of that file here.  The file is: $check_files[$i]",
			$remote_id,$PS_EXIT_DATA_ERROR); # This seems a bit harsh.
		}
	    }
	}
    }


# Run real command
    my (undef,$remote_command) = uri_convert($uri_command);
    my $ssh_exec_stdout = ssh_exec_command("msub -V $remote_command");
    if ($#{ $ssh_exec_stdout } != -1) { # Parse the output
	$job_id = ${ $ssh_exec_stdout }[0];
	chomp($job_id);
	$job_id =~ s/\s+//g;
    }

    unless(defined($job_id)) { # If we don't have a job_id from this command, it didn't run correctly.
	&my_die("No job_id returned.  Sorry.",$remote_id,$PS_EXIT_PROG_ERROR);
    }

    # Notify the database that this entry is currently running.
    my $command = "$remotetool -updaterun -remote_id $remote_id ";
    $command .= " -set_state run ";
    $command .= " -set_job_id $job_id ";
    $command .= " -dbname $dbname " if defined $dbname;
    
    system($command);
    
}

# Check that we have a valid job_id, either from the command line, or from the msub command.
unless(defined($job_id)) { # If we don't have a job_id from this command, it didn't run correctly.
    &my_die("No job_id returned.  Sorry.",$remote_id,$PS_EXIT_PROG_ERROR);
}


# Poll the job status?
my $poll_count = 0;
my $poll_max   = 5;
my $poll_sleep = 60;
my $poll_response = 0;
while (($poll_count < $poll_max)&&($poll_response != 1)&&($poll_response != -1)) {
    if ($verbose) {
	print "Polling code.  Sleeping.  $poll_count of $poll_max $poll_response\n";
    }
    unless ($poll_count == 0) {
	sleep($poll_sleep);
    }
    $poll_response = poll_job($job_id);

    if ($poll_response == 1) { # This job has completed
	last;
    }
    elsif ($poll_response == -1) { # This job has an error
	&my_die("The job exited incorrectly.",$remote_id, $PS_EXIT_PROG_ERROR);
    }
    $poll_count++;
}
if ($verbose) {
    print "Stopped Polling code.  $poll_count of $poll_max $poll_response\n";
}
# Retrieve validation summary
my $fault = 0;

exit(1);
# Read this as a metadata object
my $ssh_exec_stdout = ssh_exec_command("sc_validate_processing.pl ${path_base}.out");
my $retrieveData = $mdcParser->parse(join "", @$ssh_exec_stdout);
my $retrieveData2= parse_md_list($retrieveData);

foreach my $retrieveEntry (@$retrieveData2) {
    my $process_fault = $retrieveEntry->{fault};
    my $dbcommand = $retrieveEntry->{dbcommand};
    my $filelist  = $retrieveEntry->{filelist};

    scp_get("${filelist}",${filelist});
    open(FL,${filelist});
    while(<FL>) {
	chomp;
	my $f = $_;
	scp_get("${f}",$f);
    }
    close(FL);

    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
	run(command => $dbcommand, verbose => $verbose);
    unless ($success) {
	$error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
	&my_die("The database command returned did not execute correctly. $dbcommand $process_fault",
		$remote_id,$error_code); 
    }

    if ($process_fault != 0) {
	$fault = 1;
    }
}

if (($policy ne 'ignore')&&($fault != 0)) {
    &my_die("This job exited without all components finishing successfully.  Halting.",
	    $remote_id,$fault,"halt");
}
else {
    print "This job exited without all components finishing successfully. $fault\n" if $fault;
    &my_die("This job finished.  Setting remote job to full.",
	    $remote_id,0,"full");
}

# Promote to finished.
&my_die(); # Not really, but I don't have the tool interface finished, so these are all just placeholders.
# Quick review:
# new -> pending -> run -> full
# auth

# END PROGRAM


sub check_ssh_connection {
    my $cmd = "$ssh -O check $DMZ_HOST";

    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
	run(command => $cmd, verbose => $verbose);
    unless ($success) {
	$error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
	&my_die("Authorization check failed.",$remote_id,0,'auth');

# 	my $update_command = "$remotetool -updaterun -remote_id $remote_id ";
# 	$update_command .= " -set_state auth ";
# 	$update_command .= " -dbname $dbname " if defined $dbname;
# 	system($update_command);
# 	exit(0);  # This isn't an error.  It's a state we monitor and move to automatically.
    }
}

sub scp_put {
    my $file = shift;
    my $destination = shift;
    my $cmd = "$scp $file ${DMZ_HOST}:${SEC_HOST}:${destination}";

    my $directory = dirname($destination);
    ssh_exec_command("mkdir -p $directory");

    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
	run(command => $cmd, verbose => $verbose);
    unless ($success) {
	$error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
	&my_die(); 
    }
}

sub scp_get {
    my $destination = shift;
    my $file = shift;

    my $cmd = "$scp ${DMZ_HOST}:${SEC_HOST}:${destination} $file ";

    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
	run(command => $cmd, verbose => $verbose);
    unless ($success) {
	$error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
	&my_die(); 
    }
}

sub ssh_exec_command {
    my $cmd = shift;
    $cmd = "$ssh $DMZ_HOST ssh ${SEC_HOST} $cmd";
    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
	run(command => $cmd, verbose => $verbose);
    unless ($success) {
	$error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
	&my_die(); 
    }
    return ($stdout_buf);
}    

sub poll_job {
    my $job_id = shift;
    my $response_array = ssh_exec_command("checkjob -v $job_id");
    my $response = join "\n", @$response_array;
    
    if ($response =~ /State: Running/) { 
	return(0);
    }
    elsif ($response =~ /State: Completed/) {
	return(1);
    }
    elsif ($response =~ /State: Idle/) {
	return(-2);
    }
    else {
	return(-1);
    }
}


sub uri_convert {
    my $neb_uri = shift;
    my $ipp_disk= $ipprc->file_resolve( $neb_uri );
    my $remote_disk = $ipp_disk;
    
    unless(defined($ipp_disk)) {
	my_die();
    }

    $remote_disk =~ s%^.*/%%;   # Remove nebulous path
    $remote_disk =~ s%^\d+\.%%; # Remove ins_id
    $remote_disk =~ s%:%/%g;    # Replace colons with directories
    $remote_disk = "${remote_root}/${remote_disk}";
    return($ipp_disk,$remote_disk);
}

sub uri_to_outputs {
    my $neb_uri = shift;
    my ($ipp_disk, $remote_disk) = uri_convert( $neb_uri );

#    print TRANSFER "$ipp_disk\n";
#    print CHECK    "$remote_disk\n";
    return($ipp_disk,$remote_disk);
}

sub uri_local_to_remote {
    # This needs to replace the nebulous tag with the remote root.
    my $local_uri = shift;
    $local_uri =~ s%^.*?/%%; # neb:/
    $local_uri =~ s%^.*?/%%; # /
    $local_uri =~ s%^.*?/%%; # @HOST@.0/
    my $remote_uri = "${remote_root}/" . $local_uri;

    return($remote_uri);
}
 
sub uri_remote_to_local {
    # This needs to replace the remote root directory with the nebulous tag.
    my $remote_uri = shift;
    $remote_uri =~ s%${remote_root}%%;
    my $local_uri  = "neb:///" . $remote_uri;
    
    return($local_uri);
}
sub my_die {
    my $msg = shift;
    my $id  = shift;
    my $exit_code = shift;
    my $exit_state = shift;

    $exit_code = $PS_EXIT_PROG_ERROR unless defined $exit_code;

    carp($msg);
    
    if (defined $id and not $no_update) {
	my $command = "$remotetool -updaterun -stage_id $id";
	$command .= " -fault $exit_code " if defined $exit_code;
	$command .= " -set_state $exit_state " if defined $exit_state;
	$command .= " -dbname $dbname " if defined $dbname;

	system($command);
    }

    exit($exit_code);
}
