/**
*
*   File: bondAgent.java
*
*   Contains:
*      the basic structure for a generic agent
*
*      @author Lotzi Boloni
*      Bond Lab, Computer Science Dept. 115, Purdue University
*      created: April 20, 1997
*
*      modified: Nov 16, 2000 Kyungkoo Jun
*          -- modify kill(), to remove bondFaultStatus
*      modified: Nov 15, 2000 Kyungkoo Jun
*          -- add sphFaultDetection()
*          -- add initStatusTable()
*      modified: Nov 8, 2000 Kyungkoo Jun
*          -- change return type of populateModel() to boolean from void
*      modified: Nov 6, 2000 Kyungkoo Jun
*          -- :populate-model performative added
*          -- add populateModel()
*          -- :model parameter can be either URL or XML-based model
*          -- add initDropbox()
*      modified: Nov 4, 2000 Kyungkoo Jun
*          -- :model parameter added for startAgent message handling
*      modified: April 24th, 1999 Lotzi Boloni
*          -- kill()
*      modified: February 3rd, 1999 Lotzi Boloni
*          -- bondAgentPlane moved out.
*      modified: February 1st, 1999 Lotzi Boloni
*          -- new massive changes for the multiplane structure of the bondAgent
*      modified: January 31, 1999 Lotzi Boloni
*          -- header shows the agent's name
*      modified: December 15th, Lotzi Boloni
*          -- support for writing into the model
*      modified: November 11th, Lotzi Boloni
*          -- blueprint loader moved
*          -- constructor for the blueprint loader.
*          -- it is not an abstract class any more...
*          -- starting to implement the blueprint loader
*      modified: November 10th, Lotzi Boloni
*      modified: November 7th, Lotzi Boloni
*      modified: October 19th, Lotzi Boloni
*          -- comments, simplifications
*      modified: October 11th, Lotzi Boloni
*          -- agent control subprotocol, sort of done
*      modified: October 9th, Lotzi Boloni
*          -- generic model initialized from the constructor
*      modified: October 6th, Lotzi Boloni
*          -- new model structure
*      modified: August 29th, Lotzi Boloni: the basic agent structure in place
*      modified: August 28th, Lotzi Boloni, moved to bond.runnable.agent
*      modified: August 27th, 1998
**/

package bond.agent;
import java.util.*;
import java.io.*;
import bond.core.*;
import bond.core.util.*;
import bond.core.gui.controlpanel.*;
import bond.core.gui.editor.*;
import bond.agent.interfaces.*;
import bond.agent.modelXML.*;
import bond.agent.FaultDetection.*;

public class bondAgent
  extends bondExecutable {
  //    Thread AgentThread = null, KillThread = null;
  public bondModel model = null;
  public bondAgenda agenda = null;
  public boolean running = false;
  public bondShadow beneficiary;
  boolean restricted_control = false; // if restricted, only the beneficiary can control

  public Vector strategypath = new Vector();
  public Vector history = new Vector(); // records the history of the agent
  public Vector planes = new Vector();
  public bondSubProtocol sp = null;
  boolean softstop;
  bondActionScheduler basched = null;
  public bondEventSemantic semantic;

  public bondFaultStatus fs = null;

  /**
     Constructs a new, empty bondAgent
  */
  public bondAgent() {
    model = new bondModel();
    initStrategyPath();
    // the default is to initialize a round robin sheduler
    String schedulerName = System.getProperty("bond.scheduler");
    if (schedulerName.equals("MT")) {
      basched = new bondMTActionScheduler(this);
    } else {
      if (!schedulerName.equals("RR")) {
	Log.Debug("Action scheduler not specified or invalid, using RR");
      }
      basched = new bondRRActionScheduler(this);
    }
      
      
    // default semantic: state machine
    semantic = new bondStateMachineSemantic(planes, model);
  }

  /**
     initializes the strategy paths where the strategies, strategy groups and
     agendas are searched for.
  */
  private void initStrategyPath() {
    strategypath = new Vector();
  };


  /**
     Starts the agent by creating it's thread.
  */
  public void start() {
    running = true;
    basched.start();
  }


  /**
     Stops the agent
  */
  public void stop() {
    if (!running) return;
    running = false;
    basched.stop();
  }

  /**
       Stops the agent in a non-invasive way
     */
  public void softStop() {
    if (!running) return;
    running = false;
    basched.softStop();
  }


  /**
      The message handling function of the agent. The only trick here is that
      messages in the control subprotocol are delivered to the finite state machine.
  */
  public void say(bondMessage m, bondObject sender) {

    try {
     
      // auto handling here...
      if (m.getSubprotocol().equals("AgentControl")) {
	sphAgentControl(m, sender);
	return;
      } 

      // Fault Detection 
      if (m.getSubprotocol().equals("FaultDetection")) {
	sphFaultDetection(m, sender);
	return;
      }

      if (genericSPH(m, sender)) {
	return;
      }
      // if it is in my subprotocol deliver it to the finite state machine
      if (m.getSubprotocol().equals(sp.getName())) {
	Log.Debug("External transition: "+m.compose());
	// the message is delivered to all finite state machines
	for(Enumeration e=planes.elements(); e.hasMoreElements(); ) {
	  bondAgentPlane bap = (bondAgentPlane) e.nextElement();	
	  bap.fsm.say(m,sender);
	}
      } else {
	super.say(m, sender); // otherwise, go upper
      }
    }
    catch (NullPointerException e) {

    }
  }

  

  public void kill() {
    bondTabbedControlPanel t = (bondTabbedControlPanel)get("Visual");
    if (t != null)
      t.close();
    bondEditor be = (bondEditor) get("Editor");
    if (be != null)
      be.close();

    if (fs != null)
      fs.kill();
    softStop();

    dir.unregister(this);

  }

  public void sphFaultDetection(bondMessage m, bondObject sender) {

    if (fs == null)
      return;  // not implementing
    fs.say(m, sender);
  }

  public void initFaultDetection() {
    String tmp;
    if ( (tmp = System.getProperty("bond.faultDetection")) != null && tmp.equals("true")) {
      fs = new bondFaultStatus(dir.getAlias(this)+"+"+com.localaddress+":"+com.localport);
      fs.startFaultDetectionMessageHandler();
      // load system graph
      fs.initStatusTable(System.getProperty("default.monitoring.agent"));
      // start searching
      fs.startMonitorSearcher(true); 
    }
  }


  /**
  *   The agent control subprotocol is handled here
  */
  public void sphAgentControl(bondMessage m, bondObject sender) {

    Log.Debug("Agent received: "+m.compose());

    if (sender == null)
      sender = m.getSender();
    if (restricted_control && !sender.equals(beneficiary)) {
      sender.say( m.createReply("(deny)"),this);
    }
    // state inquiry
    if (m.content.equals("get-state")) {
      // the state is a concatenation of the states of the planes
      String state = "";
      for(Enumeration e=planes.elements(); e.hasMoreElements(); ) {
	bondAgentPlane bap = (bondAgentPlane) e.nextElement();
	state += "."+bap.fsm.getState().getName();
      }
      sender.say( m.createReply("(tell :content state :state "+state+")")
		  ,this);
    }
    if (m.content.equals("start-agent")) {
      initDropBox();
      populateModel(m.getParameter(":model"));

      String als = (String)m.getParameter(":alias");
      if (als != null) {
	dir.addAlias(als, this);
        initFaultDetection();
      }
      start();
      return;
    }
    if (m.content.equals("stop-agent")) {
      // stops the agent
      softstop = true;
      // wait() of softstop
      sender.say( m.createReply("(tell :content ok)"),this); 
      return;
    }
    if (m.content.equals("kill-agent")) {
      kill();
      sender.say( m.createReply("(tell :content ok)"),this);
      return;
    }
    if (m.content.equals("getModel")) {
      Object val = model.get((String)m.getParameter(":property"));
      bondMessage rep = m.createReply("(tell :content value)");
      rep.setParameter(":value", val);
      sender.say(rep,this);
      return;
    }
    if (m.content.equals("setModel")) {
      model.set((String)m.getParameter(":property"), m.getParameter(":value"));
      // if needs a reply, send a confirmation
      if (m.getParameter(":createReply") != null) {
	m.sendReply("(tell :content ok)", this);
      }
      return;
    }
    if (m.content.equals("populate-model")) {
      bondDropBox box = (bondDropBox)model.get("Dropbox");
      box.addLast(m.getParameter(":model"));
    }
  }

  /*
   * initialize a dropbox for model-populating requests
   */
  private void initDropBox() {
    model.set("Dropbox", new bondDropBox());
  }

  /*
   * add name-value pairs given in URL or String to model
   * @param xmlmodel URL or String containing name-value pairs
   */
  public boolean populateModel(Object mXML) {

    if (mXML == null)
      return false;
    
    bondXMLmodel temp = new bondXMLmodel();
    temp.setModel(model);

    if (mXML instanceof bondEmbeddedBlueprint) {
      bondEmbeddedBlueprint model_XML = (bondEmbeddedBlueprint)mXML;
      temp.fromXML(model_XML.getReader());
    }
    else {
      String model_XML = (String)mXML;
      if (model_XML.startsWith("http://") || 
	  model_XML.startsWith("HTTP://") ||
	  model_XML.startsWith("file:/") ||
	  model_XML.startsWith("FILE:/")) {
	temp.fromXML(model_XML);
      }
      else {
	temp.fromXML(new ByteArrayInputStream(model_XML.getBytes()));
      }
    }
    return true;
  }

  /**
        Adding a new plane. Returns the finite state machine
     */
  public bondFiniteStateMachine addPlane(String name, int priority) {        
    bondAgentPlane ap;
    ap = new bondAgentPlane();
    ap.setName(name);
    ap.fsm = new bondFiniteStateMachine();
    ap.fsm.plane = ap;
    ap.fsm.agent = this;
    ap.priority = priority;
    planes.addElement(ap);
    return ap.fsm;
  }

  /*
      Finds a plane and returns the corresponding finite state machine
     */
  public bondAgentPlane getPlane(String name) {
    for(Enumeration e=planes.elements(); e.hasMoreElements(); ) {
      bondAgentPlane bap = (bondAgentPlane) e.nextElement();
      if (bap.getName().equals(name)) {
	return bap;
      }
    }
    return null;
  }

  /**
       Delete plane
     */
  public void deletePlane(String name) {
    for(int i=0; i!=planes.size(); i++) {
      bondAgentPlane current = (bondAgentPlane)planes.elementAt(i);
      if (current.getName().equals(name)) {
	planes.removeElementAt(i);
	return;
      }
    }	
  }

  /**
      Generates the subprotocol object agent by taking all the transitions on all the planes
  */
  public void generateSubProtocol() {
    // for all transitions add a message!!! and generate the description!!!
    for(Enumeration e2=planes.elements(); e2.hasMoreElements(); ) {
      bondAgentPlane bap=(bondAgentPlane)e2.nextElement();
      for(Enumeration e=bap.fsm.transitions.elements(); 
	  e.hasMoreElements(); ) {
	bondFSMTransition tr = (bondFSMTransition) e.nextElement();
	String message = ((bondMessage) tr.event).compose();
	String pars[]=new String[0];
	sp.addMember(bondMessage.PF_ACHIEVE, message, "Description lacking", pars);
      }
    }
  }

  /**
       Trims the agent
     */
  public void Trim() {
    for(Enumeration e2=planes.elements(); e2.hasMoreElements(); ) {
      bondAgentPlane bap=(bondAgentPlane)e2.nextElement();
      bap.fsm.Trim();
    }
  }

  public void setAutoTrim(boolean val) {
    for(Enumeration e2=planes.elements(); e2.hasMoreElements(); ) {
      bondAgentPlane bap=(bondAgentPlane)e2.nextElement();
      bap.fsm.autoTrim = val;
    }
  }

}

