#!/usr/local/bin/perl
#
#Send email based on a template

use strict;
use warnings;

use Getopt::Long;
use lib '/software/glue-cluster/lib/site_perl';
use Template;

my $HomeDir = $ENV{HOME};
my $TEMPLATE_ROOT="$HomeDir/templates";
my $SENDMAIL="/usr/lib/sendmail";

my $tt = Template->new(
                INCLUDE_PATH => $TEMPLATE_ROOT,
                EVAL_PERL => 0,
                ABSOLUTE => 1,
                RELATIVE => 1,
                );


sub usage()
{	print STDERR <<EOF;
$0: Script for sending email based on template file

Usage:
$0 [ -nomail ] [ -template Template ] \\
	[ -macro MACRO1=something [ -macro MACRO2=else ... ]

This script takes a template file and a list of key value pairs given
by the macro arguments, and sends email using the send_template_mail
routine in Glue::HPCC::Utils.

If -template is not given, the first parameter after options parsed will
be used as the template file.  If it does not contain a slash (/) and is
not present in current directory, it is assumed to be 
in $TEMPLATE_ROOT.

The macro arguments, and any addition (possibly after template file) parameters
are expected to be KEY=VALUE pairs.  If any special characters, you will
need to quote the whole "KEY=VALUE" string.  Any strings \$KEY in the
template file will be expanded to VALUE before emailing.  Typically, there
is a parameter EMAIL which needs to be set to the recipients of the email
(note that this script does not have a "to" option, so recipients must be
in template file, whether hard-coded or macroed.  Repeated arguments with
the same KEY result in the macro for KEY being defined as a list ref.

If -nomail is given, the template will be printed to stdout instead of mailed.

EOF
}

#Expands a text template.
#Takes a template and a macro hash and expands using Template::Toolkit
#Setting variables XXX from macro_hash (key of macro has is TT var, value is value).
#macro hash ( XXX => 'expands to this' ) etc.
#Returns resultant text as a text string
#
#Template can be given as any of the following:
#	scalar: filename containing template, relative to TEMPLATE_ROOT
#	scalar ref: a text string containing the template
#	array ref: a list of text strings, one per line, forming the template
#	glob ref: assumed to be input filehandle from which template is to be read
sub expand_template($$)
{	my $template = shift;
	my $macros = shift || {};

	die "Must supply a template" unless $template;

	if ( ref($template) eq 'ARRAY' )
	{	chomp @$template;
		my $str = join "\n", @$template;
		$template = \$str;
	}
	
	my $output;

	$tt->process($template,$macros, \$output);
	unless ( $output )
	{	die "Template Error: " . $tt->error() . ".";
	}

	return $output;
}

sub split_header_and_body($)
#Given a list ref of strings representing a header plus message body of
#a mail message, returns two list refs representing header and body.
#Can also take a single string;
{	my $all = shift;

	die "Must supply an argument to split_header_and_body."
		unless $all;
	unless ( ref($all) )
	{	#Got a scalar string, split into an array
		$all = [ split /\n/, $all ];
	}
	die "Invalid argument all='$all' given to split_header_and_body.\n" .
		"Expecting a list ref.\n"
		unless $all && ref($all) eq 'ARRAY';

	my $header = [];
	my $body = [ @$all ];

	my ($line);
	$line = shift @$body;
	$line =~ s/^\s*// if $line; 
	$line =~ s/\s*$// if $line;

	while ( $line )
	{	push @$header, $line;
		$line = shift @$body;
		$line =~ s/^\s*// if $line; 
		$line =~ s/\s*$// if $line;
	}

	return ($header, $body);
}
	
sub send_email($$;$)
#Takes header and message variables and sends email.
#The header MUST have a To: line, as we do not specify a destination on cmdline
#
#Header can be a hash ref or array ref, or scalar.  If scalar, it is treated
#the same as a single element array ref.  Arrays are just printed out, one
#element per line. Hash ref get printed out with key as the header field and
#value as header value.
#
#Message can be array ref or scalar.  Array ref is treated as lines of text,
#and scalar simply as straightforward message.
#
#If $dryrun set, don't actually send out mail
{	my $header = shift;
	my $message = shift;
	my $dryrun = shift;

	die "You must supply a header to send_email" unless $header;

	my $text = "";
	my $tmp;
	$header = [ $header ] unless ref($header);
	my $has_to = 0;
	if ( ref($header) eq 'ARRAY' )
	{	my @tmp = grep /^To: /i, @$header;
		$has_to = 1 if @tmp;
		#We want to handle whether they include CRs or not
		#So chomp first , then add
		chomp @$header;
		$text .= join "\n", @$header;
	} elsif ( ref($header) eq 'HASH' )
	{	my @tmp = grep /^To$/i, keys %$header;
		$has_to = 1 if @tmp;
		foreach my $key (keys %$header)
		{	$tmp = $header->{$key};
			chomp $tmp;
			$text .= "$key: $tmp\n";
		}
	} else
	{	die "Illegal value $header for header in send_email";
	}

	$text .= "\n\n"; #First CR is for last header, next for blank line
	if ( ref($message) eq 'ARRAY' )
	{	chomp @$message;
		$message = join "\n", @$message;
	}
	$text .= $message;

	if ( $dryrun )
	{	print STDERR "Would be mailing:\n-------------------------\n";
		print STDERR "$text\n";
		print STDERR "-------------------------------------\n";
		return;
	}

	open(MAIL, "| $SENDMAIL -oi -t") or 
		die "Unable to open pipe to sendmail";

	print MAIL "$text\n";
	close(MAIL);
}

sub send_template_mail($$;$)
#Takes a template (as array ref of strings, one per line) and a 
#macro hash (keys are macron names, values are expansion),
#and sends out mail.
#
#Template MUST include the full mail headers, including the To: line.
#
#If $dryrun flag is set, only pretend to do it.
{	my $template = shift;
	my $macros = shift;
	my $dryrun = shift;

	my $text = expand_template($template,$macros);
	die "Invalid text: text is empty" unless $text;
	my ($header, $body) = split_header_and_body($text);
	send_email($header, $body, $dryrun);
}

sub yorn($)
#Ask a yes or no question ( y or n ) and wait for user to respond.
#Returns true if user typed yes or equivalent.
#returns false otherwise.
{	my $msg = shift;
	print $msg;
	my $ans = <STDIN>;
	chomp $ans;
	return 1 if ( $ans=~/^\s*y(es)?\s*$/i );
	return;
}


#------------------------------------------------------------------
#			Main
#------------------------------------------------------------------

my $help;
my $listonly = 0;
my $mail = 1; # -nomail will unset
my $template;
my $macros = [];

my $res = GetOptions(
	'help!' =>\$help,
	'list!' => \$listonly,
	'mail!' => \$mail,
	'template=s' => \$template,
	'macro=s' => $macros,
	);
	
unless ( $res )
{	usage;
	die "Error parsing arguments.\n";
}

if ( $help )
{	usage;
	exit 0;
}

unless ( $template )
{	#No -template arg, take next parameter
	$template = shift @ARGV;
	unless ( $template )
	{	usage;
		die "You must provide a template file.\n";
	}
}

#Remaining args get treated as macros

push @$macros, @ARGV;


#Check for template file
unless ( $template =~ '/' )
{	#No /, prefix with $DEF_TEMPLATE_DIR unless in current dir
	unless ( -f "./$template" )
	{	$template = "$TEMPLATE_ROOT/$template";
	}
}
unless ( -f "$template" )
{	die "Unable to find template file $template.\n";
}

#Read the template
open(FILE,"<$template") or die "Error opening template file $template: $!";
my @lines = <FILE>;
close(FILE);
$template = [ @lines ];

if ( $listonly && scalar(@$macros) )
{	usage;
	die "The -list and -macro args are exclusive.\n";
}

if ( $listonly )
{	list_macros_in_template($template);
	exit 0;
}

#Create macro hash from arguments
my $mhash = {};
my ($macro, $key, $val, $tmp);
foreach $macro (@$macros)
{	unless ( $macro =~ /=/ )
	{	usage;
		die "Invalid macro definition '$macro' : missing = sign\n";
	}
	($key, $val ) = split /\s*=/, $macro, 2; #Allow = and spaces in $val
	if ( exists $mhash->{$key} )
	{	#Set more than once, assume should be an ARRAY ref
		$tmp = $mhash->{$key};
		if ( ref($tmp) eq 'ARRAY' )
		{	push @$tmp, $val;
		} else
		{	$tmp = [ $tmp, $val ];
			$mhash->{$key} = $tmp;
		}
	} else
	{	#Only once (so far), assume a scalar
		$mhash->{$key} = $val;
	}
}

unless ( scalar(keys %$mhash) )
{	warn <<EOF;
No macros have been defined.  This seems a silly use of this script.
Should I continue?
EOF
	my $res = yorn("Send template mail without macros?");
	die "Aborting.\n" unless $res;
}

my $dryrun = 0;
$dryrun = 1 unless $mail;
send_template_mail($template, $mhash, $dryrun);
