/* 
 * xFS.java --
 *
 *      java animation of xFS operations.
 *
 * Copyright 1995 Regents of the University of California
 * Permission to use, copy, modify, and distribute this
 * software and its documentation for any purpose and without
 * fee is hereby granted, provided that this copyright
 * notice appears in all copies.  The University of California
 * makes no representations about the suitability of this
 * software for any purpose.  It is provided "as is" without
 * express or implied warranty.
 *
 * rcsid = "$Header: /disks/barad-dur/now/rywang/src/java/classes/xfs/xFS.java,v 1.18 1996/02/10 05:13:57 rywang Exp $ xFS (Berkeley)"
 */

import java.awt.*;
import java.awt.image.*;
import java.net.*;
import java.applet.*;
import java.util.Vector;
import java.util.Hashtable;
import java.util.Enumeration;
import java.io.*;

import ryw.*;


/*
 ***********************************************************************
 * some more low level widgets.
 ***********************************************************************
 */



class XBlob extends Canvas {
    public String label = "";
    public Color  bg    = Color.blue;
    public Color  fg    = Color.yellow;
    int    fontSize     = 24;
    
    
    public XBlob (Dimension d) {
	resize (d);
    }

    public XBlob (Dimension d, String label) {
	this (d);
	this.label = label;
    }

    public XBlob (Dimension d, int label) {
	this (d, Integer.toString (label));
    }

    public XBlob (Dimension d, int label, int fontSize) {
	this (d, Integer.toString (label));
	this.fontSize = fontSize;
    }

    public XBlob (Dimension d, String label, Color bg) {
	this (d, label);
	this.bg = bg;
    }

    public XBlob (Dimension d, String label, Color bg, Color fg) {
	this (d, label, bg);
	this.fg = fg;
    }

    public XBlob (Dimension d, String label, Color bg, Color fg,
		  int fontSize) {
	this (d, label, bg, fg);
	this.fontSize = fontSize;
    }

    public void paint (Graphics g) {
	Dimension size = size ();
	g.setColor (bg);
	g.fill3DRect (0, 0, size.width, size.height, true);
	g.setColor (fg);
	Font theFont = g.getFont ();

	if (fontSize != 0) {
	    theFont = new Font (theFont.getName (),
				theFont.BOLD,
				fontSize);
	    g.setFont (theFont);
	}
	    
	FontMetrics fm = g.getFontMetrics (theFont);
	Rectangle bounds = bounds ();
	g.drawString (label,
		      (bounds.width  - fm.stringWidth  (label)) / 2,
		      (bounds.height - fm.getHeight    ())      / 2 +
		      fm.getHeight ());
    }
}


class XBalloon extends Canvas {
    final     int      defaultFontSize = 14;
    final     int      bigFontSize = 30;
    final     int      resizeInter = 10;
    final     int      resizeInc = 25;
    final     int      resizeSteps = 5;
    final     int      bigSleep = 6000;
    public    String   messages[];
    protected Image    image;
    public    int      fontSize = defaultFontSize;
    public    boolean  skipWords = false;


    public XBalloon (Image image, Dimension d, String messages[]) {
	this.image     = image;
	this.messages  = messages;
	resize (d);
    }

    public XBalloon (Image image, Dimension d, String messages[],
		     int fontSize) {
	this (image, d, messages);
	this.fontSize = fontSize;
    }

    public void paint (Graphics g) {
	
	int xp[] = new int[3];
	int yp[] = new int[3];
	
	Dimension d = size ();
/*	
	g.drawImage (image, 0, 0, d.width, d.height, this);
*/

	xp[0] = 0;       yp[0] = 40;
	xp[1] = 10;      yp[1] = 60;
	xp[2] = 10;      yp[2] = 80;

	g.setColor (Color.red);

/*	
	g.drawPolygon (xp, yp, 6);
*/

	for (int i = 10; i < 15; i++)
	    g.drawRoundRect (i, i, d.width-i-i, d.height-i-i, 50, 50);

	g.fillPolygon (xp, yp, 3);

	g.setColor (Color.blue);
	
	if (skipWords)
	    return;

	Rectangle bounds = bounds ();
	Font font = g.getFont ();
	Font newFont = new Font (font.getName (), font.BOLD, fontSize);
	g.setFont (newFont);
	FontMetrics fm = g.getFontMetrics (newFont);
	int fontHeight = fm.getHeight ();
	int numStrings = messages.length;

	for (int i = 0; i < numStrings; i++) {
	    g.drawString (messages[i],
			  (bounds.width - fm.stringWidth (messages[i])) / 2
			  + 2,
			  (bounds.height - fontHeight * numStrings) / 2 +
			  fontHeight * (i + 1));
	}
    }

    public void expand () {
	Dimension d;

	d = size ();
	d.width  += d.width;
	d.height += d.height;

	resize (d);
    }
}



/**
 * button with a picture and a label.  used to denote a machine.
 */
class XButton extends Canvas {
    protected Image   image;
    protected boolean down;
    protected String  label;

    
    public XButton (String label, Image image) {
	this.label = label;
	this.image = image;
    }

    
    protected String getLabel () {
	return label;
    }

    
    public void paint (Graphics g) {
	Rectangle bounds = bounds ();
	Font font = g.getFont ();
	Font newFont;
	FontMetrics fm;
	String label = getLabel ();
	
	g.setColor (down ? Color.gray : Color.lightGray);
	g.fill3DRect (0, 0, bounds.width, bounds.height, !down);
	
	g.setFont (newFont = new Font (font.getName  (),
				       font.BOLD,
				       36));
	g.setColor (down ? Color.yellow : Color.blue);

	g.drawImage (image, 0, 0, this);

	fm = g.getFontMetrics (newFont);
	g.drawString (label,
		      (bounds.width - fm.stringWidth (label)) / 2 - 6,
		      40);
    }

    
    public void setDown(boolean down) {
	if (down != this.down) {
	    this.down = down;
	    repaint ();
	}
    }

    
    public boolean mouseDown (Event ev, int x, int y) {
	setDown (inside (x, y));
	getParent ().action (ev, "Focus");
	return true;
    }

    
    public boolean mouseDrag(Event ev, int x, int y) {
	return true;
    }

    
    public boolean mouseUp(Event ev, int x, int y) {
	return true;
    }
}



/**
 * panel with further extensions.
 */
class XPanel extends XPanel0 {

    public boolean action (Event evt, Object arg) {
	
	if (evt.target instanceof XButton ||
	    evt.target instanceof Button) {
	    getParent ().action (evt, arg);
	}
	return true;
    }
}



/*
 ***********************************************************************
 * some higher level panels (more xFS specific)
 ***********************************************************************
 */


/**
 * a bunch of buttons: the machine, "Create", "Flush", and "Manager",
 * in a stack.
 */
class HostPanel extends XPanel {
    final  int buttonWidth = 85;
    final  int buttonHeight = 40;
    final  int hostWidth = 85;
    final  int hostHeight = 80;
    final  int fontSize = 24;
    
    public int hostID;
    public String hostName;
    public XButton machineButton;

    
    public HostPanel (int hostID, Image image, boolean hasAction) {
	int y;
	Button but;
	Font theFont;
	
	this.hostID = hostID;
	this.hostName = Integer.toString (hostID);

	machineButton = new XButton (hostName, image);
	add (machineButton, 0, 0, hostWidth, hostHeight);
	y = hostHeight;
	add (but = new Button ("Create"), 0, y, buttonWidth, buttonHeight);
	theFont = new Font ("Dialog",
			    Font.PLAIN,
			    fontSize);
	but.setFont (theFont);
	but.setForeground (Color.blue);
	y += buttonHeight;
	add (but = new Button ("Flush"), 0, y, buttonWidth, buttonHeight);
	but.setFont (theFont);
	but.setForeground (Color.blue);
	y += buttonHeight;
//	add (new Button ("Manager"), 0, y, buttonWidth, buttonHeight);

	if (hasAction) {
	    add (but = new Button ("XTerm"), 0, y, buttonWidth, buttonHeight);
	    but.setFont (theFont);
	    but.setForeground (Color.blue);
	    y += buttonHeight;
	}
	
	resize (new Dimension (buttonWidth, y));
    }
}



/**
 * the space occupied by one machine, including a bunch of buttons,
 * and the empty space around it.  its main purpose is to figure out
 * how to place the "file blocks" in this space.
 */
class MachPanel extends XPanel {
    final     int leftBlkX  = 92;
    final     int topBlkY = 3;
    final     int slope = 3;
    final     int shiftIncrX = 3;
    final     int shiftIncrY = 60;
    
    protected int rightBlkX;
    protected int intercep;
    protected int currX;
    protected int currInter;
    
    public    int     hostID;
    public    XButton machineButton;
    protected int     side;
    public    Vector  dirty;
    public    int     direction;

    
    public MachPanel (Image image, int hostID, int side, int dir, 
		      boolean hasAction) {
	HostPanel hostPanel;
	
	this.hostID = hostID;
	this.side   = side;
	direction   = dir;

	rightBlkX   = side - leftBlkX - 20;
	intercep    = side - 55;
	currX       = 0;
	currInter   = intercep;
	
	add (new XFrame (side, side), 0, 0);
	add (hostPanel = new HostPanel (hostID, image, hasAction), 2, 2);
	machineButton = hostPanel.machineButton;
	resize (new Dimension (side, side));
	dirty = new Vector (16, 16);
    }

    
    public Point topLeft () {
	Point loc = location ();
	return new Point (loc.x + leftBlkX, loc.y + topBlkY);
    }

    
    private int linear (int xoff, int yoff) {
	return slope * xoff + yoff;
    }

    
    private int solve (int intercep, int slope) {
	return -intercep/slope;
    }

    
    /**
     * returns where the next file block is supposed to go.
     * the file blocks are arranged so they form a bunch of rows
     * parallel to the "main diagonal".
     */
    public Point nextBlkLoc () {
	int nextX, nextY, nextInter;
	Point location = location ();
	int x = location.x;
	int y = location.y;

	Point p = new Point (x + currX + leftBlkX,
			     y + linear (currX, currInter) + topBlkY);

	/*
	 * the default next position is to simply move along the
	 * imaginery line whose slope is "slope" and whose y-intercept
	 * is "intercep".
	 */
	nextX = currX + shiftIncrX;
	nextY = linear (nextX, currInter);

	if (nextX <= rightBlkX && nextY <= intercep) {
	    /*
	     * the default next position is still within our
	     * alotted space, we are done!
	     */
	    currX = nextX;
	} else {

	    /*
	     * we are moving out of the alloted space, so we have
	     * to start a new line.
	     */
	    nextInter = currInter - shiftIncrY;
	    if (nextInter < -intercep)
		nextInter = intercep;
	    nextX = 0;
	    nextY = linear (0, nextInter);
	    if (nextY < 0)
		nextX = solve (nextInter, slope);
	    currX = nextX;
	    currInter = nextInter;
	}

	return p;
    }

    
    public boolean action (Event evt, Object arg) {

	if (evt.target instanceof XButton ||
	    evt.target instanceof Button) {
	    getParent ().action (new Event (this, evt.id, evt.arg), arg);
	    return true;
	}

	return super.action (evt, arg);
    }
}



/**
 * one "file block".
 */
class Block extends Button {
    public static final     int       blkWidth  = 40;
    public static final     int       blkHeight = 46;
    final     String    blkFont = "Helvetica";
    public    int       blkID;
    public    boolean   owned;
    public    MachPanel machine;
    

    public Block (String blkName, MachPanel machine) {
	super (blkName);
	this.blkID   = Integer.parseInt (blkName);
	this.machine = machine;
	resize (new Dimension (blkWidth, blkHeight));
	this.setFont (new Font("Dialog", Font.PLAIN, 24));
    }


    public Block (int blkID, MachPanel machine) {
	this (Integer.toString (blkID), machine);
    }


    public Block (int blkID, MachPanel machine, boolean drawn) {
	this (blkID, machine);
    }


    public synchronized void Kill () {
	getParent ().remove (this);
    }
}



/**
 * read, write, or delete checkbox.
 */
class ActionPanel extends XPanel {
    public final static int READ   = 0;
    public final static int WRITE  = 1;
    public final static int DELETE = 2;

    xFSPanel xpanel;

    public ActionPanel (xFSPanel xpanel) {
	this.xpanel = xpanel;
	setLayout (new FlowLayout ());
	CheckboxGroup group = new CheckboxGroup ();
	add (new Checkbox ("Read",   group, true));
	add (new Checkbox ("Write",  group, false));
	add (new Checkbox ("Delete", group, false));
	xpanel.setAction (READ);
    }


    public boolean action (Event evt, Object arg) {
	if (evt.target instanceof Checkbox) {
	    String label = ((Checkbox) evt.target).getLabel ();
	    xpanel.tickerMsg ("switch action to: " + label + ".  ");

	    if (label.equals ("Read")) {
		xpanel.setAction (READ);
	    } else if (label.equals ("Write")) {
		xpanel.setAction (WRITE);
	    } else if (label.equals ("Delete")) {
		xpanel.setAction (DELETE);
	    }
	}
	return true;
    }
}



/**
 * the "top-level" panel.
 */
class xFSPanel extends XPanel {
    final  int netWidth   = 176;
    final  int netHeight  = 113;
    final  int quesWidth  = 32;
    final  int quesHeight = 32;
    final  int balloonWidth  = 120;
    final  int balloonHeight = 170;
    final  int bombWidth  = 27;
    final  int bombHeight = 39;
    final  int spacing    = 4;
    // final  int topOff     = 175;
    final  int topOff     = 160;
    final  int leftOff    = 80;
    final  int bottomOff  = 100;
    final  int titleOff   = 20;
    final  int titleHeight= 50;
    // final  int tickOff    = 10;
    final  int tickOff    = 0;
    // final  int tickHeight = 25;
    final  int tickHeight = 0;
    final  int stripeFragBlks = 2;
    final  int stripeSSGsize  = 4;
    final  int stripeFragSleep = 2000;
    final  int balloonInterSleep = 2000;
    final  int balloonWaitSleep = 3000;
    final  int adjust4 = 115;

    public static final  int NORTH      = 0;
    public static final  int EAST       = 1;
    public static final  int SOUTH      = 2;
    public static final  int WEST       = 3;

    public int numHosts;

    Image      bombImage;
    Image      quesImage;
    Image      balloonImage;
    int        xwidth;
    MachPanel  currMach;
    Counter    blkIDcount;
    MachPanel  machines[];
    Management management;
    Point      netPos;
    Dimension  quesDim;
    Dimension  bombDim;
    XTicker    ticker;
    int        currAction;
    int        stripeSegBlks;
    String     actionServer;
    int        actionPort;
    Dimension  balloonDimension = new Dimension (balloonWidth,
						 balloonHeight);

    XPict      quesPrefetch = null;
    XPict      bombPrefetch = null;

    
    public xFSPanel () {
	blkIDcount = new Counter ();
	currMach = null;
	quesDim = new Dimension (quesWidth, quesHeight);
	bombDim = new Dimension (bombWidth, bombHeight);
    }

    
    public void initialize (int numHosts, int xwidth,
			    Image hostImage, Image myriImage,
			    Image bombImage, Image quesImage,
			    Image balloonImage,
			    String actionServer, int actionPort) {
	int quart;
	int i;
	int side;
	int x;
	int y;
	int wide;
	int high;
	int hosts;
	MachPanel thisMach;
	int tickWidth;
	boolean hasAction;

	this.numHosts  = numHosts;
	this.xwidth    = xwidth;
	this.bombImage = bombImage;
	this.quesImage = quesImage;
	this.balloonImage = balloonImage;
	this.actionServer = actionServer;
	this.actionPort = actionPort;
	hasAction = actionServer != null;

	MediaTracker tracker = new MediaTracker (this);
	tracker.addImage (bombImage,    0);
	tracker.addImage (quesImage,    1);
	tracker.addImage (balloonImage, 2);
	tracker.addImage (hostImage,    3);
	tracker.addImage (myriImage,    4);
	try {
	    tracker.waitForAll ();
	} catch (InterruptedException e) {
	    System.err.println ("unable to load images");
	    return;
	}

	/*
	 * figure out how big things have to be and initialize.
	 */
	quart = numHosts / 4;
	side  = (xwidth - (spacing * quart) - topOff - bottomOff) /
	    (quart + 1);
	if (numHosts == 4)
	    side -= adjust4;
	x = leftOff;
	y = topOff;
	hosts = 0;
	machines = new MachPanel [numHosts];
	stripeSegBlks = (stripeSSGsize - 1) * stripeFragBlks;
	tickWidth = xwidth - topOff - bottomOff;

	/*
	 * title first
	 */
	add (new XBlob (new Dimension (tickWidth, titleHeight),
			"xFS: Serverless File Service",
			Color.lightGray,
			Color.blue,
			24),
	     x, titleOff, tickWidth, titleHeight);

	/*
	 * ticker tape first.
	 */
	/*
	add (ticker = new XTicker (tickWidth, tickHeight),
	     x, titleOff + titleHeight + tickOff,
	     tickWidth, tickHeight);
	ticker.initialize ("initializing... ");
	ticker.start ();
	*/

	/*
	 * checkboxes next
	 */
	add (new ActionPanel (this),
	     x, titleOff + titleHeight + tickOff + tickHeight + 5,
	     tickWidth, 30);
	
	
	/*
	 * arrange all the hosts on the perimeter of a square
	 * in clockwise fashion.
	 * the first loop draws the northern side.
	 */
	for (i = 0; i < quart; i++, hosts++) {

	    add (thisMach = new MachPanel (hostImage, hosts, side,
					   NORTH, hasAction),
		 x, y, side, side);

	    machines[hosts] = thisMach;

	    if (currMach == null) {

		/*
		 * by default, the first machine becomes active.
		 */
		currMach = thisMach;
		thisMach.machineButton.setDown (true);
	    }

	    x += side + spacing;
	}

	wide = x + side;
	if (numHosts == 4) {
	    wide += adjust4;
	    x += adjust4;
	}

	/*
	 * the eastern side.
	 */
	for (i = 0; i < quart; i++, hosts++) {
	    add (thisMach = new MachPanel (hostImage, hosts, side,
					   EAST, hasAction),
		 x, y, side, side);
	    machines[hosts] = thisMach;
	    y += side + spacing;
	}

	high = y + side;
	if (numHosts == 4) {
	    high += adjust4;
	    y += adjust4;
	}

	/*
	 * the southern side.
	 */
	for (i = 0; i < quart; i++, hosts++) {
	    add (thisMach = new MachPanel (hostImage, hosts, side,
					   SOUTH, hasAction),
		 x, y, side, side);
	    machines[hosts] = thisMach;
	    x -= side + spacing;
	}

	if (numHosts == 4)
	    x -= adjust4;

	/*
	 * the western side.
	 */
	for (i = 0; i < quart; i++, hosts++) {
	    add (thisMach = new MachPanel (hostImage, hosts, side,
					   WEST, hasAction),
		 x, y, side, side);
	    machines[hosts] = thisMach;
	    y -= side + spacing;
	}

	if (numHosts == 4)
	    y -= adjust4;

	/*
	 * finally draw the network.
	 */
	x = leftOff+ (wide - leftOff - netWidth) / 2;
	y = topOff + (high - topOff  - netHeight) / 2;

	add (new XPict (myriImage), x, y, netWidth, netHeight);

	netPos = new Point (wide/2, high/2);

	/*
	 * kludge: "prefetch" some pictures.
	add (quesPrefetch = new XPict (quesImage),
	     leftOff, high + 8,
	     quesWidth, quesHeight);
	add (bombPrefetch = new XPict (bombImage),
	     leftOff + quesWidth + 8, high + 8,
	     bombWidth, bombHeight);
	 */

	resize (xwidth, xwidth);

	/*
	 * initialize the managers.
	 */
	management = new Management (machines);

    }


    protected void writeActionStream (int currMach, String action,
				      int destMach, int destBlok) {
	OutputStream outStream;
	PrintStream actionStream;
	Socket actionSock;

	if (actionServer == null)
	    return;

	try {
	    actionSock = new Socket (actionServer, actionPort, true);
	    outStream = actionSock.getOutputStream ();
	    actionStream = new PrintStream (outStream, true);
	    actionStream.println ("xFS command:" + currMach + ":" + 
				  action + ":" + destMach + ":" +
				  destBlok);
	    actionStream.close ();
	    outStream.close ();
	    actionSock.close ();
	} catch (Exception e) {
	    System.err.println ("unable to connect to " + actionServer +
				" at port " + actionPort);
	}
    }


    public void tickerMsg (String msg) {
	// ticker.addString (msg);
    }


    public void setAction (int currAction) {
	this.currAction = currAction;
    }


    /**
     * create a file block.
     */
    protected void createBlock (MachPanel machine) {
	Block block; 
	Point loc;

	/*
	 * create an animation object.
	 */
	XMover mover = new XMover ();

	/*
	 * get a new block ID.
	 */
	int newBlkID = blkIDcount.current ();

	/*
	 * figure out who is the manager for this block.
	 */
	MachPanel manager = management.managerMachine (newBlkID);

	tickerMsg ("creating blk " + newBlkID +
		   ", informing mngr " + manager.hostID + ".  ");

	Component balloon1 = balloon3Msgs (mover, machine,
					   "host " + machine.hostID,
					   "creating",
					   "block " + newBlkID);
	Component balloon2 = balloon3Msgs (mover, manager,
					   "host " + manager.hostID + ",",
					   "manager of",
					   "block " + newBlkID);
	mover.addElement (new XSleepElement (balloonWaitSleep));
	mover.addElement (new CompKillAnimElement (this, balloon1));
	mover.addElement (new CompKillAnimElement (this, balloon2));

	/*
	 * animate a message from the creater to the manager.
	 */
	mover.addElement (new XMoveElement (this, quesImage, quesDim,
					    machine.location (),
					    netPos,
					    manager.location (),
					    true));
	/*
	 * add the new block to this machine.
	 */
	loc = machine.nextBlkLoc ();
	block = new Block (blkIDcount.nextStr (), machine);
	mover.addElement (new BlkAddAnimElement (this, loc, block));

	mover.start ();
	
	/*
	 * update the manager meta data.
	 */
	management.createBlk (block);
	machine.dirty.addElement (block);

	writeActionStream (currMach.hostID, "create",
			   block.machine.hostID, block.blkID);
    }

    
    /**
     * make the machine that generated the event into the
     * current active machine.
     */
    protected void changeCurrentMachine (MachPanel newMachine) {
	if (quesPrefetch != null) {
	    remove (quesPrefetch);
	    quesPrefetch =  null;
	}
	if (bombPrefetch != null) {
	    remove (bombPrefetch);
	    bombPrefetch =  null;
	}
	
	currMach.machineButton.setDown (false);
	currMach = newMachine;
	currMach.machineButton.setDown (true);
    }



    protected Component balloonMsg (XMover mover,
				    MachPanel machine,
				    String messages[]) {
	XBalloon balloon = new XBalloon (balloonImage, balloonDimension,
					 messages);
	mover.addElement (new XAddElement (this, balloon,
					   machine.topLeft (),
					   balloonDimension));

	for (int i = 0; i < balloon.resizeSteps; i++) {
	    mover.addElement (new XSleepElement (balloon.resizeInter));
	    mover.addElement (new BalloonSkipElem (balloon, true));
	    mover.addElement (new ItemResizeElem (balloon,
						  balloon.resizeInc));
	}

	mover.addElement (new BalloonSkipElem (balloon, false));
	mover.addElement (new BalloonFontElem (balloon,
					       balloon.bigFontSize));
	mover.addElement (new ItemResizeElem (balloon,
					      balloon.resizeInc));
	mover.addElement (new XSleepElement (balloon.bigSleep));
	mover.addElement (new BalloonSkipElem (balloon, true));
	mover.addElement (new BalloonFontElem (balloon,
					       balloon.defaultFontSize));
			  
	for (int i = 0; i < balloon.resizeSteps; i++) {
	    mover.addElement (new XSleepElement (balloon.resizeInter));
	    mover.addElement (new ItemResizeElem (balloon,
						  -balloon.resizeInc));
	}
			  
	mover.addElement (new BalloonSkipElem (balloon, false));
	mover.addElement (new ItemResizeElem (balloon,
					      -balloon.resizeInc));
	return balloon;
    }



    protected Component balloon3Msgs (XMover mover,
				      MachPanel machine,
				      String msg1, String msg2, String msg3) {
	String blnMsgs[] = new String[3];
	blnMsgs[0] = msg1;
	blnMsgs[1] = msg2;
	blnMsgs[2] = msg3;
	return balloonMsg (mover, machine, blnMsgs);
    }


    
    protected Component balloon4Msgs (XMover mover,
				      MachPanel machine,
				      String msg1, String msg2,
				      String msg3, String msg4) {
	String blnMsgs[] = new String[4];
	blnMsgs[0] = msg1;
	blnMsgs[1] = msg2;
	blnMsgs[2] = msg3;
	blnMsgs[3] = msg4;
	return balloonMsg (mover, machine, blnMsgs);
    }


    
    protected Component balloon5Msgs (XMover mover,
				      MachPanel machine,
				      String msg1, String msg2,
				      String msg3, String msg4,
				      String msg5) {
	String blnMsgs[] = new String[5];
	blnMsgs[0] = msg1;
	blnMsgs[1] = msg2;
	blnMsgs[2] = msg3;
	blnMsgs[3] = msg4;
	blnMsgs[4] = msg5;
	return balloonMsg (mover, machine, blnMsgs);
    }


    
    protected Component balloon7Msgs (XMover mover,
				      MachPanel machine,
				      String msg1, String msg2,
				      String msg3, String msg4,
				      String msg5, String msg6,
				      String msg7) {
	String blnMsgs[] = new String[7];
	blnMsgs[0] = msg1;
	blnMsgs[1] = msg2;
	blnMsgs[2] = msg3;
	blnMsgs[3] = msg4;
	blnMsgs[4] = msg5;
	blnMsgs[5] = msg6;
	blnMsgs[6] = msg7;
	return balloonMsg (mover, machine, blnMsgs);
    }


    
    protected void killBalloons (XMover mover,
				 Vector balloons) {
	Enumeration blns = balloons.elements ();
	while (blns.hasMoreElements ()) {
	    Component balloon = (Component) blns.nextElement ();
	    mover.addElement (new CompKillAnimElement (this, balloon));
	}
    }



    /**
     * read a block from a peer client.
     */
    protected void readBlock (Block block) {

	/*
	 * if the current machine already has the block, then done.
	 */
	if (management.findMachine (block.blkID, currMach)) {
	    tickerMsg ("host "             + currMach.hostID +
		       " already has blk " + block.blkID + ". ");
	    return;
	}

	Point newBlockDest = currMach.nextBlkLoc ();
	Point newBlockSrc  = block.location ();
	Block newBlock     = new Block (block.blkID, currMach);
	

	/*
	 * figure out who is the manager for this block.
	 */
	MachPanel manager = management.managerMachine (block.blkID);

	tickerMsg ("host "         + currMach.hostID +
		   " reading blk " + block.blkID     +
		   " -> mngr "     + manager.hostID  +
		   " -> host "     + block.machine.hostID + " forwards.  ");

	/*
	 * create an animation object.
	 */
	XMover mover = new XMover ();

	Component balloon1 = balloon3Msgs (mover, currMach,
					   "host " + currMach.hostID,
					   "reading",
					   "block " + block.blkID);
	Component balloon2 = balloon4Msgs (mover, manager,
					   "host " + manager.hostID,
					   "manages",
					   "locations of",
					   "block " + block.blkID);
	Component balloon3 = balloon4Msgs (mover, block.machine,
					   "host " + block.machine.hostID,
					   "will forward",
					   "copy of",
					   "block " + block.blkID);
	mover.addElement (new XSleepElement (balloonWaitSleep));
	mover.addElement (new CompKillAnimElement (this, balloon1));
	mover.addElement (new CompKillAnimElement (this, balloon2));
	mover.addElement (new CompKillAnimElement (this, balloon3));

	/*
	 * animate a message from the reader to the manager.
	 */
	mover.addElement (new XMoveElement (this, quesImage, quesDim,
					    currMach.location (),
					    netPos,
					    manager.location (),
					    true));
	/*
	 * animate a message from the manager to a peer client.
	 */
	mover.addElement (new XMoveElement (this, quesImage, quesDim,
					    manager.location (),
					    netPos,
					    block.machine.location (),
					    true));
	/*
	 * animate the block from the peer to the reader.
	 */
	mover.addElement (new XMoveElement (this,
					    new XBlob
					    (newBlock.size (),
					     newBlock.blkID),
					    newBlockSrc,
					    netPos,
					    newBlockDest,
					    true, false,
					    newBlock.size ()));

	mover.addElement (new BlkAddAnimElement (this,
						 newBlockDest,
						 newBlock));
						 
	mover.start ();

	/*
	 * update the manager meta data.
	 */
	management.readBlk (newBlock);

	writeActionStream (currMach.hostID, "read",
			   block.machine.hostID, block.blkID);
    }


    /**
     * kill all the blocks, except "block" itself.
     */
    protected void invalBlocks (XMover mover, Block block, MachPanel manager,
				Vector oldBalloons) {

	/*
	 * get a manager entry for the block.
	 */
	ManageEntry others = management.removeMachine (block.blkID, currMach);
	/*
	 * how many other machines have this block.
	 */
	int numOthers      = others.size ();
	/*
	 * position of these blocks.
	 */
	Point ends[]       = new Point[numOthers];
	/*
	 * a list of these blocks.
	 */
	Enumeration machineBlocks = others.elements ();


	/*
	 * find out the positions of these blocks.
	 */
	String invalMachList = " ";
	XVector balloons = new XVector ();
	for (int i = 0; machineBlocks.hasMoreElements (); i++) {
	    Block machineBlock = (Block) machineBlocks.nextElement ();
	    ends[i] = machineBlock.location ();
	    invalMachList +=  + machineBlock.machine.hostID + ",";

	    Component balloon = balloon4Msgs (mover, machineBlock.machine,
					      "host " +
					      machineBlock.machine.hostID,
					      "has obsolete",
					      "copy of",
					      "block " + block.blkID);
	    balloons.addElement (balloon);
	}

	mover.addElement (new XSleepElement (balloonWaitSleep));

	if (oldBalloons != null) {
	    balloons.addVector (oldBalloons);
	}
	killBalloons (mover, balloons);

	/*
	 * animate a message from the current machine to the manager.
	 */
	mover.addElement (new XMoveElement (this, quesImage, quesDim,
					    currMach.location (),
					    netPos,
					    manager.location (),
					    true));

	tickerMsg (" -> mngr " + manager.hostID +
		   " -> inval copies on hosts" + invalMachList + "  ");

	if (numOthers > 0) {

	    /*
	     * animate a bomb from the manager to the net.
	     */
	    mover.addElement (new XMoveElement (this, bombImage, quesDim,
						manager.location (),
						netPos,
						true));
	    /*
	     * animate a bunch of bombs from the net to the blocks.
	     */
	    mover.addElement (new XMoveElement (this, bombImage, bombDim,
						netPos,
						ends,
						true));
	}

	// System.out.println ("invalBlocks: there...");

	mover.addElement (new BlksKillAnimElement (this, others.elements ()));
    }
	

    /**
     * overwrites an existing block.
     */
    protected void writeBlock (Block block) {

	int       blkID    = block.blkID;
	MachPanel destMach = block.machine;
	MachPanel manager  = management.managerMachine (blkID);
	Block     ownBlock = management.getBlock (blkID, currMach);

	tickerMsg ("host " + currMach.hostID +
		   " writing blk " + block.blkID);
	
	/*
	 * create an animation object.
	 */
	XMover mover = new XMover ();

	/*
	 * if the writer does not have/own the block, kill the others.
	 */
	if ((ownBlock == null) || (!ownBlock.owned)) {

	    Vector balloons = new Vector ();

	    Component balloon = balloon3Msgs (mover, currMach,
					      "host " + currMach.hostID,
					      "overwriting",
					      "block " + block.blkID);
	    balloons.addElement (balloon);
	    balloon = balloon4Msgs (mover, manager,
				    "host " + manager.hostID,
				    "manages",
				    "locations of",
				    "block " + block.blkID);
	    balloons.addElement (balloon);
	    invalBlocks (mover, block, manager, balloons);
	} else {
	    tickerMsg (" which it owns. ");
	}

	/*
	 * create and add the new block.
	 */
	if (ownBlock == null) {
	    ownBlock = new Block (blkID, currMach);
	    mover.addElement (new BlkAddAnimElement (this,
						     currMach.nextBlkLoc (),
						     ownBlock));
	}

	/*
	 * start the animation.
	 */
	mover.start ();

	/*
	 * update manager meta data.
	 */
	management.writeBlk (ownBlock);
	currMach.dirty.addElement (ownBlock);

	writeActionStream (currMach.hostID, "write",
			   destMach.hostID, blkID);
    }

    

    /**
     * delete a block.
     */
    protected void deleteBlock (Block block) {

	tickerMsg ("host " + currMach.hostID +
		   " deleting blk " + block.blkID);
	
	/*
	 * create an animation object.
	 */
	XMover mover = new XMover ();

	int blkID = block.blkID;
	MachPanel destMach = block.machine;
	MachPanel manager = management.managerMachine (blkID);

	Vector balloons = new Vector ();
	
	Component balloon = balloon3Msgs (mover, currMach,
					  "host " + currMach.hostID,
					  "deleting",
					  "block " + block.blkID);
	balloons.addElement (balloon);
	balloon = balloon4Msgs (mover, manager,
				"host " + manager.hostID,
				"manages",
				"locations of",
				"block " + block.blkID);
	balloons.addElement (balloon);
	

	/*
	 * kill all other blocks.
	 */
	invalBlocks (mover, block, manager, balloons);

	/*
	 * update manager meta data.
	 */
	management.deleteBlk (block);

	/*
	 * kill my own block.
	 */
	mover.addElement (new BlkKillAnimElement (this, block));

	/*
	 * start the animation.
	 */
	mover.start ();

	writeActionStream (currMach.hostID, "delete",
			   destMach.hostID, blkID);
    }


    /**
     * flush dirty blocks on a machine.
     */
    protected void flushBlocks (MachPanel machine) {

	Enumeration dirtyBlks = machine.dirty.elements ();
	int numBlks = machine.dirty.size ();

	if (numBlks <= 0)
	    return;

	tickerMsg ("host " + machine.hostID +
		   " striping dirty blks to sw RAID. ");
	
	int i;
	int segs;

	XBlob  newBlks[] = new XBlob[stripeSegBlks];
 	XBlob  moveBlks[];
	XBlob  frags[];
	boolean more;

	XMover mover = new XMover ();

	Component balloon = balloon3Msgs (mover, machine,
					  "host " + machine.hostID,
					  "syncing dirty",
					  "blocks");
	mover.addElement (new CompKillAnimElement (this, balloon));
	    
	for (segs = i = 0;;) {
	    
	    Block blk = (Block) dirtyBlks.nextElement ();
	    XBlob newBlk = new XBlob (blk.size (), blk.blkID, 24);
	    add (newBlk, blk.location ());

	    // System.out.println ("adding " + newBlk.getLabel ());
	    
	    newBlks[i] = newBlk;
	    i++;

	    if ((more = dirtyBlks.hasMoreElements ()) && i < stripeSegBlks) {
		// System.out.println ("continuing...");
		continue;
	    }

	    // System.out.println ("striping...");

	    moveBlks = moveToLog (machine, mover, newBlks, i);
	    mover.addElement (new XSleepElement (stripeFragSleep * 2));

	    balloon  = balloon7Msgs (mover, machine,
				     "dirty data",
				     "grouped",
				     "into",
				     "fragments, ",
				     "calculate",
				     "parity on",
				     "them");
	    
	    mover.addElement (new XSleepElement (stripeFragSleep * 2));

	    frags    = drawFrags (machine, mover, moveBlks);
	    mover.addElement (new BlksRemoveAnimElement (this, moveBlks));
	    mover.addElement (new CompKillAnimElement (this, balloon));
	    mover.addElement (new XSleepElement (stripeFragSleep));

	    ssgBalloons          (mover, frags, segs);
	    segGather            (machine, mover, frags);
	    mover.addElement (new XSleepElement (stripeFragSleep));
	    segScatter           (machine, mover, frags, segs);

	    if (!more)
		break;
	    
	    i = 0;
	    segs++;
	}

	mover.start ();

	machine.dirty.removeAllElements ();

	writeActionStream (currMach.hostID, "flush",
			   machine.hostID, 0);
    }


    /**
     * move a bunch of dirty blocks to the log area.
     */
    protected XBlob[] moveToLog (MachPanel machine, XMover mover,
				 XBlob newBlks[], int i) {

	XBlob moveBlks[] = new XBlob[i];
	Point moveStarts[] = new Point[i];
	Point moveEnds[] = new Point[i];
	boolean incrX;
	int x, y;
	Point machineLoc = machine.location ();
	Dimension machineSize = machine.size ();

	LogLoc logLoc = new LogLoc (machine,
				    new Dimension (Block.blkWidth,
						   Block.blkHeight),
				    machine.direction);
	x = logLoc.x;
	y = logLoc.y;
	incrX = logLoc.incrX;

	
	for (int j = 0; j < i; j++) {
	    XBlob blk = newBlks[j];
	    blk.resize (Block.blkWidth, Block.blkHeight);
	    moveBlks[j] = blk;
	    moveStarts[j] = blk.location ();
	    moveEnds[j] = new Point (x, y);

	    if (incrX)
		x += Block.blkWidth;
	    else
		y += Block.blkHeight;
	}

	mover.addElement (new XMoveElement (this, moveBlks,
					    moveStarts, moveEnds,
					    false, true, null,
					    10, 0));

	return moveBlks;
    }

    
    /**
     * draws a bunch of fragments over the blocks in the log.
     */
    protected XBlob[] drawFrags (MachPanel machine, XMover mover,
				 XBlob moveBlks[]) {
	
	int numBlks  = moveBlks.length;
	int numFrags = (numBlks + 3) / 2;
	XBlob frags[] = new XBlob[numFrags];
	XBlob frag;
	int x, y, j;
	int fragWidth;
	int fragHeight;
	String fragName;
	Color fragBg;

	LogLoc logLoc = new LogLoc (machine,
				    new Dimension (Block.blkWidth,
						   Block.blkHeight),
				    machine.direction);

	if (logLoc.incrX) {
	    fragWidth = Block.blkWidth * stripeFragBlks;
	    fragHeight = Block.blkHeight;
	} else {
	    fragWidth = Block.blkWidth;
	    fragHeight = Block.blkHeight * stripeFragBlks;
	}

	Dimension fragDim = new Dimension (fragWidth, fragHeight);
	
	x = logLoc.x;
	y = logLoc.y;

	for (j = 0;  j < numFrags; j++) {

	    if (j != numFrags - 1) {
		fragName = "F" + j;
		fragBg   = Color.green;
	    } else {
		fragName = "P";
		fragBg   = Color.red;
	    }
		    
	    frags[j] = frag = new XBlob (fragDim, fragName, fragBg,
					 Color.black, 24);

	    // System.out.println ("drawFrags: " + x + y);

	    mover.addElement (new XAddElement (this, frag,
					       new Point (x, y),
					       new Dimension (fragWidth,
							      fragHeight)));
	    mover.addElement (new XSleepElement (stripeFragSleep));
	    
	    frag.move (x, y);

	    if (logLoc.incrX)
		x += fragWidth;
	    else
		y += fragHeight;
	}

	return frags;
    }

    
    /**
     * gather the fragments for transmission.
     */
    protected void segGather (MachPanel machine, XMover mover,
			      XBlob frags[]) {

	int numFrags   = frags.length;
	Point starts[] = new Point[numFrags];
	Point ends[]   = new Point[numFrags];
	Point machLoc  = machine.location ();
	int   x        = machLoc.x;
	int   y        = machLoc.y;
	
	for (int i = 0; i < numFrags; i++) {
	    starts[i] = frags[i].location ();
	    ends[i]   = new Point (x, y);
	    frags[i].move (x, y);
	    x += 4;
	    y += 4;
	}


	mover.addElement (new XMoveElement (this, frags,
					    starts, ends,
					    false, true, null,
					    40, 0));
    }

    

    protected void ssgBalloons (XMover mover,
				XBlob frags[], int segs) {
	
	int numFrags    = frags.length;
	int   machI     = segs * stripeSSGsize;
	Vector balloons = new Vector ();
	Component balloon;

	for (int i = 0; i < numFrags; i++) {
	    machI    %= numHosts;
	    String fragName = i == numFrags - 1 ?
		"parity frag" : "fragment " + i;
	    balloon   = balloon4Msgs (mover, machines[machI],
				      "storage",
				      "server " + machines[machI].hostID,
				      "receives",
				      fragName);
	    balloons.addElement (balloon);
	    machI++;
	}

	mover.addElement (new XSleepElement (balloonWaitSleep));
	killBalloons (mover, balloons);
    }



    /**
     * scatter the fragments into the network.
     */
    protected void segScatter (MachPanel machine, XMover mover,
			       XBlob frags[], int segs) {
	
	int numFrags   = frags.length;
	Point starts[] = new Point[numFrags];
	Point mids[]   = new Point[numFrags];
	Point ends[]   = new Point[numFrags];
	int   machI    = segs * stripeSSGsize;

	for (int i = 0; i < numFrags; i++) {
	    machI    %= numHosts;
	    starts[i] = frags[i].location ();
	    mids[i]   = netPos;
	    ends[i]   = machines[machI].location ();
	    machI++;
	}

	mover.addElement (new XMoveElement (this, frags,
					    starts, mids, ends,
					    true, true, null,
					    40, 0));

    }


    protected void xterm (MachPanel machine) {
	writeActionStream (currMach.hostID, "xterm",
			   machine.hostID, 0);
    }
    

    /**
     * some interesting event happened in the "top" panel.
     */
    public boolean action(Event evt, Object arg) {

	if (evt.target instanceof MachPanel) {

	    if ("Create".equals (arg)) {
		createBlock ((MachPanel) evt.target);
	    }

	    if ("Flush".equals (arg)) {
		flushBlocks ((MachPanel) evt.target);
	    }

	    if ("XTerm".equals (arg)) {
		xterm ((MachPanel) evt.target);
	    }

	    if ("Focus".equals (arg)) {
		changeCurrentMachine ((MachPanel) evt.target);
	    }
	}

	if (evt.target instanceof Block) {
	    Block block    = (Block) evt.target;

	    switch (currAction) {
	        case ActionPanel.READ: {
		    readBlock  (block);
		    break;
		}
		case ActionPanel.WRITE: {
		    writeBlock (block);
		    break;
		}
		case ActionPanel.DELETE: {
		    deleteBlock (block);
		    break;
		}
	    }
	}
	
	return true;
    }
}



/*
 ***********************************************************************
 * misc.
 ***********************************************************************
 */


class BlkKillAnimElement extends XMoverItem {
    xFSPanel xfsPanel;
    Block blk;

    public BlkKillAnimElement (xFSPanel xfsPanel, Block blk) {
	this.xfsPanel = xfsPanel;
	this.blk = blk;
    }

    public void doMove () {
	blk.machine.dirty.removeElement (blk);
	xfsPanel.remove (blk);
/*	
	blk.Kill ();
*/	
    }
}
	


class CompKillAnimElement extends XMoverItem {
    xFSPanel xfsPanel;
    Component comp;

    public CompKillAnimElement (xFSPanel xfsPanel, Component comp) {
	this.xfsPanel = xfsPanel;
	this.comp = comp;
    }

    public void doMove () {
	xfsPanel.remove (comp);
    }
}
	


class BlksKillAnimElement extends XMoverItem {
    xFSPanel xfsPanel;
    Enumeration deadBlks;

    public BlksKillAnimElement (xFSPanel xfsPanel, Enumeration deadBlks) {
	this.xfsPanel = xfsPanel;
	this.deadBlks = deadBlks;
    }

    public void doMove () {
	/*
	 * shrink down and kill these blocks.
	 */

	// System.out.println ("BlksKillAnimElement: doMove: entered...");
	
	for (int i = 0; deadBlks.hasMoreElements (); i++) {
	    Block machineBlock = (Block) deadBlks.nextElement ();
	    
	    // System.out.println ("BlksKillAnimElement: doMove: killing " +
				   // machineBlock.blkID);
	    machineBlock.machine.dirty.removeElement (machineBlock);
	    xfsPanel.remove (machineBlock);
/*	    
	    machineBlock.Kill ();
*/	    
	}
    }
}



class BlkAddAnimElement extends XMoverItem {
    xFSPanel xfsPanel;
    Point location;
    Component newBlk;

    public BlkAddAnimElement (xFSPanel xfsPanel, Point location,
			      Component newBlk) {
	this.xfsPanel = xfsPanel;
	this.location = location;
	this.newBlk   = newBlk;
    }

    public void doMove () {
	xfsPanel.add (newBlk, location);
    }
}



class BlksRemoveAnimElement extends XMoverItem {
    xFSPanel xfsPanel;
    Component blks[];

    public BlksRemoveAnimElement (xFSPanel xfsPanel, Component blks[]) {
	this.xfsPanel = xfsPanel;
	this.blks = blks;
    }

    public void doMove () {
	int numBlks = blks.length;
	for (int j = 0; j < numBlks; j++)
	    xfsPanel.remove (blks[j]);
    }
}


class ItemResizeElem extends XMoverItem {
    Component comp;
    int inc;

    public ItemResizeElem (Component comp, int inc) {
	this.comp = comp;
	this.inc  = inc;
    }

    public void doMove () {
	Dimension d;

	d = comp.size ();
	d.width  += this.inc;
	d.height += this.inc;

	comp.resize (d);
    }
}


class BalloonFontElem extends XMoverItem {
    XBalloon ball;
    int fontSize;

    public BalloonFontElem (XBalloon ball, int fontSize) {
	this.ball = ball;
	this.fontSize = fontSize;
    }

    public void doMove () {
	this.ball.fontSize = this.fontSize;
    }
}


class BalloonSkipElem extends XMoverItem {
    XBalloon ball;
    boolean skip;

    public BalloonSkipElem (XBalloon ball, boolean skip) {
	this.ball = ball;
	this.skip = skip;
    }

    public void doMove () {
	this.ball.skipWords = this.skip;
    }
}


class LogLoc {
    final int spacing = 3;
    int x, y;
    boolean incrX;

    public LogLoc (Component comp, Dimension off, int direction) {
	Point loc = comp.location ();
	Dimension size = comp.size ();

	incrX = true;
	
	switch (direction) {
	    case xFSPanel.NORTH: {
		x = loc.x;
		y = loc.y - off.height - spacing;
		break;
	    }
	    case xFSPanel.EAST: {
		x = loc.x + size.width + spacing;
		y = loc.y;
		incrX = false;
		break;
	    }
	    case xFSPanel.SOUTH: {
		x = loc.x;
		y = loc.y + size.height + spacing;
		break;
	    }
	    case xFSPanel.WEST: {
		x = loc.x - off.width - spacing;
		y = loc.y;
		incrX = false;
		break;
	    }
	    default: {
		System.err.println ("LogLoc: bad direction");
		break;
	    }
	}
	
    }	
}

    

class Counter {
    protected int counter;

    
    public Counter () {
	counter = 0;
    }

    
    public Counter (int i) {
	counter = i;
    }

    
    public int next () {
	return counter++;
    }

    
    public String nextStr () {
	return Integer.toString (next ());
    }

    
    public int current () {
	return counter;
    }
}



/*
 ***********************************************************************
 * location management
 ***********************************************************************
 */


/**
 * a manager is a hash table of some blocks.
 */
class Manager extends Hashtable {
    public MachPanel machine;

    
    public Manager (MachPanel machine) {
	this.machine = machine;
    }
}



/**
 * an entry kept by a manager is a list of blocks.
 */
class ManageEntry extends Vector {
    public int    blkID;

    
    public ManageEntry (int blkID) {
	this.blkID = blkID;
    }

    
    public ManageEntry (int blkID, Block block) {
	this (blkID);
	block.owned = true;
	addElement (block);
    }


    /**
     * removes all elements.  then add this block to the element list.
     */
    public void setBlock (Block block) {
	removeAllElements ();
	block.owned = true;
	addElement (block);
    }


    /**
     * returns the block on a machine
     */
    public Block getBlock (MachPanel machine) {
	Enumeration machineBlocks = this.elements ();

	while (machineBlocks.hasMoreElements ()) {
	    Block machineBlock = (Block) machineBlocks.nextElement ();
	    if (machine == machineBlock.machine) {
		return machineBlock;
	    }
	}

	return null;
    }
	
    
    /**
     * is "machine" in the entry?
     */
    public boolean findMachine (MachPanel machine) {
	return getBlock (machine) != null;
    }


    /**
     * remove "machine" from the entry if it's in it.
     */
    public ManageEntry removeMachine (MachPanel machine) {
	ManageEntry newEntry = (ManageEntry) this.clone ();
	Enumeration machineBlocks = newEntry.elements ();

	while (machineBlocks.hasMoreElements ()) {
	    Block machineBlock = (Block) machineBlocks.nextElement ();
	    if (machine == machineBlock.machine) {
		newEntry.removeElement (machineBlock);
		break;
	    }
	}

	return newEntry;
    }


    /**
     * turn off owned bits in all sharers.
     */
    public void noOwner () {
	Enumeration blocks = this.elements ();

	while (blocks.hasMoreElements ()) {
	    Block block = (Block) blocks.nextElement ();
	    block.owned = false;
	}
    }
}
	


/**
 * location management: an array of managers.
 */
class Management {
    Manager managers [];

    
    public Management (MachPanel machines[]) {
	int i;
	int numMachines;

	numMachines = machines.length;
	managers = new Manager[numMachines];
	for (i = 0; i < numMachines; i++) {
	    managers[i] = new Manager (machines[i]);
	}
    }

    
    private int managerIndex (int blkID) {
	return blkID % managers.length;
    }

    
    private Manager whichManager (int blkID) {
	return managers[managerIndex (blkID)];
    }

    
    public MachPanel managerMachine (int blkID) {
	return whichManager (blkID).machine;
    }

    
    public void createBlk (Block block) {
	ManageEntry entry = new ManageEntry (block.blkID, block);
	Manager manager = whichManager (block.blkID);
	manager.put (new Integer (block.blkID), entry);
    }


    public void deleteBlk (Block block) {
	Manager manager = whichManager (block.blkID);
	manager.remove (new Integer (block.blkID));
    }


    private ManageEntry findEntry (int blkID) {
	Manager manager = whichManager (blkID);
	ManageEntry entry = (ManageEntry)
	    manager.get (new Integer (blkID));
	return entry;
    }


    /**
     * one more machine has read this block,
     * add the new block to the list of sharers.
     */
    public void readBlk (Block blkCopy) {
	ManageEntry entry = findEntry (blkCopy.blkID);
	entry.addElement (blkCopy);
	entry.noOwner ();
    }


    /**
     * returns the block on a machine.
     */
    public Block getBlock (int blkID, MachPanel machine) {
	ManageEntry entry = findEntry (blkID);
	return entry.getBlock (machine);
    }


    /**
     * does "machine" have block "blkID"?
     */
    public boolean findMachine (int blkID, MachPanel machine) {
	ManageEntry entry = findEntry (blkID);
	return entry.findMachine (machine);
    }


    /**
     * find all other sharers of a block.
     */
    public ManageEntry removeMachine (int blkID, MachPanel machine) {
	ManageEntry entry = findEntry (blkID);
	return entry.removeMachine (machine);
    }

    /**
     * overwrites an existing block.
     */
    public void writeBlk (Block block) {
	ManageEntry entry = findEntry (block.blkID);
	entry.setBlock (block);
    }
}



/*
 ***********************************************************************
 * the top-level applet
 ***********************************************************************
 */


public class xFS extends Applet {
    final int defaultWidth = 600;
    final int defaultNumHosts = 12;
    final int defaultActionPort = 8989;
    
    public xFS () {
	this.setLayout (new BorderLayout ());
    }

    
    public void init() {
	int xwidth;
	int numHosts;
	Image hostImage;
	Image myriImage;
	Image bombImage;
	Image quesImage;
	Image balloonImage;
	String s;
	xFSPanel xp;
	URL docBase;
	String actionServer;
	int    actionPort;

	Socket actionSock;

	OutputStream outStream;
	PrintStream actionStream;
	
	numHosts = defaultNumHosts;
	xwidth = defaultWidth;

	docBase   = getDocumentBase ();
	hostImage = getImage (docBase, "pics/host.xbm");
	myriImage = getImage (docBase, "pics/myri.gif");
	bombImage = getImage (docBase, "pics/bomb.xbm");
	quesImage = getImage (docBase, "pics/ques.gif");
	balloonImage = getImage (docBase, "pics/balloon.gif");

	/*
	 * get the parameters from the "command arguments":
	 * number of hosts, and the width of the main window.
	 */
	s = getParameter ("hosts");
	if (s != null)
	    numHosts = Integer.parseInt (s);

	s = getParameter ("width");
	if (s != null)
	    xwidth = Integer.parseInt (s);

	/*
	 * should the applet talk to an action server?
	 */
	actionServer = getParameter ("actionServer");

	s = getParameter ("actionPort");
	if (s != null)
	    actionPort = Integer.parseInt (s);
	else
	    actionPort = defaultActionPort;

	actionStream = null;
	actionSock = null;
	if (actionServer != null) {
	    try {
		actionSock = new Socket (actionServer, actionPort, true);
		outStream = actionSock.getOutputStream ();
		actionStream = new PrintStream (outStream, true);
		System.out.println ("xFS.init: successfully connected to " +
				    actionServer + " at port " + actionPort);
		actionStream.println ("here");
		actionStream.close ();
		outStream.close ();
		actionSock.close ();
	    } catch (Exception e) {
		System.err.println ("unable to connect to " + actionServer +
				    " at port " + actionPort);
	    }
	} else {
	    // System.out.println ("no action server");
	}

	/*
	 * create the top level panel.
	 */
	this.add (xp = new xFSPanel ());
	xp.initialize (numHosts, xwidth,
		       hostImage, myriImage, bombImage, quesImage,
		       balloonImage,
		       actionServer, actionPort);
    }

    
    public static void main (String args[]) {
	Frame f = new Frame ("xFS");
	xFS xfs = new xFS   ();
	
	xfs.init  ();
	xfs.start ();

	f.add     ("Center", xfs);
	f.resize  (600, 600);
	f.show    ();
    }
}

