package JavaAgent.agent;

import JavaAgent.resource.*;

import java.util.Hashtable;
import java.io.*;
import java.util.Vector;
import java.util.Enumeration;


/**
 * Object which handles all message transmissions for an Agent, maintains the
 * link to the CommInterface. When the agent makes a call to sendMessage(), 
 * the message is put in the outgoing buffer. Similarly, when the 
 * CommInterface receives a message, it is put in the incoming buffer. Both
 * the outgoing and incoming buffers are instances of MessageBuffer, which 
 * provides for synchronized access to the stored messages. Instances of 
 * TransmitterThread and ReceiverThread pull messages out of the buffers for
 * either transmission by the CommInterface or processing.<p>
 *
 * Message transmission is normally serialized using the pending_transmission
 * flag
 * (i.e. receipt of transmission for each message is secured before the next 
 * message is sent). The releasePending() method can be called to allow for 
 * non-serialized message transmission, this done with address retrieval (the 
 * current message must release the pending flag so that the address
 * retrieval message can actually be sent). <p>
 *
 * Message processing is not serialized: as each message is pulled off the 
 * buffer a separate thread is started to process the message. Serialized 
 * processing can be accomplished by rewriting the ReceiverThread class.
 * When a message is finally sent it is put in a rotating transmitted messages
 * buffer, the same is done for received messages.<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 MessageHandler {
  
  /**
   * Agent whose Resources are managed.
   */
  protected Agent parent;

  /** 
   * Mechanism through which messages are sent and received. The CommInterface
   * must be intialized and assigned in the AgentContext which contains
   * the Agent.
   */
  protected CommInterface comm;
  
  /**
   * Buffer for messages waiting to be sent.
   */
  protected MessageBuffer outgoing = new MessageBuffer();

  /**
   * Thread which pulls messages from the outgoing MessageBuffer and calls
   * the CommInterface for transmission.
   */
  protected TransmitterThread transmitter;

  /**
   * Buffer for received messages waiting to be processed.
   */
  protected MessageBuffer incoming = new MessageBuffer();

  /**
   * Thread which pulls messages from the incoming MessageBuffer and calls
   * the specified Interpreter object for interpretation.
   */
  protected ReceiverThread receiver;

  /**
   * KQMLmessage which is currently being interpreted, or null if no
   * message is being interpreted.
   */
  protected KQMLmessage current_message = null;

  /**
   * Storage for received messages. Rolling buffer of size defined by
   * MESSAGE_STORAGE. Type KQMLmessage.
   */
  protected Vector messages_in = new Vector();

  /**
   * Storage for transmitted messages. Rolling buffer of size defined by
   * MESSAGE_STORAGE. Type KQMLmessage.
   */
  protected Vector messages_out = new Vector();
  
  /**
   * Buffer for messages waiting to be sent.
   */
  protected Vector output_buffer = new Vector();

  /**
   * Is this the last message which needs to be send before termination?
   */
  protected KQMLmessage last_message = null;

  /**
   * Is there a message currently being sent?
   */
  protected boolean transmission_pending = false;
  
  /**
   * Creates a new MessageHandler. Creates and starts
   * the ReceiverThread.
   * @param parent Agent which this handler represents.
   */
  
  public MessageHandler(Agent parent){
    this.parent = parent;
    receiver = new ReceiverThread(this);
    receiver.start();
  } 

  /**
   * Sets the CommInterface for the MessageHandler and creates and starts
   * the TransmitterThread.
   * @param comm CommInterface which performs the low-level message
   * transmission.
   */

  public void setComm(CommInterface comm){
    this.comm = comm;
    transmitter = new TransmitterThread(this);
    transmitter.start();
  } 
  
 /**
   * Called when a non-Resource object has changed.
   * @param type Type of resource.
   */

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

  /**
   * Sends a system message to the agent.
   * @param message The system message to output.
   */
  
  public void addSystemMessage(String message){
    parent.addSystemMessage(message);
  }

  /**
   * Sends a system message to the agent w/ exception.
   * @param message The system message to output.
   * @param e Exception which generated the message, may be null.
   */
  
  public void addSystemMessage(String message, Exception e){
    parent.addSystemMessage(message,e);
  }

  /**
   * Returns true if the Agent is currently interpreting a message.
   * @return true if a message is being interpreted.
   */
  
  public boolean interpretingMessage(){
    boolean answer = false;
    if(currentMessage() != null){
      answer = true;
    } 
    return answer;
  }

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

  /**
   * Called to send a given KQMLmessage. Inserts the message in the
   * outgoing buffer.
   * @param message The KQMLmessage to send.
   */
  
  public void sendMessage (KQMLmessage message) {
    outgoing.add(message);
    addSystemMessage("System stat:transmission started");
  }    

  /**
   * Called by the TransmitterThread to send a message. Calls the 
   * sendMessage() method of the CommInterface.
   * @param message KQMLmessage to send.
   */

  protected void sendToComm(KQMLmessage message){
    isPending();
    transmission_pending = true;
    try {
      message.addFieldValuePair("sender", parent.getName());
      comm.sendMessage(message);
    } catch (Exception e) {
      addSystemMessage("failed to send message.", e);
    }
  }
  
  /**
   * Saves the message in the given storage. Only keep the most recent
   * MESSAGE_STORAGE messages.
   * @param storage The storage Vector.
   * @param message Message to save.
   */

  protected void saveMessage(Vector storage, KQMLmessage message){
    if(storage.size() >= AgentParams.MESSAGE_STORAGE){
      storage.removeElementAt(0);
    }
    storage.addElement(message);
    parent.resourceChanged("message");
  }

  /**
   * 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 messages_out;
  }

  /**
   * 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 messages_in;
  }

  /**
   * This method will block until there is no longer a transmission pending.
   */

  synchronized public void isPending(){
    while(transmission_pending){
      try{
	wait();
      } catch (InterruptedException e){}
    }
    
  }

  /**
   * Sets the transmission_pending flag to false.
   */

  synchronized public void releasePending(){
    transmission_pending = false;
    notify();
   }

  /**
   * 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){
     releasePending();
     addSystemMessage("System stat:transmission finished");
     if(!status){
       addSystemMessage("Unable to send message: \n\t" +
			message.getReadString() + "\nfor reason: " + reason);
     } else {
       addSystemMessage("Message sent: " + message.getSendString());
       saveMessage(messages_out,message);
       if(message.equals(last_message)){
	 terminate();
       }
     }
   }
  
  /** 
   * 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) {
    incoming.add(message);
    addSystemMessage("System stat:processing started");
  }
  
  /**
   * Called by the ReceiverThread to interpret a specific message.
   */
  protected void interpretMessage(KQMLmessage message){

    current_message = message;
    Interpreter Interpreter = new Interpreter();

    /* Save the message */
    addSystemMessage("Message received: " + message.getSendString());
    saveMessage(messages_in,message);
    
    try{
      Interpreter =
	(Interpreter)(parent.getResource("interpreter")).
	   getElement(message.getValue("ontology"),RetrievalResource.SEND_MSG);
    } catch (ResourceException e){
      addSystemMessage("Failed to get Interpreter " + 
		         message.getValue("ontology") + ".", e);
    }
    
    try{
      Interpreter.interpretMessage(message,parent);
    } catch (Exception e){ 
      addSystemMessage("Unable to interpret message.", e);
    }
        
    current_message = null;/* unset the current message */
  }

  /**
   * Called when the current address has been invalidated.
   * Sends an invalidate address message to the ANS.
   */

  public void invalidateAddress(){
    try{    
      AgentAddress aa = 
	(AgentAddress)(parent.getResource("address")).getElement(parent.getName(), Resource.NO_RETRIEVAL);
      String message = "(evaluate :receiver ANS :ontology agent " +
	":language KQML " +
	":sender " + parent.getName() + " :content (invalidate-resource " +
	" :type address :name " + parent.getName() +
	" :value " + aa.getValue() + "))";
      last_message = new KQMLmessage(message);
      sendMessage(last_message);
    } catch (ResourceException e){
      addSystemMessage("Unable to invalidate address.", e);
    }
  }

  /**
   * Called when the last termination message has been
   * sent.
   */

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

  /**
   * Called when the executable class which contains the Agent terminates.
   * The agent sends a remove-address message to the ANS.
   */
  
  public void initiateTermination(){
    if(comm != null){
      comm.disconnect();
      if(parent.getResource("address").hasElement("ANS")){
	invalidateAddress();
      } else { /* no ANS so just tell the context to quit */
	terminate();
      }
    }
  }
}



