package JavaAgent.resource;

import JavaAgent.agent.AgentParams;
import JavaAgent.agent.*;
import java.io.*;
import java.net.URL;


/**
 * Subclass of Interpreter which defines the messages sent between agents
 * regarding addressing and code/file acquisition. Only defined for messages 
 * whose syntax is KQML.<p>
 *
 * The AgentInterpreter has five defined messages with multiple variants of each
 * message type. (each message represents the content field of a top level KQML
 * message):<p>
 *
 * <ul>
 * <li><i>(ask-resource :type &ltresource type&gt :name &ltresource name&gt) 
 * </i>. This message type is used to ask about the value of a resource.
 * <li><i>(tell-resource :type &ltresource type&gt :name &ltresource name&gt
 * :value &ltresource value&gt )</i>. This message type is used to inform an
 * Agent about a resource of a specific type, name and value. Where type may be one
 * of "address", "class", "interpreter", "language", "file" or "local-file" 
 * (could be some other type
 * of resource). Value is interpreted uniquely for each type. 
 * (For address value is "host:port"; for class, Interpreter and
 * language value is the "code_base class_name"; for file the value is
 * "base_url file_name") If a local version of the file exists, it is not
 * replaced.
 * If &ltresource value&gt is "?",
 * then the sender does not know the value or location of the resource.
 * <li><i>(invalidate-resource :type &ltresource type&gt
 * :name &ltresource name&gt)</i>. Sent by one Agent to invalidate a given 
 * resource. Should result in the resource being removed from local storage.
 * <li><i>(non-unique-name :non-unique-name &ltagent name&gt
 * :unique-name &ltagent name&gt)</i>. Sent by the ANS to an agent who
 * submitted a non-unique agent name. The ANS provides a new unique name
 * for the agent.
 * </ul><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 AgentInterpreter extends Interpreter {
  
  /**
   * Interpret the Language instance, from the message, for the reciever
   * according to the present Interpreter. Throws a InterpretationException
   * if the language type cannot be handled.
   *
   * @param message Message which needs to be interpreted.
   * @param receiver Agent who received the message.
   * @param language Instance of Language which represents the message
   * contents.
   */
  
  protected void interpretLanguage(KQMLmessage message, Agent receiver, Language language)
    throws InterpretationException {

      if(message.getValue("language").equals("KQML")){
	KQMLmessage content = (KQMLmessage)language;
	String performative = content.getValue("performative");
		
	if(performative.equals("tell-resource")){
	  tell_resourceAction(content,receiver);
	} else if(performative.equals("ask-resource")){
	  ask_resourceAction(content, message.getValue("sender"), receiver);
	} else if(performative.equals("invalidate-resource")){
	  invalidate_resourceAction(content, receiver);
	} else if(performative.equals("non-unique-name")){
	  non_uniqueAction(content, receiver);
	}
      } else {
	throw new InterpretationException("Interpreter " + 
					  message.getValue("ontology") +
					  " does not support language " +
					  message.getValue("langauge"));
      } 
    }
  
  /**
   * Handles general tell-resource messages.
   */

  protected void tell_resourceAction(KQMLmessage message, Agent parent){
    String type = message.getValue("type");
    if(type.equals("address")){
      tell_addressAction(message,parent);
    } else if(type.equals("class")){
      tell_codeAction(message, parent, parent.getResource("class"), false);
    } else if(type.equals("language")){
      tell_codeAction(message, parent, parent.getResource("language"), false);
    } else if(type.equals("interpreter")){
      tell_codeAction(message, parent, parent.getResource("interpreter"), true);
    } else if(type.equals("file")){
      tell_fileAction(message, parent, parent.getResource("file"));
    }
  }

  /**
   * Handles general ask-resource messages.
   */

  protected void ask_resourceAction(KQMLmessage message, String sender, Agent parent){
    String type = message.getValue("type");
    if(type.equals("address")){
      ask_addressAction(message,sender,parent);
    } else if(type.equals("class")){
      ask_codeAction(message, sender, parent, parent.getResource("class"));
    } else if(type.equals("language")){
      ask_codeAction(message, sender, parent, parent.getResource("language"));
    } else if(type.equals("interpreter")){
      ask_codeAction(message, sender, parent, parent.getResource("interpreter"));
    } else if(type.equals("file")){
      ask_fileAction(message, sender, parent, parent.getResource("file"));
    }
  }

  /**
   * Handles general invalidate-resource messages.
   */

  protected void invalidate_resourceAction(KQMLmessage message, Agent parent){
     String type = message.getValue("type");
     if(type.equals("address")){
      invalidate_addressAction(message,parent);
     } else if(type.equals("class")){
       invalidate_code_fileAction(message,parent, 
				  parent.getResource("class"));
     } else if(type.equals("language")){
       invalidate_code_fileAction(message,parent, 
				  parent.getResource("language"));
     } else if(type.equals("interpreter")){
       invalidate_code_fileAction(message,parent, 
				  parent.getResource("interpreter"));
     } else if(type.equals("file")){
       invalidate_code_fileAction(message,parent, 
				  parent.getResource("file"));
     }
   }

  /**
   * Provides the location of class code for langauge, Interpreter or class
   * resources.
   * Resource is the Agent storage, instance is true if it should be stored as
   * an instance, false if as a class.
   */
  
  protected void tell_codeAction(KQMLmessage message, Agent parent, Resource resource,
		       boolean instance){
    String location = message.getValue("value");
    String name = message.getValue("name");
    if(location.equals("?")){
      parent.addSystemMessage("Unable to retrieve the location of code, " +
			      name);
      resource.addElement(name,new NullResource(),false);
    } else {
      try{
	String url = location.substring(1,location.indexOf(" "));
	String class_name = location.
	    substring(location.indexOf(" ")+1, location.lastIndexOf(")"));
	URL code_base = new URL(url);
	FileLocation fl = new FileLocation(code_base, class_name);
	Class c = parent.loadClass(code_base, class_name);
	resource.addLocation(name,fl,true);
	if(instance){
	  resource.addElement(name,c.newInstance(),true);
	} else {
	  resource.addElement(name,c,true);
	}
      } catch (Exception e){
	resource.addElement(name,new NullResource(),true);
      }
    }
  }

  /**
   * Provides the location of a remote file which must be copied to the
   * local disk.
   */
  
  protected void tell_fileAction(KQMLmessage message, Agent parent, Resource resource){
    String location = message.getValue("value");
    String name = message.getValue("name");
   
    parent.addSystemMessage("Received a tell file message: " + location);
    if(parent.localIO()){
      parent.addSystemMessage("Unable to save file " +
			      name + " to the local disk");
      resource.addElement(name,new NullResource(),true);
    } else if(location.equals("?")){
      parent.addSystemMessage("Unable to retrieve the location of file " +
			      name);
      resource.addElement(name,new NullResource(),true);
    } else {
      try{	
	URL url = 
	  new URL(location.substring(1, location.indexOf(" ")));
	String file_name = location.substring(location.indexOf(" ")+1, location.lastIndexOf(")"));
	FileLocation fl = new FileLocation(url, file_name);
	File f = FileIO.remote_copy(new URL(url,file_name),
				    parent.getWorkingDir(),file_name,
				    AgentParams.FILE_EXISTS_ACTION);
	if(f != null){
	  resource.addLocation(name,fl,true);
	  resource.addElement(name,f,true);
	} else {
	  parent.addSystemMessage("Error copying file " +
				  name + " to the local disk.");
	  resource.addElement(name,new NullResource(),true);
	}
      } catch (Exception e){
	parent.addSystemMessage("Error retrieving file " + name + ".",e);
	resource.addElement(name,new NullResource(),true);
      }
    }
  }  

  /**
   * Request for the location of class code for langauge, Interpreter or class
   * resources.
   * resource is the Agent storage, instance is true if it should be stored as
   * an instance, false if as a class.
   */
  
  protected void ask_codeAction(KQMLmessage message, String sender, Agent parent,
		      Resource resource){
    String name = message.getValue("name");
    String msg_str = "(evaluate :sender " + parent.getName() + " :receiver " +
      sender + " :language KQML :ontology agent)";
    KQMLmessage msg = new KQMLmessage(msg_str);
    KQMLmessage content = new KQMLmessage("(tell-resource :type " +
					  message.getValue("type") + " :name " +
					  name + ")");

    if(resource.hasElement(name)){
      if(resource.getLocation(name) != null){
	FileLocation fl = (FileLocation)resource.getLocation(name);
	String value = "(" + fl.getFileBase().toExternalForm() + " " + 
	  fl.getObjectName() + ")";
	content.addFieldValuePair("value", value);
      } else {
	try{
	  Object obj = resource.getElement(name,Resource.NO_RETRIEVAL);
	  String class_name = "";
	  if(message.getValue("type").equals("interpreter")){
	    class_name = obj.getClass().getName();
	  } else {
	    class_name = ((Class)obj).getName();
	  }
	  String value = "(" + parent.getSharedClassesURL().toExternalForm()
	    + "classes/ " + class_name + ")";
	  content.addFieldValuePair("value", value);
	} catch (Exception e){}
      }
    } else {
      content.addFieldValuePair("value", "?");
    }
    msg.addFieldValuePair("content", content.getSendString());
    parent.sendMessage(msg);
  }
  
  /**
   * Request for the location of a file.
   */
  
  protected void ask_fileAction(KQMLmessage message, String sender, Agent parent,
		      Resource resource){
    String name = message.getValue("name");
    String msg_str = "(evaluate :sender " + parent.getName() + " :receiver " +
      sender + " :language KQML :ontology agent)";
    KQMLmessage msg = new KQMLmessage(msg_str);
    KQMLmessage content = new KQMLmessage("(tell-resource :type " +
					  message.getValue("type") + " :name " +
					  name + ")");

    if(resource.hasElement(name) && resource.getLocation(name) != null){
      FileLocation fl = (FileLocation)resource.getLocation(name);
      String value = "(" + fl.getFileBase().toExternalForm() + " " + 
	fl.getObjectName() + ")";
      content.addFieldValuePair("value", value);
    } else {
      content.addFieldValuePair("value", "?");
    }
    msg.addFieldValuePair("content", content.getSendString());
    parent.sendMessage(msg);
  }


  /**
   * Request to invalidate a current code resource. If the code resource is
   * present, remove it and its location.
   */

  protected void invalidate_code_fileAction(KQMLmessage message, Agent parent,
			     Resource resource){
   
    String name = message.getValue("name");

    parent.addSystemMessage(message.getValue("type") + " " +
			    name + " has been invalidated.");
    
    if(resource.hasElement(name)){
      resource.removeLocation(name);
      resource.removeElement(name);
    }
  }


  /**
   * Provides the address of an agent.
   */
  
  protected void tell_addressAction(KQMLmessage message, Agent parent){
    if(Interpreter.isSuper(parent.getClass(),"JavaAgent.agent.ANS")){
      tell_addressANS(message, (ANS)parent);
    } else {
      tell_addressAgent(message, parent);
    }
  }

  /**
   * ANS has received the tell_address message, check for unique name, if
   * unique, store in buffer, else send a non-unique-name message back.
   */

  protected void tell_addressANS(KQMLmessage message, ANS parent){
    String name = (String)message.getValue("name");
    String value = (String)message.getValue("value");
    String host = value.substring(0,value.indexOf(":"));
    String port_str = value.substring(value.indexOf(":")+1);
    Integer port = new Integer(port_str);
    SocketAddress sa = new SocketAddress(host,port.intValue());

    if(parent.getResource("address").hasElement(name)){ /* is the name unique?*/
      /* create a unique name from address url */
      parent.getResource("address").addElement(value, sa, true);
      String reply_string = new String("(evaluate :sender ANS :receiver " +
				       value +
				       " :language KQML :ontology agent" +
				       " :content (non-unique-name :non-unique-name " +
				       name + " :unique-name " + value + "))");
      KQMLmessage nonunique = new KQMLmessage(reply_string);
      parent.sendMessage(nonunique);
    } else {
      parent.getResource("address").addElement(name,sa,true);
    }
    
  }

  /**
   * Agent has received a tell-address message, store the received address in
   * the local buffer. If the sender did not know the address then store a
   * null in addresses.
   */

  protected void tell_addressAgent(KQMLmessage message, Agent parent){
    String name = message.getValue("name");
    String value = (String)message.getValue("value");
    if(value.equals("?")){
      parent.addSystemMessage("Unable to retrieve the address for Agent " +
			      name);
      parent.getResource("address").addElement(name,new NullResource(),true);
    } else {
      String host = value.substring(0,value.indexOf(":"));
      String port_str = value.substring(value.indexOf(":")+1);
      Integer port = new Integer(port_str);
      SocketAddress sa = new SocketAddress(host, port.intValue());
      parent.getResource("address").addElement(name,sa,true);
    } 
  }

  /**
   * Agent wants an address. Do not attempt to retrieve the address if the
   * Agent does not possess it currently.
   */
  
  protected void ask_addressAction(KQMLmessage message, String sender, Agent parent){
    String name = (String)message.getValue("name");

    String reply_string = "(evaluate :sender " + parent.getName() +
      " :receiver " +
	sender + " :language KQML :ontology agent";
    KQMLmessage reply = new KQMLmessage(reply_string);
    KQMLmessage content = new KQMLmessage("(tell-resource :type address " +
					  " :name " + name +")");
    try{
      SocketAddress sa =  
	(SocketAddress)parent.getResource("address").getElement(name,Resource.NO_RETRIEVAL);
      content.addFieldValuePair("value", sa.getValue());
    } catch (ResourceException e){
      content.addFieldValuePair("value", "?");
    }
    
    reply.addFieldValuePair("content",content.getSendString());
    parent.sendMessage(reply);
    
  }

  /**
   * Remove-address message received.
   */
 
  protected void invalidate_addressAction(KQMLmessage message, Agent parent){
    if(Interpreter.isSuper(parent.getClass(),"JavaAgent.agent.ANS")){
      invalidate_addressANS(message, (ANS)parent);
    } else {
      invalidate_addressAgent(message, parent);
    }
  }

  /**
   * Agent received a invalidate-address message, if the address exists in the
   * local buffer, remove it.
   */

  protected void invalidate_addressAgent(KQMLmessage message, Agent parent){
    if(parent.getResource("address").hasElement(message.getValue("name"))){
      parent.addSystemMessage("Agent " + message.getValue("name") +
			      " is disconnecting.");
      parent.getResource("address").removeElement(message.getValue("name"));
    }
  }

  /**
   * ANS received an invalidate-address message, if the address exists in the
   * local buffer, remove it, then echo the invalidate-address message to all
   * of the agents which are in the buffer.
   */

  protected void invalidate_addressANS(KQMLmessage message, ANS parent){
    String name = (String)message.getValue("name");
    String value = (String)message.getValue("value");
    String host = value.substring(0,value.indexOf(":"));
    String port_str = value.substring(value.indexOf(":") +1);
    Integer port = new Integer(port_str);
    SocketAddress sa = new SocketAddress(host,port.intValue());
    
    parent.addSystemMessage("Agent " + message.getValue("name") +
			    " is disconnecting.");
    if(parent.getResource("address").hasElement(name)){
    
      try{
	SocketAddress address=(SocketAddress)parent.getResource("address").getElement(name,Resource.NO_RETRIEVAL);
	if(address.host.equals(host) &&
	   (address.port == port.intValue())){
	  parent.getResource("address").removeElement(name);
	  parent.SendRemovalMessage(name, sa);
	}
      } catch (ResourceException e){} 
    }
  }
  
  /**
   * Agent tried to report a non-unique name, change name to the supplied
   * unique-name, based on the address.
   */

  protected void non_uniqueAction(KQMLmessage message, Agent parent){
    try{
      SocketAddress address =
	(SocketAddress)parent.getResource("address").getElement(parent.getName(),Resource.NO_RETRIEVAL);
      parent.getResource("address").removeElement(parent.getName());
      parent.setName(message.getValue("unique-name"));
      parent.getResource("address").addElement(parent.getName(), address, true);
      parent.sendInitMessages();
    } catch(ResourceException e){}
  }
 


}


