/*****************************************************************
 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.proto.states.*;
import jade.domain.FIPAException;
import jade.domain.FIPAAgentManagement.RefuseException;
import jade.domain.FIPAAgentManagement.FailureException;
import jade.domain.FIPAAgentManagement.NotUnderstoodException;

/**
 Single Session version of the Responder role in the Iterated-Fipa-Request
 protocol.
 
 @author Giovanni Caire - TILAB
 */
public class SSIteratedAchieveREResponder extends SSResponder {
	/**
	 Key to retrieve from the DataStore of the behaviour the last received
	 REQUEST ACLMessage
	 */
	public final String REQUEST_KEY = RECEIVED_KEY;
	/**
	 Key to retrieve from the DataStore of the behaviour the last received
	 CANCEL ACLMessage
	 */
	public final String CANCEL_KEY = RECEIVED_KEY;
	
	public static final String HANDLE_REQUEST = "Handle-Request";
	public static final String HANDLE_CANCEL = "Handle-Cancel";
	
	static final String ACL_USERDEF_TERMINATED_SESSION = "iterated-fipa-request-terminated-session";
	
	private boolean sessionClosed = false;
	private int initiationPerformative;
	
	/**
	 Construct a SSIteratedAchieveREResponder that is activated 
	 by the reception of a given initiation REQUEST message.
	 */
	public SSIteratedAchieveREResponder(Agent a, ACLMessage request) {
		this(a, request, new DataStore());
	}
	
	/**
	 Construct a SSIteratedAchieveREResponder that is activated 
	 by the reception of a given initiation REQUEST message and uses 
	 a given DataStore.
	 */
	public SSIteratedAchieveREResponder(Agent a, ACLMessage request, DataStore store) {
		// 4th parameter is false since in this protocol we treat the initiation message exactly as all subsequent incoming messages
		super(a, request, store, false);
		
		initiationPerformative = request.getPerformative();
		
		registerDefaultTransition(HANDLE_REQUEST, SEND_REPLY);
		registerTransition(SEND_REPLY, RECEIVE_NEXT, ACLMessage.INFORM);
		registerTransition(RECEIVE_NEXT, HANDLE_CANCEL, MsgReceiver.TIMEOUT_EXPIRED); 
		registerTransition(CHECK_IN_SEQ, HANDLE_REQUEST, initiationPerformative, new String[]{HANDLE_REQUEST, SEND_REPLY, RECEIVE_NEXT, CHECK_IN_SEQ}); 
		registerTransition(CHECK_IN_SEQ, HANDLE_CANCEL, ACLMessage.CANCEL); 
		registerDefaultTransition(HANDLE_CANCEL, DUMMY_FINAL);
		
		Behaviour b;
		
		// HANDLE_REQUEST
		b = new RequestHandler(myAgent);
		registerFirstState(b, HANDLE_REQUEST);
		b.setDataStore(getDataStore());
		
		// HANDLE_CANCEL
		b = new CancelHandler(myAgent);
		registerDSState(b, HANDLE_CANCEL);
	}
	
	
	/**
	 This method is called to handle the initial REQUEST message and 
	 then again whenever a REQUEST message is received.
	 This default implementation does nothing and returns null.
	 Programmers have to override it to react to this event.
	 @param  request the REQUEST message to handle.
	 @return the reply message to be sent back to the initiator. Returning 
	 a message defferent from INFORM (or returning null) terminates the protocol. 
	 An optional AGREE message can be sent back to the initiator by
	 calling the <code>sendAgree()</code> method.
	 @exception RefuseException if the REQUEST is refused. Throwing a 
	 RefuseException has the same effect as returning a REFUSE message, 
	 but automatically manages the <code>:content</code> slot.
	 @exception FailureException if there is an error serving the REQUEST.
	 Throwing a FailureException has the same effect as returning a FAILURE
	 message, but automatically manages the <code>:content</code> slot.
	 @exception NotUnderstoodException if the REQUEST content is not undrerstood.
	 Throwing a NotUnderstoodException has the same effect as returning a NOT_UNDERSTOOD
	 message, but automatically manages the <code>:content</code> slot.
	 */
	protected ACLMessage handleRequest(ACLMessage request) throws RefuseException, FailureException, NotUnderstoodException {
		return null;
	}
	
	/**
	 This method is called when a CANCEL message is received from the 
	 initiator.
	 This default implementation does nothing.
	 Programmers may override it to react to this event.
	 @param cancel the received CANCEL message or null if no 
	 further REQUEST message is received from the initiator within the timeout
	 specified in the <code>:reply-by</code> slot of the last INFORM message.
	 */
	protected void handleCancel(ACLMessage cancel) {
	}
	
	/**
	 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 sets the 
	 data store of the registered <code>Behaviour</code> to the
	 DataStore of this current behaviour.
	 <br>
	 The registered behaviour can retrieve the received <code>REQUEST</code> 
	 message from the datastore at the <code>REQUEST_KEY</code> key.
	 <br>
	 It is responsibility of the registered behaviour to put the
	 reply to be sent back to the initiator into the datastore at the 
	 <code>REPLY_KEY</code> key. Putting a message defferent from INFORM 
	 (or null) terminates the protocol. 
	 An optional AGREE message can be sent back to the initiator by
	 calling the <code>sendAgree()</code> method.
	 @param b the Behaviour that will handle this state
	 */
	public void registerHandleRequest(Behaviour b) {
		registerFirstState(b, HANDLE_REQUEST);
		b.setDataStore(getDataStore());
	}
	
	/**
	 This method allows to register a user defined <code>Behaviour</code>
	 in the HANDLE_CANCEL state.
	 This behaviour would override the homonymous method.
	 This method also sets the 
	 data store of the registered <code>Behaviour</code> to the
	 DataStore of this current behaviour.
	 <br>
	 The registered behaviour can retrieve the <code>CANCEL</code> 
	 message received from the datastore at the <code>CANCEL_KEY</code> key.
	 <br>
	 @param b the Behaviour that will handle this state
	 */
	public void registerHandleCancel(Behaviour b) {
		registerDSState(b, HANDLE_CANCEL);
	}
	
	/**
	 Utility method to send an optional AGREE message back to the 
	 initiator ensuring that all protocol fields are properly set.
	 */
	public void sendAgree(ACLMessage agree) {
		agree.setPerformative(ACLMessage.AGREE);
		ReplySender.adjustReply(myAgent, agree, (ACLMessage) getDataStore().get(REQUEST_KEY));
		myAgent.send(agree);
	}
	
	/**
	 Close the ongoing session, as soon as the next INFORM will 
	 be sent back to the initiator without the need for an explicit 
	 CANCEL message. The initiator will be able to detect that the 
	 session has been closed by calling the 
	 <code>isSessionTerminated()</code> method of the 
	 <code>SSIteratedAchieveREInitiator</code> class.
	 */
	public void closeSessionOnNextReply() {
		sessionClosed = true;
	}
	
	/**
	 Reset this protocol behaviour
	 */
	public void reset() {
		sessionClosed = false;
		super.reset();
	}
	
	
	//#APIDOC_EXCLUDE_BEGIN
	protected boolean checkInSequence(ACLMessage received) {
		return received.getPerformative() == initiationPerformative || received.getPerformative() == ACLMessage.CANCEL;
	}
	
	protected void beforeReply(ACLMessage reply) {
		if (sessionClosed && reply != null) {
			// Set the user defined parameter that will allow the
			// initiator to detect that the session has been closed
			reply.addUserDefinedParameter(ACL_USERDEF_TERMINATED_SESSION, String.valueOf(true));
			// Terminate the protocol by forcing a transition to the 
			// DUMMY_FINAL state
			forceTransitionTo(DUMMY_FINAL);
		}
	}
	//#APIDOC_EXCLUDE_END
	
	
	/**
	 Inner class RequestHandler
	 */
	private static class RequestHandler extends OneShotBehaviour {
		private static final long     serialVersionUID = 5463827646358001L;
		
		public RequestHandler(Agent a) {
			super(a);
		}
		
		public void action() {
			SSIteratedAchieveREResponder parent = (SSIteratedAchieveREResponder) getParent();
			ACLMessage reply = null;
			try {
				reply = parent.handleRequest((ACLMessage) getDataStore().get(parent.REQUEST_KEY));
			}
			catch (FIPAException fe) {
				reply = fe.getACLMessage();
			}
			getDataStore().put(parent.REPLY_KEY, reply);
		}
	} // End of inner class RequestHandler
	
	
	/**
	 Inner class CancelHandler
	 */
	private static class CancelHandler extends OneShotBehaviour {
		private static final long     serialVersionUID = 5463827646358002L;
		
		public CancelHandler(Agent a) {
			super(a);
		}
		
		public void action() {
			SSIteratedAchieveREResponder parent = (SSIteratedAchieveREResponder) getParent();
			parent.handleCancel((ACLMessage) getDataStore().get(parent.CANCEL_KEY));
		}
	} // End of inner class CancelHandler  
}	


