package StingArgs;

use strict;
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
use Term::ANSIColor qw(:constants);

require Exporter;

@ISA = qw(Exporter AutoLoader);

@EXPORT = qw( getCommandArguments printCommandArguments moduleArguments PrintCommandHeader );

sub _getFormattingCharacterMap {
	my %fcmap = (
		'section' => '',
		'arg' => '',
		'default' => '',
		'end' => ''
	);

	if (defined($ENV{'ARACHNE_PRETTY_HELP'})) {
		if ($ENV{'ARACHNE_PRETTY_HELP'} eq 'Color') {
			$fcmap{'section'} = (RED . BOLD);
			$fcmap{'arg'} = (MAGENTA . BOLD);
			$fcmap{'default'} = BLUE;
			$fcmap{'end'} = RESET;
		} elsif ($ENV{'ARACHNE_PRETTY_HELP'} eq 'Bold') {
			$fcmap{'section'} = BOLD;
			$fcmap{'arg'} = BOLD;
			$fcmap{'arg'} = BOLD;
			$fcmap{'default'} = "";
			$fcmap{'end'} = RESET;
		}
	}

	return %fcmap;
}

sub _usage {
	my ($requiredArgsRef, $helpRef) = @_;
	my %requiredArgs = %$requiredArgsRef;
	my %help = (defined($helpRef)) ? %$helpRef : ();
	my %optionalArgs;
	my %fcmap = &_getFormattingCharacterMap();
	
	print "\n$fcmap{'section'}Usage: $0 arg1=value1 arg2=value2 ...$fcmap{'end'}\n\n";

	print "$fcmap{'section'}Required arguments:$fcmap{'end'}\n\n";

	foreach my $key (sort { $a cmp $b } keys(%requiredArgs)) {
		next if ($key =~ /_postprocess/ || $key =~ /_preprocess/);
		if (defined($requiredArgs{$key})) { $optionalArgs{$key} = $requiredArgs{$key}; }
		else {
			print "$fcmap{'arg'}$key$fcmap{'end'}\n";

			if (defined($help{$key})) {
				print "  $help{$key}\n";
			}
		}
	}
	print "\n";
	
	return unless keys(%optionalArgs);
	
	print "$fcmap{'section'}Optional arguments:$fcmap{'end'}\n\n";

	foreach my $key (sort { $a cmp $b } keys(%optionalArgs)) {
		if (defined($requiredArgs{$key})) {
			print "$fcmap{'arg'}$key$fcmap{'end'} $fcmap{'default'}default: " . ((ref($requiredArgs{$key}) eq 'ARRAY') ? "\"{" . join(",", @{$requiredArgs{$key}}) . "}\"" : $requiredArgs{$key}) . "$fcmap{'end'}\n";

			if (defined($help{$key})) {
				print "  $help{$key}\n";
			}
		}
	}
	print "\n";
}

# Parse the command-line arguments in Arachne style (including the ability to have whitespace between an equal sign and the parameter.
sub getCommandArguments {
	my %requiredArgs = @_;
	my %help;

	# Clean up our required arguments
	foreach my $key (keys(%requiredArgs)) {
		if (ref($requiredArgs{$key}) eq 'HASH') {
			$help{$key} = ${$requiredArgs{$key}}{'help'};
			$requiredArgs{$key} = ${$requiredArgs{$key}}{'value'};
		}
		if (defined($requiredArgs{$key})) {
			$requiredArgs{$key} =~ s/[\r\n]//g;
		}
	}

	# Set our required argument defaults.
	my %args = (
		'NO_HEADER' => 0,
		%requiredArgs,
	);

	if (defined($requiredArgs{'NH'}) && $requiredArgs{'NH'} =~ /(True|true|1)/) {
		$args{'NO_HEADER'} = 1;
		delete($args{'NH'});
		delete($requiredArgs{'NH'});
	}

	# Print usage and exit if we're not supplied with any arguments.
	if ($#ARGV == -1) {
		if (defined($requiredArgs{'_usage'})) { &{$requiredArgs{'_usage'}}(\%requiredArgs, \%help); }
		else { &_usage(\%requiredArgs, \%help); }
		exit(-1);
	}

	# Clean up the command-line arguments so that we can accept arguments with spaces and things like 'KEY= VALUE' in addition to the normal 'KEY=VALUE'.
	for (my $i = 0; $i <= $#ARGV; $i++) {
		my $arg = $ARGV[$i];
		if ($arg =~ /\w+=$/) {
			until (($i+1) > $#ARGV || $ARGV[$i+1] =~ /\w+=/) { $arg .= $ARGV[++$i]; }
		}
		
		if ($arg =~ /(NO_HEADER|NH)=(\s)?(True|true|1)/) { # Turn off automatic banner
			$args{'NO_HEADER'} = 1;
		} elsif ($arg =~ /(.+)=(.+)/) {
			my ($key, $value) = ($1, $2);

			# Store arguments that are of no interest to us in a separate variable.  This makes it convenient to allow certain arguments to pass through to another script by simply appending this extra argument to its command-line.
			if (!exists($requiredArgs{$key})) {
				$args{'_extra'} .= " $key=\"$value\"";
			}

			if    ($value eq 'True'  || $value eq 'true'  || ($value =~ /^\d+$/ && $value == 1)) { $args{$key} = 1; } # Parse boolean values
			elsif ($value eq 'False' || $value eq 'false' || ($value =~ /^\d+$/ && $value == 0)) { $args{$key} = 0; }
			elsif ($value =~ /{(.+)}/) { # Parse array values
				$value = $1;
				$value =~ s/\s+//g;
				my @values = split(",", $value);
				$args{$key} = \@values;
			} else { # Parse a regular ol' KEY=VALUE pair
				$args{$key} = $value;
			}
		} elsif ($arg =~ /(.+)=$/) { # Parse a KEY=VALUE pair where VALUE is empty
			$args{$1} = "";
		} elsif ($arg =~ /-(h|help|\?)/) { # Print help
			if (defined($requiredArgs{'_usage'})) { &{$requiredArgs{'_usage'}}(\%requiredArgs, \%help); }
			else { &_usage(\%requiredArgs, \%help); }

			exit(-1);
		}
	}

	# Pre-process arguments
	if (defined($requiredArgs{'_preprocess'})) {
		&{$requiredArgs{'_preprocess'}}(\%args);
		delete($args{'_preprocess'});
	}

	# Print the header box with info about the command
	&PrintCommandHeader() unless ($args{'NO_HEADER'});

	# Did the user forget any arguments?
	my $missingArgs = 0;
	foreach my $requiredArg (keys(%requiredArgs)) {
		if ($requiredArg !~ /_(pre|post)process/ && !defined($args{$requiredArg})) {
			print "$requiredArg must be supplied.\n";
			$missingArgs = 1;
		}
	}

	if ($missingArgs) { die ("Error: some required arguments were not supplied.\n"); }

    # Post-process arguments
    if (defined($requiredArgs{'_postprocess'})) {
		&{$requiredArgs{'_postprocess'}}(\%args);
		delete($args{'_postprocess'});
	}

	# We're all good!
	return %args;
}

# Print our command arguments
sub printCommandArguments {
	my ($argsref) = @_;
	my %args = %$argsref;

	foreach my $key (sort { $a cmp $b } keys(%args)) {
		if (ref($args{$key}) eq 'ARRAY') {
			print "$key => {" . join(",", @{$args{$key}}) . "}\n";
		} else {
			print "$key => $args{$key}\n";
		}
	}
}



# Returns a hash (by reference) with the required command-line arguments
# for the named C++ module.
# The trick is to run the module with no arguments, prompting the usage message
# (defined in system/ParsedArgs.cc), and then capture and parse this message.
sub moduleArguments($) {
    my ($module_name) = @_;
    my %args;
    my ($key, $value);
    
    # Temporarily setenv ARACHNE_PRETTY_HELP to "Bold" - to help with parsing
    my $temp = $ENV{'ARACHNE_PRETTY_HELP'};
    $ENV{'ARACHNE_PRETTY_HELP'} = "Bold";
    
    # Escape character - appears in output when ARACHNE_PRETTY_HELP="Bold"
    my $esc = chr(27);
    my $optional = 0;
    
    open (FH, "$module_name |");
    
    # Process each line of output into a command-line argument
    foreach my $line (<FH>) {
	
	$optional = 1 if ($line =~ /Optional arguments/);
	
	# Match line against the specific stdout format given in ParsedArgs
	next unless ($line =~ /$esc\[01m(.+?)$esc\[0m(.+)/);
	$key = $1;
	
	# If an argument is optional, but no value is specified in the usage
	# message, this means that the default value is in fact an empty string
	$value = $optional ? "" : undef;
	
	# Look for a default value in this line
	if ($2 =~ /default\: (.+)$/) {
	    $value = $1;
	}
	
	$args{$key} = $value;
    }
    close FH;
    $ENV{'ARACHNE_PRETTY_HELP'} = $temp;
    
    
    
    return \%args;
}




# Print the fancy header box with info about the command,
# including arguments supplied to it
# This parallels the function PrintTheCommandPretty in system/ParsedArgs.cc
sub PrintCommandHeader {
    
    my ($fh, $prefix, $thickbar) = (*STDOUT, ''); # default values
    $fh     = $_[0] if ($_[0]); # filehandle to print the header to
    $prefix = $_[1] if ($_[1]); # string to be prepended to every line of the header
    $thickbar = $_[2] if ($_[2]); # print a thicker version of the bar ('=')
    my $width = 80;
    
    my @stat = stat $0;
    my $mtime = localtime($stat[9]);
    
    my $bar = $thickbar ? '='x$width : '-'x$width;
    my $timestamp = localtime() . " run (pid=" . $$ . "), last modified $mtime";
    
    my $command = $0;
    $command .= ' ' while length $command < $width - 1;
    $command .= "\\";
    
    # Fill @args_parsed with lines of (parsed) info about the args
    my @args = @ARGV;
    my @args_parsed = ();
    my $line = '    ';
    while (@args) {
	my $arg = shift @args;
	
	# Start a new line, if necessary
	if (length($line) + 1 + length($arg) >= $width - 1 &&
	    $line ne '') {
	    $line .= ' ' while length $line < $width - 1;
	    $line .= "\\";
	    push @args_parsed, $line;
	    $line = '    ';
	}
	
	$line .= "$arg ";
    }
    if ($line) {
	push @args_parsed, $line;
    }
    
    
    # We have prepared the output lines;
    # now, prepend each line with the prefix, and append a newline
    map {$_ = "$prefix$_\n"} ($bar, $timestamp, $command, @args_parsed);
    
    # Print lines to filehandle
    print $fh $bar;
    print $fh $timestamp;
    print $fh $command;
    print $fh @args_parsed;
    print $fh $bar;
    print $fh "\n";
}


1;
