/*****************************************************************
 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.domain.introspection;

//#J2ME_EXCLUDE_FILE

import java.util.Map;
import java.util.TreeMap;

import jade.core.AID;
import jade.core.behaviours.*;

import jade.domain.FIPANames;

import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;

import jade.content.lang.sl.SLCodec;
import jade.util.leap.Serializable;

/**
 This behaviour subscribes to the AMS to receive notifications
 about platform-wide events. The <code>installHandlers</code> 
 method must be redefined to define the handlers for events the
 agent executing this behaviour is interested in.
 @author Giovanni Caire - TILAB
 @author Giovanni Rimassa - Universita' di Parma
 */
public abstract class AMSSubscriber extends SimpleBehaviour {
	// FIXME: Change the values of these constants
	public static final String AMS_SUBSCRIPTION = "tool-subscription";
	public static final String AMS_CANCELLATION = "tool-cancellation";
	public static final String PLATFORM_EVENTS = "platform-events";
	
	private AID ams = null;
	private ACLMessage AMSSubscription = new ACLMessage(ACLMessage.SUBSCRIBE);
	private ACLMessage AMSCancellation = new ACLMessage(ACLMessage.CANCEL);
	
	private MessageTemplate listenTemplate;
	private boolean active = true;
	
	// Ignore case for event names
	//#DOTNET_EXCLUDE_BEGIN
	private Map handlers = new TreeMap(String.CASE_INSENSITIVE_ORDER);
	//#DOTNET_EXCLUDE_END
	/*#DOTNET_INCLUDE_BEGIN
	 private Map handlers = new TreeMap(new CaseInsensitiveComparator() );
	 #DOTNET_INCLUDE_END*/
	
	/**
	 This interface must be implemented by concrete event handlers
	 installed by this AMSSubscriber.
	 */
	public static interface EventHandler extends Serializable {
		void handle(Event ev);
	}
	
	
	/**
	 * Construct an AMSSubscriber behaviour to receive notifications about platform events
	 * from the local AMS
	 */
	public AMSSubscriber() {
		super();
		
		// Prepare the template to receive AMS notification
		MessageTemplate mt1 = MessageTemplate.MatchLanguage(FIPANames.ContentLanguage.FIPA_SL0);
		MessageTemplate mt2 = MessageTemplate.MatchOntology(IntrospectionOntology.NAME);
		MessageTemplate mt12 = MessageTemplate.and(mt1, mt2);
		
		mt1 = MessageTemplate.MatchInReplyTo(AMS_SUBSCRIPTION);
		mt2 = MessageTemplate.MatchPerformative(ACLMessage.INFORM);
		listenTemplate = MessageTemplate.and(mt1, mt2);
		listenTemplate = MessageTemplate.and(listenTemplate, mt12);
		
		// Fill the event handler table, using a deferred operation.
		installHandlers(handlers);
	}
	
	/**
	 * Construct an AMSSubscriber behaviour to receive notifications about platform events
	 * from the AMS of a remote platform.
	 * @param ams The AID of the remote platform AMS
	 */
	public AMSSubscriber(AID ams) {
		this();
		this.ams = ams;
	}
	
	public void onStart() {
		if (ams == null) {
			ams = myAgent.getAMS();
		}
		
		// Register the Introspection ontology 
		myAgent.getContentManager().registerOntology(IntrospectionOntology.getInstance());
		
		// Register the SL0 language
		myAgent.getContentManager().registerLanguage(new SLCodec(), FIPANames.ContentLanguage.FIPA_SL0);
		
		// Fill the subscription message
		AMSSubscription.addReceiver(ams);
		AMSSubscription.setLanguage(FIPANames.ContentLanguage.FIPA_SL0);
		AMSSubscription.setOntology(IntrospectionOntology.NAME);
		AMSSubscription.setReplyWith(AMS_SUBSCRIPTION);
		AMSSubscription.setConversationId(myAgent.getLocalName());
		AMSSubscription.setContent(PLATFORM_EVENTS);
		
		// Fill the cancellation message
		AMSCancellation.addReceiver(ams);
		AMSCancellation.setLanguage(FIPANames.ContentLanguage.FIPA_SL0);
		AMSCancellation.setOntology(IntrospectionOntology.NAME);
		AMSCancellation.setReplyWith(AMS_CANCELLATION);
		AMSCancellation.setConversationId(myAgent.getLocalName());
		// No content is needed (cfr. FIPA 97 Part 2 page 26)
		
		// Subscribe to the AMS
		myAgent.send(AMSSubscription);
	}
	
	/**
	 This method has to be implemented by concrete subclasses,
	 filling the <code>Map</code> passed as parameter with
	 implementations of the <code>EventHandler</code> interface,
	 using the name of the event as key (see the <code>Event</code>
	 interface.
	 @param handlersTable The table that associates each event name
	 with a proper handler.
	 */
	protected abstract void installHandlers(Map handlersTable);
	
	public final void action() {
		if (active) {
			ACLMessage current = myAgent.receive(listenTemplate);
			if(current != null) {
				// Handle 'inform' messages from the AMS
				try {
					Occurred o = (Occurred) myAgent.getContentManager().extractContent(current);
					EventRecord er = (EventRecord)o.getWhat();
					Event ev = er.getWhat();
					String eventName = ev.getName();
					EventHandler h = (EventHandler)handlers.get(eventName);
					if(h != null) {
						h.handle(ev);
					}
				}
				catch(ClassCastException cce) {
					cce.printStackTrace();
				}
				catch(Exception fe) {
					fe.printStackTrace();
				}
			}
			else {
				block();
			}
		}
	}
	
	public final boolean done() {
		return !active;
	}
	
	public void cancel() {
		myAgent.send(getCancel());
		active = false;
		restart();
	}
	
	/**
	 Retrieve the <code>subscribe</code> ACL message used to subscribe 
	 to the AMS.
	 This message is automatically sent when this behaviour is added to an
	 Agent.
	 @return The subscription ACL message.
	 */
	public final ACLMessage getSubscribe() {
		return AMSSubscription;
	}
	
	/**
	 Retrieve the <code>cancel</code> ACL message 
	 used to cancel the subscription to the AMS.
	 Since this behaviour is cyclic (never ends) it is the responsibility
	 of the agent executing this behaviour to send the <code>cancel</code>
	 message to the AMS when notifications are no longer required.
	 @return The cancellation ACL message.
	 */
	public final ACLMessage getCancel() {
		return AMSCancellation;
	}
	
	/*#DOTNET_INCLUDE_BEGIN
	 //This class is used to obtain a Comparator for compare two strings
	  //with case ignoring.
	   private class CaseInsensitiveComparator implements java.util.Comparator, java.io.Serializable 
	   {
	   // use serialVersionUID from JDK 1.2.2 for interoperability
	    private static final long serialVersionUID = 8575799808933029326L;
	    
	    public int compare(Object o1, Object o2) 
	    {
	    String s1 = (String) o1;
	    String s2 = (String) o2;
	    int n1=s1.length(), n2=s2.length();
	    for (int i1=0, i2=0; i1<n1 && i2<n2; i1++, i2++) 
	    {
	    char c1 = s1.charAt(i1);
	    char c2 = s2.charAt(i2);
	    if (c1 != c2) 
	    {
	    c1 = Character.toUpperCase(c1);
	    c2 = Character.toUpperCase(c2);
	    if (c1 != c2) 
	    {
	    c1 = Character.toLowerCase(c1);
	    c2 = Character.toLowerCase(c2);
	    if (c1 != c2) 
	    {
	    return c1 - c2;
	    }
	    }
	    }
	    }
	    return n1 - n2;
	    }
	    }
	    #DOTNET_INCLUDE_END*/
}

