/*****************************************************************
 JADE - Java Agent DEvelopment Framework is a framework to develop
 multi-agent systems in compliance with the FIPA specifications.
 Copyright (C) 2000 CSELT S.p.A.
 
 GNU Lesser General Public License
 
 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Lesser General Public
 License as published by the Free Software Foundation, 
 version 2.1 of the License. 
 
 This library is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 Lesser General Public License for more details.
 
 You should have received a copy of the GNU Lesser General Public
 License along with this library; if not, write to the
 Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 Boston, MA  02111-1307, USA.
 *****************************************************************/

package jade.proto;

//#CUSTOM_EXCLUDE_FILE

import jade.core.*;
import jade.core.behaviours.*;
import jade.lang.acl.*;
import jade.domain.FIPAAgentManagement.NotUnderstoodException;
import jade.domain.FIPAAgentManagement.RefuseException;
import jade.domain.FIPAAgentManagement.FailureException;
import jade.domain.FIPANames;
import jade.proto.states.*;
import jade.util.leap.Iterator;

/**
 *
 * This is a single homogeneous and effective implementation of
 * all the FIPA-Request-like interaction protocols defined by FIPA,
 * that is all those protocols where the initiator sends a single message
 * (i.e. it performs a single communicative act) within the scope
 * of an interaction protocol in order to verify if the RE (Rational
 * Effect) of the communicative act has been achieved or not.
 * @see AchieveREInitiator
 * @author Giovanni Caire - TILab
 * @author Fabio Bellifemine - TILab
 * @author Tiziana Trucco - TILab
 * @version $Date: 2006-05-25 15:29:42 +0200 (gio, 25 mag 2006) $ $Revision: 5884 $
 **/
public class AchieveREResponder extends FSMBehaviour implements FIPANames.InteractionProtocol {
	
	/** 
	 * key to retrieve from the DataStore of the behaviour the ACLMessage 
	 *	object sent by the initiator.
	 **/
	public final String REQUEST_KEY = "__request" + hashCode();
	/** 
	 * key to retrieve from the DataStore of the behaviour the ACLMessage 
	 *	object sent as a response to the initiator.
	 **/
	public final String RESPONSE_KEY = "__response" + hashCode();
	/** 
	 * key to retrieve from the DataStore of the behaviour the ACLMessage 
	 *	object sent as a result notification to the initiator.
	 **/
	public final String RESULT_NOTIFICATION_KEY = "__result-notification" + hashCode();
	
	// FSM states names
	private static final String RECEIVE_REQUEST = "Receive-request";
	private static final String HANDLE_REQUEST = "Handle-request";
	private static final String SEND_RESPONSE = "Send-response";
	private static final String PREPARE_RESULT_NOTIFICATION = "Prepare-result-notification";
	private static final String SEND_RESULT_NOTIFICATION = "Send-result-notification";
	
	
	// The MsgReceiver behaviour used to receive request messages
	MsgReceiver rec = null;
	
	/**
	 This static method can be used 
	 to set the proper message Template (based on the interaction protocol 
	 and the performative)
	 into the constructor of this behaviour.
	 @see FIPANames.InteractionProtocol
	 **/
	public static MessageTemplate createMessageTemplate(String iprotocol){
		
		if(CaseInsensitiveString.equalsIgnoreCase(FIPA_REQUEST,iprotocol))
			return MessageTemplate.and(MessageTemplate.MatchProtocol(FIPA_REQUEST),MessageTemplate.MatchPerformative(ACLMessage.REQUEST));
		else
			if(CaseInsensitiveString.equalsIgnoreCase(FIPA_QUERY,iprotocol))
				return MessageTemplate.and(MessageTemplate.MatchProtocol(FIPA_QUERY),MessageTemplate.or(MessageTemplate.MatchPerformative(ACLMessage.QUERY_IF),MessageTemplate.MatchPerformative(ACLMessage.QUERY_REF)));
			else
				return MessageTemplate.MatchProtocol(iprotocol);
	}
	
	
	// Inner classes for the FSM states
	
	private static class HandleRequest extends OneShotBehaviour {
		
		public HandleRequest(Agent a) {
			super(a);
		}
		
		public void action() {
			DataStore ds = getDataStore();
			AchieveREResponder fsm = (AchieveREResponder)getParent();
			ACLMessage request = (ACLMessage) ds.get(fsm.REQUEST_KEY);
			
			ACLMessage response = null;
			try {
				response = fsm.handleRequest(request);
			}
			catch (NotUnderstoodException nue) {
				response = nue.getACLMessage();
			}
			catch (RefuseException re) {
				response = re.getACLMessage();
			}
			ds.put(fsm.RESPONSE_KEY, response);
		}
	} // End of HandleRequest class
	
	
	private static class SendResponse extends ReplySender {
		
		public SendResponse(Agent a, String replyKey, String msgKey) {
			super(a, replyKey, msgKey);
		}
		
		// For persistence service
		private SendResponse() {
		}
		
		public int onEnd() {
			int ret = super.onEnd();
			if (ret != ACLMessage.AGREE && ret != ReplySender.NO_REPLY_SENT) {
				AchieveREResponder fsm = (AchieveREResponder)getParent();
				fsm.reset();
			}
			return ret;
		}
		
	} // End of SendResponse class
	
	
	private static class PrepareResult extends OneShotBehaviour {
		
		public PrepareResult(Agent a) {
			super(a);
		}
		
		// For persistence service
		private PrepareResult() {
		}
		
		public void action() {
			DataStore ds = getDataStore();
			AchieveREResponder fsm = (AchieveREResponder)getParent();
			ACLMessage request = (ACLMessage) ds.get(fsm.REQUEST_KEY);
			ACLMessage response = (ACLMessage) ds.get(fsm.RESPONSE_KEY);
			ACLMessage resNotification = null;
			try {
				resNotification = fsm.prepareResultNotification(request, response); 
			}
			catch (FailureException fe) {
				resNotification = fe.getACLMessage();
			}
			ds.put(fsm.RESULT_NOTIFICATION_KEY, resNotification);
		}
		
	} // End of PrepareResult class
	
	
	private static class SendResult extends ReplySender {
		
		public SendResult(Agent a, String replyKey, String msgKey) {
			super(a, replyKey, msgKey);
		}
		
		// For persistence service
		private SendResult() {
		}
		
		public int onEnd() {
			AchieveREResponder fsm = (AchieveREResponder)getParent();
			fsm.reset();
			return super.onEnd();
		}
		
	} // End of SendResult class
	
	
	
	/**
	 * Constructor of the behaviour that creates a new empty DataStore
	 * @see #AchieveREResponder(Agent a, MessageTemplate mt, DataStore store) 
	 **/
	public AchieveREResponder(Agent a, MessageTemplate mt){
		this(a,mt, new DataStore());
	}
	
	/**
	 * Constructor.
	 * @param a is the reference to the Agent object
	 * @param mt is the MessageTemplate that must be used to match
	 * the initiator message. Take care that 
	 * if mt is null every message is consumed by this protocol.
	 * @param store the DataStore for this protocol
	 **/
	public AchieveREResponder(Agent a, MessageTemplate mt, DataStore store) {
		super(a);
		
		setDataStore(store);
		
		// Register the FSM transitions
		registerDefaultTransition(RECEIVE_REQUEST, HANDLE_REQUEST);
		registerDefaultTransition(HANDLE_REQUEST, SEND_RESPONSE);
		registerTransition(SEND_RESPONSE, PREPARE_RESULT_NOTIFICATION, ACLMessage.AGREE);
		registerTransition(SEND_RESPONSE, PREPARE_RESULT_NOTIFICATION, ReplySender.NO_REPLY_SENT);
		registerDefaultTransition(SEND_RESPONSE, RECEIVE_REQUEST);
		registerDefaultTransition(PREPARE_RESULT_NOTIFICATION, SEND_RESULT_NOTIFICATION);		
		registerDefaultTransition(SEND_RESULT_NOTIFICATION, RECEIVE_REQUEST);
		
		// Create and register the states that make up the FSM
		Behaviour b = null;
		
		// RECEIVE_REQUEST
		rec = new MsgReceiver(myAgent, mt, -1, getDataStore(), REQUEST_KEY);
		registerFirstState(rec, RECEIVE_REQUEST);
		
		// HANDLE_REQUEST
		b = new HandleRequest(myAgent);
		b.setDataStore(getDataStore());		
		registerState(b, HANDLE_REQUEST);
		
		// SEND_RESPONSE
		b = new SendResponse(myAgent,RESPONSE_KEY,REQUEST_KEY);
		b.setDataStore(getDataStore());		
		registerState(b, SEND_RESPONSE);	
		
		// PREPARE_RESULT_NOTIFICATION
		b = new PrepareResult(myAgent);
		b.setDataStore(getDataStore());		
		registerState(b, PREPARE_RESULT_NOTIFICATION);
		
		// SEND_RESULT_NOTIFICATION
		b = new SendResult(myAgent, RESULT_NOTIFICATION_KEY, REQUEST_KEY);
		b.setDataStore(getDataStore());		
		registerState(b, SEND_RESULT_NOTIFICATION);
	} 
	
	// For persistence service
	private AchieveREResponder() {
	}
	
	
	/**
	 Reset this behaviour using the same MessageTemplate.
	 */
	public void reset() {
		super.reset();
		DataStore ds = getDataStore();
		ds.remove(REQUEST_KEY);
		ds.remove(RESPONSE_KEY);
		ds.remove(RESULT_NOTIFICATION_KEY);
	}
	
	/**
	 This method allows to change the <code>MessageTemplate</code>
	 that defines what messages this FIPARequestResponder will react to and reset the protocol.
	 */
	public void reset(MessageTemplate mt) {
		this.reset();
		rec.reset(mt, -1, getDataStore(), REQUEST_KEY);
	}
	
	
	/**   
	 * This method is called when the protocol initiation message (matching the 
	 * MessageTemplate specified in the constructor) is received. 
	 * This default implementation returns null which has
	 * the effect of sending no reponse. Programmers should
	 * override this method in case they need to react to this event.
	 * @param request the received message
	 * @return the ACLMessage to be sent as a response (i.e. one of
	 * <code>AGREE, REFUSE, NOT_UNDERSTOOD, INFORM</code>.
	 **/
	protected ACLMessage handleRequest(ACLMessage request) throws NotUnderstoodException, RefuseException {
		// Call prepareResponse() for backward compatibility
		return prepareResponse(request);
	}
	
	/**
	 * @deprecated Use handleRequest() instead   
	 */
	protected ACLMessage prepareResponse(ACLMessage request) throws NotUnderstoodException, RefuseException {
		System.out.println("prepareResponse() method not re-defined");
		return null;
	}
	
	/**   
	 * This method is called after the execution of the handleRequest() method if
	 * no response was sent or the response was an <code>AGREE</code> message.
	 * This default implementation returns null which has
	 * the effect of sending no result notification. Programmers should
	 * override the method in case they need to react to this event.
	 * @param request the received message
	 * @param response the previously sent response message
	 * @return the ACLMessage to be sent as a result notification (i.e. one of
	 * <code>INFORM, FAILURE</code>. 
	 * @see #handleRequest(ACLMessage)
	 **/
	protected ACLMessage prepareResultNotification(ACLMessage request, ACLMessage response) throws FailureException {
		System.out.println("prepareResultNotification() method not re-defined");
		return null;
	}
	
	
	/**
	 This method allows to register a user defined <code>Behaviour</code>
	 in the HANDLE_REQUEST state.
	 This behaviour would override the homonymous method.
	 This method also set the DataStore of the registered <code>Behaviour</code> to the
	 DataStore of this AchieveREResponder.
	 It is responsibility of the registered behaviour to put the
	 response to be sent into the DataStore at the <code>RESPONSE_KEY</code>
	 key.
	 @param b the Behaviour that will handle this state
	 */
	public void registerHandleRequest(Behaviour b) {
		registerState(b, HANDLE_REQUEST);
		b.setDataStore(getDataStore());
	}
	
	/**
	 * @deprecated Use registerHandleRequest() instead.
	 */
	public void registerPrepareResponse(Behaviour b) {
		registerHandleRequest(b);
	}
	
	/**
	 This method allows to register a user defined <code>Behaviour</code>
	 in the PREPARE_RESULT_NOTIFICATION state.
	 This behaviour would override the homonymous method.
	 This method also set the DataStore of the registered <code>Behaviour</code> to the
	 DataStore of this AchieveREResponder.
	 It is responsibility of the registered behaviour to put the
	 result notification message to be sent into the DataStore at the 
	 <code>RESULT_NOTIFICATION_KEY</code>
	 key.
	 @param b the Behaviour that will handle this state
	 */
	public void registerPrepareResultNotification(Behaviour b) {
		registerState(b, PREPARE_RESULT_NOTIFICATION);
		b.setDataStore(getDataStore());
	}
	
}



