package rmi.rmiSynth;

// rmi.rmiSynth.HostManager

import java.rmi.AlreadyBoundException;
import java.rmi.RMISecurityManager;
import java.rmi.RemoteException;
import java.rmi.StubNotFoundException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.Enumeration;
import java.util.Vector;

/**
 *    The <code>HostManager</code> is invoked
 * remotely by unicast remote protocol. New workers that
 * enter into the grid must know the IP addBk.address of the HostManager
 * We need to provide a leasing mechanism that expires a host.
 * First start the host manager. Then start the computation servers.
 * Finally start the compute client
 */

public class HostManager
    extends java.rmi.server.UnicastRemoteObject
    implements HostManagerInterface {
  // a Multi-cast mechanism is needed to
  // obtain the host addBk.address.
  // Otherwise it must be hard-coded, like this:

  public static final String hostManagerAddress = "192.168.1.96";
  // but some routers do not pass multi-cast packets!
  // So the string stays.

  private Vector remoteHosts = new Vector();

  private static HostManagerInterface proxy = null;

  public Host[] getHosts() throws RemoteException {
    Host h[] = new Host[remoteHosts.size()];
    remoteHosts.copyInto(h);
    return h;
  }


  private HostManager() throws RemoteException {
  }


  /**
   *    Use getProxy to gain remote access to the host manager.
   * If proxy was already obtained, this invocation will not
   * look it up again.
   */
  public static HostManagerInterface getProxy()
      throws RemoteException {
    if (proxy != null) return proxy;
    try {
      //locate the server class on remote machine
      System.out.println("locating remote registry...");
      Registry r = LocateRegistry.getRegistry(hostManagerAddress);
      System.out.println("looking up Manager...");
      return
          (HostManagerInterface) r.lookup("HostManager");

    } catch (Exception e) {
      e.printStackTrace();
    }
    return null;

  }

  public void add(Host h) throws RemoteException {
    if (contains(h)) {
      update(h);
      return;
    }
    remoteHosts.addElement(h);
    System.out.println("added host:" + h + " size=" + remoteHosts.size());
  }

  public boolean contains(Host h) {
    return indexOf(h, 0) >= 0;
  }

  public synchronized int indexOf(Host h, int index) {
    int numberOfHosts = remoteHosts.size();
    if (h == null) {
      for (int i = index; i < numberOfHosts; i++)
        if (remoteHosts.elementAt(i) == null)
          return i;
    } else {
      for (int i = index; i < numberOfHosts; i++)
        if (equals(h, (Host) remoteHosts.elementAt(i)))
          return i;
    }
    return -1;
  }

  // ok, here is a compelling argument for Java generics.
  // The Vector code had to be duplicated and strongly
  // linked to the Host equals method, or else the WRONG
  // equals is used and therefore does not work as expected.
  // A discovery. But is it interesting?
  // Yes, if it is used in RMI, an equals results in a call-back
  // which is not normally needed. The host must register and
  // the overhead on this computation is extreme.
  public static boolean equals(Host h1, Host h2) {
    return h1.getIP().equals(h2.getIP());
  }


  private int hostIndex = 0;

  /**
   *   <code>getNextHost</code> returns the next host in the list, with
   * wrap-around.
   */
  public Host getNextHost() throws RemoteException {
    hostIndex = (hostIndex + 1) % remoteHosts.size();
    return (Host) remoteHosts.elementAt(hostIndex);

  }

  private Job landLord = new Job(4) {
    public void doCommand() {
      removeOldHosts();
    }
  };

  public void removeOldHosts() {
    for (Enumeration e = remoteHosts.elements(); e.hasMoreElements();) {
      Host h = (Host) e.nextElement();
      if (System.currentTimeMillis() - h.getTimeOfUpdate() > 4000)
        remove(h);
    }
  }

  public void remove(Host h) {
    int i = indexOf(h, 0);
    remoteHosts.remove(i);
    System.out.println("HostManager Alert: Host has timed out:" + h + " and was removed");
  }

  public void update(Host h) throws RemoteException {
    System.out.println("update invoked on host manager" + new java.util.Date());
    int i = indexOf(h, 0);
    h.setTimeOfUpdate(System.currentTimeMillis());
    remoteHosts.setElementAt(h, i);

  }

  public static void main(String args[]) {
    try {
      startHostManager();
    } catch (StubNotFoundException e) {
      System.out.println("ERROR:Did you remember to gui.run RMIC?"
                         + "\n rmic rmi.rmiSynth.HostManager");
    } catch (AlreadyBoundException e) {
      System.out.println("ERROR:is the registry running?");
      e.printStackTrace();
    } catch (RemoteException e) {
      e.printStackTrace();
    }

  }

  /**
   *  To start the host manager, you must invoke the main method.
   * To get an instance of the host manager, you must get a proxy.
   */
  private static HostManager startHostManager()
      throws RemoteException, AlreadyBoundException {
    // Create and install a security manager
    System.setSecurityManager(new RMISecurityManager());
    HostManager hm = new HostManager();
    System.out.println("HostManager starting...");
    //Create the registry and bind the Server class to the registry
    try {
      LocateRegistry.createRegistry(1099);
      System.out.println("located registry...");
    } catch (Exception e) {
      System.out.println("ERROR:is the registry already on port 1099?");
    }
    Registry r = LocateRegistry.getRegistry();
    bindToRegistry(r, hm);
    System.out.print("HostManager is running on:");
    try {
      System.out.println(Host.getAddress());
    } catch (java.net.UnknownHostException e) {
      System.out.println(
          "ERROR:HostManager,"
          + " can't get local addBk.address?");
    }
    return hm;
  }

  private static void bindToRegistry(Registry r, HostManager hm) throws RemoteException {
    try {
      r.bind("HostManager", hm);
    } catch (RemoteException e) {
      e.printStackTrace();
    } catch (AlreadyBoundException e) {
      System.out.println("HostManager:Rebinding. Was this already running?");
      r.rebind("HostManager", hm);
    }
  }


}