#!/usr/bin/perl -w

use strict;
use DBI;
use Data::Dumper;
use Date::Parse;
use CGI qw/:standard/;
use CGI::Carp qw(fatalsToBrowser);
use POSIX qw(strftime);

my $debug = 0;

# comment out following field if you don't want links to omni reports
# or haven't installed repgen.pl
my $repgen_url="/omni/"; # URL to pages with rptgen.pl

my $width = 600;	# width of bar
my $height = 15;	# height of bar
my $use_js = 1;		# use JavaScript pop-up

# status colors
my %cols = (
	'In Progress' 		=> '0,255,0',
	'In Progress/Failures'	=> '192,64,192',
	'In Progress/Errors'	=> '255,128,128',
	'Queuing'		=> '255,255,0',
	'Queuing/Errors'	=> '255,192,64',
	'Aborted'		=> '255,0,0',
	'Failed'		=> '255,0,0',
	'Completed'		=> '64,255,64',
	'Completed/Errors'	=> '255,128,0',
	'Completed/Failure'	=> '128,0,128',
	'Mount Request'		=> '128,128,255',
	'Mount/Errors'		=> '255,64,128',
	'Mount/Failures'	=> '255,128,192',
	);

my $int_t = (12 * 60 * 60);	# interval to display on one screen
my $min_l = 3;			# min length of bar segment (in pixels)

#--- no user servicable parts below this line

my $q=new CGI;

my ($from_t,$to_t) = (time()-$int_t,time());

$from_t = param('from_t') if (param('from_t'));
$to_t = param('to_t') if (param('to_t'));

if (param('f-1')) {
	$from_t = $from_t - param('int_f-1') || $int_t;
} elsif (param('f+1')) {
	$from_t = $from_t + param('int_f+1') || $int_t;
} elsif (param('t-1')) {
	$to_t = $from_t - param('int_t-1') || $int_t;
} elsif (param('t+1')) {
	$to_t = $from_t + $int_t;
}

# time range
$int_t = m_round($int_t);

# round to nearest minute
sub m_round {
	my $t = shift @_;
	return ($t - ($t % 60));
}

$to_t = m_round($to_t);
$from_t = m_round($from_t);

my $to=strftime("%Y-%m-%d %H:%M",localtime ($to_t));
my $from=strftime("%Y-%m-%d %H:%M",localtime ($from_t));


# keep count of each status
my %count;	

if (path_info()) {
	print "Content-type: image/png\nCache-Control: max-age=86400, must-revalidate\nExpires: ",scalar localtime(time()+24*60*60),"\nLast-Modified: ",scalar localtime(0),"\n";
	# create picture using GD
	use GD;
	my $im = new GD::Image(1,$height);
	my $back = $im->colorAllocate(255,255,255);
	$im->transparent($back);
	my $col = path_info(); $col =~ s,/,,g;
	my ($r,$g,$b) = split(/,/,$col);
	$col = $im->colorAllocate($r,$g,$b);
	$im->fill(0,0,$col);
	print "Content-Length: ",length($im->png),"\n\n";
	binmode STDOUT;
	print $im->png;
	exit;
}

# open DBI connection
my $dbh = DBI->connect("DBI:Pg:dbname=gantt","","") || die $DBI::errstr;

print "Content-type: text/html
Cache-Control: max-age=60, must-revalidate

<html>
<head>
<title>OmniBack Gantt: $from - $to</title>
<meta HTTP-EQUIV=\"Refresh\" CONTENT=600>";
if ($use_js) {
print '
<script type="text/javascript" language="javascript" src="1k.js"></script>
<script type="text/javascript" language="javascript" src="tooltip.js"></script>
<script language="javascript" type="text/javascript">
    onload=function(){
        T.init()
        T.follows = false // false by default
        T.delay = .3     // any nonnegative value (0.7 by default)
    }
</script>
';
}
print "</head><body>
	<form action=".$q->url(-relative=>1)." method=get>
	<input type=hidden name=from_t value=$from_t>
	<input type=hidden name=to_t value=$to_t>
";

# all vars ending in *_t have utime in them.
#
my $len_t = $to_t - $from_t;

die "interval must be positive and bigger than 1 sec !" if (abs($len_t) < 1);


sub mknav {
	my $f = shift @_;	# from_t
	my $t = shift @_;	# to_t
	my $d = shift @_;	# delta
	my $ch = shift @_;	# char

	return "
	<select name=int_${d}>
	<option value=".(12*60*60).">12h</option>
	<option value=".(24*60*60).">day</option>
	<option value=".(7*24*60*60).">week</option>
	</select>
	<input type=submit name=$d value=\"$ch\">
	";
	#return "<a href=\"".$q->url(-relative=>1)."?from_t=${f}&to_t=${t}\">$ch</a>";
}

print "<table cellspacing=0 cellpadding=0>";
print "<tr bgcolor=#e0e0e0><td>Specification</td><td align=left>";
print mknav($from_t,$to_t,'f-1','&lt;&lt;'),$from;
print mknav($from_t,$to_t,'f+1','&gt;&gt;') if ($from_t+$int_t < $to_t);
print "</td><td align=right>";
print mknav($from_t,$to_t,'t-1','&lt;&lt;') if ($to_t+$int_t > $from_t);
print $to,mknav($from_t,$to_t,'t+1','&gt;&gt;'),"</td></tr>\n";

# draw hour grid
sub hour_grid {
	print "<tr><td align=right><small>hour grid</small></td><td colspan=2>";
	my @c = ("255,255,128","255,192,128");
	my $hr=strftime("%H",localtime ($from_t));
	sub hour_bar {
		my ($t,$c,$hr) = @_;
		my $clock_hr = $hr % 24;
		my $alt = sprintf("%02d:00",$clock_hr);
		if ($clock_hr == 0) {
			my $pix_hr = int(1 / $width);
			print color_bar($pix_hr,"0,0,0",strftime("%Y-%m-%d", localtime($from_t + $hr*3600)));
			print color_bar($t-$pix_hr,$c,$alt);
		} else {
			print color_bar($t,$c,$alt);
		}
	}
	hour_bar(3600 - $from_t % 3600,$c[0],$hr++);
	for (my $i=1; $i<int(($to_t-$from_t)/3600); $i++) {
		hour_bar(3600,$c[1],$hr++);
		push @c, shift @c;
	}
	hour_bar($from_t % 3600,$c[1],$hr);
	print "</td></tr>\n";
}

hour_grid();

my $fix_d = 0;	# used to fix graph len (in pixels)
my $fix_s = 0;	# used to collect round errors of size

sub bar {
	my $l = shift @_;
	my $status = shift @_;
	my $alt = shift @_;

	my $col;

	if ($status) {
		if ($cols{$status}) {
			$col .= $cols{$status};
		} else {
			$col .= "0,0,0";	# unknown status, black
		}
		$count{$status}++;
	} else {
#		$col .= '240,240,240';
		$col .= '220,220,220';
	}
	return color_bar($l,$col,$alt,$min_l);
}

sub color_bar {
	my $l = shift @_;			# lenght of event utime
	my $col = shift @_ || '240,240,240';	# default color (filler)
	my $alt = shift @_ || undef;
	my $min_l = shift @_ || 1;
	my $h = shift @_ || $height;

	my $size = $l / ($len_t / $width);
	$fix_s += $size - int($size);
	$size=int($size);
	# add rounding error to size
	if ($fix_s > 1) {
		my $i = $fix_s ; $i = int($i);
		$fix_s -= $i;
		$size += $i;
	}

	if ($alt && $size < $min_l) {
		$fix_d += ($min_l - $size);
		print STDERR "fix_d: $fix_d\n" if ($debug);
		$size = $min_l;
	}
	if ($fix_d && $size > $fix_d+$min_l) {
		$size -= $fix_d;
		$fix_d = 0;
		print STDERR "fix_d: $fix_d\n" if ($debug);
	}

	print STDERR "bar[$col] len:$l s scale:",($len_t/$width)," size:$size px<br> alt:$alt\n" if ($debug);

	my $html;

	my ($html_end, $img_end) = ("","");
	if ($repgen_url && $alt =~ m#(\d\d\d\d)/(\d\d)/(\d\d)-(\d+)#) {
		$html .= "<a href=\"$repgen_url/$1-$2-$3-$4.html\">";
		$html_end .= "</a>";
		$img_end .= " border=0";
	}

	$html .= "<img src=\"".$q->url(-relative=>1)."/$col\" width=\"$size\" height=\"$h\"";

	if ($use_js && $alt) {
		$html .= " onmouseover=\"T('$alt')\" onmouseout=\"T()\"";
	} elsif ($alt) {
		$html .= " alt=\"$alt\"";
	}
	$html .= "$img_end>$html_end";

	return($html);
}

my $sql = "select start,finish,specification,status,user_group_host,
		type,sessionid,device,host
	from gantt
	where (start < '$from' and finish > '$from') or
	(start > '$from' and start < '$to')
	order by device,specification,start
	";

my $sth = $dbh->prepare($sql) || die "sql: $sql ".$dbh->errstr;
print STDERR "sql: $sql\n" if ($debug);

my %spec;	# specification hash

my $curr_spec = "";
my $curr_t = $from_t;

$sth->execute() || die "sql: $sql ".$dbh->errstr;

my $dev;	# current device
my @dev_t;	# all minutes in one hour [1440]
my @dev_sum_t;

sub sum_bar {
	my $label = shift @_;
	my @dev_t = @_;
	# display device summary
	my $max_use = 0;
	for (my $i=0; $i < $len_t; $i++) {
		$max_use = $dev_t[$i] if (defined $dev_t[$i] && $dev_t[$i] > $max_use);
	}
	print "<tr><td align=right><small>$label [$max_use]</small></td><td colspan=2>";
	my $last_var = 0;
	my $len = 0;
	for (my $i=1; $i<= ($to_t-$from_t); $i++) {
		my $v = $dev_t[$i] || 0;
		if ($v == $last_var) {
			$len++;
		} else {
			my $h = $last_var / $max_use * $height;
			my $c = 255 - int (255 * $last_var / $max_use);
			print color_bar($len,"$c,$c,$c","$last_var concurrent jobs",1,$h+1);
			$len = 0;
			$last_var = $v;
		}
	}
	# fix division by zero
	$max_use++ if ($max_use == 0);
	my $h = $last_var / $max_use * $height;
	my $c = 255 - int (255 * $last_var / $max_use);
	print color_bar($len,"$c,$c,$c","$last_var concurrent jobs",1,$h+1) if ($len);
	print "</td></tr>";
}

my @line_t;	# all seconds for this specification
my @line_arr = ("");	# all segments on line which are not fillers

sub html_spec {
	print "<tr><td>$curr_spec</td><td colspan=2>";

	my $last_var = 0;
	my $len = 0;
	my $i;
	for (my $t=0; $t<=$len_t; $t++) {
		$i = $line_t[$t] || 0;
		if ($i == $last_var) {
			$len++;
		} else {
			if ($last_var == 0) {
				print bar($len);
			} else {
				print bar($len,split(/\t/,$line_arr[$last_var],2));
			}
			$len = 0;
			$last_var = $i;
		}
	}
	print bar($len,split(/\t/,$line_arr[$i],2));

	print "</td></tr>\n";
}

while(my $row = $sth->fetchrow_hashref) {
	if ($row->{specification} ne $curr_spec) {

		if ($curr_t < $to_t && $curr_spec) {
			my $t = $to_t - $curr_t;
			print STDERR "[end filler $curr_t:$t]" if ($debug);
#			print bar($t);
		}

#		print "</td></tr>\n" if ($curr_t != $from_t);


		# init vars for next line
		($fix_s,$fix_d) = (0,0); # init fix vars for bar

		if ($curr_spec ne "") {
			$curr_spec =~ s/ +/&nbsp;/g;
			html_spec;
		}

		$curr_t = $from_t;	# init timeline
		$curr_spec = $row->{specification};

		@line_t = ();
		@line_arr = ( "" );

		if ($dev && @dev_t && $dev ne $row->{device}) {
			sum_bar("summary for $dev",@dev_t);
			$dev = $row->{device};
			@dev_t = ();
		} elsif (! $dev) {
			$dev = $row->{device};
		}
	}

	my $start_t = str2time($row->{start});
	my $fin_t = str2time($row->{finish});

	# Can I squeeze here 1 pixel of time (many seconds) ?
	if ($start_t > $curr_t + ($len_t / $width)) {
		my $t = $start_t - $curr_t;
		print STDERR "[middle filler $curr_t:$t]" if ($debug);
#		print bar($t);
		$curr_t = $start_t;
	} else {
		# prepend too few seconds to next event
		$start_t = $curr_t;
	}

	my $len = $fin_t - $start_t;
#	$len = $len_t if ($len > $len_t);
	my $less = '';
	my $more = '';

	if ($start_t < $from_t) {
#		$len += ($from_t - $start_t);
		$len = ($fin_t - $curr_t);
		$less = "<<";
	}

	if ($fin_t > $to_t) {
#		$len -= ($fin_t - $to_t);
		$len = ($to_t - $curr_t);
		$more = ">>";	# event now shown whole
	}

	print STDERR "[$less",$row->{status}," $curr_t:$len$more]" if ($debug);

	my $alt = $row->{start}." - ".$row->{finish}."<br>";
	$alt =~ s/:\d\d\.\d+//g;
	$alt =~ s/\s+/&nbsp;/g;
	$alt .= $row->{specification}."<br>".
		$row->{type}." <b>".$row->{status}."</b><br>".
		$row->{user_group_host}." <i>".$row->{sessionid}."</i><br>".
		$row->{device}."&nbsp;on&nbsp;".$row->{host};
#	print bar($len,$row->{status},$alt);

	push @line_arr,$row->{status}."\t".$alt;
	# store use of devices for each minute
	for (my $s=0; $s<$len; $s++) {
		$line_t[$curr_t-$from_t+$s] = $#line_arr;
		$dev_t[$curr_t-$from_t+$s]++;
		$dev_sum_t[$curr_t-$from_t+$s]++;
	}

	$curr_t += $len;

#	print Dumper($row);
	
}

if ($curr_t == $from_t) {	# no entries in database!
#	print "<tr><td></td><td colspan=2>";
}

if ($curr_t < $to_t ) {
	my $t = $to_t - $curr_t;
	print STDERR "[last_line filler $curr_t:$t]" if ($debug);
}

undef $sth;
$dbh->disconnect;

html_spec();
sum_bar("summary for $dev",@dev_t);
hour_grid();
sum_bar("summary for all devices",@dev_sum_t);
print "</table>";


# label and usage

print "<hr>\nColors for statuses and usage (#):\n";
print "<table border=0><tr bgcolor=#e0e0e0><th>status</th><th>#</th><th>color</th></tr>\n";
my $max = 0;
foreach my $status (keys %count) {
	$max = $count{$status} if ($count{$status} > $max);
}
$len_t = $max ; # disable bar scaling

foreach my $status (keys %count) {
#foreach my $status (keys %cols) {
	print "<tr bgcolor=#e0e0e0><td><small>$status</small></td><td><small>",$count{$status}*1,"</small></td><td>",bar($count{$status},$status),"</td></tr>\n" if ($status ne "");
	# *1 in line above is a cludge to display correct number on
	# occurences on graph and without one on legend! If you remove * op
	# it will first evaluate bar sub (thus increasing number by one) and
	# then display number (wrongly).
}
print "</table></form>\n<p>Reload <a href=\"",$q->url(-relative=>1),"\">current</a> or see <a href=\"db2gantt_help.html\">help</a>.</p></body></html>";
