# helper utilities class
# author : Jayakrishnan Mundarath. (jayakris@cs.wisc.edu)

package VisurdHelper;

$VERSION = 1.0;

# include my db2 package
use DB2Wrap;

# include the time and the graphics packages
use Time::Local;
use Chart::Points;
use Chart::LinesPoints;
use Chart::Lines;
use Chart::Bars;
use Chart::Split;

# Standard CGI Packages
use CGI;
use CGI qw(:standard :html3);
use CGI::Carp qw(fatalsToBrowser);

#################################################################################
#####################     VisurdHelper Package Methods     ######################
#################################################################################

# method new - initializes the VisurdHelper object
sub new 
{
    my ($class,@params) = @_;
    
    # create an anonymous hash to hold our instance variables
    # this becomes our object once it is "blessed"
    my($this) = {};
    
    $this->{'db'} = new DB2Wrap();
    
    # bless us as belonging to the DB2Wrap class
    bless ($this, $class);
    return $this;
}

# method prepareDB 
# input : DSN name
# output : none
# creates a db connection
sub prepareDB
{
    my ($this) = shift;
    my ($dbname) = @_;
    
    my ($mydb) = $this->{'db'};
    my ($dbinst) = $this->{DB2INST};
    
    $mydb->setDB2Env($dbinst);
    $mydb->getDB2Handle("DBI:DB2:".$dbname,"visurd","v!surd");
}

################ DATA EXTRACTING and PROCESSING METHODS ##################

# method generateResults
# generic method which extracts data from the db and prepares for rendition
sub generateResults
{
    my($this) = shift;
    my($fields) = @_;
    
    my ($mydb) = $this->{'db'};
    $this->prepareDB($fields->{dbname}); # initializes the dbwrap object

    my (%cfgparams);
    $cfgparams = $this->getConfig(\%cfgparams,$fields->{cfg_file});

    # retrieve the time, offset and query parameters
    my ($start_time) = timegm(0,0,$fields->{fromhour},$fields->{fromday},$fields->{frommonth},$fields->{fromyear});
    my ($end_time) = timegm(59,59,$fields->{tohour},$fields->{today},$fields->{tomonth},$fields->{toyear});
    my ($offset) = $fields->{resolution};
    my ($cmdstr) = $cfgparams->{query};

    $fields->{start_time_sec} = gmtime($start_time);
    $fields->{end_time_sec} = gmtime($end_time);
    $fields->{start_time} = $start_time;
    $fields->{end_time} = $end_time;

    # bind the query with the generic filter parameters
    $cmdstr =~ s/{srcid}/$fields->{srcid}/g;
    $cmdstr =~ s/{destid}/$fields->{destid}/g;
    $cmdstr =~ s/{start_time}/$start_time/g;
    $cmdstr =~ s/{end_time}/$end_time/g;
    $cmdstr =~ s/{offset}/$offset/g;
    $cmdstr =~ s/{schema_name}/$fields->{schema}/g;
    $cmdstr =~ s/{start_hour}/$fields->{fromhour}/g;
    foreach (@{$cfgparams->{ascii_colnames}})
    {
	$cmdstr =~ s/<colname>/$_/;
    }

    # figure out whether the chosen rendering mode is supported - else report error
    my ($match) = 0; 
    if ( ref($cfgparams->{render}) )
    {
	foreach (@{$cfgparams->{render}})
	{
	    if($_ == $fields->{render})
	    {
		$match = 1;
		last;
	    }
	}
    }
    else
    {
	$match = 1;
    }

    if ( $match == 0 )
    {
	$this->DisplayError(3);
	return 0;
    }

    # figure out whether the chosen resolution is supported - else report error
    $match = 0;
    if( ref($cfgparams->{res_nums}) )
    {
	foreach (@{$cfgparams->{res_nums}})
	{
	    if($_ == $fields->{resolution})
	    {
		$match = 1;
		last;
	    }
	}
    }
    else
    {
	$match = 1;
    }

    if ( $match == 0 )
    {
	$this->DisplayError(4);
	return 0;
    }

    if( $fields->{render} == 3 ) # sql command mode
    {  
	$this->DisplaySQL($cmdstr,$start_time,$end_time);
	return 1;
    }

    my ($sth) = $mydb->execDB2Cmd($cmdstr); # execute the query
    if ( $sth == -1 )  
    {   
	my ($errstr) = "Execute of the query failed. \nReason : \n".$mydb->{'ERROR'};
	
	$this->DisplayError(1,$errstr); # means the command did not execute properly
	return 0; 
    }

    if( $fields->{render} == 2) # ascii mode
    {
	$fields->{SQLCMD} = $cmdstr;
	$this->DisplayASCII( $sth,\@{$cfgparams->{ascii_cols}},\@{$cfgparams->{ascii_colnames}},$fields);
	return 1;
    }

    # if we have reached here it means we are in image mode
    return $this->renderImage($cfgparams,$sth,$fields);
}

# method runManualQuery - to run query typed in by users
sub runManualQuery
{
    my ($this) = shift;
    my ($qryarray,$dbname) = @_;

    my ($mydb) = $this->{'db'};
    $this->prepareDB($dbname); # initializes the dbwrap object
    my ($cmdstr) = join(" ",@{$qryarray});

    my ($sth) = $mydb->execDB2Cmd($cmdstr); # execute the query
    if ( $sth == -1 )  
    {   
	my ($errstr) = "Execute of the query failed. \nReason : \n".$mydb->{'ERROR'};
	
	$this->DisplayError(1,$errstr); # means the command did not execute properly
	return 0; 
    }
    $this->DisplayRawData($cmdstr,$sth);
    return 1;
}

########### Extracting and Processing methods end ###########

########### DISPLAY METHODS : outputs HTML code #############

# 1. DisplayFrontPage : displays the front page of the tool
sub DisplayFrontPage
{
    my ($this) = shift;
    my ($cgi,$db_name,$schema_name,$script_name) = @_;
   
    # read the general filter options from file for display purposes	
    open(srcfile,$this->{DISPFILE});
    my (@categs,%categ_names,%categ_labels,%categ_defaults,$srcfile);
    my ($currCateg) = "NONE";
    while(<srcfile>)
    {
	@data = split;
	
	next if (m/\A\#/);

	if($data[0] ne $currCateg)
	{
	    $currCateg = $data[0];
	    push(@categs,$currCateg);
	    $categ_defaults{$currCateg} = $data[1];
	}

	$categ_defaults{$currCateg} = $data[1] if ( defined($data[3]) and ($data[3] eq "*") ); # default name

	push(@{$categ_names{$currCateg}},$data[1]); # names
	${$categ_labels{$currCateg}}{$data[1]} = $data[2]; # labels
    }
    close(srcfile);
    
    
    # some additional options
    my (@res_names,%res_labels,@ren_names,%ren_labels,@yscale_names,%yscale_labels,@gr_names,@fmt_names);

    @res_names = ("60","3600","86400");
    $res_labels{$res_names[0]} = "Minute";
    $res_labels{$res_names[1]} = "Hour";
    $res_labels{$res_names[2]} = "Day";
    
    @ren_names = ("1","2","3");
    $ren_labels{$ren_names[0]} = "Image";
    $ren_labels{$ren_names[1]} = "ASCII";
    $ren_labels{$ren_names[2]} = "SQL Query";

    @yscale_names = ("linear","log");
    $yscale_labels{$yscale_names[0]} = "Linear";
    $yscale_labels{$yscale_names[1]} = "Log";

    @gr_names = ( "inline", "file" );
    @fmt_names = ( "png", "jpeg", "gif" );

    # get node names and ids from the db
    my ($mydb) = $this->{'db'};
    $this->prepareDB($db_name);
    $cmdstr = "select nodeid,name from ".$schema_name."nodes order by name\;";
    my ($sth) = $mydb->execDB2Cmd($cmdstr);
    my ($count) = 0;
    my (@data);

    while((@data = $sth->fetchrow))
    {
	$np_names[$count] = $data[0];
	$np_labels{$np_names[$count++]} = $data[1];
    }
    $mydb->stopDB2;

    # these are the form submit options - read from plugins.cfg file
    my (@categ1, @categ2, @categgen,$plg);
    open (plg,$this->{PLUGINSFILE});
    while( <plg> )
    {
	@rows = split(",");

	# filter out comments and empty lines
	s/\A[ \t]//g;
	if( (m/\A\#/) or !defined($rows[1]) or !defined($rows[2]) )
	{
	    next;
	}

	$rows[1] =~ /"(.*)"/;
	$rows[1] = $1;

	if ($rows[2] == 1)
	{
	    push(@categ1,$rows[1]);
	}
	elsif ($rows[2] == 2)
	{
	    push(@categ2,$rows[1]);
	}
	else
	{
	    push(@categgen,$rows[1]);
	}
    }
    close(plg);
    # all data structures populated.

    #### the actual display code begins now. ####
    # html visualization code

    # print header
    print(  $cgi->header(-type => "text/html"),
	    $cgi->start_html(-title => "ViSurD 1.0 : Specifications Page",
			     -author => "jayakrishnan")
	    );
    print $cgi->h1("ViSurD","1.0");
    print "<b>A visualization tool for the <a href='description.html'>Measurements Database</a> ";
    print "of the <a href='surveyorinfo.html'>Surveyor Project</a></b><BR>\n";
    print "<i>(Click on the Parameter Descriptions for Help)</i><BR>\n";
        
    # print the formatted form	
    print $cgi->startform(-method => 'POST'), "\n";
    print "<HR>";
    
    # general time filter
    print "<h3>Time Filter Specifications</h3>\n";
    
    # from date
    print "<a href=$script_name?gethelp=1&helpkey=FromDate>";
    print "FROM</a>: <BR>\n";
    print "<i>Year</i> ", $cgi->popup_menu(-name=> "fromyear",
					   -values=> \@{$categ_names{'fromyear'}},
					   -default=> $categ_defaults{'fromyear'},
					   -labels=> \%{$categ_labels{'fromyear'}}), "&nbsp; &nbsp;";
    print "<i>Month</i> ", $cgi->popup_menu(-name=> "frommonth",
					    -values=> \@{$categ_names{'frommonth'}},
					    -default=> $categ_defaults{'frommonth'},
					    -labels=> \%{$categ_labels{'frommonth'}}), "&nbsp; &nbsp;";
    print "<i>Day</i> ", $cgi->popup_menu(-name=> "fromday",
					  -values=> \@{$categ_names{'fromday'}},
					  -default=> $categ_defaults{'fromday'},
					  -labels=> \%{$categ_labels{'fromday'}}), "&nbsp; &nbsp;";
    print "<i>Hour</i> ", $cgi->popup_menu(-name=> "fromhour",
					  -values=> \@{$categ_names{'fromhour'}},
					  -default=> $categ_defaults{'fromhour'},
					  -labels=> \%{$categ_labels{'fromhour'}}), "&nbsp; &nbsp;";
    print "<i>Minute</i> <b>00</b> &nbsp; &nbsp; <i>Second</i> <b>00</b> &nbsp; &nbsp; <b>GMT (+6 CST, +5 EST)</b>";
    print "<BR><BR>\n";

    # to date
    print "<a href=$script_name?gethelp=1&helpkey=ToDate>";
    print "TO</a>: <BR>\n";
    print "<i>Year</i> ", $cgi->popup_menu(-name=> "toyear",
					   -values=> \@{$categ_names{'toyear'}},
					   -default=> $categ_defaults{'toyear'},
					   -labels=> \%{$categ_labels{'toyear'}}), "&nbsp; &nbsp;";
    print "<i>Month</i> ", $cgi->popup_menu(-name=> "tomonth",
					    -values=> \@{$categ_names{'tomonth'}},
					    -default=> $categ_defaults{'tomonth'},
					    -labels=> \%{$categ_labels{'tomonth'}}), "&nbsp; &nbsp;";
    print "<i>Day</i> ", $cgi->popup_menu(-name=> "today",
					  -values=> \@{$categ_names{'today'}},
					  -default=> $categ_defaults{'today'},
					  -labels=> \%{$categ_labels{'today'}}), "&nbsp; &nbsp;";
    print "<i>Hour</i> ", $cgi->popup_menu(-name=> "tohour",
					  -values=> \@{$categ_names{'tohour'}},
					  -default=> $categ_defaults{'tohour'},
					  -labels=> \%{$categ_labels{'tohour'}}), "&nbsp; &nbsp;";
    print "<i>Minute</i> <b>59</b> &nbsp; &nbsp; <i>Second</i> <b>59</b> &nbsp; &nbsp; <b>GMT (+6 CST, +5 EST)</b><BR><BR>\n\n";

    print "<a href=$script_name?gethelp=1&helpkey=resolution>";
    print "<i>Data Time Resolution</i></a> : &nbsp; &nbsp;", $cgi->radio_group(-name=> 'resolution',
						   -values=> \@res_names,
						   -default=> $res_names[0],
						   -labels=> \%res_labels),"\n";

    # general rendition specifications
    print "<BR><BR>\n";
    print "<h3>Rendering Specifications</h3>";

    print "<a href=$script_name?gethelp=1&helpkey=render>";
    print "<i>Render Data as</i></a> : &nbsp; &nbsp;", $cgi->radio_group(-name=> 'render',
						   -values=> \@ren_names,
						   -default=> $ren_names[0],
						   -labels=> \%ren_labels), "<BR><BR>\n\n";

    # image specific options
    print "<a href=$script_name?gethelp=1&helpkey=ImageSize>";
    print "<i>Set Image Size (pixels)</i></a> : &nbsp; &nbsp;";
    print $cgi->textfield('xres',1000,5,4), " &nbsp; x &nbsp;";
    print $cgi->textfield('yres',600,5,4), "<BR><BR>\n";
   
    print "<a href=$script_name?gethelp=1&helpkey=ImageDelivery>";
    print "<i>Image Delivery</i></a> : &nbsp; &nbsp;", $cgi->radio_group(-name    => 'graphics',
						    -values  => \@gr_names,
						    -default => $gr_names[0]), "<BR><BR>\n\n";
    print "<a href=$script_name?gethelp=1&helpkey=ImageType>";
    print "<i>Image Type</i></a> : &nbsp; &nbsp;",	  $cgi->radio_group(-name    => 'format',
						    -values  => \@fmt_names,
						    -default => $fmt_names[0]), "<BR><BR>\n\n"; 
    print "<a href=$script_name?gethelp=1&helpkey=AxisScale>";
    print "<i>Set y-axis scale (for images) as</i></a> : &nbsp; &nbsp;", $cgi->radio_group(-name=> 'yscale',
						   -values=> \@yscale_names,
						   -default=> $yscale_names[0],
						   -labels=> \%yscale_labels), "<BR><BR>\n";
      
    print "<HR>\n";
    
    # characteristics across all paths - category 1

    my (@allcateg) = (@categ1, @categ2, @categgen);
    # print the help utility for all categories
    print "<a href=$script_name?gethelp=1&helpkey=ButtonHelp>";
    print "<i>Button Help</i></a> : &nbsp; &nbsp;", $cgi->popup_menu(-name=> 'ButtonHelp',
						   -values=> \@allcateg)," &nbsp;";
    print $cgi->submit(-name=> 'submit', -value=> 'View Help', -style=> 'width: 10em;')," &nbsp;";

    defined(@categ1) and print "<h3>View General Characteristics (Across all Source-Destination Pairs)</h3>";
    
    for (@categ1)
    {
	print $cgi->submit(-name=> 'submit', -value=> $_, -style=> 'width: 15em;')," &nbsp;";
    }
    


    # path specific characteristics - category 2
    defined(@categ2) and print "<h3>View Path Specific Characteristics</h3>";
    # print source-destination filter options
     print "<a href=$script_name?gethelp=1&helpkey=SrcDest>";
    print "<i>Select a Source-Dest Pair</a></i> &nbsp; &nbsp;";
    print "<i>Source</i> : ", $cgi->popup_menu(-name=> "srcid",
					-values=> \@np_names,
					-labels=> \%np_labels), "&nbsp; &nbsp;";
    print "<i>Destination</i> : ", $cgi->popup_menu(-name=> "destid",
					-values=> \@np_names,
					-labels=> \%np_labels), "<BR><BR>";
    for (@categ2)
    {
	print $cgi->submit(-name=> 'submit', -value=> $_, -style=> 'width: 15em;')," &nbsp;";
    } 
    print "<BR><BR>";

    # special category for special cases
    defined(@categgen) and print "<h3>Special</h3>";
    for (@categgen)
    {
	print $cgi->submit(-name=> 'submit', -value=> $_, -style=> 'width: 15em;')," &nbsp;";
    }
    print "<BR>";

    print $cgi->reset(-name=>'Reset Filter', -style=> 'width: 15em;'), "\n";
    print "<HR>\n";
    print $cgi->endform, "\n";
    
    # f manual query mode supported, then display manual query form
    if ( $this->{allow_manual_query} eq 'true' )
    {
	print "<h4>Run Manual Query to Surveyor Database</h4>\n";
	print $cgi->startform(-method => 'POST'), "\n";
	print "Type in your SQL Query : <BR>\n";
	print $cgi->textarea(-name => 'querystring',
			     -rows => 10,
			     -columns => 80), "<BR>\n";
	print $cgi->submit(-name=> 'submit', -value=> 'Execute Query', -style=> 'width: 15em;');
	print $cgi->reset(-name=>'Clear Query', -style=> 'width: 15em;') , "\n";
	print $cgi->endform, "\n<HR>";
    }
    # manual query ends

    print($cgi->end_html);
}

# 2. DisplayImage - displays the requested image
sub DisplayImage
{
    my ($this) = shift;
    my ($g,$image_title,$image_format,$image_rendition,$image_path,$cfgparams) = @_;
    
    if (defined($cfgparams->{img_mapfn}))
    {
	# plot image as an imagemap with a pointer onto a special plugin method
	$g->$image_format($image_path);
	my ($imdata) = $g->imagemap_dump();
	my ($dataref) = $g->get_data();
	print CGI::header(-expires=>'now');
	print start_html(-title=> "ViSurD ImageMap");
	print "<h3>$image_title</h3>\n";
	print "<h4><i>Click on Specific Points of the Image to View $cfgparams->{img_mapfn}</i></h4><BR>\n";
	print "<MAP NAME=\"rtmap\">\n";
	my ($x,$y,$x1,$y1);
	my ($method) = $cfgparams->{img_mapfn};
	my ($sz1) = scalar(@{$cfgparams->{img_data}});
	my ($sz2) = $cfgparams->{img_totcount};
	my ($scriptname) = $cfgparams->{script_name};
	my ($i,$j);
	for ($i = 0; $i < $sz1-1; $i++)
	{
	    for ($j = 0; $j < $sz2; $j++)
	    {
		if( $cfgparams->{img_point} eq "bar-pt" or $cfgparams->{img_point} eq "bar-st" )
		{
		    ($x,$y,$x1,$y1) = @{$imdata->[$i+1][$j]};
		    my ($tempdatx, $tempdaty) = ($dataref->[0][$j],$dataref->[$i+1][$j]) ;
		    
		    print "<AREA HREF='$scriptname?method=$method&vals=$tempdatx,$tempdaty&img=1' TARGET='_blank' ";
		    print "SHAPE=RECT COORDS='$x,$y,$x1,$y1'>\n";
		}
		else
		{
		    ($x,$y) = @{$imdata->[$i+1][$j]};
		    my ($tempdatx, $tempdaty) = ($dataref->[0][$j],$dataref->[$i+1][$j]) ;
		    print "<AREA HREF='$scriptname?method=$method&vals=$tempdatx,$tempdaty&img=1' TARGET='_blank' ";
		    print "SHAPE=CIRCLE COORDS='$x,$y,5'>\n";
		}
	    }
	}
	print "</MAP>\n";
	print img {src=>$image_path, align=>'LEFT', usemap=>'#rtmap'};
	print end_html;
    }
    elsif ($image_rendition eq 'file') # use temp file on the disk
    {
	$g->$image_format($image_path); # ask the chart object to create the image on the disk
            
	print CGI::header(-expires=>'now'); 
	print start_html(-title=> "ViSurD Image");
	print "<h3>$image_title</h3>\n";
	print img {src=>$image_path, align=>'LEFT'};     
	print end_html;
    }
    else 
    {
	my ($method) = "cgi_".$image_format;
	$g->$method(); # ask the chart object to send the image directly back to the client
    }
}

# 3. DisplayASCII - displays data in ascii format as returned by the db
sub DisplayASCII
{
    my ($this) = shift;
    my ($sth,$cols,$labels,$fields) = @_;

    my ($res)= $fields->{resolution};
    my ($st_time,$en_time) = ( scalar($fields->{start_time_sec}),scalar($fields->{end_time_sec}) );
    my ($cmd) = $fields->{SQLCMD};
    $cmd =~ s/\n/\n\# /g;
    # setting the column filter
    if (!ref($labels)) # if filter not set, show all columns retrieved
    {
	$labels = \@{$sth->{NAME}};
    }

    my ($names) = join(", ",@{ $labels });
    my ($rsn) = "Minutes";
    if ($res == 3600)
    {
	$rsn = "Hours";
    }
    elsif ($res == 86400)
    {
	$rsn = "Days";
    }
    elsif ($res != 60)
    {
	$rsn = "Units";
    }
    $names =~ s/{resolution}/$rsn/g; # substitute the <resolution> tag, if any, with actual value
    $names =~ s/{start_at}/$st_time/g;
  
    print CGI::header(-expires=>'now');
    print start_html(-title=>"ViSurD Data - ASCII format");
    print "<PRE>";
    print "#### Time Params ####\n";
    print "# ", $fields->{start_time}, " => $st_time GMT\n";
    print "# ", $fields->{end_time}, " => $en_time GMT\n#\n";
    print "#### SQL Command ####\n";
    print "# $cmd \n#\n";
    print "#### DATA ####\n";
    print "# $names\n";

    my (@dat);
    if (defined($cfgparams->{ascii_mapfn}))
    {
	my ($method) = $cfgparams->{ascii_mapfn};
	my ($script_name) = $fields->{script_name};
	while(@dat = $sth->fetchrow) # display the columns based on the filter specs with links
	{
	    my (@tmpdat,$temp);
	    if (!ref($cols))
	    {
		$cols = \@dat;
	    }

	    foreach (@{$cols})
	    {
		# enclose VARCHAR types in quotes . 12 means VARCHAR
		$dat[$_-1] =~ s/(.*)/\"$1\"/ if $sth->{TYPE}->[$_-1] == 12;
		push(@tmpdat,$dat[$_-1]);
	    }
	    my ($val) = join(",",@tmpdat);
	    $temp = "<a href='$scriptname?method=$method&vals=$val' target='_blank'>$tmpdat[0]</a>";
	    $tmpdat[0] = $temp;
	    print join(", ",@tmpdat),"\n";
	}
    }
    else
    {
	while(@dat = $sth->fetchrow) # display the columns based on the filter specs
	{
	    my (@tmpdat);
	    if (!ref($cols))
	    {
		$cols = \@dat;
	    }
	    foreach (@{$cols})
	    {
		# enclose VARCHAR types in quotes . 12 means VARCHAR
		$dat[$_-1] =~ s/(.*)/\"$1\"/ if $sth->{TYPE}->[$_-1] == 12;
		push(@tmpdat,$dat[$_-1]);
	    }
	    print join(", ",@tmpdat),"\n";
	}
    }

    print "</PRE>\n";
    print end_html;
}

# 4. Display SQL - displays the sql command
sub DisplaySQL
{
    my ($this) = shift;
    my ($sqlstr,$st_time,$en_time) = @_;
    
    my ($stf,$enf) = ( scalar(gmtime($st_time)), scalar(gmtime($en_time)) );
    print CGI::header(-expires=>'now');
    print "<h2>SQL Command</h2>\n";
    print "<PRE>\n";
    print "############## Time Range Parameters ##############\n";
    print "# $st_time => $stf GMT\n";
    print "# $en_time => $enf GMT\n";
    print "###################################################\n\n";
    print "$sqlstr\n";
    print "</PRE>\n";
    print end_html;
}

# 5. DisplayError - general function for printing out errors
sub DisplayError
{
    my ($this) = shift;
    my ($error_code,$details) = @_;
    my ($errfile,@temp);

    open(errfile,$this->{MSGFILE});
    my ($error_str) = "Unknown error.";
    # read error description corresponding to the passed index from the file 
    while(<errfile>)
    {
	@temp = split(":");
	if($temp[0] == $error_code)
	{
	    $temp[1] =~ s/\"//g;
	    $temp[2] =~ s/<BR>/\n/g;
	    $temp[2] =~ s/\A[\s\t]//g;
	    $temp[2] =~ s/\"//g;
	    $error_hdr = $temp[1];
	    $error_str = $temp[2];
	    last;
	}
    }

    print CGI::header(-expires=>'now');
    print start_html(-title=>"ViSurD Message");
    print "<h2>$error_hdr</h2>\n";
    print "<PRE>\n";

    print "<h3>Message Code</h3>$error_code\n";
    print "<h3>Message Description</h3>$error_str\n";

    # if further details have been provided by caller, print them too
    if (defined($details)) 
    {
	print "<h3>Extended Description</h3>";
	print "$details \n";
    }

    print "</PRE>\n";
    print end_html;
}

# 6. DisplayHelp - general function for printing help
sub DisplayHelp
{
    my ($this) = shift;
    my ($helpkey) = @_;

    open(infile,$this->{HELPFILE});
    my ($helpon) = 0;
    print CGI::header;
    print start_html(-title=>"ViSurD Help : \"$helpkey\" ");
    print h1("Help Page");
    print "<h3><i>Keyword : </i> $helpkey</h3>\n";
    print "<PRE>\n";
    $helpkey =~ s/\(/\\\(/g; # put escape character for '(' and ')'. otherwise
    $helpkey =~ s/\)/\\\)/g; # the matching operation goes wacky
    while (<infile>)
    {
	next if ( m/\A[\s\t]*\#/ ); # ignore comment lines
	if ( !($helpon) and (m/<helpkey>$helpkey<\/helpkey>/) )
	{
	    $helpon = 1;
	    next;
	}
	if ( $helpon )
	{
	    last if (m/<endhelp>/);
	    print;
	}
    }
    close(infile);
    print "Help not found for <i>$helpkey</i>\n" if (!$helpon);
    
    print "</PRE>\n";
    print end_html;
}

# 7. DisplayRawData - displays ascii data for manually run queries
sub DisplayRawData
{
    my ($this) = shift;
    my ($qrystring,$sth) = @_;
    
    $qrystring =~ s/\n/\n\# /g;
    my ($colnames) = join(", ",@{$sth->{NAME}});

    my (@data);
    my ($count) = 0;
    # display the html
    print CGI::header(-expires=>'now');
    print start_html(-title=>"ViSurD Query Results");
    print "<PRE>";
    print "#### <b>RESULTS</b> #### \n#\n";
    print "#### SQL Command #### \n";
    print "# $qrystring \n#\n";
    print "#### Data #### \n";
    print "# $colnames \n";
    while (@data = $sth->fetchrow)	
    {
	my ($index) = 0;
	foreach (@data)
	{
	     # enclose VARCHAR type in quotes. 12 means type varchar
	    (s/(.*)/\"$1\"/) if ($sth->{TYPE}->[$index] == 12); 
	    $index++;
	}
	print join(", ",@data), "\n";
	$count++;
    }
    print "# Your Query Returned $count Results";
    print "</PRE>\n";
    print end_html;
}

################### DISPLAY METHODS END ###################


############################# UTILITY METHODS #################################
# method getConfig
# method for parsing the config file and populating the configuration parameters
sub getConfig
{
     my($this) = shift;
     my($cfgparams,$cfgfilename) = @_;

     my($cfgfile,@rows,$mode,$tmp,$cmdstr,@arr);
     open(cfgfile,$cfgfilename);
     while ( <cfgfile> )
     {
	 if($mode ne "query")
	 {
	     s/\A[\s\t]//g; # remove leading white spaces
	     chop; # remove the trailing linefeed character
	     next if( (m/\A[\s\t]*\#/) or !(m/[^\s\t]/) );  # filter out comments and empty lines
	 }
	 
	 /<mode>(.*)<\/mode>/;       # retrieving the mode
	 if(defined($1)) 
	 {   # implies we encountered a <mode>..</mode> tagset - time to set new mode
	     $mode = $1;
	     $mode =~ s/[ \t]//g; # remove leading and trailing white spaces
	     next if ($mode ne "endquery"); # endquery - we should pass it so that the whole query gets recorded
	 }
	 
	 if ( $mode eq "render" )    # render mode - filter specifying which of image, ascii, sql modes is/are supported
	 {
	     @rows = split(",");
	     @{$cfgparams->{render}} = @rows;
	 }
	 elsif ( $mode eq "image" )  # image mode - if image mode supported, then specifications for the image
	 {
	    @rows = split("=>");
	    
	    $_ = $rows[0];
	    if ( /title/ )
	    {
		chomp($rows[1]);
		$cfgparams->{img_title} = $rows[1];
	    }
	    elsif ( /legend/ )
	    {
		chomp($rows[1]);
		my (@lg) = split(",",$rows[1]);
		@{$cfgparams->{img_legend}} = @lg;
	    }
	    elsif ( /xlabel/ )
	    {
		chomp($rows[1]);
		$cfgparams->{img_xlabel} = $rows[1];
	    }
	    elsif ( /ylabel/ )
	    {
		chomp($rows[1]);
		$cfgparams->{img_ylabel} = $rows[1];
	    }
	    elsif ( /rmode/ )
	    {
		chomp($rows[1]);
		$cfgparams->{img_point} = $rows[1];
	    }
	    elsif ( /data/ )
	    {
		my (@imgdata) = split(",",$rows[1]);
		@{$cfgparams->{img_data}} = @imgdata;
	    }
	    elsif ( /map/ )
	    {
		chomp($rows[1]);
		$cfgparams->{img_mapfn} = $rows[1];
	    }
	    elsif ( /use_handler/ )
	    {
		chomp($rows[1]);
		$cfgparams->{img_customhandler} = $rows[1];
	    }
	 }
	 elsif ( $mode eq "ascii" )    # ascii mode specifications
	 {
	     @rows = split("=>");
	    
	     $_ = $rows[0];
	     if ( /col_names/ )
	     {
		 my (@labels) = split(",",$rows[1]);
		 @{$cfgparams->{ascii_colnames}} = @labels;
	     }
	     elsif ( /col_numbers/ )
	     {
		 my (@cols) = split(",",$rows[1]);
		 @{$cfgparams->{ascii_cols}} = @cols;
	     }
	     elsif ( /map/ )
	     {
		 chomp($rows[1]);
		 $cfgparams->{ascii_mapfn} = $rows[1];
	     }
	 }
	 elsif ( $mode eq "resolution" )  # resolution filter - e.g. minute, day, hour etc.
	 {
	     my (@resnums) = split(",");
	     @{$cfgparams->{res_nums}} = @resnums;
	 }
	 elsif ( $mode eq "query" )  # query mode - the actual query string
	 {
	     $tmp = $cmdstr;
	     $cmdstr = $tmp.$_;
	 }
	 elsif ( $mode eq "endquery")  # just a tag saying multiline query is over so record final value of query string
	 {
	     $cfgparams->{query} = $cmdstr;
	 }
     }
     close(cfgfile);
     return $cfgparams;
}

# method : renderImage
sub renderImage
{
    my($this) = shift;
    my($cfgparams,$sth,$fields) = @_;

    my ($meth) = $cfgparams->{img_customhandler};
    ( $this->$meth ($cfgparams,$sth,$fields) and return 1) if ( defined($meth) ); 

    my ($g, @idarray);
    my ($pt_mode) = 'false';
    
    my ($xres) = $fields->{image_xres};
    my ($yres) = $fields->{image_yres};

    # invoke the appropriate Chart object
    defined($cfgparams->{img_point}) or $cfgparams->{img_point} = "point-pt";
    if ( $cfgparams->{img_point} eq "point-pt" or $cfgparams->{img_point} eq "point-st" )
    {
	$g = Chart::Points->new($xres,$yres);
	if ($cfgparams->{img_point} eq "point-pt")
	{
	    $pt_mode = 'true';
	}
    }
    elsif ( $cfgparams->{img_point} eq "lines-pt" or $cfgparams->{img_point} eq "lines-st" )
    {
	$g = Chart::Lines->new($xres,$yres);
	if ( $cfgparams->{img_point} eq "lines-pt" )
	{
	    $pt_mode = 'true';
	}
    }
    elsif ( $cfgparams->{img_point} eq "bar-pt" or $cfgparams->{img_point} eq "bar-st" )
    {
	$g = Chart::Bars->new($xres,$yres);
	if ( $cfgparams->{img_point} eq "bar-pt" )
	{
	    $pt_mode = 'true';
	}
    }

    # reading the column filter for plotting - specified in the 'data' field in the 'image' mode
    if(!defined($cfgparams->{img_data}))
    {
	my ($num) = 0;
	foreach (@{$sth->{NAME}})
	{
	    push(@idarray,++$num);
	}
	@{$cfgparams->{img_data}} = @idarray;
    }
    else
    {
	@idarray = @{$cfgparams->{img_data}};
    }

    my ($count) = 0;
    my ($invalid) = 0;
    my ($xaxis_val) = 0;
    my ($mode) = $fields->{scale_mode};
    while( (@data = $sth->fetchrow) )
    {
	my (@tmparr);
	$invalid = 0;
	$xaxis_val = 1;
	foreach (@idarray)
	{
	    if (defined($data[$_-1]))
	    {
		if($mode eq "linear" or $xaxis_val == 1)
		{
		    push(@tmparr,$data[$_-1]);
		    $xaxis_val = 0;
		}
		else
		{
		    push(@tmparr,log($data[$_-1]));
		}
	    }
	    else
	    {
		$invalid = 1;
		last;
	    }
	}
	next if ($invalid == 1);

	$count++;
	$g->add_pt(@tmparr);
    }

    if ( $count == 0 )  
    {   # sorry - no data to display
	$this->DisplayError(2); 
	return 0; 
    }
    $cfgparams->{img_totcount} = $count;
    
    # setting the image properties
    my ($rsn) = "Minutes";
    if($fields->{resolution} == 3600)
    {
	$rsn = "Hours";
    }
    elsif($fields->{resolution} == 86400)
    {
	$rsn = "Days";
    }

    my ($st_time,$en_time) = ( scalar($fields->{start_time_sec}),scalar($fields->{end_time_sec}) );
    my ($imgtitle) = $cfgparams->{img_title}."- FROM ($st_time) TO ($en_time) GMT";

    $cfgparams->{img_xlabel} =~ s/{resolution}/$rsn/g;
    $cfgparams->{img_ylabel} =~ s/{resolution}/$rsn/g;
    $cfgparams->{img_xlabel} =~ s/{start_at}/$st_time/g;

    if($fields->{scale_mode} eq "log")
    {
	$cfgparams->{img_ylabel} = $cfgparams->{img_ylabel}." (in log base-e scale)";
	$g->set('precision' => '2');
    }
    else
    {
	$g->set('precision' => '0');
    }
    
    $g->set('xy_plot' => $pt_mode,'pt_size' => '10');
    $g->set (  'title' => $imgtitle,
	       'legend_labels' => \@{$cfgparams->{img_legend}},
	       'x_label' => $cfgparams->{img_xlabel},
	       'y_label' => $cfgparams->{img_ylabel}, 
	       'grid_lines' => 'false',
	       'include_zero' => 'false',
	       'legend' => 'bottom'
	       );

    $g->set('imagemap' => 'true') if (defined($cfgparams->{img_mapfn}));
    $cfgparams->{script_name} = $fields->{script_name};

    $this->DisplayImage($g, $imgtitle, $fields->{format}, $fields->{graphics}, $fields->{output_file},$cfgparams);
}

######################### UTILITY METHODS END ###########################

########################  CUSTOM PLUGIN and HANDLER METHODS #####################
sub routehops
{
    my($this) = shift;
    my ($values,$img,$dbname,$schema_name) = @_;
            
    my ($mydb) = $this->{'db'};
    my (@dat) = split(",",$values);
    
    $this->prepareDB($dbname); # initializes the dbwrap object
    $cmdstr = "select rh.hopno, rr.ipaddr, rr.dnsname from ".$schema_name."routehop rh, ".$schema_name."routers rr where rh.routeid = $dat[0] and rh.routerid = rr.routerid order by rh.hopno";

    my ($sth) = $mydb->execDB2Cmd($cmdstr); # execute the query
    print CGI::header(-expires=>'now');
    print "Hop Number, IP Address, DNS Name<BR><BR>\n";
    while((@data = $sth->fetchrow))
    {
	my ($str) = join(", ", @data);
	print $str, "<BR>\n";
    }
}

sub routeTimeLine
{
    my($this) = shift;
    my($cfgparams,$sth,$fields) = @_;

    my ($count) = 0;
    my (@data);
    my ($xres) = $fields->{image_xres};
    my ($yres) = $fields->{image_yres};

    my ($g) = Chart::Lines->new($xres,$yres);
    while( (@data = $sth->fetchrow) )
    {
	if( defined($data[0] && $data[1] && $data[2] ) )
	{
	    $count++;
	    $g->add_pt($data[1],$data[0]);
	    $g->add_pt($data[2],$data[0]);
	}
    }

    if ( $count == 0 )  
    {   # sorry - no data to display
	$this->DisplayError(2); 
	return 0; 
    }

    $cfgparams->{img_totcount} = $count;
    my ($st_time,$en_time) = ( scalar($fields->{start_time_sec}),scalar($fields->{end_time_sec}) );
    my ($imgtitle) = $cfgparams->{img_title}."- FROM ($st_time) TO ($en_time) GMT";

    $cfgparams->{img_xlabel} =~ s/{start_at}/$st_time/g;

    $g->set('precision' => '0');
    $g->set('xy_plot' => 'true','pt_size' => '10');
    $g->set (  'title' => $imgtitle,
	       'legend' => 'none',
	       'x_label' => $cfgparams->{img_xlabel},
	       'y_label' => $cfgparams->{img_ylabel}, 
	       'grid_lines' => 'false',
	       'include_zero' => 'false',
	       'brush_size' => 1,
	       );
    
    $this->DisplayImage($g, $imgtitle, $fields->{format}, $fields->{graphics}, $fields->{output_file},$cfgparams);
}

################# PLUGIN METHODS OVER ###################

############## Package independent utility functions ################

# method myformatter - to format the axis labels of the Chart utility
# add your own formatter and specify it in the chart plotting options as and when required
sub myformatter
{
    my ($label) = @_;
    $label = int($label);

    $mylabel = sprintf("%.2f",$label/60);
    return $mylabel;
}
