package com.ibm.able.examples.conversation;

import java.rmi.RemoteException;
import java.util.Iterator;
import java.util.Vector;

import javax.agent.AgentName;
import javax.agent.Locator;
import javax.agent.service.directory.AgentDescription;

import com.ibm.able.Able;
import com.ibm.able.AbleException;
import com.ibm.able.conversation.AbleConversationManager;
import com.ibm.able.conversation.AblePlatformConversationAgent;
import com.ibm.able.platform.AblePlatform;

/*
 * The ChatterboxAgent class is an example of an AblePlatformConversationAgent that carries
 * on a chat conversation with another agent.
 * <br>
 * When initialized, ChatterboxAgent installs in itself a decision logic module for executing
 * the "Chat3" conversation policy. Then, in its <code>process()</code> method, it randomly
 * selects an agent (looked up from the platform directory service) and either
 * sends a simple test message or initiates a conversation with it.
 */
public class ChatterboxAgent extends AblePlatformConversationAgent {

	// ===========================================
	// Class variables
	// ===========================================

	public static final String ABLE_AGENT_TYPE = "Chatterbox";

	// ===========================================
	// Instance variables
	// ===========================================

	int totalConversations = 1;
	int conversationCount = 0;
	boolean testMessageEnabled = false;
	boolean conversationEnabled = true;
	AbleChat3DummyLogic chat3DecisionLogic = null;
	
	// ===========================================
	// Constructors
	// ===========================================

	/**
	 * Constructs a ChatterboxAgent instance, with the given display name.
	 * 
	 * @param instanceName The displayName of the agent.
	 * @throws RemoteException If the superclass constructor throws one.
	 * @throws AbleException If the superclass constructor throws one.
	 */
	public ChatterboxAgent(String instanceName)
		throws RemoteException, AbleException {
		super(instanceName);
		setAgentType(ABLE_AGENT_TYPE);
		
		chat3DecisionLogic = new AbleChat3DummyLogic(this);
	}

	// ===========================================
	// Properties
	// ===========================================

	/**
	 * Returns the total number of conversations this agent is allowed to have.
	 * 
	 * @return The total number of conversations this agent may have.
	 */
	public int getTotalConversations() {
		return totalConversations;
	}

	/**
	 * Sets the total number of conversations this agent may have.
	 * 
	 * @param i The new total.
	 */
	public void setTotalConversations(int i) {
		totalConversations = i;
	}

	/**
	 * Indicates whether this agent may initiate conversations.
	 * 
	 * @return true if this agent may initiate converstions, false if not.
	 */
	public boolean isConversationEnabled() {
		return conversationEnabled;
	}

	/**
	 * Indicates whether this agent may send a test message.
	 * 
	 * @return true if this agent may send a test message, false if not.
	 */
	public boolean isTestMessageEnabled() {
		return testMessageEnabled;
	}

	/**
	 * Toggles whether this agent may initiate conversations.
	 * 
	 * @param b New indicator for whether this agent may initiate conversations.
	 */
	public void setConversationEnabled(boolean b) {
		conversationEnabled = b;
	}

	/**
	 * Toggles whether this agent may send test messages.
	 * 
	 * @param b New indicator for whether this agent may send messages.
	 */
	public void setTestMessageEnabled(boolean b) {
		testMessageEnabled = b;
	}

	// ===========================================
	// Operation
	// ===========================================

	/* 
	 * Initializes this agent. Calls superclass <code>init()</code>, then installs 
	 * a decision logic module for the "Chat3" conversation.
	 *  
	 * @see com.ibm.able.AblePlatformConversationAgent#init()
	 */
	public void init() throws AbleException {
//		this.getLogger().text(Able.MSG_INFO, this, "init", "start");

		super.init();
		this.getLogger().text(
			Able.MSG_INFO,
			this,
			"init",
			"[" + this.getName() + "] CPs loaded: " + listKnownCpNames());
			
		super.installTargetForCp("Chat3", "Role1", chat3DecisionLogic);
		super.installTargetForCp("Chat3", "Role2", chat3DecisionLogic);
			
//		this.getLogger().text(Able.MSG_INFO, this, "init", "done");
	}

	/* 
	 * This agent's main lifecycle method. Calls superclass <code>process()</code>, then
	 * takes an action. Depending on the parameter settings, this action may be any one
	 * of:
	 * <ul>
	 * <li>Sending a test message to a randomly selected agent.</li>
	 * <li>Initiating a "Chat3" conversation with a randomly selected agent.</li>
	 * <li>Doing nothing.</li>
	 * </ul>
	 * 
	 * @see com.ibm.able.AblePlatformConversationAgent#process()
	 */
	public void process() throws AbleException {
		super.process();

		// this.getLogger().text(Able.MSG_INFO, this, "process", "start");

		boolean sendTest = decideToSendTestMessage();
		boolean startConv =
			(sendTest == true) ? false : decideToStartConversation();

		if (startConv == true) {
			logMessage(Able.MSG_INFO,
			"process", "decided to start conversation");
		}
		else if (sendTest == true) {
			logMessage(
				Able.MSG_INFO,
			"process",
				"decided to send test message");
		}
		else {
//			logMessage(Able.MSG_INFO,
//				"process",
//				"decided not to send any messages");
		}

		AgentDescription newPartner = null;
		if (sendTest == true || startConv == true) {
			newPartner = selectConversationPartner();
			if (newPartner != null) {
//				logMessage(Able.MSG_INFO,
//				"process",
//					"found partner");
			}
			else {
				logMessage(
					Able.MSG_INFO,
				"process",
					"no partner found");
			}
		}

		if (sendTest == true && newPartner != null) {
			logMessage(
				Able.MSG_INFO,
			"process",
				"sending test message");
			sendTestMessage(newPartner);
		}
		else if (startConv == true && newPartner != null) {
			logMessage(
				Able.MSG_INFO,
				"process",
				"starting conversation");
			conversationCount++;
			startConversation(newPartner, "Chat3", "Role1");
		}

//		this.getLogger().text(Able.MSG_INFO, this, "process", "done");
	}

	/**
	 * Indicates whether this agent should send a test message as part of its current
	 * processing step.
	 * 
	 * @return true if it should, false if not.
	 */
	protected boolean decideToSendTestMessage() {
		return testMessageEnabled;
	}

	/**
	 * Indicates whether this agent should initiate a conversation as part of its
	 * current processing step.
	 *  
	 * @return true if it should, false if not.
	 */
	protected boolean decideToStartConversation() {
		if (conversationCount < totalConversations) {
			return conversationEnabled;
		}
		else {
			return false;
		}
	}

	/**
	 * Selects, from the platform registry, an agent to communicate with.
	 * It first obtains from the registry the AgentDescriptions of all agents
	 * that have registered themselves as being Chatterbox agents. Then it selects
	 * and returns one at random from the list.
	 * 
	 * @return The description of the selected agent.
	 */
	protected AgentDescription selectConversationPartner() {

		// Use the Agent Directory Service to look up agents with the 
		// desired attributes.   
		String lookupAttr = AblePlatform.AbleAgentType;
		String lookupValue = ChatterboxAgent.ABLE_AGENT_TYPE;

		AgentDescription[] lookupResults = null;
		try {
			lookupResults = lookUpAgent(lookupAttr, lookupValue);
//			logMessage(
//				Able.MSG_INFO,
//				"selectConversationPartner",
//				"Found " + lookupResults.length + " agents to talk to");
		}
		catch (Exception ex) {
			logException("selectConversationPartner", ex);
		}

		// Select a partner from the list.
		AgentDescription selection = null;
		if (lookupResults != null && lookupResults.length > 0) {
			// Presumably, one of the elems in lookupResults is this agent's own description.
			// Create a new list that does NOT contain it.
			Vector possibilities = new Vector();
			for (int i=0; i<lookupResults.length; i++) {
				if (myJasAgentName.equals(lookupResults[i].getAgentName()) == false)
					possibilities.add(lookupResults[i]);
			}

			// Choose one of the possibilites at random.
			if (possibilities.size() > 0) {
				int selectionIndex = (int)(Math.random() * possibilities.size());
				selection = (AgentDescription)possibilities.elementAt(selectionIndex);
			}
		}
			
//		this.getLogger().text(
//			Able.MSG_INFO,
//			this,
//			this.getName() + ":selectConversationPartner",
//			"Selected " + selection);
		return selection;
	}

	/**
	 * Sends a simple test message to the given agent.
	 * 
	 * @param partnerDesc The AgentDescription of the agent to send the message to.
	 */
	protected void sendTestMessage(AgentDescription partnerDesc) {
		AgentName partnerAgentName = partnerDesc.getAgentName();

		// Get the RMI Locator for the agent.
		Locator rmiLocator = getLocatorForType(partnerDesc, "rmi");

		// Send the message.
		if (rmiLocator != null) {
			try {
				sendTransportMessage(
					rmiLocator,
					partnerAgentName,
					"Hello, partner!");
			}
			catch (Exception ex) {
				logException(
					"sendTestMessage",
					ex);
			}
		}

	}

	// ===========================================
	// Debugging
	// ===========================================

	/**
	 * Returns a space-separated list of the names of all known conversation policies.
	 * 
	 * @return The list.
	 */
	public String listKnownCpNames() {
		AbleConversationManager convMgr = super.conversationManager;
		Iterator iter =
			convMgr
				.getConversationPolicyHandlerFactory()
				.getPolicyNames()
				.iterator();
		StringBuffer sbuf = new StringBuffer();
		String delimiter = " ";
		while (iter.hasNext()) {
			sbuf.append((String)iter.next());
			if (iter.hasNext())
				sbuf.append(delimiter);
		}
		return sbuf.toString();
	}

}
