package asc;

import aglet.*;
import aglet.event.*;
import java.net.*;
import java.io.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;


class Semaphore {
  private int value;
  public String name;
  
  public Semaphore (int value, String name) {
    this.value = value;
    this.name = name;
  }

  public synchronized void down () {
    // System.out.println("\t" + name + ": down");
    value--;
    while (value < 0) {
      try { wait(); }
      catch(InterruptedException ex) { ex.printStackTrace(); }
    }
    // System.out.println("\t" + name + ": got through");
  }

  public synchronized void up () {
    // System.out.println("\t" + name + ": up");
    if ( value < 0 ) { notify(); }
    value++;
  }
}


class Broadcaster extends Thread {
  public AscLocal ascLoc;
  private String source;
  private String msg;

  public Broadcaster (AscLocal ascLoc) {
    this.ascLoc = ascLoc;
    System.out.println(ascLoc.agID + ": Broadcaster thread = " + Thread.currentThread().getName());
  }

  public void Resume (String source, String msg) {
    System.out.println(ascLoc.agID + ": Broadcaster thread = " + Thread.currentThread().getName());
    this.source = source;
    this.msg = msg;
    try { this.resume(); }
    catch (Exception ex) { ex.printStackTrace(); };
  }

  public void run () {
    String name;
    AgletProxy ap;
    Message Infomsg;
    Enumeration en;

    while( true ) {
      System.out.println(ascLoc.agID + ": Broadcaster suspended");
      try { this.suspend(); }
      catch (Exception ex) { ex.printStackTrace(); };

      System.out.println(ascLoc.agID + ": Broadcaster resumed, thread = " + 
			 Thread.currentThread().getName());

      ascLoc.Sem.down();
      en = ascLoc.Dests.keys();
      ascLoc.Sem.up();

      if ( !en.hasMoreElements() ) {
	ascLoc.ascFr.addStatus("Broadcaster: empty destinations list");
	System.out.println(ascLoc.agID + ": Broadcaster: empty destinations list");
	continue;
      }

      if ( ascLoc.RemAgProxs == null ) {
	ascLoc.ascFr.addStatus("Broadcaster: empty proxies list");
	System.out.println(ascLoc.agID + ": Broadcaster: empty proxies list");
	continue;
      }

      Infomsg = new Message("bcast");
      Infomsg.setArg("agID", ascLoc.agID);
      Infomsg.setArg("name", source);
      Infomsg.setArg("txt", msg);

      while ( en.hasMoreElements() ) {
	name = (String)en.nextElement();
      
	if ( !name.equals(source) ) {
	  try {
	    ascLoc.Sem.down();
	    ap = (AgletProxy)ascLoc.RemAgProxs.get(name);
	    ascLoc.Sem.up();

	    if ( (ap == null) /*|| (!ap.isValid())*/ ) {
	      System.out.println(ascLoc.agID + ": no proxy for " + name + ", wait 3 sec");
	      try { Thread.currentThread().sleep(3000); }
	      catch (Exception ex) { ex.printStackTrace(); }
	      ascLoc.Sem.down();
	      ap = (AgletProxy)ascLoc.RemAgProxs.get(name);
	      ascLoc.Sem.up();
	    }

	    if ( (ap != null) /*&& (ap.isValid())*/ ) {
	      System.out.println(ascLoc.agID + ": sending to " + name);
	      ap.sendAsyncMessage(Infomsg);
	    }
	    else {
	      System.out.println(ascLoc.agID + ": presume " + name + " crashed");
	      ascLoc.Sem.down();
	      ascLoc.Dests.remove(name);
	      ascLoc.Sem.up();
	    }
	  } catch (Exception ex) { ex.printStackTrace(); };
	}
      }    
    }
  }  
}



class AscLocalFrame extends Frame {
  Frame f;
  TextArea input;
  TextArea output;
  TextArea status;

  Button dest;
  Button retract;
  Button disp;
  Button clear;
  Button bcast;
  Button quit;

  public AscLocalFrame(String title) {
    f = new Frame();
    f.setSize(440, 540);
    Font normFont = new Font("Helvetica", Font.PLAIN, 10);
    Font boldFont = new Font("Helvetica", Font.BOLD, 10);
    f.setTitle(title);
    f.setFont(boldFont);
    f.setLayout(new BorderLayout());    

    Panel buttonP = new Panel();

    dest = new Button("set name");
    buttonP.add(dest);

    disp = new Button("dispatch");
    buttonP.add(disp);

    clear = new Button("clear");
    clear.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        input.setText(""); }});
    buttonP.add(clear);

    retract = new Button("retract");
    buttonP.add(retract);

    bcast = new Button("bcast msg");
    buttonP.add(bcast);

    quit = new Button("quit");
    buttonP.add(quit);

    f.add(buttonP, "North");

    input = new TextArea("", 5, 40,  TextArea.SCROLLBARS_VERTICAL_ONLY);
    input.setText("origin");
    f.add(input, "Center");

    Panel outputP = new Panel();
    outputP.setLayout(new BorderLayout());

    output = new TextArea("", 15, 40,  TextArea.SCROLLBARS_VERTICAL_ONLY);
    output.setEditable(false);
    output.setText("INSTRUCTIONS:\n" +
		   "Enter your name in the text area above, where the default \n" + 
		   "name, 'origin', is currently shown, then press the 'set name' \n" +
		   "button.");
    outputP.add(output, "Center");

    status = new TextArea("", 10, 40,  TextArea.SCROLLBARS_VERTICAL_ONLY);
    status.setFont(normFont);
    status.setEditable(false);
    outputP.add(status, "South");

    f.add(outputP, "South");

    f.validate();
    f.show();    
  }

  public void addStatus(String txt) {
    status.append("\n" + txt);
    status.select(status.getText().length(), status.getText().length());
  }

  public void setStatus(String txt) {
    status.setText(txt);
    status.select(status.getText().length(), status.getText().length());
  }

  public void addOutput(String txt) {
    output.append("\n" + txt);
    output.select(output.getText().length(), output.getText().length());
  }

  public void setOutput(String txt) {
    output.setText(txt);
    output.select(output.getText().length(), output.getText().length());
  }

}


public class AscLocal extends Aglet implements MobilityListener  {

  public AscLocalFrame ascFr = null;
  public Hashtable Dests = new Hashtable();
  public Hashtable RemAgProxs;
  public AgletID agID;
  public String ID = "origin";
  public Semaphore Sem;  

  private int NumAgs = 0;
  private int Gen = 0;
  private Hashtable Timeouts = new Hashtable();
  private Broadcaster Bcaster;
  private String OrigMsg;
  private int DefaultTimeout = 600;  // seconds  

  public void onCreation (Object o) { 
    super.onCreation(o);
    addMobilityListener(this);

    agID = getAgletID();
    System.out.println("\n" + agID + ": onCreation thread = " + 
		       Thread.currentThread().getName());
    createUI(agID + ": origin");

    Sem = new Semaphore(1, "sync1");
    Bcaster = new Broadcaster(this);
    Bcaster.start();    
  }


  public boolean handleMessage (Message msg) {
    System.out.println(agID + ": handleMessage thread = " + 
		       Thread.currentThread().getName());

    String kind = msg.getKind();
    String sender_id;
    AgletID sender_agID;
    int i;
    String txt;
    Enumeration en;
    AgletProxy ap;
    String name;
    AgletProxy sender_ap;

    if ( kind.equals("info") ) {
      ascFr.addStatus("\n" + msg.getArg("agID") + " info msg:");
      sender_id = (String)msg.getArg("name");
      txt = (String)msg.getArg("txt");
      ascFr.addStatus("info from " + sender_id + ":\n" + txt);

      if ( txt.startsWith("forward") ) { 
	// ascFr.addStatus("removing " + sender_id + " from RemAgProxs");
	Sem.down();
	RemAgProxs.remove(sender_id); 
	Sem.up();
      }

      return(true);
    }

    if ( kind.equals("forward") ) {
      sender_agID = (AgletID)msg.getArg("agID");
      sender_id = (String)msg.getArg("name");
      txt = (String)msg.getArg("txt");
      ap = (AgletProxy)msg.getArg("newproxy");

      ascFr.addStatus("\n" + msg.getArg("agID") + " forward msg:");
      ascFr.addStatus(sender_id + " new location: " + txt);

      Sem.down();
      RemAgProxs.put(sender_id, ap);
      Dests.put(sender_id, txt);
      Sem.up();

      return(true);
    }

    if ( kind.equals("reply") ) {
      sender_agID = (AgletID)msg.getArg("agID");
      sender_id = (String)msg.getArg("ID");
      ascFr.addStatus(sender_agID + ": reply msg");
      txt = (String)msg.getArg("txt");
      ascFr.addOutput("\nreply from " + sender_id + ":");
      ascFr.addOutput(txt);
      return(true);
    }
    
    if ( kind.equals("bcast") ) {
      sender_agID = (AgletID)msg.getArg("agID");

      ascFr.addStatus(sender_agID + ": bcast msg");
      sender_id = (String)msg.getArg("ID");
      txt = (String)msg.getArg("txt");
      ascFr.addOutput("\nbcast from " + sender_id + ":");
      ascFr.addOutput(txt);
      OrigMsg = OrigMsg.concat("\n\nbcast from " + sender_id + ":\n" + txt);

      Bcaster.Resume(sender_id, txt);
      return(true);
    }
    
    if ( kind.equals("dispose") ) {
      ascFr.addStatus("\n" + msg.getArg("agID") + " dispose msg");
      sender_id = (String)msg.getArg("name");

      Sem.down();
      RemAgProxs.remove(sender_id);
      Dests.remove(sender_id);
      Sem.up();

      return(true);
    }

    if ( kind.equals("error") ) {
      ascFr.addStatus("\n" + msg.getArg("agID") + " ERROR msg:");
      ascFr.addStatus((String)msg.getArg("txt"));
      return(true);
    }

    System.out.println(agID + ": unknown message type '" + kind + "'");
    return(true);
  }


  public void getDests () {
    int end = -1;
    int start = -2;
    int i;
    String allDests = ascFr.input.getText();
    Vector temp = new Vector();

    String tmp;
    String dest;
    String name;
    Integer time;
    Enumeration en;

    while ( end < allDests.length() ) {
      start = Math.min(end + 1, allDests.length());
      end = allDests.indexOf("\n", start);
      if (end < 0) end = allDests.length();

      dest = allDests.substring(start, end);
      if ( ! dest.equals("") ) { temp.addElement(dest); }      
    }

    Gen++;
    NumAgs += temp.size();
    for (i = 0; i < temp.size(); i++) {
      tmp = (String)temp.elementAt(i);
      end = tmp.indexOf("#");
      if (end < 0) end = tmp.length();
      dest = tmp.substring(0, end).trim();

      start = Math.min(end + 1, tmp.length());
      end = tmp.indexOf("#", start);
      if (end < 0) end = tmp.length();
      name = tmp.substring(start, end).trim();
      if (name.equals("")) name = Integer.toString(Gen) + "." + Integer.toString(i + 1);

      Sem.down();
      Dests.put(name, dest);
      Sem.up();

      start = Math.min(end + 1, tmp.length());
      end = tmp.length();
      tmp = tmp.substring(start, end).trim();
      if ( !tmp.equals("") )  time = new Integer(tmp);
      else  time = new Integer(DefaultTimeout);

      Timeouts.put(name, time);
    } 

    ascFr.addStatus("destinations [" + NumAgs + "]:");
    en = Dests.keys();
    while ( en.hasMoreElements() ) {
      name = (String)en.nextElement();
      ascFr.addStatus(Dests.get(name) + "  " + name + "  " + Timeouts.get(name));
    }

    ascFr.input.setText("original message");
    ascFr.setOutput("INSTRUCTIONS:\n" +
		    "Type the message in the text area above, then press \n" +
		    "the 'dispatch' button.");
  }


  public void getName () {
    ID = ascFr.input.getText();
    ascFr.addStatus("ID = " + ID);
    
    ascFr.dest.removeActionListener(nameGetter);

    ascFr.dest.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        getDests(); }});

    ascFr.input.setText("school.cs.indiana.edu:7777 # name1 # 300" + "\n" + 
			"school.cs.indiana.edu:7777 # name2 # 180");

    ascFr.setOutput("INSTRUCTIONS:\n" +
		    "Enter the destinations, names and timeouts (in seconds) using \n" + 
		    "the following format:\n" +
		    "<hostname>:<port> # <name> # <time>  (one set per row), \n" +
		    "where <name> must be a string without '#' characters, and \n" +
		    "<time> must be an integer (see the example above), then press \n" +
		    "the 'set dests' button. If you ommit a name or a timeout, the \n" +
		    "defaults (row number for the name and 600 sec for the timeout) \n" +
		    "will be used. To specify a timeout only, and no name, you must \n" +
		    "leave ' # # ' between the destination and the timeout.");
    ascFr.dest.setLabel("set dests");
  }


  public void run() {
    System.out.println(agID + ": run");
    System.out.println(agID + ": run thread = " + Thread.currentThread().getName());
  }

  public void onDispatching(MobilityEvent event) {
    System.out.println(agID + ": leaving for " + event.getLocation());
    destroyUI();
  }

  public void onArrival(MobilityEvent event) {
    System.out.println(agID + ": arrived at " + event.getLocation());
    createUI(agID + ": running at " + event.getLocation());
  }

  public void onReverting(MobilityEvent event) {}

  
  ActionListener nameGetter;

  public void createUI (String info) {
    System.out.println(agID + ": creating UI");
    ascFr = new AscLocalFrame("ascLocal");
    ascFr.setStatus(info);

    ascFr.retract.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        RetractAll(); }});

    nameGetter = new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        getName(); }};

    ascFr.dest.addActionListener(nameGetter);

    ascFr.disp.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        SendOut(); }});

    ascFr.bcast.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        HandleLocalBcast(); }});

    ascFr.quit.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        dispose(); }});
  }

  public void CheckID () {
    System.out.println(agID + ": checkID");
  }


  public void SendOut () {
    System.out.println(agID + ": SendOut");
    //String msg = ascFr.input.getText();
    String name;
    AgletProxy ap;
    Enumeration en = Dests.keys();

    if (RemAgProxs == null) {
      OrigMsg = ascFr.input.getText();
      RemAgProxs = new Hashtable();
    }
   
    if ( !en.hasMoreElements() ) {
      ascFr.addStatus("ERROR SendOut: empty destinations list");
      System.out.println(agID + ": SendOut: empty destinations list");
    }

    while ( en.hasMoreElements() ) {
      name = (String)en.nextElement();
      if (RemAgProxs.get(name) == null) {
	try {
	  ap = (getAgletContext()).createAglet(null, "asc.AscRemote", agID);

	  Message Synomsg = new Message("synopsis");
	  Synomsg.setArg("agID", agID);
	  Synomsg.setArg("name", ID);
	  Synomsg.setArg("id", name);
	  Synomsg.setArg("time", (Integer)Timeouts.get(name));
	  Synomsg.setArg("txt", OrigMsg);
	  ap.sendMessage(Synomsg);

	  ap = ap.dispatch(new URL("atp://" + (String)Dests.get(name)));

	  Sem.down();
	  RemAgProxs.put(name, ap);
	  Sem.up();
	}
	catch (Exception ex) { ex.printStackTrace(); };
      }
    }
    ascFr.setOutput("INSTRUCTIONS:\n" +
		    "You can retract all remote agents using the 'retract' \n" +
		    "button, you can send them all a message by typing it in \n" +
		    "the text area and then pressing the 'bcast msg' button. \n" +
		    "You can add new remote agents by entering a new set of \n" +
		    "locations, names and timeouts and pressing the 'set dests' \n" +
		    "and 'dispatch' buttons \n" +
		    "\n\ninitial message:\n" + OrigMsg);
  }


  public void HandleLocalBcast () {
    String msg = ascFr.input.getText();
    ascFr.addOutput("\nlocal bcast:\n" + msg);
    Bcaster.Resume(ID, msg);

    OrigMsg = OrigMsg.concat("\n\nbcast from " + ID + ":\n" + msg);
  }


  public void onDisposing () {
    RetractAll();
    System.out.println(agID + ": normal exit");
    Bcaster = null;
    destroyUI();
  }

  public void destroyUI () {
    ascFr.dispose();
    ascFr = null;
  }

  public void RetractAll () {
    System.out.println(agID + ": RetractAll");

    String name;
    AgletProxy ap;
    Enumeration en = Dests.keys();

    if ( !en.hasMoreElements() ) {
      ascFr.addStatus("RetractAll: empty proxies list");
      System.out.println(agID + ": RetractAll: empty proxies list");
      return;
    }

    if ( RemAgProxs == null ) {
      ascFr.addStatus("RetractAll: empty proxies list");
      System.out.println(agID + ": RetractAll: empty proxies list");
      return;
    }

    while ( en.hasMoreElements() ) {
      name = (String)en.nextElement();
      
      try {
	ap = (AgletProxy)RemAgProxs.get(name);

	if (ap == null) {
	  System.out.println(agID + ": no proxy for " + name + ", wait 3 sec");
	  try { Thread.currentThread().sleep(3000); }
	  catch (Exception ex) { ex.printStackTrace(); }
	  ap = (AgletProxy)RemAgProxs.get(name);
	}

	if (ap != null) {
	  System.out.println(agID + ": retracting " + name);
	  ap.dispose();
	}
	else {
	  System.out.println(agID + ": presume " + name + " crashed");
	  Sem.down();
	  Dests.remove(name);
	  Sem.up();
	}
      }
      catch (Exception ex) { ex.printStackTrace(); };
    }
  }

}


