package Statusmon::grouping;

use Statusmon::Statusmon;
use BigSister::common;

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


sub new
{

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

    bless($self, $class);

    # I want to receive "statuschange" events
    $self->register( "statuschange" );
    $self->register( "grouping" );
    $self->register( "config" );
    $self->sched_frequency( 1 );

    $self->{"states"} = {};
    $self->{"groups"} = {};
    $self->{"group_status"} = {};

    $self->{"group_titles"} = {};
    $self->{"groups_by_sub"} = {};
    $self->{"subs_by_group"} = {};
    $self->{"locked_title"} = {};
    $self->{"host_there"} = {};
    $self->{"read_grouping"} = 1;
    $self->add_feature_provider( "grouping" );
    return($self);
}


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

    my $groupfile = $self->getarg( "groupfile" );
    ($groupfile = "$BigSister::common::fs{'var'}/grouping") unless( $groupfile );
    $groupfile = BigSister::common::find_file( $groupfile );
    $self->{"groupfile"} = $groupfile;

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

    my( $check, $status );
    while( ($check, $status) = each %BigSister::common::current_status ) {
	my( $host, $item ) = BigSister::common::separate_service( $check );
	$self->set( $host, $item, $status->{"status"} );
    }
}
    


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

    $self->{"autojoin_all"} = $newconfig->{"autojoin_all"} 
      if( defined $newconfig->{"autojoin_all"} ); 
    $self->{"autojoin_new"} = $newconfig->{"autojoin_new"} 
      if( defined $newconfig->{"autojoin_new"} ); 
    $self->{"autojoin_allhosts"} = $newconfig->{"autojoin_allhosts"} 
      if( defined $newconfig->{"autojoin_allhosts"} ); 
    $self->{"autojoin_pattern"} = $newconfig->{"autojoin_pattern"} 
      if( defined $newconfig->{"autojoin_pattern"} ); 

    if( $oldconfig ) {
        my $sub, $groups;
	while( ($sub, $groups) = each %{$oldconfig->{"groups"}} ) {
	    my $group;
	    foreach $group( @$groups ) {
	        $self->leave_group( $sub, $group );
	    }
	}
	foreach $sub (keys %{$oldconfig->{"grouptitle"}}) {
	    $self->{"locked_title"}->{$sub} = 0;
	    $self->displayname( $sub );
        }
    }

    my $sub, $groups;
    while( ($sub, $groups) = each %{$newconfig->{"groups"}} ) {
	my $group;
	foreach $group( @$groups ) {
	    $self->join_group( $sub, $group );
	}
    }

    foreach $sub (keys %{$newconfig->{"grouptitle"}}) {
	$self->displayname( $sub, $newconfig->{"grouptitle"}->{$sub} );
	$self->{"locked_title"}->{$sub} = ($sub ne ($newconfig->{"grouptitle"}->{$sub}));
    }

    if( ($newconfig->{"options"}->{"KeepGroups"}) && $self->{"read_grouping"} ) {
        open( _IN, "<".($self->{"groupfile"}) );
	while( <_IN> ) {
	    chomp;
	    my $line = $_;
	    my @words = split( /[\s\t]+/ );
	    if( $words[0] eq "join" ) {
	        for( my $i=2; $i<=$#words; $i++ ) {
		    $self->join_group( $words[1], $words[$i] );
	        }
	    }
	    elsif( $line =~ /^displayname ([^\s\t]+) (.*)/ ) {
	        $self->displayname( $1, $2 );
	    }
	}
	close _IN;
	$self->{"read_grouping"} = 0;
    }
}



sub undefine_group {
    my( $self, $group ) = @_;

    return if( ! defined $self->{"subs_by_group"}->{$group} );
    delete $self->{"groups"}->{$group};
    delete $self->{"subs_by_group"}->{$group};
    delete $self->{"groups_by_sub"}->{$group};
    $self->post_event( {
        "cmd_group" => "metagrouping",
	"cmd" => "rmgroup",
	"group" => $group
	}, $self->{"current_event"} );
}



sub define_group {
    my( $self, $group, $newtitle ) = @_;

    return if( defined $self->{"subs_by_group"}->{$group} );
    $self->{"groups"}->{$group} = {};
    $self->{"subs_by_group"}->{$group} = [];
    $self->{"groups_by_sub"}->{$group} = [];
    $self->post_event( {
        "cmd_group" => "metagrouping",
	"cmd" => "newgroup",
	"group" => $group
	}, $self->{"current_event"} );
    unless( defined $self->{"group_titles"}->{$group} ) {
        $self->displayname( $group, $newtitle );
    }
}



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

    my $host = $cmd->{"host"};
    my $groups = $cmd->{"groups"};
    my $group;
    foreach $group (@$groups) {
       $self->join_group( $host, $group );
    }
}



sub join_group {
    my( $self, $sub, $group ) = @_;
    my( $subs );

    unless( defined $self->{"subs_by_group"}->{$group} ) {
        $self->define_group( $group );
    }
    $subs = $self->{"subs_by_group"}->{$group};
    return if( grep( $_ eq $sub, @$subs ) );
    push( @$subs, $sub );

    unless( defined $self->{"groups_by_sub"}->{$sub} ) {
        $self->define_group( $sub );
    }
    $subs = $self->{"groups_by_sub"}->{$sub};
    push( @$subs, $group ) unless( grep( $_ eq $group, @$subs ) );

    $self->{"dirty"}->{$group} = 1;
    $self->{"isdirty"} = 1;
    $self->{"groupdirty"} = 1;

    $self->post_event( { 
        "cmd_group" => "metagrouping",
	"cmd" => "groupmembers",
	"group" => $group,
	"members" => $self->{"subs_by_group"}->{$group},
	"join" => [ $sub ]
	}, $self->{"current_event"} );

    if( $self->{"autojoin_all"} && ($group ne $self->{"autojoin_all"}) ) {
        $self->join_group( $sub, $self->{"autojoin_all"} );
    }
}
 


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

    my $host = $cmd->{"host"};
    my $groups = $cmd->{"groups"};
    my $group;
    foreach $group (@$groups) {
       $self->leave_group( $host, $group );
    }
}



sub leave_group {
    my( $self, $sub, $group ) = @_;
    my( $subs, $parents );

    return unless( defined $self->{"groups_by_sub"}->{$sub} );
    if( $group eq "*" ) {
        my $groups = $self->{"groups_by_sub"}->{$sub};
	my $group;
	foreach $group (@$groups) {
	    $self->leave_group( $sub, $group ) unless( $group eq "*" );
	}
	if(  $#{$self->{"subs_by_group"}->{$sub}} < 0 ) {
	    $self->undefine_group( $sub );
	}
	return;
    }
    return unless( defined $self->{"groups_by_sub"}->{$group} );
    $subs = $self->{"subs_by_group"}->{$group};
    return unless( grep( $_ eq $sub, @$subs ) );
    @$subs = grep( $_ ne $sub, @$subs );

    $parents = $self->{"groups_by_sub"}->{$sub};
    @$parents = grep( $_ ne $group, @$parents );

    $self->{"dirty"}->{$group} = 1;
    $self->{"isdirty"} = 1;
    $self->{"groupdirty"} = 1;

    $self->post_event( { 
        "cmd_group" => "metagrouping",
	"cmd" => "groupmembers",
	"group" => $group,
	"members" => $subs,
	"leave" => [ $sub ]
	}, $self->{"current_event"} );
}
 


sub displayname {
    my( $self, $group, $title ) = @_;

    return if( $self->{"locked_title"}->{$group} );
    if( ! $title ) {
        $title = $group;
	$title =~ s/\Q$BigSister::common::domain_encode\E/./g;
    }
    return if( $self->{"group_titles"}->{$group} eq $title );
    $self->{"group_titles"}->{$group} = $title;
    $self->post_event( {
        "cmd_group" => "metagrouping",
	"cmd" => "grouptitle",
	"group" => $group,
	"title" => $title
	}, $self->{"current_event"} );
    $self->{"groupdirty"} = 1;
}

        

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 "join" ) {
	$self->join_groups( $cmd );
    }
    elsif( $cmd->{"cmd"} eq "leave" ) {
	$self->leave_groups( $cmd );
    }
    elsif( $cmd->{"cmd"} eq "displayname" ) {
	$self->displayname( $cmd->{"group"}, $cmd->{"name"} );
    }
    elsif( $cmd->{"cmd"} eq "displayconfig" ) {
	$self->read_config( $cmd->{"config"}, $cmd->{"previous_config"} );
    }
    elsif( $cmd->{"cmd"} eq "statuschange" ) {
	$self->set( $cmd->{"host"}, $cmd->{"check"}, $cmd->{"statuscode"} );
    }
    elsif( $cmd->{"cmd"} eq "remove" ) {
	delete $self->{"states"}->{$cmd->{"host"}}->{$cmd->{"check"}};
	my $groups = $self->get_groups($cmd->{"host"});
	foreach my $i (@$groups) {
	    $self->{"dirty"}->{$i} = 1;
	}
	$self->{"isdirty"} = 1;
    }
    else {
	return;
    }
}


sub group_write {
    my( $self ) = @_;;
    my( $hash, $group, $name, $ref );
    my( $value );

    print STDERR "Statusmon::grouping::group_write()\n" if( $BigSister::common::dl>2 );
    open( OUT, ">".$self->{"groupfile"} );
#    $ref = $self->{"config"};
#    while( ($name,$value) = each %$ref ) {
#	print OUT "set $name=$value\n";
#    }
    $hash = $self->{"group_titles"};
    while( ($group,$name) = each %$hash ) {
        print OUT "displayname $group $name\n";
    }
    $hash = $self->{"groups_by_sub"};
    while( ($group,$ref) = each %$hash ) {
	print OUT "join $group ".join(" ",@$ref)."\n" if( @$ref );
    }
    close OUT;
    $self->{"groupdirty"} = 0;
}





sub set {
    my( $self, $group, $item, $state ) = @_;

    if( $self->{"autojoin_new"} && ! defined $self->{"groups_by_sub"}->{$group} ) {
	$self->join_group( $group, $self->{"autojoin_new"} );
    }
    unless( $self->{"host_there"}->{$group} ) {
	$self->{"host_there"}->{$group} = 1;
	$self->join_group( $group, $self->{"autojoin_allhosts"} ) 
	  if( $self->{"autojoin_allhosts"} );
	if( defined $self->{"autojoin_pattern"} ) {
	    while( my( $pattern, $join ) = each %{$self->{"autojoin_pattern"}} ) {
		eval {
		    if( $group =~ /$pattern/i ) {
			$self->join_group( $group, $join );
		    }
		};
	    }
	}
    }
    $self->_set( "states", $group, $item, $state );
}




sub _set {
    my( $self, $base, $group, $item, $state ) = @_;
    my( $grps, $nstat, $update );

    print STDERR "grouping::_set( $base, $group, $item, $state )\n" if( $BigSister::common::dl>6 );
    return if( (defined $self->{$base}->{$group}->{$item}) && ($self->{$base}->{$group}->{$item} == $state) );

    if( $base eq "groups" ) {
	my $event = {
	    "cmd_group" => "metagrouping",
	    "cmd" => "groupstatuschange",
	    "group" => $group,
	    "check" => $item,
	    "statuscode" => $state
	    };
	if( defined $self->{$base}->{$group}->{$item} ) {
	    $event->{"oldstatuscode"} = $self->{$base}->{$group}->{$item};
	}
	$self->post_event( $event, $self->{"current_event"} );
    }

    $self->{$base}->{$group}->{$item} = $state;
    $self->{"isdirty"} = 1;
    $grps = $self->{"groups_by_sub"}->{$group};
    foreach $group (@$grps) {
	$nstat = (defined $self->{"groups"}->{$group}->{$item})?($self->{"groups"}->{$group}->{$item}):100;
	next if( $state == $nstat );
	if( $state < $nstat ) {
	    $self->_set( "groups", $group, $item, $state );
	    if( $self->{"group_status"}->{$group} > $state ) {
		$self->post_event( {
		    "cmd_group" => "metagrouping",
		    "cmd" => "groupsummarychange",
		    "group" => $group,
		    "statuscode" => $state,
		    "oldstatuscode" => $self->{"group_status"}->{$group}
		} , $self->{"current_event"} );
		$self->{"group_status"}->{$group} = $state;
	    }
	}
	else {
	    $self->{"dirty"}->{$group} = 1;
	}
    }
}





sub recompute {
    my( $self ) = @_;
    my( @dirty, $pass, $dirty, $group, $sub, $key, $state, $val, $ref, $minstat );

    $dirty = $self->{"dirty"};
    $pass = 0;
    while( ($pass++<20) && (@dirty = keys %$dirty) ) {
	while( @dirty ) {
	    my %cont;
	    $group = shift @dirty;
	    delete $$dirty{$group};
	    next if( defined $self->{"states"}->{$group} );
	    $self->{"groups"}->{$group} = {};
	    $ref = $self->{"subs_by_group"}->{$group};
	    foreach $sub (@$ref) {
		if( defined $self->{"states"}->{$sub} ) {
		    $val = $self->{"states"}->{$sub};
		} else {
		    $val = $self->{"groups"}->{$sub};
		}
		while( ($key, $state) = each %$val ) {
		    if( (! defined $cont{$key}) || ($state < $cont{$key}) ) {
			$cont{$key} = $state;
		    }
		}
	    }
	    $minstat = 100;
	    while( ($key, $state) = each %cont ) {
		($minstat = $state) if( $state < $minstat );
		$self->_set( "groups", $group, $key, $state );
	    }
	    my $event = {
		"cmd_group" => "metagrouping",
		"cmd" => "groupsummarychange",
		"group" => $group,
		"statuscode" => $minstat
		};
	    if( defined $self->{"group_status"}->{$group} ) {
		$event->{"oldstatuscode"} = $self->{"group_status"}->{$group};
	    }
	    if( (! defined $event->{"oldstatuscode"}) 
	      || ($event->{"oldstatuscode"} != $minstat) ) {
		$self->post_event( $event, $self->{"current_event"} );
	    }
	    if( $minstat == 100 ) { $minstat = $self->get_statuscode("clear"); }
	    $self->{"group_status"}->{$group} = $minstat;
	}
    }
}




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

    if( $self->{"groupdirty"} ) {
        $self->group_write();
    }
    if( keys %{$self->{"dirty"}} ) {
	$self->recompute();
    }
}


############ grouping feature provider ################

sub isingroup {
    my( $self, $sub, $group ) = @_;

    return(undef) if( ! defined( $self->{"subs_by_group"}->{$group} ) );
    return( grep( $_ eq $sub, @{$self->{"subs_by_group"}->{$group}} ) );
}


sub get_title {
    my( $self, $group ) = @_;

    if( defined $self->{"group_titles"}->{$group} ) {
	return( $self->{"group_titles"}->{$group} );
    }
    return "";
}


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

    return( keys %{$self->{"group_titles"}} );
}


sub get_members {
    my( $self, $group ) = @_;

    if( defined $self->{"subs_by_group"}->{$group} ) {
	return( $self->{"subs_by_group"}->{$group} );
    }
    return undef;
}


sub get_groups {
    my( $self, $group ) = @_;

    if( defined $self->{"groups_by_sub"}->{$group} ) {
	return( $self->{"groups_by_sub"}->{$group} );
    }
    return undef;
}


sub get_checks {
    my( $self, $group ) = @_;

    if( defined $self->{"states"}->{$group} ) {
	return( $self->{"states"}->{$group} );
    }
    elsif( defined $self->{"groups"}->{$group} ) {
	return( $self->{"groups"}->{$group} );
    }
    return {};
}


sub group_status {
    my( $self, $group ) = @_;

    return( $self->get_statustext($self->{"group_status"}->{$group}) );
}


sub read_groups {
    my( $self, $file ) = @_;

    ($file = $self->{"groupfile"}) unless( $file );
    open( _IN, "<$file" );
    while( <_IN> ) {
	chomp;
	/^displayname (.*?) (.*)$/ && $self->displayname( $1, $2 );
	if( /^(join|leave) (.*?) (.*)$/ ) {
	    my( $cmd, $group, $groups ) = ($1,$2,$3);
	    my $sng;
	    foreach $sng (split( " ", $groups )) {
		if( $cmd eq "join" ) {
		    $self->join_group( $group, $sng );
		}
		else {
		    $self->leave_group( $group, $sng );
		}
	    }
	}
    }
    close( _IN );
}



sub isgroup {
    my( $self, $group ) = @_;
    return( (defined $self->{"groups"}->{$group}) && (! defined $self->{"states"}->{$group} ) );
} 
    

1;
