//----------------------------------------------------------------------------
// Copyright (C) 2011  Ingrid Nunes
// 
// 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; either
// version 2.1 of the License, or (at your option) any later version.
// 
// 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
// 
// To contact the authors:
// http://inf.ufrgs.br/prosoft/bdi4jade/
//
//----------------------------------------------------------------------------

package bdi4jade.core;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import bdi4jade.belief.Belief;
import bdi4jade.core.GoalUpdateSet.GoalDescription;
import bdi4jade.event.GoalEvent;
import bdi4jade.event.GoalListener;
import bdi4jade.goal.Goal;
import bdi4jade.goal.GoalStatus;
import bdi4jade.goal.Softgoal;
import bdi4jade.message.BDIAgentMsgReceiver;
import bdi4jade.reasoning.AgentBeliefRevisionStrategy;
import bdi4jade.reasoning.AgentDeliberationFunction;
import bdi4jade.reasoning.AgentOptionGenerationFunction;
import bdi4jade.reasoning.AgentPlanSelectionStrategy;
import bdi4jade.reasoning.DefaultAgentBeliefRevisionStrategy;
import bdi4jade.reasoning.DefaultAgentDeliberationFunction;
import bdi4jade.reasoning.DefaultAgentOptionGenerationFunction;
import bdi4jade.reasoning.DefaultAgentPlanSelectionStrategy;
import bdi4jade.util.ReflectionUtils;
import jade.core.Agent;
import jade.core.behaviours.CyclicBehaviour;
import jade.lang.acl.ACLMessage;
import jade.proto.states.MsgReceiver;

/**
 * This class is an abstract implementation of the {@link BDIAgent} interface.
 * It is an extension of {@link Agent}. It also has a set of {@link Capability}
 * - an agent is an aggregation of capabilities, and a {@link MsgReceiver}
 * behavior to receive all messages that the agent current plans can process.
 * 
 * @author Ingrid Nunes
 */
public abstract class AbstractBDIAgent extends Agent implements BDIAgent {

	/**
	 * This class is a {@link CyclicBehaviour} that runs during all the
	 * {@link BDIAgent} life in order to provide the reasoning engine.
	 * 
	 * @author Ingrid Nunes
	 */
	class BDIInterpreter extends CyclicBehaviour {

		private static final long serialVersionUID = -6991759791322598475L;

		private BDIInterpreter(BDIAgent bdiAgent) {
			super((Agent) bdiAgent);
		}

		/**
		 * This method is a variation of the BDI-interpreter cycle of the BDI
		 * architecture. It first reviews the beliefs of this agent, by invoking
		 * the {@link AgentBeliefRevisionStrategy#reviewBeliefs()} method.
		 * 
		 * After it removes from the intention set the ones that are finished,
		 * i.e. associated with goals with status achieved, no longer desired or
		 * unachievable, and notifies goal listeners about this event. This is
		 * performed using the {@link #processIntentions(Collection)} method.
		 * 
		 * Then, it generate a an updated set of goals, dropping existing ones
		 * that are no longer desired and also creating new ones. This updated
		 * set of goals is given by the
		 * {@link AgentOptionGenerationFunction#generateGoals(GoalUpdateSet, Map)}
		 * method.
		 * 
		 * Finally, from the set of current goals, they are now filtered, by
		 * invoking the {@link AgentDeliberationFunction#filter(Set, Map)}
		 * method, to select the current agent intentions. The non-selected
		 * goals will be set to wait ({@link Intention#doWait()}) and the
		 * selected ones will be tried to be achieved (
		 * {@link Intention#tryToAchive()}).
		 * 
		 * @see jade.core.behaviours.Behaviour#action()
		 */
		@Override
		public void action() {
			log.trace("Beginning BDI-interpreter cycle.");

			log.trace("Reviewing beliefs.");
			beliefRevisionStrategy.reviewBeliefs();

			synchronized (allIntentions) {
				// Removing finished goals and generate appropriate goal events
				GoalUpdateSet agentGoalUpdateSet = processIntentions(agentIntentions);
				Map<Capability, GoalUpdateSet> capabilityGoalUpdateSets = new HashMap<>();
				for (Capability capability : capabilities) {
					GoalUpdateSet capabilityGoalUpdateSet = processIntentions(capability.getIntentions());
					capabilityGoalUpdateSets.put(capability, capabilityGoalUpdateSet);
				}

				// Generating new goals and choosing goals to drop
				optionGenerationFunction.generateGoals(agentGoalUpdateSet, capabilityGoalUpdateSets);

				// Adding generated goals
				for (GoalDescription goal : agentGoalUpdateSet.getGeneratedGoals()) {
					try {
						Intention intention = addIntention(goal.getDispatcher(), goal.getGoal(), goal.getListener());
						if (intention != null)
							agentGoalUpdateSet.addIntention(intention);
					} catch (IllegalAccessException exc) {
						log.error(exc);
					}
				}
				for (GoalUpdateSet goalUpdateSet : capabilityGoalUpdateSets.values()) {
					for (GoalDescription goal : goalUpdateSet.getGeneratedGoals()) {
						try {
							Intention intention = addIntention(goal.getDispatcher(), goal.getGoal(),
									goal.getListener());
							if (intention != null)
								goalUpdateSet.addIntention(intention);
						} catch (IllegalAccessException exc) {
							log.error(exc);
						}
					}
				}

				// Removing dropped goals
				for (GoalDescription goal : agentGoalUpdateSet.getDroppedGoals()) {
					goal.getIntention().noLongerDesire();
					fireGoalEvent(goal.getIntention());
					agentIntentions.remove(goal.getIntention());
					allIntentions.remove(goal.getGoal());
					agentGoalUpdateSet.removeIntention(goal);
				}
				for (GoalUpdateSet goalUpdateSet : capabilityGoalUpdateSets.values()) {
					for (GoalDescription goal : goalUpdateSet.getDroppedGoals()) {
						goal.getIntention().noLongerDesire();
						fireGoalEvent(goal.getIntention());
						goal.getDispatcher().removeIntention(goal.getIntention());
						allIntentions.remove(goal.getGoal());
						goalUpdateSet.removeIntention(goal);
					}
				}

				// Filtering options
				Map<Capability, Set<GoalDescription>> capabilityGoals = new HashMap<>();
				for (Capability capability : capabilityGoalUpdateSets.keySet()) {
					capabilityGoals.put(capability, capabilityGoalUpdateSets.get(capability).getCurrentGoals());
				}
				Set<Goal> selectedGoals = deliberationFunction.filter(agentGoalUpdateSet.getCurrentGoals(),
						capabilityGoals);

				log.trace("Selected goals to be intentions: " + selectedGoals.size());
				for (Intention intention : allIntentions.values()) {
					if (selectedGoals.contains(intention.getGoal())) {
						intention.tryToAchive();
					} else {
						intention.doWait();
					}
				}

				if (allIntentions.isEmpty()) {
					log.trace("No goals or intentions: blocking cycle.");
					this.block();
				}
			}

			log.trace("BDI-interpreter cycle finished.");
		}

		/**
		 * Processes all intentions of the given collection. Intentions
		 * associated with goals that finished are removed and goal listeners (
		 * {@link GoalListener}) are notified. Goal listeners are also notified
		 * if a plan failed while trying to achieve a goal (intentions with
		 * {@link GoalStatus#PLAN_FAILED}).
		 * 
		 * @param intentions
		 *            the collection of intentions to be processed.
		 * @return the {@link GoalUpdateSet} with current goals initialized with
		 *         current intentions.
		 */
		private GoalUpdateSet processIntentions(Collection<Intention> intentions) {
			GoalUpdateSet goalUpdateSet = new GoalUpdateSet();
			List<Intention> intentionsList = new ArrayList<>(intentions);
			for (Intention intention : intentionsList) {
				GoalStatus status = intention.getStatus();
				if (status.isFinished()) {
					fireGoalEvent(intention);
					intentions.remove(intention);
					allIntentions.remove(intention.getGoal());
				} else {
					if (GoalStatus.PLAN_FAILED.equals(status)) {
						fireGoalEvent(intention);
					}
					goalUpdateSet.addIntention(intention);
				}
			}
			return goalUpdateSet;
		}

	}

	private static final Log log = LogFactory.getLog(AbstractBDIAgent.class);
	private static final long serialVersionUID = -841774495336214256L;

	private final Collection<Intention> agentIntentions;
	private final Set<Capability> aggregatedCapabilities;
	private final Map<Goal, Intention> allIntentions;
	private final BDIInterpreter bdiInterpreter;
	private AgentBeliefRevisionStrategy beliefRevisionStrategy;
	private Set<Capability> capabilities;
	private AgentDeliberationFunction deliberationFunction;
	protected final List<GoalListener> goalListeners;
	private AgentOptionGenerationFunction optionGenerationFunction;
	private AgentPlanSelectionStrategy planSelectionStrategy;
	private Map<Class<? extends Capability>, Set<Capability>> restrictedAccessOwnersMap;
	private final Set<Softgoal> softgoals;

	/**
	 * Default constructor.
	 */
	public AbstractBDIAgent() {
		this.bdiInterpreter = new BDIInterpreter(this);
		this.capabilities = new HashSet<>();
		this.restrictedAccessOwnersMap = new HashMap<>();
		this.allIntentions = new HashMap<>();
		this.aggregatedCapabilities = new HashSet<>();
		this.agentIntentions = new LinkedList<>();
		this.softgoals = new HashSet<>();
		this.goalListeners = new LinkedList<>();

		// Initializing reasoning strategies
		setBeliefRevisionStrategy(null);
		setOptionGenerationFunction(null);
		setDeliberationFunction(null);
		setPlanSelectionStrategy(null);
	}

	/**
	 * Adds a capability to this agent.
	 * 
	 * @param capability
	 *            capability to be added.
	 */
	void addCapability(Capability capability) {
		synchronized (aggregatedCapabilities) {
			this.aggregatedCapabilities.add(capability);
			resetAllCapabilities();
			computeGoalOwnersMap();
		}
	}

	/**
	 * @see bdi4jade.core.BDIAgent#addGoal(bdi4jade.core.Capability,
	 *      bdi4jade.goal.Goal)
	 */
	@Override
	public final boolean addGoal(Capability dispatcher, Goal goal) {
		try {
			addIntention(dispatcher, goal, null);
			return true;
		} catch (IllegalAccessException exc) {
			log.error(exc);
			return false;
		}
	}

	/**
	 * @see bdi4jade.core.BDIAgent#addGoal(bdi4jade.core.Capability,
	 *      bdi4jade.goal.Goal, bdi4jade.event.GoalListener)
	 */
	@Override
	public final boolean addGoal(Capability dispatcher, Goal goal, GoalListener goalListener) {
		try {
			addIntention(dispatcher, goal, goalListener);
			return true;
		} catch (IllegalAccessException exc) {
			log.error(exc);
			return false;
		}
	}

	/**
	 * @see bdi4jade.core.BDIAgent#addGoal(bdi4jade.goal.Goal)
	 */
	@Override
	public final boolean addGoal(Goal goal) {
		try {
			addIntention(null, goal, null);
			return true;
		} catch (IllegalAccessException exc) {
			log.error(exc);
			return false;
		}
	}

	/**
	 * @see bdi4jade.core.BDIAgent#addGoal(bdi4jade.goal.Goal,
	 *      bdi4jade.event.GoalListener)
	 */
	@Override
	public final boolean addGoal(Goal goal, GoalListener goalListener) {
		try {
			addIntention(null, goal, goalListener);
			return true;
		} catch (IllegalAccessException exc) {
			log.error(exc);
			return false;
		}
	}

	/**
	 * @see bdi4jade.core.BDIAgent#addGoalListener(bdi4jade.goal.Goal,
	 *      bdi4jade.event.GoalListener)
	 */
	@Override
	public boolean addGoalListener(Goal goal, GoalListener goalListener) {
		synchronized (allIntentions) {
			Intention intention = this.allIntentions.get(goal);
			if (intention != null) {
				intention.addGoalListener(goalListener);
				return true;
			}
			return false;
		}
	}

	/**
	 * @see bdi4jade.core.BDIAgent#addGoalListener(bdi4jade.event.GoalListener)
	 */
	@Override
	public final void addGoalListener(GoalListener goalListener) {
		synchronized (goalListeners) {
			goalListeners.add(goalListener);
		}
	}

	/**
	 * Adds a new goal to this agent to be achieved by creating an intention. It
	 * also may add a listener to observe events related to this goal.
	 * 
	 * @param dispatcher
	 *            the capability that dispatched this goal.
	 * @param goal
	 *            the goal to be achieved.
	 * @param goalListener
	 *            the listener to be notified.
	 */
	private final Intention addIntention(Capability dispatcher, Goal goal, GoalListener goalListener)
			throws IllegalAccessException {
		Intention intention = null;
		synchronized (allIntentions) {
			intention = allIntentions.get(goal);
			if (intention != null) {
				log.info("This agent already has goal: " + goal);
				if (goalListener != null) {
					intention.addGoalListener(goalListener);
				}
				return null;
			}

			intention = new Intention(goal, this, dispatcher);
			this.allIntentions.put(goal, intention);
			if (dispatcher == null) {
				agentIntentions.add(intention);
			} else {
				dispatcher.addIntention(intention);
			}
			if (goalListener != null) {
				intention.addGoalListener(goalListener);
			}
		}
		fireGoalEvent(new GoalEvent(goal));
		restart();
		return intention;
	}

	/**
	 * @see bdi4jade.core.BDIAgent#addSoftgoal(bdi4jade.goal.Softgoal)
	 */
	@Override
	public final void addSoftgoal(Softgoal softgoal) {
		synchronized (softgoals) {
			this.softgoals.add(softgoal);
		}
	}

	/**
	 * @see bdi4jade.core.BDIAgent#canHandle(jade.lang.acl.ACLMessage)
	 */
	@Override
	public boolean canHandle(ACLMessage msg) {
		synchronized (aggregatedCapabilities) {
			for (Capability capability : aggregatedCapabilities) {
				if (capability.canHandle(msg)) {
					return true;
				}
			}
			return false;
		}
	}

	private final void computeGoalOwnersMap() {
		this.restrictedAccessOwnersMap = new HashMap<>();
		for (Capability capability : aggregatedCapabilities) {
			ReflectionUtils.addGoalOwner(restrictedAccessOwnersMap, capability);
		}
	}

	/**
	 * @see bdi4jade.core.BDIAgent#dropGoal(bdi4jade.goal.Goal)
	 */
	@Override
	public final void dropGoal(Goal goal) {
		synchronized (allIntentions) {
			Intention intention = allIntentions.get(goal);
			if (intention != null) {
				intention.noLongerDesire();
			}
		}
	}

	/**
	 * @see bdi4jade.core.BDIAgent#dropSoftoal(bdi4jade.goal.Softgoal)
	 */
	@Override
	public final void dropSoftoal(Softgoal softgoal) {
		synchronized (softgoals) {
			this.softgoals.remove(softgoal);
		}
	}

	/**
	 * Notifies all listeners, if any, about a goal event.
	 * 
	 * @param goalEvent
	 *            the event to notify.
	 */
	private final void fireGoalEvent(GoalEvent goalEvent) {
		synchronized (goalListeners) {
			for (GoalListener goalListener : goalListeners) {
				goalListener.goalPerformed(goalEvent);
			}
		}
	}

	/**
	 * Creates a goal event given an intention, and notifies all listeners, if
	 * any, about a goal event.
	 * 
	 * @param intention
	 *            the intention used to create the goal event.
	 */
	private final void fireGoalEvent(Intention intention) {
		Goal goal = intention.getGoal();
		GoalStatus status = intention.getStatus();
		log.debug("Goal: " + goal.getClass().getSimpleName() + " (" + status + ") - " + goal);

		GoalEvent goalEvent = new GoalEvent(goal, status);
		synchronized (goalListeners) {
			for (GoalListener goalListener : goalListeners) {
				goalListener.goalPerformed(goalEvent);
			}
			for (GoalListener goalListener : intention.getGoalListeners()) {
				goalListener.goalPerformed(goalEvent);
			}
		}
	}

	/**
	 * @see BDIAgent#getAllCapabilities()
	 */
	@Override
	public final Collection<Capability> getAllCapabilities() {
		synchronized (aggregatedCapabilities) {
			return capabilities;
		}
	}

	/**
	 * @see bdi4jade.core.BDIAgent#getBeliefRevisionStrategy()
	 */
	@Override
	public final AgentBeliefRevisionStrategy getBeliefRevisionStrategy() {
		return beliefRevisionStrategy;
	}

	/**
	 * @see bdi4jade.core.BDIAgent#getBeliefs()
	 */
	@Override
	public final Collection<Belief<?, ?>> getBeliefs() {
		synchronized (aggregatedCapabilities) {
			Collection<Belief<?, ?>> beliefs = new LinkedList<>();
			for (Capability capability : capabilities) {
				beliefs.addAll(capability.getBeliefBase().getBeliefs());
			}
			return beliefs;
		}
	}

	/**
	 * @see bdi4jade.core.BDIAgent#getCapabilities()
	 */
	public final Set<Capability> getCapabilities() {
		synchronized (aggregatedCapabilities) {
			return aggregatedCapabilities;
		}
	}

	/**
	 * @see bdi4jade.core.BDIAgent#getDeliberationFunction()
	 */
	@Override
	public final AgentDeliberationFunction getDeliberationFunction() {
		return deliberationFunction;
	}

	/**
	 * @see bdi4jade.core.BDIAgent#getGoalListeners()
	 */
	@Override
	public final List<GoalListener> getGoalListeners() {
		return goalListeners;
	}

	/**
	 * Returns the capability instances that owns a dispatched goal, considering
	 * the aggregated capabilities of this agent.
	 * 
	 * If this method returns an empty set, it means that this agent cannot add
	 * a goal without the scope of a dispatcher that has access to it.
	 * 
	 * @param owner
	 *            the capability class that is the goal owner.
	 * @param internal
	 *            a boolean indicated whether the goal is internal. It is true
	 *            if the goal is internal, false otherwise.
	 * @return the capability instances related to this capability that owns the
	 *         goal, or an empty set if the agent cannot add this goal.
	 */
	protected final Set<Capability> getGoalOwner(Class<? extends Capability> owner, boolean internal) {
		if (internal) {
			return new HashSet<Capability>();
		} else {
			Set<Capability> restrictedAccessOwners = restrictedAccessOwnersMap.get(owner);
			return restrictedAccessOwners == null ? new HashSet<Capability>() : restrictedAccessOwners;
		}
	}

	/**
	 * @see bdi4jade.core.BDIAgent#getGoals()
	 */
	@Override
	public final Set<Goal> getGoals() {
		synchronized (allIntentions) {
			return allIntentions.keySet();
		}
	}

	/**
	 * @see bdi4jade.core.BDIAgent#getIntentions()
	 */
	@Override
	public final Set<Intention> getIntentions() {
		synchronized (allIntentions) {
			Set<Intention> activeIntentions = new HashSet<Intention>();
			for (Intention intention : activeIntentions) {
				if (!GoalStatus.WAITING.equals(intention.getStatus()))
					activeIntentions.add(intention);
			}
			return activeIntentions;
		}
	}

	/**
	 * @see bdi4jade.core.BDIAgent#getOptionGenerationFunction()
	 */
	@Override
	public final AgentOptionGenerationFunction getOptionGenerationFunction() {
		return optionGenerationFunction;
	}

	/**
	 * @see bdi4jade.core.BDIAgent#getPlanSelectionStrategy()
	 */
	@Override
	public final AgentPlanSelectionStrategy getPlanSelectionStrategy() {
		return planSelectionStrategy;
	}

	/**
	 * @see bdi4jade.core.BDIAgent#getSoftgoals()
	 */
	@Override
	public final Set<Softgoal> getSoftgoals() {
		synchronized (softgoals) {
			return this.softgoals;
		}
	}

	/**
	 * @see bdi4jade.core.BDIAgent#hasGoal(bdi4jade.goal.Goal)
	 */
	@Override
	public boolean hasGoal(Goal goal) {
		return allIntentions.get(goal) != null;
	}

	/**
	 * This method initializes the BDI agent. It is invoked by the
	 * {@link #setup()} method. This is an empty method that should be overriden
	 * by subclasses.
	 */
	protected void init() {

	}

	/**
	 * Removes a capability from this agent.
	 * 
	 * @param capability
	 *            capability to be removed.
	 * 
	 * @return true if the capability exists and was removed, false otherwise.
	 */
	boolean removeCapability(Capability capability) {
		synchronized (aggregatedCapabilities) {
			boolean removed = this.aggregatedCapabilities.remove(capability);
			if (removed) {
				resetAllCapabilities();
				computeGoalOwnersMap();
			}
			return removed;
		}
	}

	/**
	 * @see bdi4jade.core.BDIAgent#removeGoalListener(bdi4jade.event.GoalListener)
	 */
	@Override
	public final void removeGoalListener(GoalListener goalListener) {
		synchronized (goalListeners) {
			goalListeners.remove(goalListener);
		}
	}

	final void resetAllCapabilities() {
		synchronized (aggregatedCapabilities) {
			Set<Capability> oldCapabilities = this.capabilities;
			Set<Capability> allCapabilities = new HashSet<>();
			for (Capability capability : aggregatedCapabilities) {
				allCapabilities.add(capability);
				capability.addRelatedCapabilities(allCapabilities);
			}
			this.capabilities = allCapabilities;
			log.debug("Capabilities: " + this.capabilities);

			Set<Capability> removedCapabilities = new HashSet<>(oldCapabilities);
			removedCapabilities.removeAll(allCapabilities);
			for (Capability capability : removedCapabilities) {
				capability.setMyAgent(null);
			}

			Set<Capability> addedCapabilities = new HashSet<>(allCapabilities);
			addedCapabilities.removeAll(oldCapabilities);
			for (Capability capability : addedCapabilities) {
				if (capability.getMyAgent() != null) {
					throw new IllegalArgumentException(
							"Capability already binded to another agent: " + capability.getFullId());
				}
				capability.setMyAgent(this);
			}
		}
	}

	/**
	 * @see BDIAgent#restart()
	 */
	@Override
	public final void restart() {
		this.bdiInterpreter.restart();
	}

	/**
	 * Sets the belief revision strategy of this agent.
	 * 
	 * @param beliefRevisionStrategy
	 *            the beliefRevisionStrategy to set.
	 */
	public final void setBeliefRevisionStrategy(AgentBeliefRevisionStrategy beliefRevisionStrategy) {
		if (beliefRevisionStrategy == null) {
			this.beliefRevisionStrategy = new DefaultAgentBeliefRevisionStrategy();
		} else {
			this.beliefRevisionStrategy.setAgent(null);
			this.beliefRevisionStrategy = beliefRevisionStrategy;
		}
		this.beliefRevisionStrategy.setAgent(this);
	}

	/**
	 * Sets the deliberation function of this agent.
	 * 
	 * @param deliberationFunction
	 *            the deliberationFunction to set.
	 */
	public final void setDeliberationFunction(AgentDeliberationFunction deliberationFunction) {
		if (deliberationFunction == null) {
			this.deliberationFunction = new DefaultAgentDeliberationFunction();
		} else {
			this.deliberationFunction.setAgent(null);
			this.deliberationFunction = deliberationFunction;
		}
		this.deliberationFunction.setAgent(this);
	}

	/**
	 * Sets the option generation function of this agent.
	 * 
	 * @param optionGenerationFunction
	 *            the optionGenerationFunction to set.
	 */
	public final void setOptionGenerationFunction(AgentOptionGenerationFunction optionGenerationFunction) {
		if (optionGenerationFunction == null) {
			this.optionGenerationFunction = new DefaultAgentOptionGenerationFunction();
		} else {
			this.optionGenerationFunction.setAgent(null);
			this.optionGenerationFunction = optionGenerationFunction;
		}
		this.optionGenerationFunction.setAgent(this);
	}

	/**
	 * Sets the plan selection strategy of this agent.
	 * 
	 * @param planSelectionStrategy
	 *            the planSelectionStrategy to set.
	 */
	public final void setPlanSelectionStrategy(AgentPlanSelectionStrategy planSelectionStrategy) {
		if (planSelectionStrategy == null) {
			this.planSelectionStrategy = new DefaultAgentPlanSelectionStrategy();
		} else {
			this.planSelectionStrategy.setAgent(null);
			this.planSelectionStrategy = planSelectionStrategy;
		}
		this.planSelectionStrategy.setAgent(this);
	}

	/**
	 * Initializes the BDI agent. It adds the behavior to handle message
	 * received and can be processed by capabilities and the
	 * {@link BDIInterpreter} behavior as well. It invokes the {@link #init()}
	 * method, so that customized initializations can be perfomed by subclasses.
	 * 
	 * @see jade.core.Agent#setup()
	 */
	@Override
	protected final void setup() {
		this.addBehaviour(new BDIAgentMsgReceiver(this));
		this.addBehaviour(bdiInterpreter);
		init();
	}

	/**
	 * Removes all capabilities of this agent, before it stops its execution.
	 * 
	 * @see jade.core.Agent#takeDown()
	 */
	@Override
	protected void takeDown() {
		synchronized (aggregatedCapabilities) {
			Iterator<Capability> iterator = aggregatedCapabilities.iterator();
			while (iterator.hasNext()) {
				removeCapability(iterator.next());
			}
		}
	}

}
