package Office;

use strict;

require Idle;
require SendZ;

BEGIN {@main::Ids = (@main::Ids, '$Id: Office.pm,v 1.15 2002/09/28 19:12:06 ingolia Exp $ ');}

return 1;

# Fields:
# heads_file is the name of the file from which office heads are read.
# last_cleaning is the time() at which the office was last cleaned.
# max_idle is the maximum number of minutes for which a user can be
#          idle and still be considered non-idle to be counted against
#          required users for a cleaning.
# min_interval is the minimum number of seconds between office cleanings.
# avg_interval is the average number of seconds between office cleanings.
# required_users is the number of non-idle users who must be in the office
#                to allow office cleaning.

sub new {
    my $proto = shift;
    my $class = ref($proto) || $proto;

    my $self = {};

    my $heads = shift || die('No office head list specified');
    my $people = shift || die('No members and prospectives specified');
    
    $self->{heads_file} = $heads;
    $self->{people_file} = $people;
    $self->{last_cleaning} = 0;
    $self->{max_idle} = 59;
    $self->{min_interval} = 60 * 3600;
    $self->{avg_interval} = 96 * 3600;
    $self->{required_users} = 4;

# Useful values for debugging...
#    $self->{min_interval} = 0;
#    $self->{avg_interval} = 50;
#    $self->{required_users} = 1;

    bless($self, $class);
    return $self;
}


# Return the waiting time before next cleaning, which is the min. interval
#   plus an exponentially distributed random interval s.t. the average
#   total waiting time is the average interval.  The time is returned as
#   a number of seconds.
sub waiting_time {
    my $self = shift;

    my $lambda = $self->{avg_interval} - $self->{min_interval};
# I want an exponentially-distributed random number, mean value lambda.
    return $self->{min_interval} - $lambda * log(1.0 - rand());
}

# Returns true iff there is currently a "meeting cleaning blackout"
sub in_meeting {
    return (`date +%w` == '1') && ((`date +%k` == 19) or (`date +%k` == 20));
}

# If the number of non-idle users in the office meets required_users, then 
#   return a hash whose keys are non-idle users and whose values are refs to 
#   two-element arrays of name of login machine and idle time.
# If the number of non-idle users in the office is less than required_users, 
#   then return an empty list [which is false].
# A user's login is considered non-idle if it is less than max_idle.
sub sufficient_logins {
    my $self = shift;
    my %users;

    foreach my $head ($self->heads()) {
	my %logins;

	eval {
	    %logins = Idle::local_users_idle_times($head);
	} ;
	if ($@) {
	  Err::error("Error fingering $head", $@);
	    next;
	}

	foreach my $user (keys(%logins)) {
	    if ($logins{$user} < $self->{max_idle}
		and (not defined($users{$user})
		     or $users{$user}[1] > $logins{$user})) {
		$users{$user} = [$head, $logins{$user}];
	    }
	}
    }    

    my %people = $self->people();

    foreach my $user (keys(%users)) {
	if (!exists($people{$user})) {
	  SendZ::zdebug("$user not in list of people, ignoring");
	    delete($users{$user});
	    next;
	}		
	
      SendZ::zdebug("$user on $users{$user}->[0] for $users{$user}->[1]");
    }    

    my @users = keys(%users);

    if ($#users >= ($self->{required_users} - 1)) {
	return @users;
    } else {
	return ();
    }
}

# Record the occurrence of an office cleaning.
sub now_cleaned {
    my $self = shift;

    $self->{last_cleaning} = time();
}

# Read office heads from the heads_file list.  Office heads should be specified
#   one per line, with comment lines beginning with # ignored.
sub heads {
    my $self = shift;

    open(HEADS, $self->{heads_file}) 
	or die("Cannot open office heads file $self->{heads_file}: $!");

    my @heads = ();

    while (<HEADS>) {
	chop;
	(/^\#/ or /^$/ or /^-/) and next;
	@heads = (@heads, $_);
    }

    close(HEADS);

  SendZ::zdebug("Office heads from $self->{heads_file} are " 
		. join(', ', @heads));

    return @heads;
}

# Read people from the people_file list.  The first word on every line
#   that does not begin with a # is a username who we count.  Return a
#   hash in which keys are people and values are 1.
sub people {
    my $self = shift;

    open(PEOPLE, $self->{people_file})
	or die("Cannot open people file $self->{people_file}: $!");

    my %people = ();
    my $person;

    while (<PEOPLE>) {
	chop;
	my ($person) = /^(\w+)/ or next;
	$people{$person} = 1;
    }

    return %people;
}
