/*
 *  bondFaultStatus.java
 *     @contains an implementation of fault status table.
       See README in this directory for detail.
 *     @author Kyungkoo Jun
 *     Bond group, CS, Purdue Univ.
 *     created on Nov 14, 2000
 *
 *   modified by Kyungkoo Jun, Dec. 4, 2000
 *      JellyFish free
 *   modified by Kyungkoo Jun, Nov 18, 2000
 *      add foundFaulty()
 *   modified by Kyungkoo Jun, Nov 16, 2000
 *      add kill()
 */


package bond.agent.FaultDetection;

import java.util.*;
import bond.core.*;

public class bondFaultStatus
extends bondObject
{

  public final static long WAIT_TIME = 20000;
  final static boolean debug = true;
  boolean finish = false;

  private Hashtable status;
  private Vector fault_free;
  private boolean issearching;
  private String monitored_by;
  private String myid = "";
  Object blocker;
  private bondFaultMonitor fm;
  private bondFaultDetectionMH fdMH;
  private bondFaultMonitorSearcher fms;

  public bondFaultStatus(String myid) {
    status = new Hashtable(); // current status of agents being monitored
    fault_free = new Vector();
    fault_free.add(myid);

    issearching = false; // am I orphan, searching for new tester?
    monitored_by = null; // who tests me?
    fm = null;
    blocker = new Object();
    this.myid = myid; // who am I?
  }

  public Object getBlocker() {
    return blocker;
  }

  public Hashtable getStatus() {
    return status;
  }

  public void setNewStatus(Hashtable ht) {

    Enumeration e = ht.keys();
    while (e.hasMoreElements()) {
      String ky = (String)e.nextElement();
      updateFaultStatus(ky, (Integer)ht.get(ky));
    }
  }

  public void say(bondMessage m, bondObject sender) {
    fdMH.say(m, sender);
  }

  public void startFaultDetectionMessageHandler() {
    fdMH = new bondFaultDetectionMH(this);
  }

  public void initStatusTable(String id1) {

    if(getMyID().startsWith("Agent")) {
      updateFaultStatus(id1, new Integer(0));
    }

  }

  public void kill() {

    finish = true;
    if (fm != null)
      fm.setFinish(true);
    fdMH.setFinish(true);

    if (fms != null && fms.stillGoing())
      fms.setFinish(true);

    dir.unregister(this);

  }


  public synchronized void setSearchingFlag(boolean flag) {
      issearching = flag;
  }

  public String getMyID() {
    return myid;
  }

  public Vector getFaultFreeList() {
    synchronized (fault_free) {
      return (Vector)fault_free.clone();
    }
  }
  
  public String getMonitoredBy() {
    if (monitored_by == null)
      return "";
    else
      return monitored_by;
  }


  public boolean beMonitoredBy(String id) {
    if (monitored_by == null)
      return false;
    else
      return monitored_by.equals(id);
  }

  public void setMonitoredBy(String id) {
    monitored_by = id;
    synchronized (blocker) {
      blocker.notify();
    }
  }

  public synchronized void startMonitorSearcher(boolean repaired) {
    if (!issearching) {
      issearching = true;
      Log(getMyID(), "start monitor searching", false);
      fms = new bondFaultMonitorSearcher(this, repaired);
    }
  }

  public static int getIDnum(String id) {
    int index = id.indexOf('+');
    Integer i = new Integer(id.substring(5, index));
    return i.intValue();
  }

  public void removeMonitor(bondFaultMonitor mon) {
    if (fm.getTarget().equals(mon.getTarget()))
      fm = null;
  }

  public synchronized boolean startMonitor(String id, boolean repaired) {

    // to form ring-topology to prevent jellyfish failure

    if (fm == null) {
      if (repaired)
	setFaulty(id, false);
      fm = new bondFaultMonitor(this, id);
      return true;
    }
    else if (isFaulty(fm.getTarget())) {
      if (repaired)
	setFaulty(id, false);
      fm.quitMonitoring();
      fm = new bondFaultMonitor(this, id);
      return true;
    }
    else {
      // get my id
      // get current id
      // get new id
      int my_id = getIDnum(myid);
      int curr_id = getIDnum(fm.getTarget());
      int new_id = getIDnum(id);
      Log(myid, my_id+":"+curr_id+":"+new_id, false);


      if (my_id < curr_id) {
	if (my_id < new_id && new_id < curr_id) {
	  if (repaired)
	    setFaulty(id, false);
	  fm.quitMonitoring(id);
	  fm = new bondFaultMonitor(this, id);
	  return true;
	}
      }
      else { // my_id > curr_id
	if ( (my_id > new_id && new_id < curr_id) || (new_id > my_id)) {
	  if (repaired)
	    setFaulty(id, false);
	  fm.quitMonitoring(id);
	  fm = new bondFaultMonitor(this, id);
	  return true;
	}
      }
    }
    return false;
  }

  public boolean foundFaulty(String id) {

    synchronized (status) {
      if (!isFaulty(id)) {
	setFaulty(id, true);
	return true;
      }
      else
	return false;
    }
  }


  public void setFaulty(String id, boolean faulty) {

    Integer intVal = getFaultStatus(id);
    int i, init;

    if (intVal == null) {
      init = faulty ? 1 : 0;
      updateFaultStatus(id, new Integer(init)); // initialize
      return;
    }

    i = intVal.intValue();
    
    if ( (i % 2) == 0) { // even
      i = faulty ? i+1:i+2;
    }
    else { // odd
      i = faulty ? i+2:i+1;
    }
    updateFaultStatus(id, new Integer(i));

  }

  public boolean amIorphan() {
    return (monitored_by == null);
  }

  public Integer getFaultStatus(String id) {
    synchronized(status) {
      return (Integer)status.get(id);
    }
  }

  public void updateFaultStatus(String id, Integer i) {

    Log(myid, id+" is set to ("+i+")");

    synchronized(status) {
      status.put(id, i);
    }
    int iv = i.intValue();
    if ( (iv % 2) == 0) { // fault_free
      updateFaultFreeList(id, true);
    }
    else { // faulty
      updateFaultFreeList(id, false);
      if (beMonitoredBy(id)) { // became orphan
	setMonitoredBy(null);
	startMonitorSearcher(false);
      }
      if (fm != null && fm.getTarget().equals(id)) {
	fm.quitMonitoring();
      }
    }

    synchronized (blocker) {
      blocker.notify();
    }

  }

  protected void updateFaultFreeList(String id, boolean add) {
    
    synchronized (fault_free) {
	fault_free.remove(id);
	if (add) {
	  fault_free.add(id);
	}
    }

  }


  public void startInfoHandler(String id, Integer value, Vector remains, String source) {
    new bondFaultInfoHandler(id, value, remains, source, this);
  }

  public void startInfoHandler(String id, Integer value, Vector remains, 
			       String source, bondMessage msg, bondShadow bs) {
    new bondFaultInfoHandler(id, value, remains, source, this, msg, bs);
  }

  public boolean isFaulty(String id) {

    Integer intVal = getFaultStatus(id);

    if (intVal == null) {
      return false;
    }
    int i = intVal.intValue();
    return (!((i%2) == 0));
  }


  public void disseminateInfo(String id) {
    new bondFaultDisseminator(this, id);
  }


  public static bondShadow buildShadow(String dst) {

    int temp = dst.indexOf('+');
    // for debugging
    if (temp < 0) {
      System.out.println("bondFaultStatus.java:: should not be negative");
      System.exit(0);
    }

    String id = dst.substring(0, temp).trim();
    String address = dst.substring(temp+1).trim();
    return new bondShadow(id, address);

  }

  public void spreadInfo(String dst, Vector remains, String info) {

    if (finish)
      return;

    Log(getMyID(), "spread info to "+dst+"("+remains.toString()+")"+
	" about "+info+"("+getFaultStatus(info)+")");

    if (isFaulty(dst)) {
      if (remains != null && remains.size() > 0) {
	String newdst = (String)remains.remove(0);
	spreadInfo(newdst, remains, info);
      }
      return;
    }

    bondShadow bs = buildShadow(dst);

    bondMessage msg = new bondMessage("(tell :content info-msg)", "FaultDetection");
    msg.setParameter(":ID", info);
    msg.setParameter(":status", getFaultStatus(info));
    msg.setParameter(":remains", remains);
    msg.setParameter(":source", getMyID());
    int n = remains.size() + 1;

    filelogger.log(getMyID()+" info");

    //    bondMessage rep = bs.ask(msg, this, n*WAIT_TIME);
    bondMessage rep = bs.ask(msg, this, 2*WAIT_TIME);
    if (rep == null || rep.getSubprotocol() == null ||
	!rep.getSubprotocol().equals("FaultDetection")) {

      if (foundFaulty(dst)) {
	Log(myid, " found "+dst+" faulty by infoack missing");
	disseminateInfo(dst);
      }
      if (remains.size() == 0)
	return;
      else {
	String newdst = (String)remains.remove(0);
	spreadInfo(newdst, remains, info);
      }
    }      

  }

  public static void Log(String id, String msg, boolean b) {
    Log(id, msg);
  }

  public static void Log(String id, String msg) {

    if (debug) {
      System.out.println(id+" : "+msg+" : "+System.currentTimeMillis());
    }

  }
}
