#!/usr/bin/perl -w

# matlab_method.pl

# Copywrite Woods Hole Oceanographic Institution

# Program to read in data formatted as Matlab binary files.

# All necessary run-time and configuration values are read in from
# 		$config_param{'send_mail_option'}
# a configuration file specified as the first argument in the command
# line.

# Assumptions:
#	1. At least one field name must be provided, as well as the 
# Matlab, binary, data file name.

$version="V1.13 May 30, 2003";

# May 30, 200. V1.13.  Add additional examples for vectors and strings.  
#	Add example of using the "param" keyword to the example.  rcg
# November 12, 2002. V1.12.  Add array row number option to passed parameters.
#	It will be used, if present, as the "input position" for arrays instead of 
#	this parameter coming from the configuration file.  Add index_by_rows
#	configuration parameter if one needs to index by columns instead.  rcg
# September 26, 2002, V1.11.  Add "exit" to the shell created to 
#	run the matlab application.  rcg
# September 26, 2002. V1.10.  Change debug output to go back to STDOUT.  rcg
# July 8, 2002. V1.09. Add "string" option to "vector" and "array"
#	choices to handle such things as strings and dates.
# June 25, 2002. V1.08. Add additional error message text when field names
#	cannot be decoded.
# June 18, 2002. V1.07. Fix error check on system call to run batch command
#	file to test for success as 1 rather than 0.  Change to use
#	Bourne shell.  Success may be 0 after all.  Add option to
#	get mat_file_names from ARGV[1] if mat_file_names is set to
#	param. Change separation character to comma for list of
#	mat files to read in.  rcg
# June 14, 2002. V1.06. Add -nohup, output redirection to log file, and
#	redirection for errors too  as 2>&1.  Remove nohup command. rcg
# May 25, 2001. V1.05  Add back in the -nosplash option in Matlab command.  rcg
# May 17, 2001. V1.04  Change debug output to go to STDERR.  Add script name
#	to debug output.  Add sample configuration file at end by way of
#	internal documentation.  rcg
# May 16, 2001. V1.03  Use "batch" file to run matlab per the Mathworks
#	suggestion.  Clean up code that defines the various file names
#	so they are defined in one section, rather than 3.  rcg
# May 14, 2001. V1.02  Change matlab call to remove errors. Add quit to
#	end of m file.  rcg
# May 11, 2001. V1.01  Change data output to go to a file 
#	rather than unit 1.  Change output to use \t rather
#	than tab character directly.  Fix formatting and values
#	in date stamp.  rcg
# May 7, 2001.  V1.00  R. Groman

$| = 1;
$error = "&x";
$warning = "#";

$date = scalar localtime;
$day_today = (localtime)[3];
if ($day_today < 10 ) {$day_today = "0" . $day_today}
$month_today = (localtime)[4];
$month_today++;
if ($month_today < 10 ) {$month_today = "0" . $month_today}
$year_today = (localtime)[5];
if ($year_today < 1000) {$year_today = $year_today + 1900;}
$hour_today = (localtime)[2];
if ($hour_today < 10 ) {$hour_today = "0" . $hour_today;}
$minute_today = (localtime)[1];
if ($minute_today < 10 ) {$minute_today = "0" . $minute_today;}
$time_today = $hour_today . $minute_today;
$time_stamp = $year_today . $month_today . $day_today . $time_today;
$processid = getppid;

$configuration_file = $ARGV[0];

@required = (
	"shell",
	"field_name_1", 
	"matlab_command",
	"mat_file_names");

&read_configuration_file($configuration_file);

$debug = 'no';
if (exists $config_param{'debug'} and defined $config_param{'debug'} ) {
	if ($config_param{'debug'} =~ m/^y/i or 
	   $config_param{'debug'} =~ m/^1/ or 
	   $config_param{'debug'} =~ m/^t/i) {;
		$debug = "yes";
	}
	elsif ($config_param{'debug'} =~ m/^keep/i ) {
		$debug = "keep";
	}
}

&make_log_entry (">>>>>>>Begin Program=$0",
	"Date of run=$date\tVersion=$version",
	"Configuration file=$configuration_file");

if ($debug eq 'yes') {
	print STDOUT ("\n#Matlab method $0 Version: $version\n");
	print STDOUT (
		"#  Configuration file is $configuration_file\n") 
			if defined $configuration_file;
	print STDOUT ("#  Date of run: ", $date, "\n");
}

$okay = "yes";
for ($i=0; $i<=$#required; $i++) {
	unless (exists $config_param{$required[$i]} ) { 
	   $okay="no"; 
	   &sendmessage ($error,
	   	"$required[$i] is missing from configuration ",
	   	"file=$configuration_file");
	   &make_log_entry ("$required[$i] is missing from configuration file",
	   	"file=$configuration_file");
	}
	else {
#		print STDOUT ("#  $required[$i]=$config_param{$required[$i]}\n");
		&make_log_entry ("$required[$i]=$config_param{$required[$i]}");
	}
}
if ( $okay eq "no") { 
  &sendmessage ($error,
	"One or more parameters are missing from the configuration file",
		"Cannot continue.");
  &make_log_entry (
   "One or more parameters missing from config file=$configuration_file");
}
else {
	$status = &process_data;
	&make_log_entry ("Return status from processing=$status");
}

undef $error;
undef $warning;
undef $version;
undef $time_stamp;

&make_log_entry ("<<<<<<<End of $0");

exit;

#------------------------------------------------------------------------
sub read_configuration_file {

# Open and read configuration file specified as first passed parameter 
# $_[0]. Return the contents of the file as the hash array %config_param.
# Lines beginning with "#" are treated as comments.  It is assumed
# that the indirect file contains lines as

#	parameter = value

# and this information is stored as 

#	$config_param{"parameter"} = value

my $filename = $_[0];
my ($parameter, $value);

#print STDOUT ("#***debug, $0 indirect filename=$filename\n");

unless (defined $filename ) {
	&sendmessage ($error,
		"Could not open configuration file",
		"File name not specified.  Cannot continue.");
	exit;
}
unless (open CONFIG_FILE, $filename) {
	&sendmessage ($error,
		"Could not open configuration file=$filename",
		"Error code=$!.  Cannot continue.");
	exit;
}
while (<CONFIG_FILE>) {
	chomp;
	s/\s//g;
	if (m/^#/) { next;}
	unless ( m/\S+/ ) {next;}
	unless ( m/:=/ ) {
		&sendmessage ($warning,
			"No ':=' in line of configuration file=$filename",
			"Line is=$_");
		next;
	}
#	print STDOUT ("***debug, $0 config input line=$_\n");
	($parameter, $value) = split /:=/;
	$config_param{$parameter} = $value;
#	print STDOUT ("#**debug, $0 config_param{$parameter}=",
#		$config_param{$parameter}, "\n");
}
close CONFIG_FILE;
}

#------------------------------------------------------------------------

sub sendmessage {

#Send a message to the user.
#The message sent will be in the strings $_[0] and $_[1]

my ( @args, $mailfile, $message0, $message1, $prefix, $who);
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst);

$prefix=$_[0];
$message0=$_[1];
$message1=$_[2];
($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime(time);
if ($year >= 100 and $year <= 1000) {$year = $year + 1900}
if ($year < 1900) { $year = $year + 2000; }
$mon++;
if ($mon < 10) { $mon = "0" . $mon; }
if ($mday < 10) {$mday = "0" . $mday; }
if ($hour < 10) { $hour = "0" . $hour; }
if ($min < 10) { $min = "0" . $min; }
if ($sec < 10) { $sec = "0" . $sec; }
undef $wday;
undef $isdst;
$mailfile="/tmp/sendmess" . $year . $yday . $hour . $min . $sec . ".tmp";
if ( -e $mailfile) { unlink $mailfile }

if ( open TEMPFILE, ">$mailfile") {
	print TEMPFILE ("Message from $0\n");
	if ( exists $ENV{'REMOTE_HOST'} ) {$who=$ENV{'REMOTE_HOST'} ; }
	elsif (exists $ENV{'REMOTE_ADDR'} ) {$who=$ENV{'REMOTE_ADDR'} ; }
	else {$who="not available"; }
	print TEMPFILE (" Date of message: $year/$mon/$mday $hour:$min\n");
	print TEMPFILE (" From: $who\n");
	print TEMPFILE (" $message0\n");
	print TEMPFILE (" $message1\n");
	close TEMPFILE;
	`/bin/mail -s "Probem with $0" dmo\@globec.whoi.edu <$mailfile`;
	unlink $mailfile;
}

print STDOUT ($prefix," $message0\n");
print STDOUT ($prefix," $message1\n");
print STDOUT ($prefix," Above message from $0\n");
print STDOUT ($prefix," Date of message: $year/$mon/$mday $hour:$min\n");

return 0;
}

#----------------------------------------------------------------------------
sub process_data {

# Process the data from the file(s) specified in $config_param{'mat_file_names'}
# for the field names specified in $config_param{'field_name_<n>'}.

my (
	$batch_file,
	$count,
	@data_array, 
	$error1, $error2, $error3,
	@field_name, 
	@file, $file,
	$i, @junk,
	@input_structure,
	$last_value,
	$m_file,
	$output_of_batch_file,
	$output_data_file,
	@output_format, $output_string,
	@input_position,
	$stuff, $temp
	);

# Define m file, batch file and output of batch file names

$m_file = 'matlab_m_file_' . $processid . '_' . $time_stamp . '.m';
$batch_file = 	'matlab_batch_' . $processid . '_' . $time_stamp . '.bat';
$output_of_batch_file = 'matlab_output_' . $processid . '_' . $time_stamp . '.out';
$output_data_file = 'matlab_data_' . $processid . '_' . $time_stamp . '.dat';

if (exists $config_param{'output_file_name_root'} and 
   defined $config_param{'output_file_name_root'} ) {
	$m_file = $config_param{'output_file_name_root'} . '/' .$m_file;
	$batch_file = $config_param{'output_file_name_root'} . '/' . $batch_file;
	$output_of_batch_file = $config_param{'output_file_name_root'} . '/' .
				$output_of_batch_file;
	$output_data_file = $config_param{'output_file_name_root'} . '/' .$output_data_file;
}
else {
	$m_file =  '/tmp/' .$m_file;
	$batch_file =  '/tmp/' . $batch_file;
	$output_of_batch_file = '/tmp/' . $output_of_batch_file;
	$output_data_file =  '/tmp/' .$output_data_file;
}

print STDOUT ("#***debug, $0 m file=$m_file\n") if $debug eq "yes";
print STDOUT ("#***debug, $0 batch_file=$batch_file\n") if $debug eq 'yes';
print STDOUT ("#***debug, $0 output_of_batch_file=$output_of_batch_file\n") if $debug eq 'yes';
print STDOUT ("#***debug, $0 output_data=$output_data_file\n") if $debug eq 'yes';

&make_log_entry("M file=$m_file",
		"Batch file=$batch_file",
		"Output of batch file=output_of_batch_file",
		"Output data file=$output_data_file");

unless (open BATCH_FILE, ">$batch_file") {
	&make_log_entry ("Could not open batch file for writing=$batch_file");
	&sendmessage ($error, 
		"Could not open output batch file for writing $batch_file",
		"Cannot continue.");
	return "Could not open output of batch file for writing";
}

print BATCH_FILE ('#!', $config_param{'shell'}, ' -f ', "\n");

print BATCH_FILE ("# Batch file to run Matlab\n");
print BATCH_FILE ($config_param{'matlab_command'}, ' -nosplash -nohup < ',
	"$m_file", ' > ', "$output_of_batch_file", 
	' 2>&1',
	"\n");
print BATCH_FILE ("exit\n");

close BATCH_FILE;
$count = chmod 0777, $batch_file;
unless ($count > 0 ) {
	&make_log_entry ("Could not change mod to execute for file=$batch_file");
	&sendmessage ($error, 
		"Could not change mode to execute for batch file $batch_file",
		"Cannot continue.");
	return "Could not change mode to execute for batch file";
}

# Create new Matlab m file

unless (open M_FILE, ">$m_file") {
	$error1 = $!;
	&make_log_entry ("Could not open output Matlab file=$m_file", "Error=$error1");
	&sendmessage ($error, 
		"Could not open output Matlab file $m_file",
		"Error is $error1.  Cannot continue.");
	return "Could not open output Matlabe m-file";
}

&make_log_entry("M file created is=$m_file");

# Get input file names and add LOAD command to m file.

if ($config_param{'mat_file_names'} =~ m/^param/i ) {
	$config_param{'mat_file_names'} = $ARGV[1];
}

if (defined $ARGV[2] ) {
	$row = $ARGV[2];
}
@file = split /,/, $config_param{'mat_file_names'};
print STDOUT ("#***debug, $0  file(s)=@file\n") if $debug eq "yes";

print M_FILE ("% Matlab method m file created $time_stamp\n");

print M_FILE ("outfile=fopen(\'$output_data_file\',\'w\');\n");


print M_FILE ("fprintf(outfile,\'%s\\n\',\'START OUTPUT NEXT LINE\');\n");

foreach $file (@file) {
	unless ( -e $file and -r $file) {
		&make_log_entry ("Cannot find or read input file=$file");
		&sendmessage ($error, "Cannot find or read input file=$file",
			"Cannot continue");
		return "Could not find or read input file";
	}
	print M_FILE ("load $file;\n");
}

# Add special commands to execute if in conf file as config_param{command_<n>}

$i=1;
while (exists $config_param{"command_$i"} and 
   defined $config_param{"command_$i"} ) {
   	print M_FILE ($config_param{"command_$i"},"\n");
   	$i++;
}

# Split up $config_param{'field_name_<n>'}

$i = 1;

while (exists $config_param{"field_name_$i"} and 
   defined $config_param{"field_name_$i"} ) {
	($data_array[$i], $field_name[$i], 
	   $input_position[$i], $input_structure[$i], $output_format[$i], 
	   $stuff) = split /,/, 
	   	$config_param{"field_name_$i"};
	$temp = $config_param{"field_name_$i"};
	unless (defined $data_array[$i] and defined $field_name[$i] and
	   defined $input_position[$i] and defined $input_structure[$i]
	   and defined $output_format[$i] ) {
	   	&make_log_entry (
			"Input data parameter not defined in field name=field_name_$i",
			"Value of field_name_$i=$temp");
	   	&sendmessage ($error, "Input data parameter not defined for field_name_$i.",
	   		"Value of field name was $temp");
	   	return "Input data parameter not defined";
	}
	unless (lc $input_structure[$i] eq "array" or 
	  lc $input_structure[$i] eq "vector" or
	  lc $input_structure[$i] eq "string") {
		&make_log_entry (
			"Input structure not valid=$input_structure[$i]");
		&sendmessage ($error, 
			"Input structure not valid.",
			"Specified as $input_structure[$i]");
		return "Input structure not valid";
	}
	$last_value = $i;
	$i++;
}

# Write loop in output file
# This loop is not necessary if data are strings, but
# you could have 

print M_FILE ("[rows,columns] = size ($data_array[1]);\n");

if (exists $config_param{'index_by_rows'} and 
    defined $config_param{'index_by_rows'} ) {
	if ($config_param{'index_by_rows'} =~ m/^n/i or
	    $config_param{'index_by_rows'} =~ m/^f/i ) {
		print M_FILE ("for i=1:columns\n");
	}
	else {
		print M_FILE ("for i=1:rows\n");
	}
}
else {
	print STDOUT ("#***debug, index_by_rows config param not defined\n")
		unless $debug eq 'no';
	print M_FILE ("for i=1:rows\n");
}

$output_string = "\tfprintf(outfile,\'";

for ($i=1; $i <= $last_value; $i++ ) {
	unless ( defined $output_format[$i] and 
	  defined $input_structure[$i]) {next }
	$output_string = $output_string . "$output_format[$i]\\t";
}

$output_string = $output_string . "\\n\',";

for ($i=1; $i <= $last_value; $i++ ) {
	unless ( defined $output_format[$i] and 
	  defined $input_structure[$i]) {next }
	$output_string = $output_string . "$data_array[$i]\(";
	if ( lc $input_structure[$i] eq "array" ) {
		if (defined $row) {
			$output_string = $output_string . "i,$row\),";
		}
		else {
			$output_string = $output_string . "i,$input_position[$i]\),";
		}
	}
	elsif ( lc $input_structure[$i] eq "vector" ) {
		$output_string = $output_string . 'i),';
	}
	elsif ( lc $input_structure[$i] eq "string" ) {
		$output_string = $output_string . ':),';
	}
	else {
		&make_log_entry(
			"Error in logic, unknown input structure=$input_structure[$i]");
		&sendmessage($error,"Error in logic",
			"Unexpected input structure is $input_structure[$i]");
		return "Unexpected input structure";
	}
}

$output_string =~ s/\),$/\)\)/;

print M_FILE ($output_string,";\n");

print M_FILE ("\nend\n");
print M_FILE ("fclose(outfile);\nquit;\n");
close M_FILE;

$error2 = system ($batch_file);
$error3 = $!;
$error2 = $error2 / 256;
$error1 = chomp $?;
print STDOUT ("#***debug, $0 error2 (before division)=$error2\n") 
	if $debug eq 'yes';

unless ($error2 == 0 and $error1 == 0 ) {
	&make_log_entry (
	   "Could not open Matlab batch program with system call error=$error2",
		"Question error=$error1", "! error=$error3",
		"Batch file=$batch_file",
		"m file=$m_file");
	&sendmessage($error, 
	  "Could not open Matlab batch program with system call error=$error2",
	   "Error ?=$error1. ! error=$error3. Batch file is $batch_file. Output file is $m_file.");
	return "Could not open Matlab batch program with system call";
}
&make_log_entry(
	"Ran Matlab batch program to output data to file=$output_data_file",
	"Used batch file=$batch_file");

# Output data loop via reading matlab output.  Look first for special start
# string.

print STDOUT ("#***debug, output_data_file=$output_data_file\n")
	if $debug eq 'yes';
	
unless (open MATLAB_IN, $output_data_file) {
	$error1 = $!;
	chomp $error1;
	&make_log_entry (
		"Could not open Matlab output file=$output_data_file",
		"Error=$error1");
	&sendmessage($error,
		"Could not open Matlab output file=$output_data_file",
		"Error is $error1.  Cannot continue.");
	return "Could not open Matlab output file";
}

# Output field names to standard out
for ($i=1; $i <= $last_value; $i++) {
	if (defined $field_name[$i]) {
		print STDOUT ("$field_name[$i]\t");
	}
}
print STDOUT ("\n");

$ready = 'no';
while (<MATLAB_IN>) {
	if ($ready eq 'yes') {
		if (m/^>>/  ) { last}
		print STDOUT ($_);
	}
	else {
		if ( m/START OUTPUT NEXT LINE/ ) {
			$ready = 'yes';
		}
	}
}	

close MATLAB_IN	;
unlink $batch_file if -e $batch_file and $debug eq 'no';
unlink $m_file if -e $m_file and $debug eq 'no';
unlink $output_data_file if -e $output_data_file and $debug eq 'no';
unlink $output_of_batch_file if -e $output_of_batch_file and $debug eq 'no';


undef @junk;

return "okay";
}

#-----------------------------------------------------------------------
sub make_log_entry {

# Make a log entry with @_ if the log file $config_param{"logfile"} 
# exists.  Each entry in @_ are separated by tabs.

my ($login, @month, $temp);
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst);

($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime(time);
if ($year >= 100 and $year <= 1000) {$year = $year + 1900}
if ($year < 1900) { $year = $year + 2000; }
$mon++;
if ($mon < 10) { $mon = "0" . $mon; }
if ($mday < 10) {$mday = "0" . $mday; }
if ($hour < 10) { $hour = "0" . $hour; }
if ($min < 10) { $min = "0" . $min; }
if ($sec < 10) { $sec = "0" . $sec; }

if (exists $config_param{"logfile"} ) {
	$temp = ">>" . $config_param{'logfile'};
	unless ( open LOGFILE, $temp ) {
		&sendmessage ($warning, "Could not open logfile=$temp",
			"Error code=$!");
	}
	else {
		$login = getlogin || (getpwuid($<))[0] || "Intruder!!";
		$login="\tusername=" . $login;
		print LOGFILE ($year, "/" . $mon . "/" . $mday . ":" . $hour . $min . "." . 
			$sec, $login);
		foreach (@_) {
			print LOGFILE ("\t", $_);
		}
		print LOGFILE ("\n");
		close LOGFILE;
	}
}
return;
}


#-----------------------------------------------------------------

sub mailmessage {

# Send an email message to $_[0].  

#	 $_[0] - address(es) of receiptiants
# 	 $_[1] - message (multiple lines of text)

my ( $mailfile, $message, $who);
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst);

$sendee = $_[0];
$message = $_[1];

($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime(time);
if ( $year < 1000 ) {$year = $year + 1900; }
$mon++;
if ($mon < 10) { $mon = "0" . $mon; }
if ($mday < 10) {$mday = "0" . $mday; }
if ($hour < 10) { $hour = "0" . $hour; }
if ($min < 10) { $min = "0" . $min; }
if ($sec < 10) { $sec = "0" . $sec; }
$mailfile=">/tmp/sendmess" . $year . $yday . $hour . $min . $sec . ".tmp";

if ( open TEMPFILE, $mailfile) {
	if ( exists $ENV{'REMOTE_HOST'} ) {$who=$ENV{'REMOTE_HOST'} ; }
	elsif (exists $ENV{'REMOTE_ADDR'} ) {$who=$ENV{'REMOTE_ADDR'} ; }
	else {$who="not available"; }
	print TEMPFILE ("Message from $0");
	print TEMPFILE (" Date of message: $year/$mon/$mday $hour:$min\n");
	print TEMPFILE (" From host: $who\n");
	print TEMPFILE (" Message text:\n$message\n");
	close TEMPFILE;
	$sendee =~ s/\@/\\\@/;
	`/usr/bin/mail -s \"FleetLink message\" $sendee <$mailfile`;
	unlink $mailfile;
}
return;
}

#----------------------------------------------------------------------
#
# Sample configuration file.  Lines with leading single pound signs (#)
# should be used.  Lines with double pound signs (##) are comments
# lines within the config file.

##If more than one file name, enter the names, separated by white space

#mat_file_names := /data/cashjian/vpr/data/test_data_rcg.mat

##Or can have the file name be a parameter in the command line by
##specifying the "param" keyword for the file specification.
#mat_file_names := param

#matlab_command := /usr/local/bin/matlab

#output_file_name_root := /tmp


##Specify the field names as follows:
##	field_name_<n> := vector/matric name, field_name, column, output_format
##where	
##	n - number from 1 to m, specifying order in which you
##			want the output data to appear
##	vector/matric name - Matlab name for the vector or matrix containing
##			the data
##	field_name - name of variable.  This will be output as part of
##			JGOFS software's field name list after comments
##			and before the data output stream starts
##	column - column number within vector/matric name where data are
##			located
##	key word array or vector - specifies whether input data are in
##			an array or vector
##	output_format - format for the output string, in the form
##			%8.3f (typcial matlab/C syntax for field width
##			of 8, and 3 significant digits after the decimal
##			point.
#field_name_1 := data_array, station, 1, array, %4i
#field_name_2 := data_array, leg, 2, array, %4i
#field_name_3 := data_array, lat, 3, array, %8.3f
#field_name_4 := data_array, lon, 4, array, %9.3f
#field_name_5 := data_array, month, 5, array, %4.0f
#field_name_6 := data_array, year, 6, array, %4i
#field_name_7 := data_array, o2, 7, array, %5.3f
#field_name_8 := data_array, press, 8, array, %7.3f
#field_name_9 := data_array, sal, 9, array, %6.3f
#field_name_10 := data_array, sigth, 10, array, %6.3f
#field_name_11 := data_array, temp, 11, array, %6.3f
#field_name_12 := data_vector, some_vector, 1, vector, %10.3
#field_name_13 := cruise, cruiseid, 1, string, %10s

##If logfile is specified then log is maintained of program run
##logfile := /data/cashjian/vpr/log/e259.log
##
##Optional operations are supported using the syntax
##	command_n := <matlab command>
## where n specified the order in which the command are issued.  Must
## be consecutive numbering, starting at 1.

#command_1 := data_array(:,6) = data_array(:,6) + 1900;

#debug := yes

