/*
 * Aleph Toolkit
 *
 * Copyright 1999 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.comm;

import aleph.Aleph;
import aleph.AlephDebug;
import aleph.Config;
import aleph.Host;
import aleph.PE;
import aleph.PEGroup;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileWriter;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.NoSuchElementException;
import java.util.Vector;

/**
 * A new Client is created at the start of each computation.  It creates the PE
 * group, relays outputs from PEs, and goes away when all output streams are closed.
 *
 * @author Maurice Herlihy
 * @date   January 1999
 **/

public class Client extends java.lang.Thread {

  private static final boolean DEBUG = false; // But that trick *never* works!

  private static Client theClient; // singleton

  private int      openStreams; // how many PE streams open?
  private boolean  stopped;	// did user apply emergency brakes?
  private ActionListener listener; // notify here when done

  private PrintWriter log;	// may be monitored for quality of service

  private OutputStream[] output; // array of output streams for PEs
  private int      numPEs;      // how many PEs?
  private String[] args;        // user's command
  private Vector   selectedHosts; // where to create PEs

  private Hashtable pe2output  = new Hashtable(); // PE -> outputstream
  private int       outputIndex = 0; // how many output stream assigned?

  private PEGroup peGroup;	// my PEs

  /**
   * Constructor
   * @param numPEs how many PEs to create
   * @param selectedHosts where to create them
   * @param args User's wish is our command
   * @param logging should we log everything?
   **/
  public Client (int numPEs, 
                 Vector selectedHosts,
                 String[] args,
                 OutputStream[] output,
                 boolean logging) {
    this(null, numPEs, selectedHosts, args, output, logging);
  }

  /**
   * Constructor
   * @param listener if non-null, notify when done
   * @param numPEs how many PEs to create
   * @param selectedHosts where to create them
   * @param args User's wish is our command
   * @param logging should we log everything?
   **/
  public Client (ActionListener listener,
                 int numPEs, 
                 Vector selectedHosts,
                 String[] args,
                 OutputStream[] output,
                 boolean logging) {
    theClient = this;         // appease getClient
    this.listener      = listener;
    this.numPEs        = numPEs;
    this.selectedHosts = selectedHosts;
    this.args          = args;
    this.output        = output;
    try {
      if (logging)
        log = new PrintWriter(new FileWriter(new File(Config.logDir,
                                                      Aleph.getProperty("aleph.label") +".log")));
    } catch (Exception e) {
      Aleph.panic(e);
    }
  }
  
  /**
   * Used by EOF message to locate client.
   * @see aleph.comm.message.EOF
   **/
  public static Client getClient () {
    if (theClient == null) {
      Throwable e = new Throwable("Unitialized Client");
      e.printStackTrace(System.err);
      System.exit(-1);
    }
    return theClient;
  }

  /**
   * This method does the real work.
   **/
  public void run () {
    try {
      int numHosts = selectedHosts.size();
      
      openStreams = 2 * numPEs; // Each PE has stdout and stderr open
      
      // Enumerate selected hosts in round-robin order
      Enumeration roundRobin = new Enumeration() {	// Anonymous enumerator class
	Enumeration e = selectedHosts.elements();
	public boolean hasMoreElements () {
	  return true;
	}
	public Object nextElement () throws NoSuchElementException {
	  if (! e.hasMoreElements()) // got milk?
	    e = selectedHosts.elements();
	  return e.nextElement();
	}
      };
      peGroup = new PEGroup(roundRobin, "PE", numPEs, args);

      // Wait untill all PEs have finished
      synchronized (Client.this){
	while (!stopped && openStreams > 0) {
	  try {
	    Client.this.wait();
	  } catch (InterruptedException e)  {
            Aleph.panic(e);
          }
	}
      }

      if (log != null)
	log.close();
      
      if (DEBUG) {
	AlephDebug.println("Client terminating...");
      }
      // notify console that we are done
      if (listener != null) {
        ActionEvent done = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "Done");
        listener.actionPerformed(done);
      }
    } catch (Exception e) {
      Aleph.panic(e);
    }
  }

  /**
   * Display a string from a particular PE.
   * @param pe source
   * @param string content
   **/
  public synchronized void stdout (PE pe, String string) {
    try {
      OutputStream stream = (OutputStream) pe2output.get(pe);
      if (stream == null) {
	stream = output[outputIndex++];
	pe2output.put(pe, stream);
      }
      stream.write(string.getBytes());
      if (log != null && string != null)
	log.println(string);
    } catch (Exception e) {
      Aleph.panic(e);
    }
  }

  /**
   * Display a string from a particular PE.
   * @param pe source
   * @param string content
   **/
  public synchronized void stderr (PE pe, String string) {
    try {
      OutputStream stream = (OutputStream) pe2output.get(pe);
      if (stream == null) {
	stream = output[outputIndex++];
	pe2output.put(pe, stream);
      }
      stream.write(string.getBytes());
      if (log != null)
	log.println(string);
    } catch (Exception e) {
      Aleph.panic(e);
    }
  }

  /**
   * Shutdown immediately
   **/
  public synchronized void exit () {
    stopped = true;
    if (peGroup != null)
      peGroup.stop();		// shut it down
    notifyAll();                // wake them up
  }

  /**
   * Input stream from PE shutting down.
   **/
  public synchronized void eof () {
    openStreams--;
    if (openStreams == 0)
      notifyAll();
  }

}
