#!/usr/local/bin/perl
#
#Script to report on last_email table
use strict;
use warnings;

use lib "/cell_root/software/HPCC/lib/site_perl";
#use lib "/cell_root/software/HPCC/1.1/lib/site_perl";

use Glue::HPCC::Cluster;
use Glue::HPCC::Project::LastEmail;

use Getopt::Long;
use Time::ParseDate;
use POSIX qw(strftime);

sub usage()
{	print STDERR <<EOF;
$0: List log entries

Usage:
$0 [ -dbcluster DBCLUSTER ] \
	[ -user USER ] [ -project PROJECT ] [ -account ACCOUNT ] \
	[ -mailcode MAILCODE ] \
	[ -starttime STARTTIME ] [ -endtime ENDTIME ] \
	[ -all ]

DBCLUSTER is the cluster to use for determining which DB to attach to.
It will be defaulted from hostname.  If given, cannot be repeated.

This will dump all log entries found matching the specified criteria.  If
a class of criteria is repeated, a log entry will match that criteria class if
it matches any of the values.  Values for different criteria classes are ANDed.
If a value is not given for a particular class, it is ignored.

USER is user that was emailed.
PROJECT and ACCOUNT are the name of the project and/or account which was
emailed about.
MAILCODE is the mailcode for the email.
Only entries with timestamp between STARTTIME and ENDTIME are considered; 
these fields may NOT be repeated, and default to beginning and end of time.

At least one criterion must be given, or the flag -all if you really want
all records to be dumped.

If --debug is given, SQL will be displayed.

EOF
}


#Recursive function
sub cross_product_hash_keys($);
sub cross_product_hash_keys($)
#Takes a hash ref, values of keys are array refs.
#Returns cross product; e.g. { a => [ 1,2,3], b=>[4,5] } would return something
#like
#[	{ a=>1, b=4 }, {a=>1, b=>5} , 
#	{ a=>2, b=4 }, {a=>2, b=>5} , 
#	{ a=>3, b=4 }, {a=>3, b=>5} , 
#]
#i.e., we enumerate over all values	
{	my $hash = shift || {};

	return $hash unless scalar(%$hash); #Emtpy hash => empty hash

	my @keys = keys %$hash;
	my ($key, $vals);
	my @hashes;

	if ( scalar(@keys) == 1 )
	{	$key = $keys[0];
		$vals = $hash->{$key};
		@hashes = map { { $key => $_ } } @$vals;
		return \@hashes;
	}

	#More than 1 key, recurse
	$key = shift @keys;
	my $tmphash = { %$hash };
	$vals = delete $tmphash->{$key};

	my $hashes = cross_product_hash_keys($tmphash);
	@hashes = ();
	foreach my $val (@$vals)
	{	foreach my $tmphash (@$hashes)
		{	push @hashes, { %$tmphash, $key=>$val };
		}
	}
	return \@hashes;
}

sub get_nonfuzzy_matches($$)
#Given an Glue::HPCC::Cluster instance and a hash ref (possibly empty) of
#search criteria (in ARRAY refs, might be multiple values to OR against)
#return a list ref of matching Glue::HPCC::Project::LastEmail entries
{	my $hpc = shift;
	my $criteria = shift;
	my $me = 'get_nonfuzzy_matches';

	die "$me: Missing hpc object at " unless $hpc;
	die "$me: Invalid hpc object '$hpc' at " unless ref($hpc);
	die "$me: Invalid criteria '$criteria' at " 
		unless $criteria && ref($criteria) eq 'HASH';

	my $time = delete $criteria->{time};
	my $start = delete $criteria->{starttime};
	my $end = delete $criteria->{endtime};
	if ( $start || $end )
	{	die "$me: Cannot specify both 'time' & 'starttime/endtime' at "
			if $time;
		$time = [ $start, $end ];
		$criteria->{time} = $time;
	}

	my $recs = Glue::HPCC::Project::LastEmail->pdb_query($hpc, %$criteria);

	return $recs;
}

sub dump_rec(;$)
#Dump a last email record
{	my $rec = shift;

	my $fmt = "%10s %12s %12s %15s %15s %s\n";
	unless ( $rec )
	{	printf $fmt,
			"username", "project", "account", "mailcode",
			"Date/Time", "Desc";
		return;
	}

	my $user = $rec->username || '';
	my $project = $rec->project || '';
	my $account = $rec->account || '';
	my $mailcode = $rec->mailcode || '';
	my $time = $rec->time;
	my $tstamp = parsedate($time) if $time;
	my $tstr = strftime("%Y-%m-%d %H:%M", localtime($tstamp));
	my $desc = $rec->description || '';

	printf $fmt, $user, $project, $account, $mailcode, $tstr, $desc;
}

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

my $help;
my $all=0;
my $debug;
my $dbcluster;

my $users = [];
my $projects = [];
my $accounts = [];
my $mailcodes = [];
my ($starttime,$endtime);

my $res = GetOptions(
	'help!' => \$help,
	'all!' => \$all,
	'debug!' => \$debug,

	'dbcluster=s' => \$dbcluster,

	'starttime=s' => \$starttime,
	'endtime=s' => \$endtime,

	'user=s' => $users,
	'user=s' => $users,
	'p|project=s' => $projects,
	'a|ac|account=s' => $accounts,
	'mailcodes|codes=s' => $mailcodes,
);

unless ( $res )
{	usage;
	die "\nError processing options.\n";
}
if ( $help )
{	usage;
	exit 0;
}

if ( scalar(@ARGV) )
{	usage;
	my $tmp = join ", ", @ARGV;
	die "\nError: extraneous arguments [ $tmp ], aborting.\n";
}

#Check that we have some constraint, or -all
my $count = 0;
$count++ if $starttime;
$count++ if $endtime;
$count += scalar(@$users) + scalar(@$projects) + scalar(@$accounts);
$count += scalar(@$mailcodes);

unless ( $count || $all )
{	usage;
	die "\nYou must specify at least one condition, or give -all for all records.\n";
}

#Default cluster if needed
my $hpc = Glue::HPCC::Cluster->new_from_clustername($dbcluster);
die "\nError instantiating cluster $hpc\nDo you need to use --dbcluster? at  " unless $hpc && ref($hpc);
$dbcluster = $hpc->db_clustername;
$hpc->verbose($debug);

my $nonfuzzy_criteria = {};
$nonfuzzy_criteria->{starttime} = $starttime if $starttime;
$nonfuzzy_criteria->{endtime} = $endtime if $endtime; 
$nonfuzzy_criteria->{users} = $users if $users && scalar(@$users); 
$nonfuzzy_criteria->{projects} = $projects if $projects && scalar(@$projects); 
$nonfuzzy_criteria->{accounts} = $accounts if $accounts && scalar(@$accounts); 
$nonfuzzy_criteria->{mailcodes} = $mailcodes 
	if $mailcodes && scalar(@$mailcodes); 

#Get all log entries matching non-fuzzy criteria
my $recs = get_nonfuzzy_matches($hpc,$nonfuzzy_criteria);

unless ( $recs && ref($recs) eq 'ARRAY' && scalar(@$recs) )
{	print "No matching records.\n";
	exit 0;
}

dump_rec();
foreach my $rec (@$recs)
{	dump_rec($rec);
}

