#!/usr/bin/perl -w
use strict;
use Curses::UI;
use RRDs;
use IPC::Open2;
use Symbol qw(gensym);

use SM::Conf;

##################################################
# check for cmdline arguments
##################################################

if ( defined($ARGV[0]) && $ARGV[0] eq "-t") {
	exec("xterm -geometry 82x29 -e './$0'");
}

##################################################
# the root object
##################################################

# create the root object
my $cui = new Curses::UI (
	-clear_on_exit => 1,
);

##################################################
# define some subs
##################################################

sub fetch;
sub plot;
sub generate;
sub wrap_generate;
sub live_toggle;
sub get_live;
sub dump_graph;
sub check_graph;
sub serious_error;
sub check_window;
sub check_files;
sub exit_question;

##################################################
# some definitions / configurations
##################################################

my $cur_scr;

my $graph;

#my $DB	= "code/scrutinizer.rrd";

my $DB = $SM::Conf::RRD_DB;

#my $GNUPLOT = "/usr/bin/gnuplot";

my $GNUPLOT = $SM::Conf::CMD{gnuplot};

my $AVG = "AVERAGE";
my $MAX = "MAX";

my %plot_conf;

my $min_width = 80;
my $min_height = 29;

my $do_live = 0;

my $time_gap = 2;

my $time_real = 0;

my $default_foot_text = 
	" press '^Q' to quit, 'd' to dump graph to file or 'l' for live mode ";

##################################################
# create the header
##################################################

# define head window
my $head = $cui->add(
	undef , 'Window',
	-height	=> 1,
);

# define header
$head->add(
	undef , 'Label',
	-width	=> -1,
	-textalignment => 'middle',
	-text	=> 'scrutinizer\'s round robin database viewer',
);

# define foot window 
my $foot = $cui->add(
    undef , 'Window',
    -height => 1,
    -y  => -1,
);

# define footer
my $footer = $foot->add(
    undef, 'Label',
    -text   => $default_foot_text.get_live(),
);

##################################################
# create the screen
##################################################

# create the screen
my $win = $cui->add(
	undef, 'Window',
	-border	=> 1,
	-titlereverse => 0,
	-y	=> 1,
	-height => $cui->height - 2,
);

##################################################
# draw elements 
##################################################

# description for graph selector
$win->add(
		undef, 'Label',
		-text => 'graph:',
		-x => 1,
	);

# graph selector
my $sel_graph = $win->add(
        undef, 'Popupmenu',
        -values    => [4, 2, 3, 5, 6],
        -labels    => { 4 => 'Alerts',
                        2 => 'Requests',
                        3 => 'Throughput',
                        5 => 'Elements',
						6 => 'Processes'
                       },
		-selected => 0,
		-x => 8,
    );

# description for type selector
$win->add(
		undef, 'Label',
		-text => 'type:',
		-x => 22,
	);

# type selector
my $sel_max_avg = $win->add(
        undef, 'Popupmenu',
        -values    => [1, 2],
        -labels    => { 1 => 'Maximum', 
                        2 => 'Average', 
					   },
		-selected => 0,
		-x => 28,
    );

# label for duration textbox
$win->add(
		undef, 'Label',
		-text => 'duration:',
		-x => 39,
	);

# duration textbox
my $txt_duration = $win->add(
		undef, 'TextEntry',
		-width => 7,
		-maxlength => 5,
		-text => '10',
		-x => 49,
	);

# si selector
my $sel_si = $win->add(
        undef, 'Popupmenu',
        -values    => [1, 2, 3],
        -labels    => { 1 => 'minutes', 
                        2 => 'hours', 
						3 => 'days',
					   },
		-selected => 0,
		-x => 56,
    );

# the generate-button
my $but_generate = $win->add(
        undef, 'Buttonbox',
        -buttons   => [{
                -label => '< plot >',
                -value => 1,
                -shortcut => 'p',
                -onpress => (\&generate),
        }],
        -x => 68,
	);

# the display label
my $pic = $win->add(
		undef, 'Label',
		-y => 1,
		-width => $cui->width,	# reserve some space for th graph
		-height => $cui->height-2,
	);

# create the live timer
$cui->set_timer(
		'live_timer',
		\&wrap_generate,
		$time_gap,
	);
$cui->disable_timer('live_timer');

#################################################
# implement some subs
##################################################

# fetch data from rrd database
sub fetch
{
	# get the arguments
	my($start, $end, $type) = @_;

	# the gnuplot data 
	my $plot_data;
	my $plot_time;

	# fetch the data into $data (2D-array)
	my($dbstart, $step, $names, $data) =
		RRDs::fetch($DB, "--start=$start", "--end=$end", $type);

	# save the data into $plot_data
	foreach my $line (@$data) {
		# calc the scale in seconds
		$plot_time = ($start-$end);
		# reduce the scale to the selected si
		if ($plot_conf{'si'} == 1) {
			$plot_time *= 60;
		} elsif ($plot_conf{'si'} == 2) {
			$plot_time *= 1;
		} elsif ($plot_conf{'si'} == 3) {
			$plot_time /= 24;
		}
		$plot_data .= $plot_time;
		$start += $step;
		foreach my $val (@$line) {
			$val = "N/A" unless defined $val;
			$plot_data .= " ".$val;
		}
			$plot_data .= "\n";
	}

	# return the fetched data
	return $plot_data;
}

# plot the data
sub plot
{
	# get the arguments
	my($data, $which, $tics) = @_;

	# calc the stepwidth
	my $tic_step = $tics / 10;

	# define the output variable
    $graph = "";

	# define filehandles (empty globs)
	my $read_fh  = gensym;
	my $write_fh = gensym;

	# open gnuplot for read & write
	open2($read_fh, $write_fh, $GNUPLOT) || die("Couldn't open gnuplot!");

	# define combined filehandle
	my $fh = select $write_fh;
    local $| = 1;

	# plot the selected graph
	# write gnuplot cmds via STDIN
	# to the gnuplot application
	print << "    EndGnuplot";

		set terminal dumb

		set nokey

		set xrange [*:0]

		set yrange [0:*]

		set xtics -$tics,$tic_step,0

		plot '-' using (\$1/3600):$which w p 

		$data

    EndGnuplot

	# close the filehandle
	close $write_fh;

	# change the filehandle
    select $fh;
    local $/ = undef;

	# read the plotted graph
	# read it directly from gnuplot
	# via STDOUT
	$graph = <$read_fh>;

	# remove leading & trailing newlines
	$graph =~ s/(^\n|^\f)//mg;

	# replace char-point ('A') with ('#') 
	$graph =~s/A/\#/g;

	# return the plotted graph as ascii art
	return $graph;
}

# bring gui, fetch, plot together
sub generate
{

	# reset real time counter if not in live mode
	$time_real = 0 if (!$do_live);

	###########################
	# prepare the fetch command

	# check the duration textfield (only numbers!)
	my $t_text = $txt_duration->text();
	if ($t_text =~ /[^0-9]/ || length($t_text) == 0 || $t_text == 0) {
		# bad!!

		# disable timer if live mode is on
		$cui->disable_timer('live_timer') if ($do_live);

		# print error message
		$cui->error(
                -message => 
		"Please just enter a hole number.\nThe value '$t_text' is not valid.",
                -title   => "Bad Duration",
                -buttons => ['ok'],
        );

		# enable timer again if live mode is on
		$cui->enable_timer('live_timer') if ($do_live);

		# stop generating graph
		return;

	} else {
		# good -> save the configuration

		# grab the values from the gui
		$plot_conf{'duration'}	= $t_text;
		$plot_conf{'duration_text'} = $t_text;	# a copy for the dump
		$plot_conf{'graph'}	= $sel_graph->get();
		$plot_conf{'type'}	= $sel_max_avg->get();
		$plot_conf{'si'}	= $sel_si->get();

		# only get last if not in live mode
		if (!$do_live) {
			# get the last timestamp from rrd
			$plot_conf{'last'} = RRDs::last $DB || 
					die("Couldn't get timestamp from RRD");
		} else {
			# take the calculated running time
			$plot_conf{'last'} = $time_real;
		}
	}

	# calculate the other timestamp from duration
	if ($plot_conf{'si'} == 1) {
		$plot_conf{'duration'} *= 60;	 # minute
	} elsif ($plot_conf{'si'} == 2) {
		$plot_conf{'duration'} *= 3600;	 # hour
	} elsif ($plot_conf{'si'} == 3) {
		$plot_conf{'duration'} *= 86400; # day
	} else {
		die("Unknown SI dimension for duration.\n");
	}

	# calculate the beginning timestamp
	$plot_conf{'begin'} = $plot_conf{'last'} - $plot_conf{'duration'};

	# detect the type
	$plot_conf{'type'} = ($plot_conf{'type'} == '1' ? $MAX : $AVG);

	# finished with preparation
	###########################

	# fetch the data into plot_data
	my $plot_data = fetch($plot_conf{'begin'}, $plot_conf{'last'}, $plot_conf{'type'});

		
	# call the plot function with this data
	my $graph = plot($plot_data, $plot_conf{'graph'}, $plot_conf{'duration_text'});	

	# check if there's a graph to be plotted	
	if (check_graph()) {
		# turn off live mode
		live_toggle();
		# print an error
		serious_error("There is no data to be plotted.", "No Data");
	} 

	# draw the graph
	$pic->text($graph);
}

sub wrap_generate
{
	# calcluate my own real time
	if ($time_real == 0) {
		# initialize if first time
		$time_real = RRDs::last $DB || die("Couldn't get timestamp from RRD");
	}

	# generate every tick a new graph
	generate();

	# shift the real time for 2 seconds
	$time_real += $time_gap;
}

sub live_toggle
{
	# check if there's a graph to (start) animate 
	if (check_graph() && !$do_live) {
		# print an error
		serious_error("There is no graph to be animated.", "Live Mode Error");
		# exit this sub
		return;
	}

	# toggle the do_live flag
	$do_live = ($do_live ? 0 : 1);

	# toggle the timer
	if ($do_live) {
		# enable
		$cui->enable_timer('live_timer');
	} else {
		# disable
		$cui->disable_timer('live_timer');	
	}	
	
	# set the footer text (switch on/off)
	$footer->text($default_foot_text.get_live());
	$cui->focus();
}

sub get_live
{
	return ($do_live ? '(on)' : '(off)');
}

# dump the currently displayed graph to a file
# use timestamp & graph configuration of the
# current graph!
sub dump_graph
{
	# check if there's a graph to dump
	if (check_graph()) {
		# print an error
		serious_error("There is no graph to be dumped.", "Dump Error");
		# exit this sub
		return;
	}

	# get the unix timestamp
	my @date = localtime($plot_conf{'last'});

	# make a time stamp for the filename
	my $pretty_date = 	(1900+$date[5]).	# year
						"_".(1+$date[4]).	# month
						"_".$date[3].		# day
						"-".sprintf("%02d:%02d", @date[2,1]);	# time

	# generate a text for the selected graph
	if ($plot_conf{'graph'}		 == 4) {
		$plot_conf{'graph_name'} = "alerts";
	} elsif ($plot_conf{'graph'} == 2) {
		$plot_conf{'graph_name'} = "requests";
	} elsif ($plot_conf{'graph'} == 3) {
		$plot_conf{'graph_name'} = "throughput";
	} elsif ($plot_conf{'graph'} == 5) {
		$plot_conf{'graph_name'} = "elements";
	}

	# generate a text for the selected si
	if ($plot_conf{'si'} 	  == 1) {
		$plot_conf{'si_name'} = "minutes";
	} elsif ($plot_conf{'si'} == 2) {
		$plot_conf{'si_name'} = "hours";
	} elsif ($plot_conf{'si'} == 3) {
		$plot_conf{'si_name'} = "days";
	}

	# put the filename together
	my $filename = $pretty_date.
					"-".$plot_conf{'graph_name'}.
					"-".lc($plot_conf{'type'}).
					"-last_".$plot_conf{'duration_text'}.
					"_".$plot_conf{'si_name'}.
					".dump"; 

	# ask the user if he really wants
	my $return = $cui->dialog(
							-message => 
				"Do you really want to save the graph into\n'$filename'?",
							-title => "Saving graph",
							-buttons => ['yes', 'no'],
							);

	# if yes, save the graph to the file
	if ($return) {

		open(DUMPFILE, ">$filename") || die "Couldn't open $filename";

		print DUMPFILE $graph;

		close(DUMPFILE);
	}
}

sub check_graph
{
	# check if a graph is here
	if (!defined $graph || length($graph) == 0) {
		return 1;
	}
	return 0;
}

sub serious_error
{
	$cui->error(
			-message => shift, 
			-title => shift,
			-buttons => ['ok'],
			);
}

# the window-size function
sub check_window
{
	if ($cui->width < $min_width || $cui->height < $min_height) {
		$cui->error(
			-message => "Please use at least a ".
						$min_width."x".$min_height.
						" sized Terminal.\n(now: ".
						$cui->width()."x".$cui->height().") ".
						"Otherwise the graphs etc. are cutted.",
			-title	 => "Terminal to small",
			-buttons => ['ok'],
		);
	}
	return;
}

# check gnuplot and DB
sub check_files
{
	if(!-r $DB) {
		serious_error("RRD not found under '$DB'", "Wrong RRD");	
		exit(1);
	}

	if(!-x $GNUPLOT) {
		serious_error("Gnuplot not found under '$GNUPLOT'", "Wrong Gnuplot");
		exit(1);
	}
}

# the exit function
sub exit_question
{
		# ask user for termination
        my $return = $cui->dialog(
                -message => "Exit Scrutinizer Command Console?",
                -title   => "Terminate Application",
                -buttons => ['yes', 'no'],
        );

	if ($return) {
		# exit the program
		exit(0);
	}
}

##################################################
# bindings and focus
##################################################

# bind ctrl-q to exit function
$cui->set_binding(\&exit_question, "\cQ");

# bind p to plot
$cui->set_binding(\&generate, "p");

# bind l to live
$cui->set_binding(\&live_toggle, "l");

# bind d to dump
$cui->set_binding(\&dump_graph, "d");

# set focus to graph selector
$sel_graph->focus();

##################################################
# start up the hole thing
##################################################

# check the file locations
check_files();

# check the window size
check_window();

# keep things rolling
$cui->mainloop();
