eval 'exec perl -x $0 ${1+"$@"}' # -*-perl-*-
  if 0;
#!perl -w
#
# ======================================================================
# This file is Copyright 1998,1999 by the Purdue Research Foundation and
# may only be used under license.  For terms of the license, see the
# file named COPYRIGHT included with this software release.
# AAFID is a trademark of the Purdue Research Foundation.
# All rights reserved.
# ======================================================================
#
# aafid2: GUI for the AAFID prototype.
# AAFID project, COAST Laboratory, CERIAS, 1998-1999.
# 
# Diego Zamboni, May 05, 1998.
#
# $Id: aafid2.pm,v 1.13 1999/09/03 17:08:55 zamboni Exp $
#
# NOTE: This file is in Perl's POD format. For more information, see the 
#       manual page for perlpod(1).
#

package AAFID::GUI::aafid2;

use strict;
use Tk;
use Tk::Dialog;
use Tk::DialogBox;
use Tk::FileSelect;
use Tk::LabEntry;
use IO::File;
use IO::Handle;
use File::Basename;
use Sys::Hostname;
use AAFID::Config;
use Data::Dumper;
use Cwd;

use vars qw($to_mon
	    $from_mon
	    $MW
	    $messages_text
	    $hosts_text
	    $commands_text
	    @active_ids
	    %active_hosts
	    %host_status
	    @ISA
	    $DEBUG
	    @Agent_paths
	    $addwindow
	    %Pixmaps
	    $pixdir
	    $lastdir
	    $terminating
	    %pending_updates
	   );

######################################################################
# Code for building the GUI components
######################################################################

sub menu_bar {
  # Get the parent
  my ($p)=shift;
  my $c;
  # Create the menu bar
  my $menubar=$p->Frame(-relief => 'raised', -borderwidth => 2)
    ->pack(-side => 'top',
	   -fill => 'x',
	  );
  # Create the menus
  my $menubar_file=$menubar->Menubutton(-text => 'File',
					-underline => 0,
					-menuitems =>
     [
      [Button => '~Read commands file',  -command => \&read_cmds_file],
      [Separator => ''],
      [Button => 'E~xit',                -command => \&quit]
     ]);
  my $menubar_control=$menubar->Menubutton(-text => 'Control',
					   -underline => 0,
					   -menuitems =>
     [
      [Button => 'Start local ~monitor & transceiver', 
       -command => \&start_local_monitor],
      [Button => 'Add ~hosts',           -command => \&add_host],
      [Button => 'Start ~agents',        -command => \&start_agents],
     ]);
  my $menubar_view=$menubar->Menubutton(-text => 'View',
				        -underline => 0,
				        -menuitems =>
     [
      [Button => 'Redraw host view',
       -command => \&redraw_host_view]
     ]);
  my $menubar_help=$menubar->Menubutton(-text => 'Help',
					-underline => 0,
					-menuitems =>
     [
      [Button => 'About',               -command => \&help_about]
     ]);
  # Pack the menus
  $menubar_file->pack(-side => 'left');
  $menubar_control->pack(-side => 'left');
  $menubar_view->pack(-side => 'left');
  $menubar_help->pack(-side => 'right');

  return $menubar;
}

sub hosts_frame {
  # Get parent
  my ($p)=shift;
  my $f=$p->Frame;
  $f->Label(-text => 'Hosts')->pack(-side => 'top', -fill => 'x');
  ($hosts_text=$f->Scrolled('Text', -scrollbars => 'osoe',
			    -wrap => 'char',
			    -state => 'disabled'
		       ))
    ->pack(-fill => 'both', -expand => 'yes');

  return $f;
}

sub messages_frame {
  my ($p)=shift;
  my $f=$p->Frame;
  $f->Label(-text => 'Messages')->pack(-side => 'top', -fill => 'x');
  ($messages_text=$f->Scrolled('Text', -height => 7, -wrap => 'none',
			       -scrollbars => 'osoe'))
    ->pack(-fill => 'x', -expand => 'yes');
  
  return $f;
}

sub command_frame {
  my ($p)=shift;
  my $f=$p->Frame;
  $f->Label(-text => 'Commands to the monitor')->pack(-side => 'top', -fill => 'x');
  ($commands_text=$f->Entry())->pack(-fill => 'x');
  $commands_text->bind('<Return>', \&process_command_from_user);
  return $f;
}

sub main_window {
  $MW=MainWindow->new(-title => 'AAFID-GUI-2.0');
  menu_bar($MW);
  messages_frame($MW)->pack(-side => 'bottom', 
			      -fill => 'x');
  command_frame($MW)->pack(-side => 'bottom', -fill => 'x');
  hosts_frame($MW)->pack(-fill => 'both', 
			   -expand => 'yes');
  $MW->repeat(1000, \&request_entities);
  $MW->protocol('WM_DESTROY_WINDOW' => \&quit);
  $Pixmaps{term}=$MW->Pixmap(-file => "$pixdir/NeXTterm.xpm");
}

######################################################################
# Action code
######################################################################

sub start_local_monitor {
  if ($to_mon) {
    error("A monitor is already active.");
    return;
  }
  # Create two pipes, one for writing to it and one for reading from it.
  pipe RDRPAR, WRTCHILD
    or (error("Error creating pipe: $@"), return);
  pipe RDRCHILD, WRTPAR
    or (error("Error creating pipe: $@"), return);
  # Fork a process.
  my $pid;
  if (defined($pid=fork)) {
    if ($pid) {
      # In the parent...
      # Close the child's ends of the pipes
      close (WRTCHILD)
	or (error("Error closing in parent: $!"), return);
      close (RDRCHILD)
	or (error("Error closing in parent: $!"), return);
      # Construct IO::File objects around our ends of the pipe.
      $from_mon=IO::File->new_from_fd(\*RDRPAR, "r");
      $to_mon=IO::File->new_from_fd(\*WRTPAR, "w");
      $to_mon->autoflush(1);
      if (!$to_mon || !$from_mon) {
	error("Error creating IO::File objects in parent: $!");
	return;
      }
      # Set up an event handler on the read file handle.
      $MW->fileevent($from_mon, 'readable' => \&input_from_mon);
      # Set up our event handler for managing the messages.
      Comm::Reactor::add_handle($from_mon, \&_input_from_mon);
      # Start a local transceiver.
      my $localhost;
      ($localhost=hostname) =~ s/\..*$//;
      send_msg_to_mon(TYPE	=> 'COMMAND',
		      SUBTYPE	=> 'STARTTRANSCEIVER',
		      DATA	=> "Host => '$localhost'"
		     );
      return;
    }
    else {
      # In the child...
      # Create the new monitor.
      my $mon=AAFID::Monitor->new;
      # Close the parent's ends of the pipes;
      close (WRTPAR)
	or (error("Error closing in parent: $!"), return);
      close (RDRPAR)
	or (error("Error closing in parent: $!"), return);
      # Then redirect STDOUT and STDIN to the pipes. Make the writing
      # end non-buffered.
      # Obscure perl hackery. See PerlBook, p. 211
      select((select(WRTCHILD), $|=1)[0]);
      open (STDOUT, ">&WRTCHILD") or die "open failed in child: $!";
      select((select(STDOUT), $|=1)[0]);
      open (STDIN, "<&RDRCHILD") or die "open failed in child: $!";
      # Now we call the monitor's run() method. When it returns,
      # we die.
      $mon->run;
      exit(0);
    }
  }
  else {
    error("Fork failed: $!");
    return;
  }
}

# "Adding a host" means "start a transceiver on a host". We ask for
# the host, and ask the local monitor to start a transceiver there
# for us.
sub add_host {
  if (!$to_mon) {
    error("You must start a local monitor first.");
    return;
  }
  my $host="";
  my $dialog=$MW->DialogBox(-title => 'Add hosts',
			    -buttons => ['OK', 'Cancel']
			   );
  my $f=$dialog->add('Frame');
#  my $pix=$f->Pixmap(-file => 'NeXTterm.xpm');
  $f->Label(-image => $Pixmaps{term})->pack(-side => 'left', -fill => 'y');
  my $e=$f->LabEntry(-label => 'Please enter host names, separated by spaces', 
		     -textvariable => \$host)->pack;
  $e->focus;
  $f->pack;
  my $button=$dialog->Show;
  return if ($button eq 'Cancel');
  add_hosts($host);
}

sub add_hosts {
  my @hosts=@_;
  my $host;
  foreach $host (@hosts) {
    foreach (split /\s+/, $host) {
      send_command_to_mon("starttransceiver Host => '$_'");
    }
  }
  # To keep Perl happy when loading a config file.
  1;
}

sub help_about {
  my $dialog=$MW->DialogBox(-title => 'About this program',
			    -buttons => ['OK']
			   );
  my $f=$dialog->add('Frame');
  $f->Label(-text => 'AAFID2 GUI written by Diego Zamboni.

Please send comments, bug reports, questions and suggestions to aafid-feedback@cs.purdue.edu.',
	   -wraplength => '4i')
    ->pack(-side => 'top', -fill => 'both');
  $f->pack(-expand => 'yes', -fill => 'both');
  $dialog->Show;
}

sub quit {
  if ($to_mon) {
    $terminating=1;
    send_msg_to_mon(TYPE => 'STOP');
    # TODO: This should be fixed in a better way, but for now, forcefully exit
    # after waiting 10 seconds. 
    $MW->after(10000, sub { exit(1); });
    $MW->Dialog (
		 -title	=> 'Terminating',
		 -text	=> "Waiting for Monitor to terminate, please wait...",
		 -bitmap	=> 'error',
		 -buttons => [],
		 -wraplength	=> '4i',
	      )->Show;
  }
  else {
    exit(0);
  }
}

# Clean all data structures
sub cleanup_mess {
  $MW->fileevent($from_mon, 'readable' => '');
  close($from_mon);
  close($to_mon);
  undef($from_mon);
  undef($to_mon);
  foreach (keys %host_status) {
    delete_host($_);
  }
  undef @active_ids;
  undef %active_hosts;
  undef %host_status;
  $hosts_text->delete('1.0', 'end');
}

# Process input from the monitor
sub input_from_mon {
  # When we get here, we know that there is input in $from_mon,
  # so we just call the Reactor loop that will call our appropriate
  # callbacks.
  Comm::Reactor::flush();
# This was needed at some point to make it receive some message correctly,
# but now it seems to be working correctly with flush(), which is much
# more elegant.
#  Log("Calling reactor::_read\n");
#  Comm::Reactor::_read($from_mon);
}

# This is the routine that actually processes the messages.
sub _input_from_mon {
  my ($fh, $line)=@_;

#  Log("Got from mon: $line\n");
  if (!defined($line)) {
    cleanup_mess;
    error("Monitor died.");
  }
  else {
    my $msg=AAFID::Message->new($line);
    my $result;
    if ($msg) {
      my $msgtype=uc($msg->{TYPE});
#      Log("Trying to call message_$msgtype\n");
      $result=eval "message_$msgtype(\$msg)";
      if ($@) {
	Log("Error from eval: $@\n");
	$result=message_NoSuchMessage($msg);
      }
      if ($result) {
	message($line);
      }
    }
  }
}

sub process_command_from_user {
#  send_command_to_mon($commands_text->get());
  send_msg_to_mon($commands_text->get());
  $commands_text->delete(0, 'end');
}

sub send_command_to_mon {
  my @cmd=split(/\s+/, shift, 2);
  send_msg_to_mon(TYPE 	=> 'COMMAND',
		  SUBTYPE 	=> $cmd[0],
		  DATA		=> $cmd[1],
		  FROM		=> 'me',
		 );
}

sub send_msg_to_mon {
  my $msg=AAFID::Message->new(@_);
  if ($msg) {
    if ($to_mon) {
      if (uc($msg->{SUBTYPE}) ne "LISTENTITIES") {
	Log("Sending to monitor: ".$msg->toString()."\n");
      }
      Comm::Reactor::send_message($to_mon, $msg->toString()."\n");
      Comm::Reactor::flush();
    }
    else {
      error("No monitor active.");
    }
  }
}

sub request_entities {
  if ($to_mon) {
    send_command_to_mon("listentities");
  }
}

# Retrieve the colors for the host, according to its status.
sub get_colors {
  my $status=shift;
  my @colorlist=('green',
		 'yellow',
		 'yellow',
		 'yellow',
		 'orange',
		 'orange',
		 'orange',
		 'red',
		 'red',
		 'red',
		 'red',
		);
  if ($status<0) {
    return ('grey','black');
  }
  my $bg=$colorlist[$status];
  my $fg;
  if ($status<11) {
    $fg='black';
  }
  else {
    $fg='white';
  }
  return ($bg, $fg);
}

sub check_and_initialize_host_status {
  my ($id, $host)=@_;
  if (!$id) {
    Log("Error: no id given.\n");
    return;
  }
  if (!$host) {
    $host=AAFID::Entity::breakID($id)->{Host};
  }
  if (!exists($host_status{$id})) {
    $host_status{$id}->{Agents}={};
    $host_status{$id}->{Host}=$host;
    $host_status{$id}->{Status}=-1;
    # This will contain a reference to a list
    # [$button, $button_text, $button_image]
    $host_status{$id}->{Button}=undef;
    # Whether the cursor is in the icon or not.
    $host_status{$id}->{CursorIn}=0;
    $active_hosts{$host}=$id;
  }
}

# This subroutine updates the data structure that contains the information
# about the hosts.
sub update_hosts {
  my $id;
  # First, mark all of them as "inactive"
  foreach $id (keys %host_status) {
    $host_status{$id}->{Inactive}=1;
  }
  # Now go through the active ids, unsetting the inactive flag as we go by,
  # and updating all the other information.
  foreach $id (@active_ids) {
    check_and_initialize_host_status($id);
    # Clear the inactive flag.
    $host_status{$id}->{Inactive}=0;
  }
}

# Delete an id from the data structures
sub delete_host {
  my $id=shift or return;
  my $button_list=$host_status{$id}->{Button};
  if ($button_list) {
    foreach (0 .. (@{$button_list}-1)) {
      undef $button_list->[$_];
    }
  }
  delete $active_hosts{$host_status{$id}->{Host}};
  undef %{$host_status{$id}};
  delete $host_status{$id};
}

# This subroutine updates the display of the hosts status
sub update_hosts_text {
  my $host;
  my $id;
  my $position=0;
  # Update the host window with the new information.
  $hosts_text->configure(-state => 'normal');
  # Notice that we do the traversal by keys in the hash, not by
  # the @active_ids array, in order to be able to remove inactive
  # elements.
  my @all_ids=sort keys %host_status;
 ID_LOOP: foreach $id (@all_ids) {
    $host=$host_status{$id}->{Host};
    # Check if the host is now inactive.
    if ($host_status{$id}->{Inactive}) {
      Log("Entity $id is inactive, deleting.\n");
      # Delete from the text window
      $hosts_text->delete("1.0+$ {position}chars");
      # Delete from the data structures.
      delete_host($id);
      # Notice, we do not update $position.
      next ID_LOOP;
    }
    #    my $pixmap=$hosts_text->Pixmap(-file => 'NeXTterm.xpm');
    my $button_list=$host_status{$id}->{Button};
    my $button;
    my $button_text;
    my $button_image;
    my $button_update;
    if ($button_list) {
      ($button, $button_text, $button_image, $button_update)=@{$button_list};
      &$button_update;
    }
    else {
      $button=$hosts_text->Frame();
      $button_text = $button->Label(-text => $host);
      $button_image=$button->Label(-image => $Pixmaps{term});
      # Put it in the text box.
      $hosts_text->window('create', "1.0+$ {position}chars", 
			  -window => $button,
			  -padx => '2', -pady => '2'
			 );

      $button_text->pack(-side => 'bottom', -fill => 'x');
      $button_image->pack;

      # Make sub_update a global subroutine, and define each button's
      # update field as a short subroutine that calls the global one
      # with the appropriate parameters.
      
      my $sub_update=sub { my $in=$host_status{$id}->{CursorIn};
			   my ($bgcolor, $fgcolor)=
			     get_colors($host_status{$id}->{Status});
			   my @bold=(-background => $bgcolor,
				     -foreground => $fgcolor,
				    );
			   my @normal=(-background => $bgcolor, 
				       -foreground => $fgcolor
				      );
			   my @color=$in?(@bold):(@normal);
			   $button_text->configure(@color);
			   $button_image->configure(@color);
			   if ($in) {
			     $button->configure(@color,
						-relief => 'raised',
						-borderwidth => 2
					       );
			   }
			   else {
			     $button->configure(@color,
						-relief => 'flat',
						-borderwidth => 2
					       );
			   }
			 };
      my $sub_activate=sub { $host_status{$id}->{CursorIn}=1;
			     &$sub_update;
			   };
      my $sub_deactivate=sub { $host_status{$id}->{CursorIn}=0;
			       &$sub_update;
			     };

      $button_update=$sub_update;

      # Store it for future references
      $host_status{$id}->{Button}=
	[$button, $button_text, $button_image, $sub_update];

      &$sub_update;

      my $popup=$button->Menu(-tearoff => 'no');
      my $popup_post = 
	sub { my ($w, $x, $y, $host)=@_;
	      my @agents=get_agent_list();
	      $popup->delete(0, 'end');
	      $popup->AddItems(
			       [Button => $host, -state => 'disabled'],
			       [Separator => ''],
			       [Button => 'View details',  
				-command => sub {show_host($id)}],
			       [Cascade => 'Add agent',    
				-tearoff => 'no', -menuitems =>
				[
				 map(
				     [Button => $_,
				      -command => [sub {
						     run_agents([shift], shift)
						   },
						   $host, $_]
				     ],
				     @agents
				    )
				]
			       ],
			       [Separator => ''],
			       [Button => 'Remove',
				-command => sub {
				  send_command_to_mon("kill Entity => '$id'");
				}]
			      );
	      $popup->Post($x,$y);
	    };
      my $popup_unpost = sub { $popup->Unpost;
			     };
      $button->bind('<Button-1>' => sub {show_host($id)});
      $button_text->bind('<Button-1>' => sub {show_host($id)});
      $button_image->bind('<Button-1>' => sub {show_host($id)});
      $button->bind('<ButtonPress-3>' => [$popup_post, Ev('X'), Ev('Y'), $host]);
      $button->bind('<ButtonRelease-3>' => $popup_unpost);
      $button_text->bind('<ButtonPress-3>' => [$popup_post, Ev('X'), Ev('Y'), $host]);
      $button_text->bind('<ButtonRelease-3>' => $popup_unpost);
      $button_image->bind('<ButtonPress-3>' => [$popup_post, Ev('X'), Ev('Y'), $host]);
      $button_image->bind('<ButtonRelease-3>' => $popup_unpost);
      $button->bind('<Any-Enter>' => $sub_activate);
      $button->bind('<Any-Leave>' => $sub_deactivate);
    }
    
    $position++;

  }

  $hosts_text->configure(-state => 'disabled');
}

# Delete the host view and redraw it.
sub redraw_host_view {
  # Delete text widget and destroy all host buttons.
  $hosts_text->delete('1.0', 'end');
  my $id;
  foreach $id (keys %host_status) {
    my $button_list=$host_status{$id}->{Button};
    if ($button_list) {
      foreach (0 .. (@{$button_list}-1)) {
	undef $button_list->[$_];
      }
      $host_status{$id}->{Button} = undef;
    }
  }
  # update
  update_hosts;
  update_hosts_text;
}

sub show_host {
  my $id=shift;
  my $host=$host_status{$id}->{Host};
  my $status=$host_status{$id}->{Status};
  if (exists($host_status{$id}->{Window})) {
    $host_status{$id}->{Window}->{Top}->focusForce;
    return;
  }
  my $w=$MW->Toplevel(-title => $host);
  my ($bg, $fg)=get_colors($status);
  my $l=$w->Frame(-bg => $bg, -fg => $fg)
    ->pack(-side => 'left', -fill => 'y');
#  my $pix=$l->Pixmap(-file => 'NeXTterm.xpm');
  my $l1;
  my $l2;
  ($l1=$l->Label(-image => $Pixmaps{term}, -bg => $bg, -fg => $fg))
    ->pack(-side => 'top');
  ($l2=$l->Label(-text => $host, -bg => $bg, -fg => $fg))
    ->pack(-side => 'top');
  my $dataframe=$w->Frame->pack(-fill => 'both', -expand => 'yes');
  $dataframe->Label(-text => "Transceiver: $id", -justify => 'left')
    ->pack(-side => 'top');
  $dataframe->Label(-text => 'Agents:', -justify => 'left')
    ->pack(-side => 'top');
  $dataframe->Button(-text => 'Dismiss',
		     -command => 
		       sub {$w->destroy;
			    foreach (keys %{$host_status{$id}->{Window}}) {
			      delete $host_status{$id}->{Window}->{$_};
			    }
			    delete $host_status{$id}->{Window};
			  },
		    )->pack(-side => 'bottom', -fill => 'x');
  my $agentcmdframe=$dataframe->Frame->pack(-fill => 'x', -side => 'bottom');
  my $agentcmdentry=$agentcmdframe->Entry(-state => 'disabled')
    ->pack(-fill => 'x', -side => 'bottom');
  my $agentname=$agentcmdframe->Label(-text => 'Command for <unselected>', 
				      -justify => 'left', -anchor => 'w')
    ->pack(-side => 'bottom', -fill => 'x', -expand => 'yes');
  my $agentlist=$dataframe->Scrolled('Text', -scrollbars => 'osoe',
				     -width => '55',
				     -height => '10',
				     -wrap => 'none'
				    )
    ->pack(-fill => 'both', -expand => 'yes');
  $host_status{$id}->{Window}->{Top}=$w;
  $host_status{$id}->{Window}->{List}=$agentlist;
  $host_status{$id}->{Window}->{Backs}=[$l, $l1, $l2];
  $host_status{$id}->{Window}->{AgName}=$agentname;
  $host_status{$id}->{Window}->{AgCmd}=$agentcmdentry;
  $w->focusForce;
  update_agentlist($id);
}

sub update_agentlist {
  my $id=shift;
  my $status=$host_status{$id}->{Status};
  my @agents=keys(%{$host_status{$id}->{Agents}});
  my $a;
  if (!exists($host_status{$id}->{Window})) {
    return;
  }
  my $w=$host_status{$id}->{Window}->{Top};
  my $list=$host_status{$id}->{Window}->{List};
  $list->configure(-state => 'normal');
  $list->delete("1.0", 'end');
  foreach $a (@agents) {
    my $ag_status=$host_status{$id}->{Agents}->{$a}->{Status};
    my $ag_msg=$host_status{$id}->{Agents}->{$a}->{Message};
    my ($bg, $fg)=get_colors($ag_status);
    $list->tagConfigure($a, -background => $bg, -foreground => $fg,
			-borderwidth => 2
		       );
    $list->tagBind($a, '<1>' => 
 		   sub {
 		     my $agname=$host_status{$id}->{Window}->{AgName};
 		     my $agcmd=$host_status{$id}->{Window}->{AgCmd};
 		     $agname->configure(-text => "Command for $a:");
 		     $agcmd->configure(-state => 'normal');
 		     $agcmd->bind('<Return>' => 
				  sub {
				    my $cmd=$agcmd->get;
				    my @cmd=split(/\s+/, $cmd, 2);
				    return if !@cmd;
				    send_msg_to_mon(TYPE => 'COMMAND',
						    SUBTYPE => $cmd[0],
						    TO => $a,
						    DATA => $cmd[1]
						   );
				    if (lc($cmd) eq 'stop') {
				      delete $host_status{$id}->{Agents}->{$a};
				      update_status($id);
				      update_hosts;
				      update_hosts_text;
				      update_agentlist($id);
				    }
				    $agcmd->delete("0", 'end');
				  }
				 );
		     foreach (@agents) {
		       my ($bg, $fg)=get_colors($host_status{$id}
						->{Agents}->{$_}->{Status});
		       $list->tagConfigure($_, -relief => 'flat',
					   -background => $bg,
					   -foreground => $fg
					  );
		     }
                     if ($bg ne 'yellow') {
		         $list->tagConfigure($a, -relief => 'sunken',
					     -background => "dark$bg",
					     -foreground => 'white',
					    );
                     } else {
		         $list->tagConfigure($a, -relief => 'sunken',
					     -background => "gold",
					     -foreground => 'white',
					    );
                     }
 		   });
    my $text=sprintf "%-35s %2d (%-s)\n", $a, $ag_status, $ag_msg;
    $list->insert('end', $text, $a);
  }
  my ($bg, $fg)=get_colors($status);
  foreach (@{$host_status{$id}->{Window}->{Backs}}) {
    $_->configure(-bg => $bg, -fg => $fg);
  }
  $list->configure(-state => 'disabled');
}

sub start_agents {
  my $id;
  my $host;
  my @ids;
  my @hosts;
  if (defined($addwindow)) {
    $addwindow->focusForce;
    return;
  }
  if (!@_) {
    @hosts=pick_hosts();
    return if !@hosts;
    Log("Hosts chosen: @hosts\n");
    foreach (@hosts) {
      my $id=$active_hosts{$_};
      if (!$id) {
	error("Invalid id for $host: '$id', ignoring.");
	next;
      }
      push @ids, $id;
    }
    if (!@ids) {
      error("Empty list of IDs obtained from host names.");
      return;
    }
  }
  else {
    @ids=@_;
    foreach $id (@ids) {
      my $brokenID=AAFID::Entity::breakID($id);
      if (!$brokenID) {
	error("Invalid id: $id, ignoring");
	next;
      }
      push @hosts, $brokenID->{Host};
    }
    if (!@hosts) {
      error("Empty list of host names obtained from IDs.");
      return;
    }
  }
  my @agents=get_agent_list();

  my $w=$MW->Toplevel(-title => "Select agents");

  $w->Label(-text => "Please select the agents you want to start on ".
	             join(", ", @hosts),
	    -wraplength => '4i',
	    -relief => 'groove',
	   )
    ->pack(-side => 'top', -fill => 'x', -expand => 'yes');

  my $buttons=$w->Frame;
  $buttons->pack(-side => 'bottom', -fill => 'x', -ipady => 3, -ipadx => 3);

  my $middle=$w->Frame;
  $middle->pack(-expand => 'yes', -fill => 'both');

  $middle->Label(-text => 'Available agents:', -anchor => 'w')
    ->pack(-side => 'top', -fill => 'x');
  my $list=$middle->Scrolled('Listbox', -scrollbars => 'oe',
			     -selectmode => 'extended');
  $list->insert('end', @agents);
  $list->pack(-expand => 'yes', -fill => 'both');

  $list->bind('<Double-1>' => 
	         sub {
		   my @sel=$list->curselection();
		   run_agents(\@hosts, @agents[@sel]);
		   $w->destroy;
		   undef $addwindow;
		 }
	     );
  $buttons->Button(-text => 'Run agents', 
		   -command => 
		     sub {
		       my @sel=$list->curselection();
		       run_agents(\@hosts, @agents[@sel]);
		       $w->destroy;
		       undef $addwindow;
		     })
    ->pack(-side => 'left', -expand => 1);
  $buttons->Button(-text => 'Cancel',
		   -command => sub { $w->destroy;
				     undef $addwindow;
				   })
    ->pack(-side => 'left', -expand => 1);
  $addwindow=$w;
}

# Return all the agents available.
sub get_agent_list {
  my $p;
  my @l;
  foreach $p (@Agent_paths) {
    push @l, map { s/\.pm$//; s!^$p/+!!; $_ } glob("$p/*.pm");
  }
  @l=sort grep { !/^Template/ } @l;
  return @l;
}

sub get_agents_host {
  my @l;
  my $id=shift or return @l;
  my %host_agents=%{$host_status{$id}->{Agents}};
  my $agent_id;
  foreach $agent_id (keys %host_agents) {
    my $brokenID=AAFID::Entity::breakID($agent_id);
    if (!$brokenID) {
      error("Host $id has an invalid agent ID: $agent_id");
      next;
    }
    push @l, $brokenID->{Class};
  }
  return @l;
}

sub pick_hosts {
  my @hosts=sort keys(%active_hosts);
  my $dialog=$MW->DialogBox(-title => 'Host selection',
			    -buttons => ['OK', 'Cancel']
			   );
  my $f=$dialog->add('Frame');
#  my $pix=$f->Pixmap(-file => 'NeXTterm.xpm');
  $f->Label(-image => $Pixmaps{term})->pack(-side => 'left');
  my $f2=$f->Frame->pack(-side => 'right', -expand => 'yes', -fill => 'both');
  $f2->Label(-text => 'Please select hosts where agents will be started',
	     -wraplength => '4i')
    ->pack(-side => 'top', -fill => 'x');
  my $list=$f2->Scrolled('Listbox', -scrollbars => 'oe',
			 -selectmode => 'extended');
  $list->insert('end', @hosts);
  $list->pack(-side => 'bottom', -expand => 'yes', -fill => 'both');
  $f->pack(-expand => 'yes', -fill => 'both');
  my $bt=$dialog->Show;
  return () if $bt eq 'Cancel';
  my @sel=$list->curselection();
#  Log("curselection: @sel\n");
  return @hosts[@sel];
}

# Run requested agents on a specific host
sub run_agents {
  my $hosts=shift or return;
  my @agents=@_;
  print "agents=@agents\n";
  my $a;
  my $host;
  foreach $host (@{$hosts}) {
    foreach $a (@agents) {
      my $ra=ref($a);
      if ($ra) {
	# If it is an array ref, the first element is the name of the
	# agent, and the rest (if any) are initial commands to send to
	# the agent.
	if ($ra eq "ARRAY") {
	  my $agent=shift @$a;
	  my $cmds=Dumper($a);
	  send_command_to_mon("start Class=>'$host:$agent', Commands=>$cmds");
	}
	else {
	  # Reference to non-array, it's something weird.
	  error("Error in call to run_agents: got an $ra reference");
	}
      }
      else {
	# If it's a scalar, assume it is a simple agent name
	send_command_to_mon("start Class => '$host:$a'");
      }
    }
  }
  # This is necessary to keep Perl happy when loading a config file.
  1;
}

# This is just an alias for run_agents
sub start {
  run_agents(@_);
}

sub read_cmds_file {
  if (!$lastdir) {
    $lastdir=getcwd();
#    Log("initial directory: $lastdir\n");
  }
  my $fd=$MW->FileSelect(-directory => $lastdir);
  my $file=$fd->Show;
  if ($file) {
    read_config_file($file);
    $lastdir=dirname($file);
  }
}

######################################################################
# Message and command types
######################################################################

sub message_NoSuchMessage {
  my $msg=shift;
  message("Message type ".$msg->{TYPE}." not recognized.");
  1;
}

sub command_NoSuchCommand {
  my ($msg, %params)=@_;
  message("Command ".$msg->{SUBTYPE}." not defined.");
  1;
}

sub result_NoSuchResult {
  my ($msg, %params)=@_;
  message("Got unknown result: ". $msg->toString );
  1;
}

sub message_CONNECT {
  # Do nothing.
  1;
}

sub message_DISCONNECT {
  # If we end up here, it means that the monitor generated a DISCONNECT
  # message. Thus, if we were expecting that, we exit. Otherwise, just
  # display an error message.
  if ($terminating) {
    Log("Got DISCONNECT message from monitor. Good bye.\n");
    exit(0);
  }
  else {
    Log("Got unexpected DISCONNECT message from monitor!\n");
    cleanup_mess;
    error("Monitor died.");
  }
  1;
}

sub message_COMMAND {
#  Log("In message_COMMAND\n");
  my $msg=shift;
  my $cmd=uc($msg->{SUBTYPE});
  my %paramhash=();
  my $paramstring=$msg->{DATA};
  if ($paramstring) {
    eval "%paramhash=($ {paramstring})";
    if ($@) {
      chomp $@;
      message("Error evaluating command parameters: $@");
      error("Error evaluating command parameters: $@");
      return 1;
    }
  }
#  Log("Trying to call command_$cmd\n");
  my $r=eval "command_$cmd(\$msg, %paramhash)";
  if ($@) {
    Log("Error from eval: $@\n");
    $r=command_NoSuchCommand($msg, %paramhash);
  }
  return $r;
}

sub command_RESULT {
#  Log("In command_RESULT\n");
  my ($msg, %params)=@_;
  # To which command this is this a response.
  my $cmd=uc($params{Command});
  # Try to invoke result_$cmd.
#  Log("Trying to call result_$cmd\n");
  my $r=eval "result_$cmd(\$msg, %params)";
  if ($@) {
    Log("Error from eval: $@\n");
    $r=result_NoSuchResult($msg, %params);
  }
  return $r;
}

sub result_LISTENTITIES {
  my $i;
  my ($msg, %params)=@_;
  # Get the list of active entities reported
  my @entities=splitList($params{Entities});
  # Update our list of hosts.
  my @new_active_ids=();
  foreach (@entities) {
    push @new_active_ids, $_;
  }
  @new_active_ids=sort @new_active_ids;
  # Compare to the existing list, and update the window if we have
  # differences.
  if (@active_ids != @new_active_ids) {
    @active_ids=@new_active_ids;
    update_hosts;
    update_hosts_text;
  }
  for ($i=0; $i<=$#new_active_ids; $i++) {
    my $host=AAFID::Entity::breakID($new_active_ids[$i])->{Host};
    while ($_ = shift @{$pending_updates{$host}}) {
      Log("Doing a pending update for $host.\n");
      &message_STATUS_UPDATE($_);
    }
    if ($active_ids[$i] ne $new_active_ids[$i]) {
      @active_ids=@new_active_ids;
      update_hosts;
      update_hosts_text;
      return 0;
    }
  }
  0;
}

sub message_STATUS_UPDATE {
  my $msg=shift;
  my $fromID=$msg->{FROM};
  my $host=AAFID::Entity::breakID($fromID)->{Host};
  my $hostID=$active_hosts{$host};
  # Store updates for which we don't have the transceiver ID yet.
  if (!$hostID) {
    if (!$pending_updates{$host}) {
      $pending_updates{$host}=[]
    }
    push @{$pending_updates{$host}}, $msg;
    Log("Storing an update for $host.\n");
    return 1;
  }
  my %paramhash=();
  my $paramstring=$msg->{DATA};
  Log("Got STATUS_UPDATE: ". $msg->toString."\n");
  if ($paramstring) {
    eval "%paramhash=($ {paramstring})";
    if ($@) {
      chomp $@;
      message("Error evaluating command parameters: $@");
      error("Error evaluating command parameters: $@");
      return 1;
    }
    unless (exists($paramhash{Status}) && exists($paramhash{Status})) {
      message("Got malformed STATUS_UPDATE: ".$msg->toString());
      return 1;
    }
    $host_status{$hostID}->{Agents}->{$fromID}->{Status}=$paramhash{Status};
    $host_status{$hostID}->{Agents}->{$fromID}->{Message}=$paramhash{Message};
    update_status($hostID);
  }
  else {
    message("Got malformed STATUS_UPDATE: ".$msg->toString());
  }
  my $buttonlist=$host_status{$hostID}->{Button};
  if ($buttonlist) {
    my ($d1, $d2, $d3, $update)=@{$buttonlist};
    &$update;
  }
  else {
    update_hosts;
    update_hosts_text;
  }
  update_agentlist($hostID);
  1;
}

sub update_status {
  my $hostID=shift;
  # Update overall status. For now, just take the max.
  my $max_status=-1;
  foreach (keys(%{$host_status{$hostID}->{Agents}})) {
    if ($host_status{$hostID}->{Agents}->{$_}->{Status} > $max_status) {
      $max_status=$host_status{$hostID}->{Agents}->{$_}->{Status};
    }
  }
  $host_status{$hostID}->{Status}=$max_status;
#  Log("Updated status of $hostID.\n");
}

######################################################################
# Miscellaneous
######################################################################

sub not_impl {
  my $subr=(caller(1))[3];
  error("Sorry, $subr not implemented yet.");
}

sub message {
  my $line=shift;
  chomp $line;
  $messages_text->insert('end', "$line\n");
  $messages_text->yview(moveto => 1);
}

sub Log {
  if ($DEBUG) {
    print @_;
  }
}

sub error {
  $MW->Dialog (
	       -title	=> 'Error',
	       -text	=> shift,
	       -bitmap	=> 'error',
	       -buttons	=> ['Dandy'],
	       -wraplength	=> '4i',
	      )->Show;
}

######################################################################
# Initialization and configuration
######################################################################

sub initialize {
  my %c=AAFID::Config::configure();

  push @INC, $c{classdir}, $c{agentsdir};
  @Agent_paths=($c{agentsdir});
  use AAFID::Monitor;
  use AAFID::Message;
  use AAFID::Common;
  $DEBUG=1;
  $pixdir=$c{pixmapdir} || "$c{basedir}/lib/pixmaps";
  # Data::Dumper parameters
  $Data::Dumper::Terse=1;
  $Data::Dumper::Indent=0;
  # This is to put the host name in the cache of Sys::Hostname.
  my $hostname=hostname();
}

sub read_config_file {
  foreach (@_) {
    Log("Reading config file $_...\n");
    unless (do "$_") {
      die "Error in config file $_: $@\n";
      quit();
    }
  }
}

######################################################################
# Main program
######################################################################

#initialize;
#main_window;
#read_config_file(@ARGV);
#MainLoop;

1;

#
# $Log: aafid2.pm,v $
# Revision 1.13  1999/09/03 17:08:55  zamboni
# Changed the start line to something that is path-independent, and
# updated the copyright notice.
#
# Revision 1.12  1999/06/28 21:22:06  zamboni
# Merged with a07-port-to-linux
#
# Revision 1.11.4.1  1999/06/28 18:46:34  zamboni
# - Added support to run_agents() for the extended format that allows
#   the specification of multiple agents per host, and also to send
#   initial commands to each agent. The format is:
# 	run_agents(['host1', 'host2', ...],
# 		   agentspec, ...);
#   where agentspec is either
# 	'agentname'
#   or
# 	['agentname', 'command1 params1', 'command2 params2',...]
#   both types of agentspec can be combined in a single call.
# - Added stat() as an alias for run_agents().
# - Made add_hosts and and run_agents return 1 so that they cause config
#   files to load correctly.
#
# Revision 1.11  1999/03/29 22:33:28  zamboni
# Merged branch a05-new-comm-module, which updates it to make use of the new event-based communication mechanism.
#
# Revision 1.10.2.2  1999/03/29 18:35:44  zamboni
# - Added a timeout on the exit so that the GUI is killed after 10
#   seconds, no matter if the monitor has responded. This is a hack
#   around a bug in which the monitor is not getting the DISCONNECT
#   message from some remote transceivers. This has to be fixed and
#   then this hack will be removed.
#
# Revision 1.10.2.1  1999/03/29 16:57:32  zamboni
# - Made it work with the new event mechanism.
# - Had to do a hack to make the Reactor event mechanism and the Tk event
#   mechanism coexist. In the GUI, the Tk event mechanism is the one that
#   has the main control. A Tk file event is set on the monitor handle,
#   which calls input_from_mon when there is input on that handle. This
#   subroutine simply calls Comm::Reactor::flush() to actually process
#   the input. The subroutine _input_from_mon is set as a Reactor callback
#   on the monitor handle, so it gets called and processes the input.
# - Modified send_msg_to_mon to use Comm::Reactor::send_message instead
#   of printing directly to the handle. Since the Reactor event loop
#   is never called, we have to call Comm::Reactor::flush manually after
#   this for the message to get sent.
# - Removed some spurious Log messages.
#
# Revision 1.10  1998/09/28 15:39:51  zamboni
# Made some changes in the documentation that were suggested by
# Stephanie Miller on her evaluation of the prototype. Also, applied a
# patch provided by Frederic Dumont that adds some error checking to the
# agents and the initialization of an Entity (it checks for Init
# returning 'undef') and corrects a color in the GUI ("darkyellow"
# apparently does not exist on 8-bit displays). Finally, made the
# default value of LogFile in config/AAFID be /tmp/aafid.log.
# Added Linda Jessen to the CREDITS file.
#
# Revision 1.9  1998/09/09 09:06:44  zamboni
# * aafid2.pm: Fixed more bugs! Added more bugs!
#
# Revision 1.8  1998/09/09 03:02:04  zamboni
# * aafid2.pm:
#   - Fixed countless bugs (created many others for sure).
#   - Added some features.
#   - Improved efficiency a little.
#   - Made it wait for full termination of the monitor (and all
#     its subentities) before exiting.
#   - Added support for removing hosts.
#   - Made the updating of the hosts display much more efficient by
#     avoiding redrawing, and by not recomputing a lot of information
#     for hosts that are already there.
#
# Revision 1.7  1998/09/02 16:02:41  zamboni
# - Moved the Log keyword to the end.
# - Made it remember the last directory when loading a commands file.
# - Made it only update the host display window, instead of recreating it
#   every time. This eliminates the flickering of the host display, and
#   makes things somewhat faster.
#
# Revision 1.6  1998/09/01 21:57:50  zamboni
# - Moved the Log keyword to the end.
# - Made it remember the last directory when loading a commands file.
#
# Revision 1.5  1998/09/01 21:36:46  zamboni
# - Made it use STARTTRANSCEIVER instead of START to start the local
#   transceiver after starting the local monitor.
#
# Revision 1.4  1998/06/29 20:22:28  zamboni
# Added Copyright notice
#
# Revision 1.3  1998/06/27 04:56:06  zamboni
# *** empty log message ***
#
# Revision 1.2  1998/06/27 04:54:02  zamboni
# Added the Help->About box.
#
# Revision 1.1  1998/06/26 21:36:14  zamboni
# Initial revision
#
# Revision 1.1.1.6  1998/05/22 16:25:14  zamboni
# Latest working version. This is the version used for the COAST
# Annual Board Meeting demo on May 1998, and for my quals 2 exam.
#
# Revision 1.1.1.5  1998/05/07 04:06:02  zamboni
# Now allows to send commands to agents from the host view window.
#
# Revision 1.1.1.4  1998/05/06 22:15:41  zamboni
# GUI for starting agents in one host works now.
#
# Revision 1.1.1.3  1998/05/06 18:59:23  zamboni
# More additions. Host window now works.
#
# Revision 1.1.1.2  1998/05/06 09:01:44  zamboni
# Icon display in the host list now works. Also implemented a more complex
# data structure for keeping track of agents in each host, and the overall
# status of each host.
#
# Revision 1.1.1.1  1998/05/06 06:33:44  zamboni
# First semi-working version, with text display of hosts.
#
# Revision 1.1  1998/05/06 00:18:00  zamboni
# Initial revision
#
