/*****************************************************************
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.
 *****************************************************************/

//#J2ME_EXCLUDE_FILE

package jade.core.management;

import jade.core.ServiceFinder;
import jade.core.HorizontalCommand;
import jade.core.VerticalCommand;
import jade.core.GenericCommand;
import jade.core.Service;
import jade.core.BaseService;
import jade.core.ServiceException;
import jade.core.Sink;
import jade.core.Filter;
import jade.core.Node;

import jade.core.Profile;
import jade.core.Agent;
import jade.core.AgentState;
import jade.core.AID;
import jade.core.ContainerID;
import jade.core.AgentContainer;
import jade.core.BackEndContainer;
import jade.core.MainContainer;

import jade.core.ProfileException;
import jade.core.IMTPException;
import jade.core.NameClashException;
import jade.core.NotFoundException;
import jade.core.UnreachableException;

import jade.security.Credentials;
import jade.security.JADEPrincipal;
import jade.security.JADESecurityException;

import jade.util.leap.Map;
import jade.util.leap.HashMap;
import jade.util.leap.Iterator;

import jade.util.Logger;

/**

   The JADE service to manage the basic agent life cycle: creation,
   destruction, suspension and resumption, in the special case of a
   Back-End Container.

   @author Giovanni Rimassa - FRAMeTech s.r.l.
   @author Jerome Picault - Motorola Labs

 */
public class BEAgentManagementService extends BaseService {

	static final String NAME = "jade.core.management.AgentManagement";



	private static final String[] OWNED_COMMANDS = new String[] {
		AgentManagementSlice.REQUEST_CREATE,
		AgentManagementSlice.REQUEST_KILL,
		AgentManagementSlice.REQUEST_STATE_CHANGE,
		AgentManagementSlice.INFORM_CREATED,
		AgentManagementSlice.INFORM_KILLED,
		AgentManagementSlice.INFORM_STATE_CHANGED,
		AgentManagementSlice.KILL_CONTAINER
	};


	public void init(AgentContainer ac, Profile p) throws ProfileException {
		super.init(ac, p);

		myContainer = (BackEndContainer)ac;
	}


	public String getName() {
		return AgentManagementSlice.NAME;
	}

	public Class getHorizontalInterface() {
		try {
			return Class.forName(AgentManagementSlice.NAME + "Slice");
		}
		catch(ClassNotFoundException cnfe) {
			return null;
		}
	}

	public Service.Slice getLocalSlice() {
		return localSlice;
	}

	public Filter getCommandFilter(boolean direction) {
		return null;
	}


	public Sink getCommandSink(boolean side) {
		if(side == Sink.COMMAND_SOURCE) {
			return senderSink;
		}
		else {
			return receiverSink;
		}
	}

	public String[] getOwnedCommands() {
		return OWNED_COMMANDS;
	}


	// This inner class handles the messaging commands on the command
	// issuer side, turning them into horizontal commands and
	// forwarding them to remote slices when necessary.
	private class CommandSourceSink implements Sink {

		public void consume(VerticalCommand cmd) {

			try {
				String name = cmd.getName();
				if(name.equals(AgentManagementSlice.INFORM_KILLED)) {
					handleInformKilled(cmd);
				}
				else if(name.equals(AgentManagementSlice.INFORM_STATE_CHANGED)) {
					handleInformStateChanged(cmd);
				}
				else if(name.equals(AgentManagementSlice.INFORM_CREATED)) {
					handleInformCreated(cmd);
				}
			}
			catch(Throwable t) {
				cmd.setReturnValue(t);
			}
		}


		// Vertical command handler methods

		private void handleInformCreated(VerticalCommand cmd) throws IMTPException, NotFoundException, NameClashException, JADESecurityException, ServiceException {
			Object[] params = cmd.getParams();
			AID agentID = (AID)params[0];

			boolean startedOnBE = false;
			Agent previous = null;
			// If an actual agent instance was passed as second argument, then this agent 
			// is started within the Back-End container
			if((params.length > 1) && (params[1] instanceof Agent))  {
				Agent instance = (Agent)params[1];
				// If the instance is an AgentImage, this is a re-addition of an agent
				// living in the FE --> just do nothing
				if (!(instance instanceof BackEndContainer.AgentImage)) { 
					// Add the new agent in the LADT
					previous = myContainer.addLocalAgent(agentID, instance);
					startedOnBE = true;
				}
			}
			else {
				// Add the new agent in the images table 
				BackEndContainer.AgentImage image = (BackEndContainer.AgentImage) pendingImages.remove(agentID);
				if (image == null) {
					// The agent spontaneously born on the FrontEnd --> its image still has to be created
					image = myContainer.createAgentImage(agentID);
				}
				previous = (BackEndContainer.AgentImage) myContainer.addAgentImage(agentID, image);
			}

			// Notify the Main Container. Roll back if something fails
			try {
				ContainerID cid = myContainer.getID();
				AgentManagementSlice mainSlice = (AgentManagementSlice)getSlice(MAIN_SLICE);
				try {
					mainSlice.bornAgent(agentID, cid, cmd);
				}
				catch(IMTPException imtpe) {
					mainSlice = (AgentManagementSlice)getFreshSlice(jade.core.ServiceFinder.MAIN_SLICE);
					mainSlice.bornAgent(agentID, cid, cmd);
				}
			}
			catch (IMTPException imtpe) {
				rollBack(agentID, previous, startedOnBE);
				throw imtpe;
			}
			catch (NotFoundException nfe) {
				rollBack(agentID, previous, startedOnBE);
				throw nfe;
			}
			catch (NameClashException nce) {
				rollBack(agentID, previous, startedOnBE);
				throw nce;
			}
			catch (JADESecurityException jse) {
				rollBack(agentID, previous, startedOnBE);
				throw jse;
			}
			catch (ServiceException se) {
				rollBack(agentID, previous, startedOnBE);
				throw se;
			}
			catch (Exception e) {
				e.printStackTrace();
				rollBack(agentID, previous, startedOnBE);
				throw new IMTPException("Error creating agent " + agentID.getLocalName() + ". ", e);
			}
		}

		private void rollBack(AID agentID, Agent previous, boolean startedOnBE) {
			if (startedOnBE) {
				myContainer.removeLocalAgent(agentID);
				if(previous != null) {
					myContainer.addLocalAgent(agentID, previous);
				}
			}
			else {
				myContainer.removeAgentImage(agentID);
				if (previous != null) {
					myContainer.addAgentImage(agentID, (BackEndContainer.AgentImage) previous);
				}
			}
		}

		private void handleInformKilled(VerticalCommand cmd) throws IMTPException, ServiceException, NotFoundException {
			Object[] params = cmd.getParams();
			AID target = (AID)params[0];

			// Notify the main container through its slice
			AgentManagementSlice mainSlice = (AgentManagementSlice)getSlice(MAIN_SLICE);

			try {
				mainSlice.deadAgent(target, cmd);
			}
			catch(IMTPException imtpe) {
				// Try to get a newer slice and repeat...
				mainSlice = (AgentManagementSlice)getFreshSlice(MAIN_SLICE);
				mainSlice.deadAgent(target, cmd);
			}

			// Remove the dead agent from the agent images
			BackEndContainer.AgentImage image = (BackEndContainer.AgentImage) myContainer.removeAgentImage(target);
		}

		private void handleInformStateChanged(VerticalCommand cmd) {

			Object[] params = cmd.getParams();
			AID target = (AID)params[0];
			AgentState from = (AgentState)params[1];
			AgentState to = (AgentState)params[2];

			if (to.equals(jade.domain.FIPAAgentManagement.AMSAgentDescription.SUSPENDED)) {
				try {
					// Notify the main container through its slice
					AgentManagementSlice mainSlice = (AgentManagementSlice)getSlice(MAIN_SLICE);

					try {
						mainSlice.suspendedAgent(target);
					}
					catch(IMTPException imtpe) {
						// Try to get a newer slice and repeat...
						mainSlice = (AgentManagementSlice)getFreshSlice(MAIN_SLICE);
						mainSlice.suspendedAgent(target);
					}
				}
				catch(IMTPException re) {
					re.printStackTrace();
				}
				catch(NotFoundException nfe) {
					nfe.printStackTrace();
				}
				catch(ServiceException se) {
					se.printStackTrace();
				}
			}
			else if (from.equals(jade.domain.FIPAAgentManagement.AMSAgentDescription.SUSPENDED)) {
				try {
					// Notify the main container through its slice
					AgentManagementSlice mainSlice = (AgentManagementSlice)getSlice(MAIN_SLICE);

					try {
						mainSlice.resumedAgent(target);
					}
					catch(IMTPException imtpe) {
						// Try to get a newer slice and repeat...
						mainSlice = (AgentManagementSlice)getFreshSlice(MAIN_SLICE);
						mainSlice.resumedAgent(target);
					}
				}
				catch(IMTPException re) {
					re.printStackTrace();
				}
				catch(NotFoundException nfe) {
					nfe.printStackTrace();
				}
				catch(ServiceException se) {
					se.printStackTrace();
				}
			}
		}

		/*private void createAgentOnBE(AID target, Agent instance, VerticalCommand cmd) throws IMTPException, JADESecurityException, NameClashException, NotFoundException, ServiceException {
	    // Connect the new instance to the local container
	    Agent old = myContainer.addLocalAgent(target, instance);

	    try {
		    // Notify the main container through its slice
		    AgentManagementSlice mainSlice = (AgentManagementSlice)getSlice(MAIN_SLICE);

		    try {
					mainSlice.bornAgent(target, myContainer.getID(), cmd);
		    }
		    catch(IMTPException imtpe) {
					// Try to get a newer slice and repeat...
					mainSlice = (AgentManagementSlice)getFreshSlice(MAIN_SLICE);
					mainSlice.bornAgent(target, myContainer.getID(), cmd);
		    }
	    }
	    catch(NameClashException nce) {
		myContainer.removeLocalAgent(target);
		if(old != null) {
		    myContainer.addLocalAgent(target, old);
		}
		throw nce;
	    }
	    catch(IMTPException imtpe) {
		myContainer.removeLocalAgent(target);
		throw imtpe;
	    }
	    catch(NotFoundException nfe) {
		myContainer.removeLocalAgent(target);
		throw nfe;
	    }
	    catch(JADESecurityException ae) {
		myContainer.removeLocalAgent(target);
		throw ae;
	    }
	}*/

	} // End of CommandSourceSink class


	private class CommandTargetSink implements Sink {

		public void consume(VerticalCommand cmd) {

			try {
				String name = cmd.getName();
				if(name.equals(AgentManagementSlice.REQUEST_CREATE)) {
					handleRequestCreate(cmd);
				}
				else if(name.equals(AgentManagementSlice.REQUEST_KILL)) {
					handleRequestKill(cmd);
				}
				else if(name.equals(AgentManagementSlice.REQUEST_STATE_CHANGE)) {
					handleRequestStateChange(cmd);
				}
				else if(name.equals(AgentManagementSlice.INFORM_STATE_CHANGED)) {
					handleInformStateChanged(cmd);
				}
				else if(name.equals(AgentManagementSlice.KILL_CONTAINER)) {
					handleKillContainer(cmd);
				}
			}
			catch(IMTPException imtpe) {
				cmd.setReturnValue(new UnreachableException("Remote container is unreachable", imtpe));
			}
			catch(NotFoundException nfe) {
				cmd.setReturnValue(nfe);
			}
			catch(NameClashException nce) {
				cmd.setReturnValue(nce);
			}
			catch(JADESecurityException ae) {
				cmd.setReturnValue(ae);
			}
			catch(ServiceException se) {
				cmd.setReturnValue(new UnreachableException("A Service Exception occurred", se));
			}

		}


		// Vertical command handler methods

		private void handleRequestCreate(VerticalCommand cmd) throws IMTPException, JADESecurityException, NotFoundException, NameClashException, ServiceException {

			Object[] params = cmd.getParams();
			AID agentID = (AID)params[0];
			String className = (String)params[1];
			Object[]args = (Object[])params[2];
			JADEPrincipal owner = (JADEPrincipal) params[3];
			Credentials initialCredentials = (Credentials) params[4];
			createAgent(agentID, className, args, owner, initialCredentials);
		}

		private void handleRequestKill(VerticalCommand cmd) throws IMTPException, JADESecurityException, NotFoundException, ServiceException {

			Object[] params = cmd.getParams();
			AID agentID = (AID)params[0];

			killAgent(agentID);
		}

		private void handleRequestStateChange(VerticalCommand cmd) throws IMTPException, JADESecurityException, NotFoundException, ServiceException {

			Object[] params = cmd.getParams();
			AID agentID = (AID)params[0];
			int newState = ((Integer)params[1]).intValue();

			changeAgentState(agentID, newState);
		}

		private void handleInformStateChanged(VerticalCommand cmd) throws NotFoundException {

			Object[] params = cmd.getParams();
			AID agentID = (AID)params[0];
			String newState = (String)params[1];

			if (newState.equals(jade.domain.FIPAAgentManagement.AMSAgentDescription.SUSPENDED)) {
				suspendedAgent(agentID);
			}
			else if(newState.equals(jade.domain.FIPAAgentManagement.AMSAgentDescription.ACTIVE)) {
				resumedAgent(agentID);
			}

		}

		private void handleKillContainer(VerticalCommand cmd) {
			myContainer.shutDown();
		}

		/**
	   Force the creation of an agent on the FrontEnd.
	   Note that the agent to create can have a different owner with respect 
	   to the owner of this "container" --> Its image holding the agent's
	   ownership information must be created now and not in the bornAgent()
	   method. This image is stored in the pendingImages map for later 
	   retrieval (see bornAgent()).
		 */
		private void createAgent(AID agentID, String className, Object[] args, JADEPrincipal ownership, Credentials creds) throws IMTPException {

			BackEndContainer.AgentImage image = myContainer.createAgentImage(agentID);

			// Store the image so that it can be retrieved when the new agent starts
			BackEndContainer.AgentImage previous = (BackEndContainer.AgentImage) pendingImages.put(agentID, image);

			try {
				// Arguments can only be Strings
				String[] sargs = null;
				if (args != null) {
					sargs = new String[args.length];
					for (int i = 0; i < args.length; ++i) {
						sargs[i] = (String) args[i];
					}
				}
				myContainer.createAgentOnFE(agentID.getLocalName(), className, sargs);
			}
			catch (IMTPException imtpe) {
				// Roll back if necessary and forward the exception
				pendingImages.remove(agentID);
				if (previous != null) {
					pendingImages.put(agentID, previous);
				}
				throw imtpe;
			}
			catch (ClassCastException cce) {
				// Roll back if necessary and forward the exception
				pendingImages.remove(agentID);
				if (previous != null) {
					pendingImages.put(agentID, previous);
				}
				throw new IMTPException("Non-String argument");
			}
		}

		private void killAgent(AID agentID) throws IMTPException, NotFoundException {

			if (myContainer.getAgentImage(agentID) != null) {
				String name = agentID.getLocalName();
				myContainer.killAgentOnFE(name);
			}
			else {
				throw new NotFoundException("KillAgent failed to find " + agentID);
			}
		}

		private void changeAgentState(AID agentID, int newState) throws IMTPException, NotFoundException {
			BackEndContainer.AgentImage a = myContainer.getAgentImage(agentID);
			if(a == null)
				throw new NotFoundException("Change-Agent-State failed to find " + agentID);

			if(newState == Agent.AP_SUSPENDED) {
				myContainer.suspendAgentOnFE(agentID.getLocalName());
			}
			else if(newState == Agent.AP_ACTIVE) {
				myContainer.resumeAgentOnFE(agentID.getLocalName());
			}
		}

		private void suspendedAgent(AID name) throws NotFoundException {

		}

		private void resumedAgent(AID name) throws NotFoundException {

		}

		/*private void exitContainer() {

	    // "Kill" all agent images
	    AID[] targets = myContainer.getAgentImages();
	    for(int i = 0; i < targets.length; i++) {
		AID target = targets[i];
		try {
		    // Remove the dead agent from the agent images
		    BackEndContainer.AgentImage image = myContainer.removeAgentImage(target);

		    // Notify the main container through its slice
		    AgentManagementSlice mainSlice = (AgentManagementSlice)getSlice(MAIN_SLICE);

		    try {
			mainSlice.deadAgent(target);
		    }
		    catch(IMTPException imtpe) {
			// Try to get a newer slice and repeat...
			mainSlice = (AgentManagementSlice)getFreshSlice(MAIN_SLICE);
			mainSlice.deadAgent(target);
		    }
		}
		catch (Exception ex) {
		    ex.printStackTrace();
		}
	    }

	    myContainer.shutDown();
	}*/


	} // End of CommandTargetSink class



	/**
       Inner mix-in class for this service: this class receives
       commands from the service <code>Sink</code> and serves them,
       coordinating with remote parts of this service through the
       <code>Service.Slice</code> interface.
	 */
	private class ServiceComponent implements Service.Slice {

		// Implementation of the Service.Slice interface

		public Service getService() {
			return BEAgentManagementService.this;
		}

		public Node getNode() throws ServiceException {
			try {
				return BEAgentManagementService.this.getLocalNode();
			}
			catch(IMTPException imtpe) {
				throw new ServiceException("Problem in contacting the IMTP Manager", imtpe);
			}
		}

		public VerticalCommand serve(HorizontalCommand cmd) {
			VerticalCommand result = null;
			try {
				String cmdName = cmd.getName();
				Object[] params = cmd.getParams();

				if(cmdName.equals(AgentManagementSlice.H_CREATEAGENT)) {
					GenericCommand gCmd = new GenericCommand(AgentManagementSlice.REQUEST_CREATE, AgentManagementSlice.NAME, null);
					AID agentID = (AID)params[0];
					String className = (String)params[1];
					Object[] arguments = (Object[])params[2];
					String ownership = (String)params[3];
					Credentials certs = (Credentials)params[4];
					gCmd.addParam(agentID);
					gCmd.addParam(className);
					gCmd.addParam(arguments);
					gCmd.addParam(ownership);
					gCmd.addParam(certs);
					result = gCmd;
				}
				else if(cmdName.equals(AgentManagementSlice.H_KILLAGENT)) {
					GenericCommand gCmd = new GenericCommand(AgentManagementSlice.REQUEST_KILL, AgentManagementSlice.NAME, null);
					AID agentID = (AID)params[0];
					gCmd.addParam(agentID);

					result = gCmd;
				}
				else if(cmdName.equals(AgentManagementSlice.H_CHANGEAGENTSTATE)) {
					GenericCommand gCmd = new GenericCommand(AgentManagementSlice.REQUEST_STATE_CHANGE, AgentManagementSlice.NAME, null);
					AID agentID = (AID)params[0];
					Integer newState = (Integer)params[1];
					gCmd.addParam(agentID);
					gCmd.addParam(newState);

					result = gCmd;
				}
				else if(cmdName.equals(AgentManagementSlice.H_BORNAGENT)) {
					GenericCommand gCmd = new GenericCommand(AgentManagementSlice.INFORM_CREATED, AgentManagementSlice.NAME, null);
					AID agentID = (AID)params[0];
					ContainerID cid = (ContainerID)params[1];
					String ownership = (String) params[2];
					gCmd.addParam(agentID);
					gCmd.addParam(cid);
					gCmd.addParam(ownership);

					result = gCmd;
				}
				else if(cmdName.equals(AgentManagementSlice.H_DEADAGENT)) {
					GenericCommand gCmd = new GenericCommand(AgentManagementSlice.INFORM_KILLED, AgentManagementSlice.NAME, null);
					AID agentID = (AID)params[0];
					gCmd.addParam(agentID);

					result = gCmd;
				}
				else if(cmdName.equals(AgentManagementSlice.H_SUSPENDEDAGENT)) {
					GenericCommand gCmd = new GenericCommand(AgentManagementSlice.INFORM_STATE_CHANGED, AgentManagementSlice.NAME, null);
					AID agentID = (AID)params[0];
					gCmd.addParam(agentID);
					gCmd.addParam(jade.domain.FIPAAgentManagement.AMSAgentDescription.SUSPENDED);

					result = gCmd;
				}
				else if(cmdName.equals(AgentManagementSlice.H_RESUMEDAGENT)) {
					GenericCommand gCmd = new GenericCommand(AgentManagementSlice.INFORM_STATE_CHANGED, AgentManagementSlice.NAME, null);
					AID agentID = (AID)params[0];
					gCmd.addParam(agentID);
					gCmd.addParam(jade.domain.FIPAAgentManagement.AMSAgentDescription.ACTIVE);

					result = gCmd;
				}
				else if(cmdName.equals(AgentManagementSlice.H_EXITCONTAINER)) {
					GenericCommand gCmd = new GenericCommand(AgentManagementSlice.KILL_CONTAINER, AgentManagementSlice.NAME, null);

					result = gCmd;
				}
			}
			catch(Throwable t) {
				cmd.setReturnValue(t);
			}

			return result;
		}

	} // End of AgentManagementSlice class



	// The concrete agent container, providing access to LADT, etc.
	private BackEndContainer myContainer;

	// The local slice for this service
	private final ServiceComponent localSlice = new ServiceComponent();

	// The command sink, source side
	private final CommandSourceSink senderSink = new CommandSourceSink();

	// The command sink, target side
	private final CommandTargetSink receiverSink = new CommandTargetSink();

	// Service specific data
	private Map pendingImages = new HashMap(1);

}
