/*
 * Aleph Toolkit
 *
 * Copyright 1997, Brown University, Providence, RI.
 * 
 *                         All Rights Reserved
 * 
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose other than its incorporation into a
 * commercial product is hereby granted without fee, provided that the
 * above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of Brown University not be used in
 * advertising or publicity pertaining to distribution of the software
 * without specific, written prior permission.
 * 
 * BROWN UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ANY
 * PARTICULAR PURPOSE.  IN NO EVENT SHALL BROWN UNIVERSITY BE LIABLE FOR
 * ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

package aleph;

import aleph.comm.Address;
import aleph.comm.CommunicationManager;
import aleph.comm.Server;
import aleph.thread.Scheduler;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.InetAddress;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.Vector;

/**
 * A PE object names a Processing Element.  A PE can be
 *<il>
 *<li> a <i>PE group</i> member, or a <i>solo</i> PE
 *<li> a <i>child</i> of another PE, or <i>autonomous</i>
 *</il>
 * When a PE group is shut down, it shuts down all members.
 * When a PE exits or is shut down, it shuts down its children.
 *
 * @author Maurice Herlihy
 * @date August 1998
 **/

public class PE implements java.io.Serializable {

  private static PE      thisPE;
  private static Address parent; // parent's address
  private static PE[]    peers;	// other members of my group
  private static int     numPEs = 0; // size of my group (0 => no group)
  private static Vector  children = new Vector();

  // Used for parent/child handshake
  private static Integer lock = new Integer(0);// static synchronization
  private static int     counter; // how many responses so far?
  private static PE[]    group; // put responses here

  private String  label;        // my nickname
  private Address address;      // where I live
  private int     myIndex;	// my index in the group


  private PE (Address address) {
    try {
      this.address = address;
      this.label = Aleph.getProperty("aleph.label",
                                     "console@" +
                                     InetAddress.getLocalHost().getHostName());
      this.myIndex = Integer.parseInt(Aleph.getProperty("aleph.index", "0"));
    } catch (Exception e) {
      Aleph.panic(e);
    }
  }

  /**
   * Creates an array of new PEs in parallel.  Used by <code>PEGroup</code>
   * constructors.
   * @param hosts enumerates hosts
   * @param label group-wide label
   * @param numPEs how many PEs in group?
   * @param args user-supplied args
   **/
  static PE[] makeGroup (Enumeration hosts, String label,
                         int numPEs, String[] args) {
    try {
      synchronized (lock) {	// only the paranoid survive
	group = new PE[numPEs]; // reset buffer
	counter = 0;	// reset counter
	// Start up PEs
        CommunicationManager cManager = CommunicationManager.getManager();
	for (int i = 0; i < numPEs; i++) {
	  Host host = (Host) hosts.nextElement();
          host.startPE(cManager.getAddress(),
                       cManager.getConsoleAddress(),
                       i,
                       numPEs,
                       label + "." + i,
                       args,
                       Aleph.getProperties());
	}
        while (counter < numPEs) { // wait for PEs to report
          try {lock.wait();} catch (InterruptedException e) {}
        }
      }
      return group;		// return the goods
    } catch (Exception e) {
      Aleph.panic(e);
    }
    return null;		// not reached
  }

  /**
   * Shuts down an array of PEs in parallel.  Used by <code>PEGroup.stop()</code>.
   **/
  static public void stopGroup (PE[] peGroup) {
    try {
      synchronized (lock) {	// only the paranoid survive
	HaltMessage halt = new HaltMessage(0);
	for (int i = 0; i < peGroup.length; i++) {
	  halt.send(peGroup[i]);
	}
      }
    } catch (Exception e) {
      Aleph.panic(e);
    }
  }

  /**
   * @return this PE's address
   * @see aleph.comm.Address;
   **/
  public Address getAddress () {
    return address;
  }

  /**
   * @return whether a PE seems to be running.
   **/
  public boolean ping () {
    try {
      return CommunicationManager.getManager().ping(address);
    } catch (Exception e) {
      Aleph.panic(e);
    }
    return false;		// not reached
  }

  /**
   * Always compare PEs with <code>equals</code>, never <code>==</code>.
   **/
  public boolean equals (Object anObject) {
    if ((anObject != null) && (anObject instanceof PE)) {
      PE other = (PE) anObject;
      return (other.address.equals(this.address));
    } else {
      return false;
    }
  }

  public int hashCode (){
    return address.hashCode();
  }

  public String toString () {
    return label;
  }

  /**
   * Identity politics.
   * @return our PE
   **/
  public static PE thisPE () {
    if (thisPE == null) {
      CommunicationManager cManager = CommunicationManager.getManager();
      thisPE = new PE(cManager.getAddress());
    }
    return thisPE;
  }

  /**
   * @return number of PEs in our group.
   **/
  public static int numPEs () {
    if (peers == null)
      return 1;
    else
      return peers.length;
  }

  /**
   * @return Enumeration of all PEs in this PE group.
   **/
  public static Enumeration allPEs () {
    return new Enumeration() {	// An anonymous enumerator class
      int i = 0;
      public boolean hasMoreElements() {
	return i < peers.length;
      }
      public Object nextElement() throws NoSuchElementException {
	if (i < peers.length) // got milk?
	  return peers[i++];
	else
	  throw new NoSuchElementException();
      }
    };
  }

  /**
   * @return Enumeration of my group's PEs in round-robin order.
   **/
  public static Enumeration roundRobin () {
    return new Enumeration() {	// Anonymous enumerator class
      int i = 0;
      public boolean hasMoreElements () {
	return true;
      }
      public Object nextElement() throws NoSuchElementException {
	if (i == peers.length) // got milk?
	  i = 0;
	return peers[i++];
      }
    };
  }

  /**
   * Top-level program called by Server.  Initializes local PE state from Aleph
   * properties.  Undertakes handshake with parent to discover salient properties.
   **/
  public static void main (String[] args) {
    try {
      // Concoct user-readable name
      CommunicationManager cManager = CommunicationManager.getManager();
      AlephDebug.setTitle(thisPE().label);

      // Initialize static fields
      numPEs  = Integer.parseInt(Aleph.getProperty("aleph.numPEs", "1"));
      int index = Integer.parseInt(Aleph.getProperty("aleph.index", "0"));
      parent  = cManager.getParentAddress();
      // Fill in peers, using handshake with parent 
      synchronized (lock) {	// only the paranoid survive
        peers = null;
        cManager.send(parent, new Handshake1Message(thisPE, index));
        while (peers == null) {
          try {lock.wait();} catch (InterruptedException e) {}
        }
      }
      // Rebind I/O streams.
      if (Aleph.getVerbosity() > 0){	// Learn to love deprecation warnings!
	System.setOut(new VerbosePrintStream(new aleph.comm.OutputStream(),
					     "[" + PE.thisPE()+" out] "));
	System.setErr(new VerbosePrintStream(new aleph.comm.OutputStream(),
					     "[" + PE.thisPE()+" err] "));
      } else {
        System.setOut(new PrintStream(new aleph.comm.OutputStream()));
        System.setErr(new PrintStream(new aleph.comm.OutputStream()));
      }
      // run user's app
      if (index == 0) {
        Scheduler scheduler = Scheduler.getScheduler();
        scheduler.exec(args);
      }
    } catch (Exception e) {
      Aleph.panic(e);
    }
  }

  /**
   * Shuts down a PE and its children.  Leaves other group members unharmed.
   * @return whether shutdown is confirmed.
   * @exception SecurityException If caller is not allowed to kill this server.
   **/
  public boolean stop () throws SecurityException {
    return stop(0);
  }

  /**
   * Shuts down a PE and its children.  Leaves other group members unharmed.
   * @param code termination code
   * @return whether shutdown is confirmed.
   * @exception SecurityException If caller is not allowed to kill this server.
   **/
  public boolean stop (int code) throws SecurityException {
    try {
      CommunicationManager cManager = CommunicationManager.getManager();
      cManager.send(address, new HaltMessage(code));
      cManager.flush(address);
      return true;
    } catch (IOException e) { // timed out
      return false;
    } catch (Exception e) {
      Aleph.panic(e);
    }
    return false;		// not reached
  }

  /**
   * Shut down me, my children, and my group.
   * @param code exit code (0 is normal, etc).
   **/
  public static void exit (int code) {
    try {
      // shut down other peers
      for (int i = 0; i < peers.length; i++) {
	if (! peers[i].equals(thisPE))
	  peers[i].stop(code);
      }
    } catch (Exception e) {}
    halt(code);
  }

  /**
   * Shut down me and my children, but not other group members.
   * @param code exit code (0 is normal, etc).
   **/
  public static void halt (int code) {
    // stop children
    for (Enumeration enum = children.elements(); enum.hasMoreElements();) {
      PE pe = (PE) enum.nextElement();
      pe.stop(code);
    }
    // shut down gracefully
    closeConsoleStreams();
    CommunicationManager.getManager().close();           // close sockets
    if (Aleph.getVerbosity() > 0)
      Aleph.meterReport();
    System.exit(code);
  }

  /**
   * Shuts down peers, children, and parent.
   **/
  public static void panic () {
    panic(null);
  }

  /**
   * Shuts down peers, children, and parent.
   **/
  public static void panic (PE sender) {
    try {
      // stop children
      for (Enumeration enum = children.elements(); enum.hasMoreElements();) {
	PE pe = (PE) enum.nextElement();
        if (! pe.equals(sender))
          pe.stop(-1);
      }
      // inform parent
      System.out.close();         // flush remaining I/O
      System.err.close();
      CommunicationManager.getManager().close();           // close sockets
    } catch (Exception e) {}
    System.exit(-1);
  }

  /**
   * @return my index within group
   **/
  public int getIndex () {
    return myIndex;
  }

  /**
   * @return i-th PE, wrapping around if necessary
   **/
  public static PE getPE (int i) {
    return peers[i % peers.length];
  }

  /**
   * Inner class that implements verbose output stream.
   * Causes deprecation warning.
   **/
  static class VerbosePrintStream extends PrintStream {
    String label;
    VerbosePrintStream(OutputStream output, String label) {
      super(output, true);
      this.label = label;
    }
    public synchronized void println (String line) {
      super.print(label);
      super.println(line);
    }
  }

  /* Private message classes defined below this line */

  /**
   * Newly-created PE returns itself to the parent.  If PE is part of a group,
   * check whether group information is complete. If so, broadcast complete
   * list to all group members.
   **/
  private static class Handshake1Message extends aleph.Message{
    private PE pe;
    private int index;
    Handshake1Message(PE pe, int index) {
      this.index = index;
      this.pe = pe;
    }
    public void run () {
      try {
        CommunicationManager cManager = CommunicationManager.getManager();
	synchronized (lock) {
	  group[index] = pe;
	  children.addElement(pe);
	  if (group.length == ++counter) { // all done!
	    Handshake2Message handshake2 = new Handshake2Message(group);
	    for (int i = 0; i < group.length; i++) // tell each group member
	      cManager.send(group[i].address, handshake2);
	    lock.notifyAll(); // awake
	  }
	}
      } catch (Exception e) {
	Aleph.panic(e);
      }
    }
    public String toString () {
      return "PE.Handshake1Message[from: " + from +
	", "   + pe + ", " + index + "]";
    }
  }
  /**
   * Tell new PE about group.
   **/
  private static class Handshake2Message extends aleph.Message{
    private PE[] myPeers;
    Handshake2Message(PE[] peers) {
      myPeers = peers;
    }
    public void run () {
      synchronized (lock) {
	peers = myPeers;
	lock.notifyAll();
      }
    }
    public String toString () {
      StringBuffer s = new StringBuffer("PE.Handshake2Message[from: ");
      s.append(from);
      s.append(", PEs: ");
      s.append(myPeers[0]);
      for (int i = 1; i < myPeers.length; i++) {
	s.append(", ");
	s.append(myPeers[i]);
      }
      s.append("]");
      return s.toString();
    }
  }
  /**
   * Ask PE to shut down
   **/
  private static class HaltMessage
    extends aleph.Message implements java.io.Externalizable {
    int code;
    public HaltMessage (int code) {
      this.code = code;
    }
    public HaltMessage () {}
    public void run () {
      PE.halt(code);
    }
    public String toString () {
      return "PE.HaltMessage[from: " + from + "]";
    }
    /**
     * @see java.io.Externalizable
     **/
    public void writeExternal (ObjectOutput out) throws IOException {
      super.writeExternal(out);
      out.writeInt(code);
    }
    /**
     * @see java.io.Externalizable
     **/
    public void readExternal (ObjectInput in)
      throws IOException, java.lang.ClassNotFoundException {
      super.readExternal(in);
      code = in.readInt();
    }
  }

  /**
   * Close input and error streams to console.  Can be called when PE shuts
   * down, or if the PE wants to disassociate itself from the console.
   **/
  public static void closeConsoleStreams () {
    System.out.close();         // flush remaining I/O
    System.err.close();
  }

}
