/*
 * 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.gui.Batch;
import aleph.gui.Console;
import aleph.meter.Meter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.util.Date;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;
import java.util.StringTokenizer;

/** 
 * Provides basic utility methods.
 * <p>
 * Aleph defines the following system properties:
 * <dl>
 * <dt>aleph.version	<dd>aleph version number
 * <dt>aleph.verbosity  <dd>controls level of detail messages
 * <dt>aleph.java_g     <dd>if true, use java_g instead of java
 * <dt>aleph.profile	<dd>if true, profile everything
 * <dt>aleph.label	<dd>user-friendly name for this PE
 * <dt>aleph.numPEs	<dd>size of this PE's group
 * <dt>aleph.index	<dd>index of this PE in its group
 * </dl>
 * Other <code>aleph.*</code> properties may be defined by various packages
 * <p>
 * Any system property beginning with <code>aleph</code> is propagated to all
 * PEs when they are started up.
 *
 * @author  Maurice Herlihy
 * @date    September 98
 **/
public final class Aleph {

  static final boolean DEBUG = false; // A fine mess you've got us into ...

  private static int verbosity; // loquacity control

  private static boolean panicStarted = false;

  private static Properties properties;

  /**
   * Get loquacity level.
   * Set by <code>aleph.Config.verbosity</code>.
   * Overridden by run-time flag <code>-Daleph.verbosity=</code><it>blah</it>.
   * Further overridden by the console.
   * @return verbosity level
   **/
  public static int getVerbosity () {
    return verbosity;
  }

  /**
   * Adjust loquacity level.
   **/
  public static void setVerbosity (int newVerbosity) {
    verbosity = newVerbosity;
  }

  /** 
   * Define an Aleph property.
   **/
  public static void setProperty (String key, String value) {
    properties.put(key, value);
  }

  /** 
   * Look up an Aleph property.
   **/
  public static String getProperty (String key) {
    return properties.getProperty(key);
  }
  /** 
   * Look up an Aleph property.
   **/
  public static String getProperty (String key, String value) {
    return properties.getProperty(key, value);
  }

  /**
   * Return integer value of Aleph property.
   * @param key property name
   * @param value default if property not found or not integer
   **/
  public static int getIntProperty (String key, int value) {
    try {
      String _value = properties.getProperty(key);
      if (_value != null)
        return Integer.parseInt(_value);
      else
        return value;
    } catch (Exception e) {
      return value;
    }
  }
  /** 
   * Remove a System property.  Requires clearance to call
   * <code>System.setProperty(...)</code>.
   **/
  public static void removeProperty (String key) {
    properties.remove(key);
  }

  /**
   * Returns subset of system properties that match <code>aleph.*<code>.
   * @return aleph-related properties
   **/
  public static Properties getProperties () {
    return properties;
  }

  /**
   * Shut down the computation gracefully.
   **/
  public static void exit (String why) {
    System.err.println(why);
    exit(-1);
  }
  /**
   * Shut down the computation gracefully.
   **/
  public static void exit () {
    exit(0);
  }

  /**
   * Shut down the computation gracefully.
   * @param code status code
   **/
  public static void exit (int code) {
    try {
      if (code != 0)
	System.err.print("\007"); // feep!
      if (DEBUG)
        AlephDebug.println("Aleph.exit called");
      meterReport();
      PE.exit(code);
    } catch (Exception e) {
      Aleph.panic(e);
    }
  }

  /**
   * Dump metering information to file.
   **/
  public static void meterReport () {
    try {
      PrintWriter meterWriter
        = new PrintWriter(new FileWriter(new File(Config.logDir,
                                                  PE.thisPE().toString() + ".meter")));
      Meter.report(meterWriter);
      meterWriter.close();
    } catch (IOException e) {
      System.err.println("Cannot save metering report " + e);
    }
  }

  /**
   * Panic shutdown.  Prints exception's detail message, if any, and stack
   * trace. Also does ``core dump'' to panic file.
   * @param e Exception that caused the problem.
   **/
  public static void panic (Throwable e) {
    try {
      if (!panicStarted) {
	panicStarted = true;	// no nested panics!
        meterReport();          // dump metering information
        StringBuffer s = new StringBuffer("Panic at "); // accumulate bad news
        s.append(new Date());   // like when?
        s.append("\n");
	s.append(e.toString()); // like what?
        s.append("\n");        
        ByteArrayOutputStream dump = new ByteArrayOutputStream();
	e.printStackTrace(new java.io.PrintStream(dump)); // ignore deprecation msgs
        s.append(dump.toString());
        s.append("\n");        
        String uhoh = s.toString();
	System.err.print("\007"); // feep!
        System.err.println(uhoh); // Je meurs pour la France!
        System.err.flush();
        // Write the same damn thing to a panic file
	PrintWriter panic
          = new PrintWriter(new FileWriter(new File(Config.logDir,
                                                     PE.thisPE().toString() + ".panic")));
        panic.print(uhoh);      // diagnostic message
 	AlephDebug.dump(panic); // ms found in a bottle
	panic.close();		// force to disk
        PE.exit(-1);		// shut down everyone else
      }
    } catch (Exception x) {     // sauve qui peut
      System.exit(-1);
    }
  }

  /**
   * Panic shutdown.  Prints message and stack trace.
   * @param message Famous last words.
   **/
  public static void panic (String message) {
    panic(new Throwable(message));
  }

  /** 
   * Main startup for Aleph. Checks the configuration and either
   * launches the gui or invokes batch mode.
   **/
  public static void main (String[] args) {
    int choice = 0;
    fixManagerProperty("aleph.communicationManager",
                        Config.communicationManagers);
    fixManagerProperty("aleph.directoryManager",
                        Config.directoryManagers);
    fixManagerProperty("aleph.eventManager",
                        Config.eventManagers);
    fixManagerProperty("aleph.transactionManager",
                        Config.transactionManagers);
    try {
      // If no args provided, use default command.
      if (args.length == 0) {
        StringTokenizer toke = new StringTokenizer(Config.defaultCommand);
        args = new String[toke.countTokens()];
        int i = 0;
        while (toke.hasMoreTokens())
          args[i++] = toke.nextToken();
      }

      // announce who we are for debugging and log files
      setProperty("aleph.label",
                  "console@" + InetAddress.getLocalHost().getHostName());
      // interactive or batch?
      if (Config.batch || System.getProperty("aleph.batch") != null)
        new Batch(args);
      else
        new Console(args);
    } catch (Exception e) {
      System.err.println(e);
      System.exit(-1);
    }
  }
 
  /**
   * Users can specify managers using either short names:
   * <code>-Daleph.communicationManager=TCP</code> or class names
   * <code>-Daleph.communicationManager=aleph.comm.rmi.CommunicationManager</code>.
   * This method rebinds property to class name (or default if none specified).
   * @param property property in question
   * @param managers even array entries class names, odd entries short names
   * @return matching index, or zero
   **/
  private static void fixManagerProperty (String property, String[] managers) {
    String value = Aleph.getProperty(property);
    int index = 0;
    boolean found = false;
    if (value != null) {
      for (int i = 0; i < managers.length; i++)
        if (value.equalsIgnoreCase(managers[i])) {
          index = (i / 2) * 2;
          found = true;
          break;
        }
      if (! found)
        System.err.println("\nWarning: ignoring -D" + property + "=" + value + "\n");
    }
    Aleph.setProperty(property, managers[index]);
  }

  /**
   * Print out long banner listing managers, etc.
   **/
  public static String getLongBanner () {
    StringBuffer s = new StringBuffer(Config.banner);
    s.append("\nCommunication Manager:\t");
    s.append(Aleph.getProperty("aleph.communicationManager"));
    s.append("\nDirectory Manager:\t");
    s.append(Aleph.getProperty("aleph.directoryManager"));
    s.append("\nEvent Manager:\t\t");
    s.append(Aleph.getProperty("aleph.eventManager"));
    s.append("\nTransaction Manager:\t");
    s.append(Aleph.getProperty("aleph.transactionManager"));
    s.append("\n");
    return s.toString();
  }

  // Initialize Aleph properties here because server doesn't call Aleph.main().
  static {
    // Incorporate system properties starting with "aleph."
    properties = new Properties();
    for (Enumeration enum = System.getProperties().propertyNames();
         enum.hasMoreElements();) {
      String key = (String) enum.nextElement();
      if (key.startsWith("aleph."))
        properties.put(key, System.getProperty(key));
    }
    properties.put("aleph.version", Config.version);
    // Set verbosity level
    setVerbosity(getIntProperty("aleph.verbosity", Config.verbosity));
  }

}
