/*
 * 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.dir.arrow;

import aleph.Aleph;
import aleph.AlephDebug;
import aleph.Config;
import aleph.GlobalObject;
import aleph.PE;
import aleph.UniqueID;
import aleph.comm.Address;
import aleph.dir.DirectoryManager;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Hashtable;
import java.util.Vector;

/**
 * Directory protocol based on path reversal on a fixed spanning tree.  See
 * M.P. Herlihy and M. Demmer.  The Arrow Directory Protocol.  in <i>12th
 * International Symposium on Distributed Computing</i>, September 1998, Andros
 * Island, Greece.<p>
 *
 * The PEs in a PGroup are organized as a binary tree based on index.  (This
 * protocol does not currently work across PEGroups!) The first time an object
 * is referenced, a <code>FindMessage</code> is sent to the object's home.
 *
 * @author Maurice Herlihy
 * @date   August 1998
 **/

public  class ArrowDirectory extends DirectoryManager {

  private static ArrowDirectory theManager; // convenient access for messages

  // id -> object status
  private Hashtable arrow  = new Hashtable();

  private static final boolean DEBUG = false;

  /**
   * Constructor
   **/
  public ArrowDirectory() {
    theManager = this;
    AlephDebug.register("Arrow Directory", this);
    if (DEBUG)
      AlephDebug.println(this.toString());
  }

  /**
   * @see aleph.dir.DirectoryManager#newObject 
   * @param key    Global object
   * @param object Initial state for global object.
   * @param hint   <code>String</code> passed to directory
   **/
  public synchronized void newObject (GlobalObject key, Object object, String hint) {
    Status status = new Status(PE.thisPE());
    status.busy   = false;
    status.object = object;
    arrow.put(key, status);
  }

  /**
   * @see aleph.dir.DirectoryManager#importObject
   * @param key    Global object
   **/
  public void importObject (GlobalObject key) {}

  /**
   * @see aleph.dir.DirectoryManager#open
   * @param object The object to open.
   * @param mode   Mode in which to open object.
   **/
  public synchronized Object open (GlobalObject key, String mode) {
    Status status  = (Status) arrow.get(key);
    PE     thisPE  = PE.thisPE();
    int    myIndex = thisPE.getIndex();
    if (status == null)  {      // object is unknown
      status = new Status(thisPE);
      arrow.put(key, status);   // legalize it
      FindMessage find = new FindMessage(key);
      find.send(route(key.getHome()));
    } else if (status.count == 0 && ! status.direction.equals(thisPE)
               && ! status.busy) { // known elsewhere
      FindMessage find = new FindMessage(key);
      find.send(status.direction);
      if (DEBUG)
        AlephDebug.println("open sending " + find + " to " + status.direction);
      status.direction = thisPE;  // flip arrow
    }
    /* wait for the object to appear */
    status.count += 1;
    if (DEBUG)
      AlephDebug.println("open waits, status = " + status);
    while (status.object == null || status.busy) {
      try { wait();} catch (InterruptedException e) {}
    }
    status.busy = true;
    status.count--;
    if (DEBUG)
      AlephDebug.println("open returns, status = " + status);
    return status.object;
  }

  /**
   * @see aleph.dir.DirectoryManager#release
   * @param object Formerly interesting object.
   **/
  public synchronized void release (GlobalObject object){
    Status status = (Status) arrow.get(object);
    if (DEBUG)
      AlephDebug.println("release called\t" + status);
    status.busy = false;
    if (status.count > 0)       // local suitors
      notifyAll();              // wake up and smell the coffee
    else
      if (status.pending != null) { // remote suitor
        if (status.object == null)
          Aleph.panic("null object" + status); // debugging
        (new GrantMessage(object, status.object)).send(status.pending);
        status.object  = null;
	status.pending = null;
      }
    if (DEBUG)
      AlephDebug.println("release returns\t" + status);
  }

  /**
   * @return who we are
   **/
  public String getLabel () {
    return "Arrow";
  }

  /* PEs are arranged into a binary tree by index */
  private static int parent (int i) {	// parent's index
    return (i - 1) >> 1;
  }
  private static int left (int i) {	// left child's index
    return (i << 1) + 1;
  }
  private static int right (int i) {	// right child's index
    return (i + 1) << 1;
  }
  private static int left (int i, int d) { // left grandchild's index
    if (d == 1)
      return left(i);
    else
      return left(left(i, d-1));
  }
  private static int right (int i, int d) { // right grandchild's index
    if (d == 1)
      return right(i);
    else
      return right(right(i, d-1));
  }

  private PE parentPE () {
    int myIndex = PE.thisPE().getIndex();
    if (myIndex > 0)
      return PE.getPE(parent(myIndex));
    else
      return null;
  }

  private PE leftPE () {
    int myIndex = PE.thisPE().getIndex();
    int i = left(myIndex);
    if (i < PE.numPEs())
      return PE.getPE(i);
    else
      return null;
  }

  private PE rightPE () {
    int myIndex = PE.thisPE().getIndex();
    int i = right(myIndex);
    if (i < PE.numPEs())
      return PE.getPE(i);
    else
      return null;
  }

  private static int depth(int i) {	// depth of node in three
    int d = 0;
    int j = i + 1;
    while (j > 0) {
      j = (j >> 1);
      d++;
    }
    return d;
  }

  private static boolean isDescendant(int i0, int i1) {
    int d0 = depth(i0);
    int d1 = depth(i1);
    if (d0 >= d1)
      return false;
    int e = (d1 - d0);		// depth difference
    int lb = left(i0, e);	// left grandchild
    int rb = right(i0, e);	// right grandchild
    return (lb <= i1) && (i1 <= rb);
  }

  public String toString() {
    StringBuffer s = new StringBuffer("neighbors{");
    s.append(parentPE());
    s.append(" ");
    s.append(leftPE());
    s.append(" ");
    s.append(rightPE());
    s.append("}");
    return s.toString();
  }

  /**
   * Route message through tree for specific pe
   * @param pe destination pe
   * @return which way to send the message
   **/
  private PE route(PE pe) {
    int myIndex = PE.thisPE().getIndex();
    int i = pe.getIndex();
    if (isDescendant(myIndex,i)) {
      if (isDescendant(left(myIndex), i))
	return leftPE();
      else
	return rightPE();
    } else
      return parentPE();
  }

  /**
   * Inner class with object's location and synchronization information
   **/
  private static class Status {
    Object  object;		// object itself, or null
    PE      pending;		// next in line
    boolean busy;		// in use?
    int     count;              // how many local threads waiting?
    PE      direction;		// it went that-a-way
    Status(PE direction) {
      this.direction = direction;
      this.object    = null;
      this.pending   = null;
      this.busy      = false;
    }
    public String toString() {
      return "Status[object " + object +
        ", direction " + direction +
        ", pending " + pending +
        ", count " + count +
        ", busy " + busy + "]";
    }
  }

  /* Private message classes */
  private static class FindMessage
    extends aleph.Message implements Externalizable{
    PE from;
    GlobalObject key;
    PE requestor;
    FindMessage(GlobalObject key) {
      PE pe = PE.thisPE();
      this.from = pe;
      this.key = key;
      requestor = pe;
    }
    public FindMessage() {};
    /**
     * @see java.io.Externalizable
     **/
    public void writeExternal(ObjectOutput out) throws IOException {
      super.writeExternal(out);
      out.writeObject(from);
      out.writeObject(key);
      out.writeObject(requestor);
    }
    /**
     * @see java.io.Externalizable
     **/
    public void readExternal(ObjectInput in)
      throws IOException, java.lang.ClassNotFoundException {
      super.readExternal(in);
      from       = (PE) in.readObject();
      key        = (GlobalObject) in.readObject();
      requestor  = (PE) in.readObject();

    }
    public void run() {
      synchronized (theManager) {
	Status status = (Status) theManager.arrow.get(key);
	PE newDirection = from;
        if (ArrowDirectory.DEBUG)
          AlephDebug.println(this + " called\t " + status);
	if (status == null) {
          /* Object is unknown.
           * Install status record for object.
           * Flip arrow toward incoming edge.
           * send toward object's id.home
           */
	  status = new Status(newDirection); // install status
	  theManager.arrow.put(key, status);
	  this.from = PE.thisPE();
	  this.send(theManager.route(key.getHome()));
        } else {
          /* Object is known.
           * Flip arrow toward incoming edge.
           * Check whether object is local.
           */
          PE direction = status.direction;
          status.direction = newDirection; // flip arrow
          if (direction.equals(PE.thisPE())) { // object is local
            if (status.busy || status.count > 0 || status.object == null) {
              // object is real busy
              status.pending = requestor; // so get in line
              if (ArrowDirectory.DEBUG)
                AlephDebug.println(requestor + " getting in line");
            } else {            // object is free, send it
              if (ArrowDirectory.DEBUG)
                AlephDebug.println("granting to " + requestor);
              if (status.object == null)
                Aleph.panic("null object" + status); // debugging
              GrantMessage grant = new GrantMessage(key, status.object);
              grant.send(requestor);
              status.object = null;
            }
          } else {          // object is somewhere else
            if (ArrowDirectory.DEBUG)
              AlephDebug.println("forwarding to " + direction);
	    this.from = PE.thisPE();
            this.send(direction);
          }
        }
        if (ArrowDirectory.DEBUG)
          AlephDebug.println(this + " returned\t " + status);
      }
    }
    public String toString() {
      StringBuffer s = new StringBuffer("FindMessage[from ");
      s.append(from.toString());
      s.append(", key ");
      s.append(key.toString());
      s.append(", requestor ");
      s.append(requestor.toString());
      s.append("]");
      return s.toString();
    }
  }

  /**
   * This message conveys the object from one owner to the next.
   * It is sent directly, not through the tree.
   **/
  private static class GrantMessage
    extends aleph.Message implements Externalizable {
    GlobalObject key;
    Object       object;
    GrantMessage(GlobalObject key, Object object) {
      this.key = key;
      this.object = object;
    }
    public GrantMessage() {}
    public void run() {
      synchronized (theManager) {
	Status status = (Status) theManager.arrow.get(key);
        if (ArrowDirectory.DEBUG)
          AlephDebug.println(this + " called\t" + status);
	status.object = object;
        status.busy   = false;
        if (ArrowDirectory.DEBUG)
          AlephDebug.println(this + " returns\t" + status);
	theManager.notifyAll();
      }
    }
    /**
     * @see java.io.Externalizable
     **/
    public void writeExternal(ObjectOutput out) throws IOException {
      super.writeExternal(out);
      out.writeObject(key);
      out.writeObject(object);
    }

    /**
     * @see java.io.Externalizable
     **/
    public void readExternal(ObjectInput in)
      throws IOException, java.lang.ClassNotFoundException {
      super.readExternal(in);
      key      = (GlobalObject) in.readObject();
      object   = in.readObject();
    }

    public String toString () {
      StringBuffer s = new StringBuffer("GrantMessage[key ");
      s.append(key.toString());
      s.append(", object ");
      s.append(object.toString());
      s.append("]");
      return s.toString();
    }
  }

  public static void main(String[] args) {
    int i0 = 0;
    int i1 = 1;
    try {
      if ( args.length > 0)
        i0 = Integer.parseInt(args[0]);
      if ( args.length > 1)
        i1 = Integer.parseInt(args[1]);
    } catch (NumberFormatException e) {
      System.err.println("usage: Matrix <#dimension> <#tests>");
      Aleph.exit(1);
    }
    System.out.println("depth " + i0 + " = " + depth(i0));
    System.out.println("depth " + i1 + " = " + depth(i1));
    System.out.println("descendant(" + i0 + ", " + i1 + ") = " + isDescendant(i0,i1));
  }

}

