package RemoteService.agent;

import RemoteService.resource.*;
import RemoteService.agent.*;
import RemoteService.context.*;
import JavaAgent.agent.*;
import JavaAgent.resource.*;
import JavaAgent.context.*;
import JavaAgent.context.ContextInterface;
import java.util.Hashtable;
import java.util.Vector;
import java.util.Enumeration;
import java.io.*;
import java.net.URL;
import java.awt.Panel;

/**
 * ClientAgent: Subclass of Agent which is integrated with a local process
 * and communicates
 * with remote services (represented by ServiceAgents). Each remote service
 * exports an interface, represented by a subclass of Service, which is 
 * executed on the ClientAgent's machine. This Service object can 
 * perform local file IO and communicate with both a local process and the
 * ServiceAgent to carryout the represented service.<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 ClientAgent extends Agent {
  
  /**
   * Service waiting to be installed
   */
  
  protected String pending_service = null;

  /**
   * Contructs a ClientAgent.
   *
   * @param n String identifier for the Agent.
   * @param init_url URL for the initialization file.
   * @param shared_url 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 ClientAgent(ContextInterface context,String n, URL init_url, 
		     URL shared_url, File shared_dir,File working_dir) {
    super(context, n, init_url, shared_url, shared_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 ClientResourceManager(this);
    handler = new MessageHandler(this);
    loadInitFile(init_url);
  }
  
  /**
   * 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(){
    getAvailable();
  }
 
  /**
   * Called by a Service object to send a String message.
   * Appends the Service name (or "DesignAgent") to the front of the message.
   * @param message The String to send.
   * @param s Service which is sending the message, if null send as DesignAgent
   * @param interrupt Should an interrupt be sent following the message, for
   * synchronous comm. no interrupt is necessary.
   */
  
  public void NonAgentMsg(String message, Service s, boolean interrupt){
    try {
      PipeMessage msg = new PipeMessage(s.getName() + " " + message,
					interrupt);
      context.NonAgentMsg(msg);
    } catch (NonAgentMsgException e){
      addSystemMessage("Unable to send local message: " + message + 
		       ".", e);
    }
  }

  /**
   * Called when a message is received through the PipeCommunicator.
   * @param message String received.
   */
  
  public void PipeMsgReceived(String message){
    KQMLmessage msg = new KQMLmessage(message);
    if(msg != null){ 
      String receiver = msg.getValue("receiver");
      Services services = (Services)getResource("service");
      if(receiver.equals("DesignAgent")){
	addSystemMessage(message);
      } else if(services.hasElement(receiver)){
	try{
	  Service s = (Service)services.getElement(receiver,
						   Resource.NO_RETRIEVAL);
	  s.message_received(msg.getValue("content"), msg.getValue("sender"));
	} catch (ResourceException e){
	  addSystemMessage("Unable to retrieve service " + receiver +
			   ".", e);
	}
      } else {
	addSystemMessage("Service " + receiver + 
			 " not loaded, cannot deliver " +  "message.");
      }
    } else {
      addSystemMessage("Message not in proper KQML syntax.");
    }
  }

  /**
   * Called when the executable class which contains the Agent terminates.
   * The agent sends a remove-address message to the ANS.
   */
  
  public void initiateTermination(){
    try{
      context.NonAgentMsg(new PipeMessage("terminate",true));
    } catch (NonAgentMsgException e){}
    super.initiateTermination();
  }

  /**
   * 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("RemoteService.resource.AvailableServices")){
      type = "available_service";
      context.resourceChanged(type);
    } else if(class_name.equals("RemoteService.resource.Services")){
      runService(pending_service);
      pending_service = null;   
    } else {
      super.resourceChanged(r);
    }
  }

  /**
   * Request for a list of available services. Results in a message being
   * sent to the ServiceBroker(ANS) requesting the list of currently registered
   * services.
   */

  public void getAvailable(){
    /*compose a KQML message to the service*/
    KQMLmessage message = new KQMLmessage();
    message.addFieldValuePair("sender", getName());
    message.addFieldValuePair("receiver","ANS");
    message.addFieldValuePair("performative","evaluate");
    message.addFieldValuePair("language","KQML");
    message.addFieldValuePair("ontology","service");
    message.addFieldValuePair("content","(ask-available)");
    sendMessage(message);
  }

  /**
   * Called to execute a specific Service. The Service will be
   * loaded first, if necessary.
   * @param name Name of the Service.
   */

  public void executeService(String name){
    if(!getResource("service").hasElement(name)){
      pending_service = name;
      installService(name);
    } else {
      runService(name);
    }
  }

  /**
   * Called to run a installed Service.
   * @param name Name of the Service.
   */

  protected void runService(String name){
    if(getResource("service").hasElement(name)){
      try {
	Service s = 
	  (Service)getResource("service").getElement(name, Resource.NO_RETRIEVAL);
	if(s != null){
	  s.run();
	} else {
	  addSystemMessage("Service code does not exist!");
	}
      } catch (ResourceException e){
	addSystemMessage("Unable to run Service " + name + ".",e);
      }
    }
  }

  /**
   * Called by the ClientContext to install a specific service.
   * If the Service has not already been installed, results in a message 
   * being sent to the appropriate service agent
   * requesting the service interface.
   * @param service_name Name of the service agent
   */

  protected void installService(String service_name){
    /*compose a KQML message to the service*/
    KQMLmessage message = new KQMLmessage();
    message.addFieldValuePair("sender", getName());
    try{
      AvailableService service = 
	(AvailableService)getResource("available_service").getElement(service_name, Resource.NO_RETRIEVAL);
      message.addFieldValuePair("receiver",service.agent);
      message.addFieldValuePair("performative","evaluate");
      message.addFieldValuePair("language","KQML");
      message.addFieldValuePair("ontology","service");
      message.addFieldValuePair("content","(request-service :name " +
				service_name + ")");
      sendMessage(message);
    } catch (ResourceException e){
      addSystemMessage("Unable to send request-service message.",e);
    }
  } 

}







