package JavaAgent.agent;

import JavaAgent.resource.*;
import JavaAgent.context.ContextInterface;

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

/**
 * Class which represents a autonomous software agent whose capabilities are
 * represented by a set of resource objects which include both data and 
 * Java class definitions. Agent's communicate with one another via KQML
 * messages which are transmitted by a CommInterface object. Resource 
 * storage and management is handled by a ResourceManager object.
 * Message transmission via the CommInterface is the responsibility of
 * a MessageHandler object.<p>
 * 
 * The Agent must be created, along with the CommInterface, in an external 
 * class which implements the ContextInterface interface.
 * Upon creation the agent must be provided with:
 * <ul> 
 * <li> Reference to a ContextInterface.
 * <li> A String name for the Agent.
 * <li> A URL for an initialization file which contains a set of KQMLmessages
 * to be processed at startup. (One of these messages will provide the address
 * of an Agent Name Server (ANS) to which the Agent must send its name and 
 * address.)
 * <li> A URL for a directory which contains shared classes and files. If the
 * Agent will communicate with other Agents distributed over the Internet, this
 * URL must have the http protocol.
 * <li> A directory for local access to shared classes, which will be null 
 * if the Agent is unable to write to the local file system.
 * <li> A directory for local files, which will be null if the 
 * Agent is unable to write to the local file system.
 * </ul><p>
 *
 * Each Agent, when created, loads a set of initialization messages from
 * an initialization file. One of these messages will provide the address of an
 * Agent Name Server (ANS) to which the Agent must send its name and address.
 * Each Agent automatically "knows" the "KQML" language (KQMLmessage class)
 * and the "agent" Interpreter (AgentInterpreter class).<p>
 *
 * The internal knowledge of an Agent is contained in a set of Resource
 * classes (managed by the ResourceManager). Access to the data within these
 * classes is synchronized to 
 * prevent race conditions. A special subclass of Resource, RetrievalResource,
 * provides the storage for addresses, languages, interpreters and remotely
 * loaded classes. When an Agent attempts to access a non-existent element 
 * from a RetrievalResource instance, the access thread is blocked and a
 * message is sent to obtain the desired object. When the object is received,
 * the access thread is restarted. (See the RetrievalResource class for 
 * more information). This action is taken for unknown addresses, languages,
 * interpreters, general classes and files. For the retrieval of 
 * remote code, a Hashtable of NetworkClassLoaders is maintained.<p>
 *
 * When a message is received by an Agent the following steps occur within
 * the MessageHandler:<p>
 * <ul>
 * <li>The CommInterface receives a string message and parses it
 * to create a KQMLmessage and calls the Agent's receiveMessage method.
 * <li> The KQMLmessage is passed to the interpretMessage method of an
 * instance of the specified Interpreter. (In this context, an Interpreter is 
 * represented by a class which contains procedural instructions for
 * interpreting a specific message. A more appropriate name may be message
 * handler) If the Agent does not possess the Interpreter, a 
 * request for that class is sent, per the RetrievalResource
 * functionality, to the Agent who sent the message. 
 * <li> For each Interpreter, the message is interpreted according to 
 * the language specified for the content. (Here language represents the
 * syntatical knowledge necessary to parse the content string and construct a
 * subclass of Language which can be interpreted by an Interpreter subclass)
 * Again, if the Agent does not possess the specified Language subclass, 
 * a request is sent to the message sender.
 * </ul><p>
 *
 * Agent communications regarding addressing, code retrieval and other
 * organizational or architectural issues (not related to domain knowledge) are
 * done using KQML for the message content syntax and the "agent" 
 * MessageHandler. (See class AgentHandler).<p>
 *
 * <hr>
 * Copyright (c) 1995, H. Robert Frost, Stanford University.
 * All rights reserved.<p>
 * Copyright (c) 1996, H. Robert Frost, Enterprise Integration Technologies,
 * Inc. All rights reserved.<p>
 *
 * RESTRICTED RIGHTS LEGEND: Use, duplication or disclosure by the 
 * Government is subject to restrictions as set forth in 
 * subparagraph(c)(1)(ii) of the Rights in Technical Data and Computer
 * Software clause at DFARS 252.227-7013 and in similar clauses in the
 * FAR and NASA FAR supplement.<p>
 * 
 * This software is bound by the terms and conditions listed in the
 * attached <a href="../LICENSE">LICENSE file</A>.
 * <hr>
 * @author <A HREF="http://cdr.stanford.edu/html/people/frost-bio.html"> Rob Frost</A>
 * @version 0.3 5/21/96 for Java(tm) Language Version 1.0.2
 */

public class Agent {

  /**
   * Interface to the context in which the Agent was created.
   */

  protected ContextInterface context;

  /**
   * Object which contains and manages all of the Agent's resources.
   */

  protected ResourceManager resources;
  
  /**
   * Object which handles all message transmission and maintains a 
   * history of both sent and received messages.
   */

  protected MessageHandler handler;

  /**
   * A unique String identifier for the Agent. When an Agent is started the
   * user is requested to either provide a name or let the name default to
   * one generated from the Agent's address (will be unique). 
   */
  protected String name;

  /**
   * Working directory for saving files on the local disk, null if the Agent
   * cannot write to the local file system.
   */
  protected File working_dir = null;

  /** 
   * URL for the classes directory containing classes which will be shared
   * with other agents. 
   */
  protected URL shared_classes;

  /**
   * Directory for local access to the shared classes URL.
   */
  protected File shared_dir = null;

  /**
   * URL for initialization file.
   */
  protected URL init_url = null;

  /**
   * Can the Agent perform local file io?
   */
  protected boolean io_enabled = false;
 
 
  /**
   * Contructs an Agent with a name, init file, shared classes URL
   * and working directory.
   * @param n String identifier for the Agent.
   * @param init_url URL for the initialization file.
   * @param shared URL for shared classes.
   * @param shared_dir Directory for local access to shared classes.
   * @param working_dir Directory for local files, will be null if the 
   * Agent is unable to write to the local file system.
   */
  
  public Agent(ContextInterface context,String n, URL init_url, URL shared, 
	       File shared_dir, File working_dir) {
    this.context = context;
    name = n;
    this.init_url = init_url;
    shared_classes = shared;
    this.shared_dir = shared_dir;
    this.working_dir = working_dir;
  }
  
  /**
   * Creates the ResourceManager, MessageHandler and reads in the init_file.
   */

  public void init(){
    if(working_dir != null){
      io_enabled = true;
    }
    resources = new ResourceManager(this);
    handler = new MessageHandler(this);
    loadInitFile(init_url);
  }

  /**
   * Sets the value of the CommInterface.
   * @param c CommInterface for this Agent.
   */
  
  public void setComm(CommInterface c) {
    handler.setComm(c);
  }

  /**
   * Group of messages which are automatically sent out at startup. If
   * the name submitted by the agent is non-unique, these message will be
   * resent with the unique name.
   */
  public void sendInitMessages(){}

  /**
   * @return True if the Agent can perform local file IO.
   */
  
  public boolean localIO(){
    return io_enabled;
  }
  
  /**
   * Retrieves the URL for shared classes and files, returns null if there 
   * are no files to share.
   * @return URL for directory containing both the classes directory and
   * the directory for files.
   */
  
  public URL getSharedClassesURL(){
    return shared_classes;
  }

  /**
   * Retrieves the directory for local file storage, might be null.
   * @return Directory for local files.
   */

  public File getWorkingDir(){
    return working_dir;
  }

  /**
   * Retrieves the directory for local access to the shared classe dir.
   * @return Directory for shared classes
   */

  public File getSharedDir(){
    return shared_dir;
  }

  /**
   * Loads a set of KQMLmessages from a file at the specified URL.
   * This file must be a text file containing one KQMLmessage per line.
   * The language and Interpreter fields for the messages are limited to
   * "KQML" and "agent", respectively.
   * At a minimum this file must contain a KQMLmessage informing the Agent of
   * the address of the ANS, this message will have the following structure:
   * "(evaluate :sender file :receiver: agent :language KQML
   * :ontology agent :content (tell-address :name &ltname&gt :host &lthost&gt
   * :port &ltport&gt))".
   * @param file_url The URL for the init file.
   */
  
  public void loadInitFile(URL file_url){
    try{
      DataInputStream input =
	new DataInputStream(new BufferedInputStream( file_url.openStream()));
      while(input.available() > 0){
	  String temp = input.readLine();
	if(temp.startsWith("(")){
	  KQMLmessage message = new KQMLmessage(temp);
	  receiveMessage(message);
	}
      }
      input.close();
    } catch (Exception e){
      addSystemMessage("Error loading initialization file " + 
		       file_url.toString() + ".", e);
    }
  }
  
  /**
   * Loads a given resource. If the resource currently exists does not
   * overwrite.
   * @param type Type of resource, either language, Interpreter or class
   * @param name The identifier for the resource.
   * @param object_name The class or file name.
   * @param url The code base for the class.
   */
  
  public void loadResource(String type, String name, String object_name,
			   String url){
    resources.loadResource(type,name,object_name,url);
  }

  /**
   * Get the resource object for the specified type.
   * @param type Resource type to get, one of: "address", "language",
   * "interpreter" or "class".
   * @return Resource object or null if type does not exist.
   */

  public Resource getResource(String type){
    return resources.getResource(type);
  }

  /**
   * Called by a Resource object when the number of elements has changed.
   * @param type Type of the Resource.
   */

  public void resourceChanged(Resource r){
    String class_name = r.getClass().getName();
    String type = null;

    if(class_name.equals("JavaAgent.resource.Addresses")){
      type = "address";
    } else if(class_name.equals("JavaAgent.resource.Languages")){
      type = "language";
    } else if(class_name.equals("JavaAgent.resource.Interpreters")){
      type = "interpreter";
    } else if(class_name.equals("JavaAgent.resource.Classes")){
      type = "class";
    } else if(class_name.equals("JavaAgent.resource.Files")){
      type = "file";
    }
    context.resourceChanged(type);
  }

  /**
   * Called when a non-Resource object has changed.
   * @param type Type of resource.
   */

  public void resourceChanged(String resource){
    context.resourceChanged(resource);
  }

  /**
   * Pipes a system message to the Agent's context.
   * @param message The system message to output.
   */
  
  public void addSystemMessage(String message){
    context.addSystemMessage(message);
  }

  /**
   * Pipes a system message to the Agent's context.
   * @param message The system message to output.
   * @param e Exception which generated the message, may be null.
   */
  
  public void addSystemMessage(String message, Exception e){
    context.addSystemMessage(message,e);
  }

  /**
   * Returns true if the Agent is currently interpreting a message.
   * @return true if a message is being interpreted.
   */
  
  public boolean interpretingMessage(){
    return handler.interpretingMessage();
  }

  /**
   * Returns the message currently being interpreted, returns null
   * if no message is being interpreted.
   */
  
  public KQMLmessage currentMessage(){
    return handler.currentMessage();
  }

  /**
   * Gets name of Agent.
   * @return Agent name.
   */
  
  public String getName() {
    return name;
  }

  /**
   * Sets the name of the Agent.
   * @param name Agent name.
   */
  
  public void setName(String name) {
    this.name = name;
    context.setName(name);
  }
  
  /**
   * Returns true if the AgentContext supports a GUI.
   * @return True if the context has a GUI, false otherwise. 
   */

  public boolean hasGUI(){
    return context.hasGUI();
  }

  /**
   * If the context supports a GUI, then add the instantiated and intialized
   * panel to a new top-level window.
   * @param p Panel to add.
   */

  public void addPanel(Panel p){
    if(hasGUI()){
      context.addPanel(p);
    }
  }

  /**
   * Called to send a given KQMLmessage. Calls the sendMessage method of the
   * associated MessageHandler which will pass the message to the 
   * CommInterface for transmission.<p>
   * @param message The KQMLmessage to send, null if the buffer is being
   * flushed.
   */
  
  public void sendMessage (KQMLmessage message) {
    handler.sendMessage(message);
  }

  /**
   * Releases the transmission block.
   */

  public void releasePending(){
    handler.releasePending();
   }

  /**
   * Called by the associated CommInterface to report the success of
   * a sendMessage call.
   * @param message The KQMLmessage which the CommInterface was asked to 
   * send.
   * @param status True if the message transmission was successful, false if
   * otherwise.
   * @param reason String with detailed reason.
   */
  
  public void sendResult(KQMLmessage message, boolean status, String reason){
    handler.sendResult(message,status,reason);
  }

  /**
   * Returns a Vector of transmitted messages, if more than MESSAGE_STORAGE
   * number have been sent, only returns the most recent.
   * @return Vector of KQMLmessages.
   */

  public Vector getSentMessages(){
    return handler.getSentMessages();
  }

  /**
   * Returns a Vector of received messages, if more than MESSAGE_STORAGE
   * number have been received, only returns the most recent.
   * @return Vector of KQMLmessages.
   */

  public Vector getReceivedMessages(){
    return handler.getReceivedMessages();
  }

  /** 
   * Called by the associated CommInterface when a KQMLmessage is received.
   * The message is handled based on the Interpreter for the message content.
   * Content language is considered within the Interpreter handler.
   *
   * @param message The KQMLmessage which was received.
   */
  
  public void receiveMessage (KQMLmessage message) {
    handler.receiveMessage(message);
  }

  /**
   * Called to load a remote class into the current runtime environment.
   * First check to see if a loader for the URL already exists, only create
   * a new loader if one doesn't currently exist.
   *
   * Returns null if no code is found.
   * 
   * @param code_url code base URL
   * @param name Class name.
   * @return The loaded Class.
   */

  public Class loadClass(URL code_url, String name){
    return resources.loadClass(code_url,name);
  }
 
  /**
   * Called when the last termination message has been
   * sent.
   */

  public void terminate(){
    context.terminate();
  }

  /**
   * Called when the executable class which contains the Agent terminates.
   * The agent sends a remove-address message to the ANS.
   */
  
  public void initiateTermination(){
    handler.initiateTermination();
  }
}








