package Statusmon::Display;

use Statusmon::Statusmon;

@Statusmon::Display::ISA = qw(Statusmon::Statusmon);


sub new
{

    my ($this) = @_;
    my  $class = ref($this) || $this;
    my  $self = {};

    bless($self, $class);

    $self->register( "statuschange" );
    $self->register( "metagrouping" );
    $self->register( "config" );
    $self->sched_frequency( 1 );
    $self->{"dirty"} = {};
    $self->{"dependency_reg"} = {};
    $self->{"wwwdir"} = $BigSister::common::fs{'web'};
    return($self);
}


sub start {
    my( $self ) = @_;

    my $config = $self->get_feature_provider( "displayconfig" );
    if( $config ) { $self->read_config( $config->get_current_config() ) };
}
    


sub read_config {
    my( $self, $newconfig, $oldconfig ) = @_;

    $self->{"config"} = $newconfig;
    $self->{"dependency_reg"} = {};
}



        

sub event {
    my( $self, $cmd ) = @_;
    my $text;

    print STDERR "Statusmon::grouping got an event\n" if( $BigSister::common::dl > 6 );
    $self->{"current_event"} = $cmd;
    if( $cmd->{"cmd"} eq "groupstatuschange" ) {
	$self->{"dirty"}->{"status ".($cmd->{"group"})} = 1;
    }
    elsif( $cmd->{"cmd"} eq "groupmembers" ) {
	$self->{"dirty"}->{"members ".($cmd->{"group"})} = 1;
    }
    elsif( $cmd->{"cmd"} eq "groupsummarychange" ) {
	$self->{"dirty"}->{"summary ".($cmd->{"group"})} = 1;
    }
    elsif( $cmd->{"cmd"} eq "graphcreated" ) {
	$self->{"dirty"}->{"status ".($cmd->{"host"})} = 1;
    }
    elsif( $cmd->{"cmd"} eq "statuschange" ) {
	$self->{"dirty"}->{"status ".($cmd->{"host"})} = 1;
    }
    elsif( $cmd->{"cmd"} eq "grouptitle" ) {
	$self->{"dirty"}->{"grouptitle ".($cmd->{"group"})} = 1;
    }
    elsif( $cmd->{"cmd"} eq "displayconfig" ) {
	$self->read_config( $cmd->{"config"}, $cmd->{"previous_config"} );
    }
    elsif( $cmd->{"cmd"} eq "remove" ) {
	$self->{"dirty"}->{"status ".($cmd->{"host"})} = 1;
    }
    else {
	return;
    }
}



sub needs_update {
    my( $self, $deps ) = @_;

    return(1) if( ! defined $deps );
    my $key;
    foreach $key (keys %$deps) {
	return(1) if( defined $self->{"dirty"}->{$key} );
    }
    return 0;
}



sub update {
    my( $self ) = @_;
    my( $mtime, $page, $cont, $page_ref );
    my $newdirty = {};

    print STDERR "Display::update\n" if( $BigSister::common::dl>1 );
    $self->{"grouper"} = $self->get_feature_provider("grouping");
    unless( $self->{"grouper"} ) {
	return if( $self->{"groupwarned"} );
	BigSister::common::log( "err", "grouping module not active - I refuse to create status pages" );
	$self->{"groupwarned"} = 1;
	return;
    }
    $self->{"display"} = $self->get_feature_provider("display");
    unless( $self->{"display"} ) {
	return if( $self->{"displaywarned"} );
	BigSister::common::log( "err", "display module not active - I refuse to create status pages" );
	$self->{"displaywarned"} = 1;
	return;
    }
    $self->{"displaywarned"} = $self->{"groupwarned"} = 0;

    $page_ref = $self->{"config"}->{"pages"};
    my $updated = 0;
    while( ($page,$cont) = each %$page_ref ) {
	next unless( $cont->[0]->{"type"} eq "page" );
	my $deps = $self->{"dependency_reg"}->{$cont};
	next unless( $self->needs_update( $deps ) );
	$self->{"dependencies"} = $self->{"dependency_reg"}->{$cont} = {};
	$self->{"dependencies"}->{"page $page"} = 1;
	my $profile = $self->{"config"}->{"options"}->{"Profile"};
	my $time;
	$time = MicroTime::gettimeofday if( $profile );
	unless( $self->create_page( $page, $cont ) ) {
	    $newdirty->{"page $page"} = 1;
	}
	if( $profile ) {
	    $time = MicroTime::difftime( $time );
	    if( $time > 0.5 ) {
	        BigSister::common::log( "info", "profile: spent ".(int($time*100)/100)."s in create_page( $page )" );
 	    }
	}
	$updated++;
    }
    if( $updated ) {
	while( ($page,$cont) = each %{$self->{"config"}->{"frames"}} ) {
	    $self->create_frameset( $cont->{"name"}, $cont->{"top"},
		$cont->{"title"}, $cont->{"pages"} );
	}
    }
    $self->{"dirty"} = $newdirty;
}



sub create_page {
    my( $self, $name, $elements ) = @_;
    my( $file, $template, $pageattr, %vars, $element, $type, $skin, $i );

    print STDERR "Display::create_page( $name )\n" if( $BigSister::common::dl > 1 );
    $pageattr = $$elements[0];
    $template = $$pageattr{"template"};
    $skin = $$pageattr{"skin"};
    if( $template && ! -f $template ) {
	$template = ($self->{"wwwdir"})."/".$template;
    }
    if( $template && (-r $template) ) {
	open( _F, "<$template" );
	$template = "";
	while( <_F> ) {
	    $template .= $_;
	}
	close( _F );
    }
    else {
	$template = $skin->{"template.proto"};
    }
    unless( $template ) {
	print STDERR "$BigSister::common::prog: cannot open template file\n";
	return(1);
    }
    $file = $pageattr->{"file"};
    print STDERR "  file: $file\n" if( $BigSister::common::dl > 3 );
    $vars{"TITLE"} = $$pageattr{"title"};
    $vars{"TEXT"} = "";
    $vars{"TIME"} = localtime;
    $vars{"EXPIRES"} = Platform::strftime("%a, %d %h %Y %T GMT", time + 300);
    my $table_num = 0;
    my $image_num = 0;
    foreach $element (@$elements) {
	$type = $$element{"type"};
	if( $type eq "ref" ) {
	    $i = $skin->{"label.proto"};
	    $i =~ s/\@NAME\@/$element->{"name"}/g;
	    $vars{"TEXT"} .= $i;
	}
	elsif( $type eq "table" ) {
	    my $text = $self->create_table( $skin, $element, \%vars );
	    $vars{"TABLE_".($element->{"group"})} = $text;
	    $vars{"TABLE_".($table_num++)} = $text;
	    $vars{"TEXT"} .= $text;
	}
	elsif( $type eq "image" ) {
	    my $text;
	    eval {
		$text = $self->create_image( $skin, $element, \%vars );
	    };
	    if( $@ ) {
	        BigSister::common::log( "err", "failed to create image ".($element->{"image"}->cfg()).": $@\n" );
	    }
	    $vars{"IMAGE_".($image_num++)} = $text;
	    $vars{"TEXT"} .= $text;
	}
	elsif( $type eq "include" ) {
	    open( _IN, "<".$$element{"file"} );
	    while( <_IN> ) {
		$vars{"TEXT"} .= $_;
	    }
	    close( _IN );
	}
    }
    my $min = $vars{"MIN"};
    ($min = $BigSister::common::status_codes{"clear"}) if (!defined $BigSister::common::status_texts{$min});
    $vars{"BGROUND"} = $BigSister::common::status_texts{$min};
    $elements->[0]->{"color"} = $min;
    return( $self->{"display"}->replace_vars_file( $skin, \%vars, $template, $file ) );
}



sub create_image {
    my( $self, $skin, $image, $vars ) = @_;
    my( $text, $ref, $elem, $file, $rowrefto, $href );

    $image->{"image"}->set_display( $self->{"grouper"} );
    $image->{"image"}->create();
    if( defined $image->{"row-refto"} ) {
	$rowrefto = $image->{"row-refto"};
    }
    $file = $image->{"image"}->file;
    $file =~ s/^www\///;
    $file =~ s#^\Q$BigSister::common::fs{'web'}\E/*##;
    $text = $skin->{"image_map.proto"};
    $text =~ s/\@SOURCE\@/$file/g;
    $text =~ s/\@MAP\@/map/g;
    $text .= "\n<MAP name=\"map\">\n";
    $ref = $image->{"image"}->map;
    foreach $elem (@$ref) {
        my $mode = (defined $$elem[6]) ? $$elem[6] : "rect";
	if( $mode eq "rect" ) {
	    $text .= "<AREA shape=\"rect\" coords=\"$$elem[0],$$elem[1],";
	    $text .= ($$elem[0]+$$elem[2]).",".($$elem[1]+$$elem[3]);
	    $text .= '" ';
	}
	elsif( $mode eq "line" ) {
	    my $xw =0; my $yw = 1;
	    if( abs( $$elem[0] - $$elem[2] ) < abs( $$elem[1] - $$elem[3] ) ) {
	        $xw = 1; $yw = 0;
	    }
	    $text .= "<AREA shape=\"poly\" coords=\"" . 
	        ($$elem[0]-$xw) . "," . ($$elem[1]-$yw) . "," .
		($$elem[2]-$xw) . "," . ($$elem[3]-$yw) . "," .
		($$elem[2]+$xw) . "," . ($$elem[3]+$yw) . "," .
		($$elem[0]+$xw) . "," . ($$elem[1]+$yw) .
		"\" ";
	}

	$text .= "alt=\"$$elem[4]\"";
	$href = "";
	$href = $image->{"refto"}.$$elem[4] if( $image->{"refto"} );
	$href = $rowrefto->{$$elem[4]} if( $rowrefto && $rowrefto->{$$elem[4]} );
	$href = $$elem[5] if( $$elem[5] );
	$text .= " href=\"$href\" " if( $href );
	my $title = $self->{"grouper"}->get_title( $$elem[4] ) || $$elem[4];
	$title =~ s#"##;
	$text .= " title=\"$title\"";
	$text .= ">\n";
	$self->{"dependencies"}->{"summary ".$$elem[4]} = 1;
	if( $$elem[4] =~ /^(.*)\./ ) {
	    $self->{"dependencies"}->{"status $1"} = 1;
	}
    }
    $text .= "</MAP>\n";
}



sub create_table {
    my( $self, $skin, $table, $vars ) = @_;
    my( $text, $i, $ref, %items, $group, @groups, @items, @line, $item, $t, $proto );
    my( $refto, $rowrefto, $min, $itemref, $button, $title, $depth, @recurse, %lines, $host);

    $min = 1000;
    $min = $$vars{"MIN"} if( defined $$vars{"MIN"} );
    print STDERR "Display::create_table\n" if( $BigSister::common::dl > 1 );
    $text = $skin->{"table.proto"};
    $title = $$table{"title"};
    $title =~ s/_/ /g;
    if( $title eq "auto" ) {;
	$title = $self->{"grouper"}->get_title($$table{"group"});
	$self->{"dependencies"}->{"grouptitle ".($$table{"group"})} = 1;
    }
    $text =~ s/\@TITLE\@/$title/g;
    if( defined $$table{"refto"} ) {
	$refto = $$table{"refto"};
    }
    if( defined $$table{"row-refto"} ) {
	$rowrefto = $$table{"row-refto"};
    }
    if( defined $$table{"itemref"} ) {
	$itemref = $$table{"itemref"};
    }
    my $selected_items = undef;
    if( defined $$table{"items"} ) {
        $selected_items = $$table{"items"};
    }

    my $sort = $$table{"sort"};
    $sort = "severity" unless( $sort );
    $ref = $self->{"grouper"}->get_members($$table{"group"});
    $self->{"dependencies"}->{"members ".($$table{"group"})} = 1;
    print "table $$table{group}\n" if( $BigSister::common::dl>4 );
    return unless( $ref );
    @recurse = @$ref;
    $depth = $$table{"depth"};
    while( $depth ) {
	@groups = ();
	$depth--;
	foreach $group (@recurse) {
	    my $members = $self->{"grouper"}->get_members( $group );
	    $self->{"dependencies"}->{"members $group"} = 1;
	    if( $self->{"grouper"}->isgroup( $group ) ) {
		push( @groups, @$members );
	    }
	    else {
		push( @groups, $group );
	    }
	}
	@recurse = @groups;
    }

    if( defined $$table{"select_names"} ) {
	$selected_names = $$table{"select_names"};
	for( my $i=$#recurse; $i>=0; $i-- ) {
	    my $group = $recurse[$i];
	    my $selected = 0;
	    foreach my $pattern (@$selected_names) {
	        eval {
		    if( $group =~ /$pattern/i ) {
		        $selected = 1;
		        last;
		    };
		}
	    }
	    unless( $selected ) {
	        splice( @recurse, $i, 1 );
	    }
	}
    }

    my $select = $$table{"select"};
    my $selectfunc = sub { return 1; };
    if( $select =~ /^(<|>|<=|>=|=)([a-z]*)$/ ) {
        my( $selector, $color ) = ($1,$2);
	$color = $self->get_statuscode( $color );
	( $selector eq "<" ) && ($selectfunc = sub { return $_[0] < $color; } );
	( $selector eq "<=" ) && ($selectfunc = sub { return $_[0] <= $color; } );
	( $selector eq ">" ) && ($selectfunc = sub { return $_[0] > $color; } );
	( $selector eq ">=" ) && ($selectfunc = sub { return $_[0] >= $color; } );
	( $selector eq ">=" ) && ($selectfunc = sub { return $_[0] == $color; } );
    }
	   
    %items = ();
    @groups = ();
    foreach $i (@recurse) {
	$self->{"dependencies"}->{"status $i"} = 1;
	$group = $self->{"grouper"}->get_checks( $i );
	push( @groups, $i, $group );
	foreach $item (keys %$group) {
	    $items{$item} = 1;
	}
    }
    @items = sort keys %items;
    if( $selected_items ) {
	for( my $i=$#items; $i>=0; $i-- ) {
	    splice( @items, $i, 1 ) unless( $selected_items->{$items[$i]} );
	}
    }

    $t = "";
    foreach $i ( @items ) {
	$proto = $skin->{"table_head.proto"};
	$proto =~ s/\@TEXT\@/$i/g;
	$t .= $proto;
    }
    $text =~ s/\@TABLE_HEAD\@/$t/g;
    # $#items is one less than the number of data columns, and three less than
    # the actual number of columns (add row header and an empty separator).
    # I used @items (in scalar context) because that is the preferred idiom
    # these days.
    $i = @items + 2;
    $text =~ s/\@TABLE_COLUMNS\@/$i/g;
    my $table_text = "";
    while( @groups ) {
	$group = shift @groups;
	$ref = shift @groups;
	$i = $self->{"grouper"}->get_title($group);
	$self->{"dependencies"}->{"grouptitle $group"} = 1;
	$host = $i;
	if( $rowrefto && defined $$rowrefto{$group} ) {
	    $i = $self->table_href( $skin, $$rowrefto{$group}, $i );
	}
	elsif( $refto ) {
	    $i = $self->table_href( $skin, $refto.$group, $i );
	}
	@line = ( $i );
	my $thismin = 9;
	my $cols = 0;
	foreach $i (@items) {
	    if( defined $$ref{$i} ) {
		$item = $$ref{$i};
		$min = $item if( $item<$min );
		$thismin = $item if( $item<$thismin );
		$button = $skin->{"table_button.proto"};
		my $stext = $self->get_statustext( $item );
		$button =~ s/\@STATUS\@/$stext/g;
		if( $itemref ) {
		    my( $ref );

		    $ref = $itemref."/$group.$i";
		    if( -f ($self->{"wwwdir"})."/$ref.html" ) {
			$ref .= ".html";
		    }
		    $button = $self->table_href( $skin, $ref, $button );
		}
		push( @line, $button );
		$cols++;
	    }
	    else {
		push( @line, "-" );
	    }
	}
	# do not print empty lines if %selected_items is set
	next unless( $cols || ! $selected_items );

	$t = shift @line;
	$proto = $skin->{"table_titlecolumn.proto"};
	$proto =~ s/\@TEXT\@/$t/g;

	my $graph = "";
	if( -f ($self->{"wwwdir"})."/graphs/$group.html" ) {
	    $graph = $skin->{"graph_tablecell.proto"};
	    $graph =~ s/\@HOST\@/$group/;
	}
	$proto =~ s/\@GRAPH\@/$graph/g;

	$t = $proto;
	foreach $i ( @line ) {
	    $proto = $skin->{"table_cell.proto"};
	    $proto =~ s/\@TEXT\@/$i/g;
	    $t .= $proto;
	}
	$proto = $skin->{"table_line.proto"};
	$proto =~ s/\@TEXT\@/$t/g;
	next unless( &$selectfunc( $thismin ) );
	if( $sort eq "name" ) {
	    $lines{$host}=$proto;
	}
	else {
	    $lines{$thismin.$host} = $proto;
	}
    }
    foreach $i ( sort keys %lines ) {
	$table_text .= $lines{$i};
    }
    $text =~ s/\@TABLE_BODY\@/$table_text/g;
    $$vars{"MIN"} = $min;
    return $text."\n";
}


sub table_href {
    my( $self, $skin, $href, $text ) = @_;

    my $proto = $skin->{"table_href.proto"};
    $proto =~ s/\@HREF\@/$href/g;
    $proto =~ s/\@TEXT\@/$text/g;
    return $proto;
}



sub create_frameset {
    my( $self, $name, $top, $title, $pages ) = @_;
    my( $skin, %vars, $cont, $i, $ref, $proto, $pa );

    print "create_frameset()\n" if( $BigSister::common::dl > 1 );
    $skin = $self->{"config"}->{"defaultskin"};
    return unless( defined $skin->{"frameset.proto"} );
    $vars{"FRAMESET"} = $name;
    $title =~ s/_/ /g;
    $vars{"TITLE"} = $title;
    $vars{"TOP"} = $top;

    $cont = "";
    $ref = $self->{"config"}->{"pages"};
    foreach $i (@$pages) {
	$pa = $ref->{$i};
	$pa = $$pa[0];
	$proto = ($pa->{"type"} eq "section") ? $skin->{"frameset_section.proto"} 
					      : $skin->{"frameset_contents.proto"};
	$title = $pa->{"title"};
	$title =~ s/_/ /g;
	$proto =~ s/\@TITLE\@/$title/g;
	$proto =~ s/\@PAGE\@/$pa->{"name"}/g;
	my $pastatus = $self->get_statustext( $pa->{"color"} );
	print STDERR "page status for $title: $pastatus (".($pa->{"color"}).")\n" if( $BigSister::common::dl> 5 );
	$proto =~ s/\@COLOR\@/$pastatus/g;
	$cont .= $proto;
    }

    $vars{"CONTENTS"} = $cont;
    $vars{"EXPIRES"} = Platform::strftime("%a, %d %h %Y %T GMT", time + 60);
    $self->{"display"}->replace_vars_file( $skin, \%vars, $skin->{"frameset.proto"}, $self->{"wwwdir"}."/".$name.".html" );

    $i = 1;
    if( defined $skin->{"frame0.proto"} ) { $i=0; }
    while( defined $skin->{"frame$i.proto"} ) {
	$self->{"display"}->replace_vars_file( $skin, \%vars, $skin->{"frame$i.proto"}, $self->{"wwwdir"}."/".$name."$i.html" );
	$i++;
    }
}






sub run {
    my( $self ) = @_;

    $self->update
}




1;
