package JavaAgent.resource.cdl;

import JavaAgent.resource.*;
import JavaAgent.resource.fopl.LitLObject;
import JavaAgent.agent.Agent;

import Planning.*;
import Search.USearcher;

import java.io.*;
import java.util.*;
import java.net.URL;

/**
** This Interpreter manages storage and retrieval of capabilities for
** a capability broker. Brokers are implemented as extensions of Agent 
** Name Servers (<tt>ANS</tt>s). To know about this Interpreter the
** <tt>ANS</tt> should receive a message from its initialization file
** telling it about this resource and associating it with the name
** <tt>capabilities</tt>. Now all messages to the <tt>ANS</tt> refering
** to the ontology <tt>capabilities</tt> will be handled by an instance
** of this CDLInterpreter.
** <p> A CDLInterpreter maintains the capability descriptions of agents 
** advertising their capabilities in CDL (capability storage). On request, 
** a CDLInterpreter can find problem-solving agents for a given problem 
** (capabiliy retrieval). All messages to a CDLInterpreter are ultimately 
** handled by the function <tt>interpretMessage(KQMLmessage, Agent)</tt> 
** as described below.
*/

public class CDLInterpreter extends Interpreter {

  public CDLInterpreter() {
    try {
      traceStream = new FileOutputStream("cdltrace.txt");
    } catch (Exception e) {
      System.out.println("Failed to open trace stream for CDLInterpreter!");
    }
  }

  /**
  ** This function is called when the broker receives a KQMLmessage 
  ** with the ontology <tt>capabilities</tt>. The following performatives 
  ** are currently accepted:
  ** <ul>
  ** <li> <tt>load-ontology</tt> calls <tt>loadOntology(...)</tt>
  ** <li> <tt>advertise</tt> calls <tt>addCapability(...)</tt>
  ** <li> <tt>recommend-one</tt> calls <tt>recommendOne(...)</tt>
  ** <li> <tt>recommend-all</tt> calls <tt>recommendAll(...)</tt>
  ** <li> <tt>broker-one</tt> calls <tt>brokerOne(...)</tt>
  ** <li> <tt>broker-all</tt> calls <tt>brokerAll(...)</tt>
  ** </ul>
  ** @param message the KQMLmessage to be interpreted here
  ** @param receiver the Agent to which this CDLInterpreter belongs and which
  ** has received the message
  ** @exception InterpretationException An exception will occur if this
  ** CDLInterpreter fails to process this message for some reason.
  */
  public void interpretMessage(KQMLmessage message, Agent receiver) 
      throws InterpretationException {

    traceln("Interpreting message: " + message.getSendString());

    // calling a function to process the message according to the performative:
    String performative = message.getValue("performative");
    traceln("Performative: " + performative);

    if (performative.equals("load-ontology")) {
      if (!message.getValue("language").equals("URL"))
	throw new InterpretationException(
            "Ontology must be specified as URL.");
      try {
        loadOntology(new URL(message.getValue("content")), receiver);
      } catch (java.net.MalformedURLException mfurle) {
	throw new InterpretationException(
            "Problem with CDL ontology URL.");
      } catch (IOException ioe) {
	throw new InterpretationException(
            "Failed to load CDL ontology: " + ioe.getMessage());
      }
    } 
    else if (performative.equals("advertise")) {
      if (!message.getValue("language").equals("KQML"))
	throw new InterpretationException(
            "Content of capability advertisements must be in KQML.");
      KQMLmessage KQMLcontent = new KQMLmessage(message.getValue("content"));
      addCapability(message.getValue("sender"), KQMLcontent, receiver);
    } 
    else if (performative.equals("recommend-one")) {
      if (!message.getValue("language").equals("CDL"))
	throw new InterpretationException(
            "Content of recommendation requests must be in CDL.");
      try {
	CDLDescription CDLcontent = new CDLDescription(
            message.getValue("sender"), message.getValue("content"), 
            receiver, ontology);
	recommendOne(message.getValue("sender"), CDLcontent, receiver);
      } catch (java.text.ParseException pe) {
	throw new InterpretationException(pe.getMessage());
      }
    }
    else if (performative.equals("recommend-all")) {
      if (!message.getValue("language").equals("CDL"))
	throw new InterpretationException(
            "Content of recommendation requests must be in CDL.");
      try {
	CDLDescription CDLcontent = new CDLDescription(
            message.getValue("sender"), message.getValue("content"), 
            receiver, ontology);
	recommendAll(message.getValue("sender"), CDLcontent, receiver);
      } catch (java.text.ParseException pe) {
	throw new InterpretationException(pe.getMessage());
      }
    }
    else if (performative.equals("broker-one")) {
      if (!message.getValue("language").equals("CDL"))
	throw new InterpretationException(
            "Content of recommendation requests must be in CDL.");
      try {
	CDLDescription CDLcontent = new CDLDescription(
            message.getValue("sender"), message.getValue("content"), 
            receiver, ontology);
	brokerOne(message.getValue("sender"), CDLcontent, receiver);
      } catch (java.text.ParseException pe) {
	throw new InterpretationException(pe.getMessage());
      }
    }
    else if (performative.equals("broker-all")) {
      if (!message.getValue("language").equals("CDL"))
	throw new InterpretationException(
            "Content of recommendation requests must be in CDL.");
      try {
	CDLDescription CDLcontent = new CDLDescription(
            message.getValue("sender"), message.getValue("content"), 
            receiver, ontology);
	brokerAll(message.getValue("sender"), CDLcontent, receiver);
      } catch (java.text.ParseException pe) {
	throw new InterpretationException(pe.getMessage());
      }
    }
    else {
      throw new InterpretationException(
        "Interpreter " + message.getValue("ontology") + 
	" does not support performative " + performative);
    }
    traceln("Message interpreted.\n");
  }

  /**
  ** This function loads the action ontology for this CDLInterpreter.
  ** This function should only be called for the broker. It opens the given
  ** URL and reads expressions in brackets from there. Each expression is
  ** interpreted as a CDLDescription.
  ** @param ontologyURL the URL that specifies where to find the ontology
  ** @param receiver should be the ANS Agent
  */
  protected void loadOntology(URL ontologyURL, Agent receiver) 
      throws IOException {
    traceln("Loading ontology from: " + ontologyURL);
    ExpressionReader cder = new ExpressionReader(new BufferedInputStream(
        ontologyURL.openStream()));
    String capa = cder.readExpression();
    while (capa != null) {
      try {
	CDLDescription newCD = new CDLDescription("ANS", capa, 
            receiver, ontology);
	traceln("New capability: " + newCD);
	ontology.put(newCD.getAction(), newCD);
      } catch (java.text.ParseException pe) {
	receiver.addSystemMessage(
	    "Failed to parse " + capa + " in CDL ontology.");
      }
      capa = cder.readExpression();
    }
    traceln("Ontology loaded!");
  }

  /**
  ** This function gets called when an agent wants to advertise a new 
  ** capability. For this to be a valid capability description the 
  ** performative must be <tt>perform</tt> or <tt>achieve</tt>, the specified 
  ** receiver must be the agent that advertised the capability, the content 
  ** language must be CDL, and the content must be a correct CDL expression.
  ** @param agent the name of the agent advertising the capability
  ** @param message the KQML that has the capability description as its 
  ** content
  ** @param receiver the agent that has received this message, i.e. the
  ** broker that owns this CDLInterpreter
  ** @exception InterpretationException An exception will occur if the given
  ** capability description cannot be processed, e.g. if the content field 
  ** does not contain a valid CDL expression.
  */
  protected void addCapability (String agent, KQMLmessage message, 
      Agent receiver) throws InterpretationException {

    traceln("Adding capability from: " + agent);
    String performative = message.getValue("performative");
    if (! (performative.equals("achieve") || performative.equals("perform")))
      throw new InterpretationException(
        "Capabilities must have the performative \"achieve\" or \"perform\".");
    if (! agent.equals(message.getValue("receiver")))
      throw new InterpretationException(
        "Advertizer and performer of a capability must be the same agent!");
    if (! message.getValue("language").equals("CDL"))
      throw new InterpretationException(
	"Advertised capabilities must be described in CDL!");

    // parse the content CDL expression:
    try {
      CDLDescription cd = new CDLDescription(
          agent, message.getValue("content"), receiver, ontology);
      if (cd.isCapability()) {
	agentCapas.addElement(cd);
	traceln("Capability is: " + cd);
	//System.out.println("new capability: " + cd);
      }
      else
	throw new InterpretationException(
	    "Advertised capabilitiy must not be a task!");
    } catch (java.text.ParseException pe) {
      throw new InterpretationException(
          "Failed to parse capability description: " + pe.getMessage());
    }
    traceln("Capability added!");
  }

  /**
  ** This function gets called when an agent wants to find another agent
  ** that can solve the problem described in the given CDLDescription.
  ** @param agent the name of the agent seeking a capability
  ** @param task the CDLDescription of the task to be performed
  ** @param receiver the agent that has received this message, i.e. the
  **   broker that owns this CDLInterpreter
  ** @exception InterpretationException An exception will occur if 
  */
  protected void recommendOne (String agent, CDLDescription task, 
      Agent receiver) throws InterpretationException {
    traceln("Recommending one agent for task: " + task);
    boolean agentFound = false;
    Enumeration cds = agentCapas.elements();
    while ((! agentFound) && cds.hasMoreElements()) {
      CDLDescription cd = (CDLDescription)cds.nextElement();
      try {
	traceln("Testing capability: " + cd);
	if (cd.subsumes(task)) {
	  KQMLmessage rtnMsg = new KQMLmessage(
              "(forward :sender ANS :receiver " + agent + 
              " :ontology agent :language KQML :content " +
                "(achieve :receiver " + cd.getAgent() + 
                " :ontology OPlan :language CDL :content " +
                  cd.toString() + "))");
	  receiver.sendMessage(rtnMsg);
	  traceln("Recommending: " + cd.getAgent());
	  agentFound = true;
	}
      } catch (FormalismException fe) {
	// state language in capability description does not support
	// inference requirements; no action required
	System.out.println("Exception: " + fe.getMessage());
      }
    }
    if (! agentFound) {
      KQMLmessage sorryMsg = new KQMLmessage(
          "(sorry :sender ANS :receiver " + agent + " :ontology agent");
      receiver.sendMessage(sorryMsg);
      traceln("No matching capability found.");
    }
  }

  /**
  ** This function gets called when an agent wants to find other agents
  ** that can solve the problem described in the given CDLDescription.
  ** @param agent the name of the agent seeking a capability
  ** @param task the CDLDescription of the task to be performed
  ** @param receiver the agent that has received this message, i.e. the
  **   broker that owns this CDLInterpreter
  ** @exception InterpretationException An exception will occur if 
  */
  protected void recommendAll (String agent, CDLDescription task, 
      Agent receiver) throws InterpretationException {
    traceln("Recommending all agents for task: " + task);
    boolean agentFound = false;
    Enumeration cds = agentCapas.elements();
    while (cds.hasMoreElements()) {
      CDLDescription cd = (CDLDescription)cds.nextElement();
      try {
	traceln("Testing capability: " + cd);
	if (cd.subsumes(task)) {
	  KQMLmessage rtnMsg = new KQMLmessage(
              "(forward :sender ANS :receiver " + agent + 
              " :ontology agent :language KQML :content " +
                "(achieve :receiver " + cd.getAgent() + 
                " :ontology OPlan :language CDL :content " +
                  cd.toString() + "))");
	  receiver.sendMessage(rtnMsg);
	  traceln("Recommending: " + cd.getAgent());
	  agentFound = true;
	}
      } catch (FormalismException fe) {
	// state language in capability description does not support
	// inference requirements; no action required
      }
    }
    if (agentFound) {
      KQMLmessage eosMsg = new KQMLmessage(
          "(eos :sender ANS :receiver " + agent + " :ontology agent");
      receiver.sendMessage(eosMsg);
      traceln("No more capabilities available.");
    }
    else {
      KQMLmessage sorryMsg = new KQMLmessage(
          "(sorry :sender ANS :receiver " + agent + " :ontology agent");
      receiver.sendMessage(sorryMsg);
      traceln("No matching capability found.");
    }
  }

  /**
  ** This function gets called when an agent wants to contract another agent
  ** through the broker that can solve the problem described in the given 
  ** CDLDescription.
  ** @param agent the name of the agent seeking a capability
  ** @param task the CDLDescription of the task to be performed
  ** @param receiver the agent that has received this message, i.e. the
  **   broker that owns this CDLInterpreter
  ** @exception InterpretationException An exception will occur if 
  */
  protected void brokerOne (String agent, CDLDescription task, 
      Agent receiver) throws InterpretationException {

    traceln("Brokering (one agent) for task: " + task);
    // try to find one agent that can solve the problem:
    for (Enumeration cds = agentCapas.elements(); cds.hasMoreElements(); ) {
      CDLDescription cd = (CDLDescription)cds.nextElement();
      try {
	traceln("Testing capability: " + cd);
	if (cd.subsumes(task)) {
	  KQMLmessage rtnMsg = new KQMLmessage(
              "(achieve :receiver " + cd.getAgent() + 
                " :ontology OPlan :language CDL :content " +
                  task.toString() + ")");
	  receiver.sendMessage(rtnMsg);
	  traceln("Delegating task to: " + cd.getAgent());
	  return;
	}
      } catch (FormalismException fe) {
	// state language in capability description does not support
	// inference requirements; no action required
      }
    }

    // try to find a plan:
    traceln("No matching capability found. Attempting to find plan.");
    Plan thePlan = new Plan();
    for (Enumeration cds=agentCapas.elements(); cds.hasMoreElements(); ) {
      CDLDescription cd = ((CDLDescription)cds.nextElement());
      try {
	Operator op = getOperator(cd.instantiate());
	traceln("Operator: " + op);
	thePlan.addOperator(op);
      } catch (Exception e) {
	// perfectly normal for agents that do not use LilLObject as
	// content language in CDL expressions
      }
    }
    try {
      CDLDescription taskInst = task.instantiate();
      for (Enumeration ice=taskInst.getInputConstrs().elements(); 
          ice.hasMoreElements(); )
	thePlan.addInitial(((LitLObject)ice.nextElement()).getLiteral());
      for (Enumeration oce=taskInst.getOutputConstrs().elements(); 
          oce.hasMoreElements(); )
	thePlan.addGoal(((LitLObject)oce.nextElement()).getLiteral());
      traceln("Initial plan: " + thePlan);
      USearcher pps = new USearcher(thePlan);
      try {
	pps.setTraceStream(new BufferedOutputStream(
	    new FileOutputStream("plantrace.txt")));
      } catch (IOException ioe) {
	System.out.println("Failed to create trace file: " + ioe.getMessage());
      }
      pps.setSearchLimit(100);
      //pps.searchDepthFirst();
      pps.setRepeatTestLevel(0);
      if (pps.search()) {
	// execute the plan:
	traceln("Plan found: " + (Plan)pps.getSolutionState());
	System.out.println("Executing plan: " + (Plan)pps.getSolutionState());
	return;
      }
    } catch (Exception e) {
      // perfectly normal for tasks that do not use LilLObject as
      // content language in CDL expressions
    }
    // nothing else we can do:
    traceln("No plan found!");
    KQMLmessage sorryMsg = new KQMLmessage(
        "(sorry :sender ANS :receiver " + agent + " :ontology agent");
    receiver.sendMessage(sorryMsg);
  }

  static private Operator getOperator(CDLDescription cd) throws Exception {
    Vector vars = cd.getInputVars();
    Vector precs = new Vector();
    for (Enumeration e=cd.getInputConstrs().elements(); e.hasMoreElements(); )
      precs.addElement(((LitLObject)e.nextElement()).getLiteral());
    Vector effects = new Vector();
    for (Enumeration e=cd.getOutputConstrs().elements(); e.hasMoreElements(); )
      effects.addElement(((LitLObject)e.nextElement()).getLiteral());
    return new Operator(vars, precs, effects);
  }

  /**
  ** This function gets called when an agent wants to contract other agents
  ** through the broker that can solve the problem described in the given 
  ** CDLDescription.
  ** @param agent the name of the agent seeking a capability
  ** @param task the CDLDescription of the task to be performed
  ** @param receiver the agent that has received this message, i.e. the
  **   broker that owns this CDLInterpreter
  ** @exception InterpretationException An exception will occur if 
  */
  protected void brokerAll (String agent, CDLDescription task, 
      Agent receiver) throws InterpretationException {
    traceln("Brokering (all agents) for task: " + task);
    boolean agentFound = false;
    Enumeration cds = agentCapas.elements();
    while (cds.hasMoreElements()) {
      CDLDescription cd = (CDLDescription)cds.nextElement();
      try {
	traceln("Testing capability: " + cd);
	if (cd.subsumes(task)) {
	  KQMLmessage rtnMsg = new KQMLmessage(
              "(achieve :receiver " + cd.getAgent() + 
                " :ontology OPlan :language CDL :content " +
                  task.toString() + ")");
	  receiver.sendMessage(rtnMsg);
	  traceln("Delegating task to: " + cd.getAgent());
	  agentFound = true;
	}
      } catch (FormalismException fe) {
	// state language in capability description does not support
	// inference requirements; no action required
      }
    }
    if (! agentFound) {
      KQMLmessage sorryMsg = new KQMLmessage(
          "(sorry :sender ANS :receiver " + agent + " :ontology agent");
      receiver.sendMessage(sorryMsg);
      traceln("No matching capability found.");
    }
  }

  static void traceln(String ts) {
    try {
      traceStream.write(ts.getBytes());
      traceStream.write('\n');
    } catch (IOException ioe) {
    }
  }
  static public FileOutputStream 
      traceStream = null;

  static private Vector agentCapas = new Vector();
  static private Hashtable ontology = new Hashtable();
}

