// ----------------------------------------------------------------------------
// Copyright (C) 2008 Francisco Grimaldo, Miguel Lozano, Fernando Barber
// 
// 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:
// francisco.grimaldo@uv.es, miguel.lozano@uv.es, fernando.barber@uv.es
// http://www.uv.es/grimo
// http://grev.uv.es
//
//----------------------------------------------------------------------------

package jmadem;

import jason.NoValueException;
import jason.architecture.AgArch;
import jason.asSemantics.Event;
import jason.asSemantics.Intention;
import jason.asSemantics.Message;
import jason.asSyntax.ASSyntax;
import jason.asSyntax.Atom;
import jason.asSyntax.ListTerm;
import jason.asSyntax.Literal;
import jason.asSyntax.NumberTerm;
import jason.asSyntax.Term;
import jason.asSyntax.ListTermImpl;
import jason.asSyntax.NumberTermImpl;
import jason.asSyntax.Trigger;
import jason.asSyntax.VarTerm;
import jason.asSyntax.Trigger.TEOperator;
import jason.asSyntax.Trigger.TEType;

import java.util.Set;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Iterator;
import java.util.Random;
import java.util.TimerTask;
import java.util.logging.Logger;

/**
 * Types of Social Welfares available to MADeM agents.
 */
enum Cuf {UTILITARIAN, EGALITARIAN, ELITIST, NASH};

/**
 * Types of multimodalities available to MADeM agents.
 */
enum Multimodality {COMPETITIVE, AGGREGATE};


/**
  * Basic architecture for a Jason agent performing MADeM decisions.
  *
  * @author Francisco Grimaldo
  */
public class MADeMAgArch extends AgArch 
{
	/** Class logger */
	private Logger logger = Logger.getLogger(MADeMAgArch.class.getName());
	
	/** Random numbers generator */
	private Random random = new Random();;
	
	/** Filter functions repository */
	private ConcurrentMap<Atom, FilterFunctionInt> filterFunctions = new ConcurrentHashMap<Atom, FilterFunctionInt>();
	
	/** Utility functions repository */
	private ConcurrentMap<Atom, UtilityFunctionInt> utilityFunctions = new ConcurrentHashMap<Atom, UtilityFunctionInt>();
	
	/**
	 * Utility weights representing the importance given to each point of view
	 * in the multi-modal agent decision making.
	 */
	private ConcurrentMap<Atom, Float> utilityWeights = new ConcurrentHashMap<Atom, Float>();
	
	/**
	 * Personal weights representing the internal attitude of the agent towards
	 * other individuals.
	 */
	private ConcurrentMap<Atom, Float> personalWeights = new ConcurrentHashMap<Atom, Float>();
	
	/** Social welfare considered by the agent */
	private Cuf welfare;
	
	/** Type of multimodality used in the Winner Determination Problem */
	private Multimodality multimodality;

	/** 
	 * Maximum waiting time for bids (in milliseconds). After timeout, the agent 
	 * decides according to the bids it has collected and its own preferences.
	 */
	private int tout;

	/** 
	 * Pool of threads to schedule timeouts
	 */
	private ScheduledExecutorService timeoutScheduler;
	
	/**
	 * Data managed by MADeM for every decision being performed: id_decision, agents involved, 
	 * allocations, utility functions considered, bids...
	 */
	private ConcurrentMap<Integer, DecisionData> mademData = new ConcurrentHashMap<Integer, DecisionData>();
	
	/**
	 * Flag indicating whether to remove the data managed by MADeM after the winner determination 
	 * problem.
	 */
	private boolean automaticRemoveOfMademData;

	/**
	 * Storage devoted to hold utility values while they are calculated. Once all the values are
	 * ready, they are sent to the agent in charge of the MADeM decision (i.e. the auctioneer)
	 */
	private ConcurrentMap<ListTerm, List<Term>> utilityValues = new ConcurrentHashMap<ListTerm, List<Term>>();

	/**
	  * Constructor of the MADeMAgArch class
	  */
	public MADeMAgArch()
	{
		// Init logger 
		logger = Logger.getLogger(MADeMAgArch.class.getName() + "." + getAgName());

		// Default social welfare
		this.welfare = Cuf.ELITIST; 
		
		// Default multimodality
		this.multimodality = Multimodality.COMPETITIVE; 

		// Default timeout
		this.tout = 5000;	
		
		// Default pool of threads for managing the timeouts
		timeoutScheduler = Executors.newScheduledThreadPool(5);
		
		// Default flag about automatically removing MADeM data after the WDP
		this.automaticRemoveOfMademData = true;	
	}

	/**
	  * Sets the value of the flag indicating whether to remove the data managed by MADeM after 
	  * the winner determination problem.
	  *
	  * @param	automaticRemoveOfMademData	<tt>true</tt> if the MADeM data has to be removed after the Winner Determination Problem, <tt>false</tt> otherwise
	  */
	public void setAutomaticRemoveOfMademData(boolean automaticRemoveOfMademData) {
		this.automaticRemoveOfMademData = automaticRemoveOfMademData;
	}

	/**
	  * Gets the value of the automatic remove of MADeM data flag.  
	  *
	  * @return <tt>true</tt> the agent automatically removes all the data related to a MADeM decision after it has been made, <tt>false</tt> otherwise 
	  */
	public boolean isAutomaticRemoveOfMademData() {
		return automaticRemoveOfMademData;
	}

	/**
	  * Registers a new filter function in the MADeM agent.
	  *
	  * @param	fFuncName	name of the filter function to be added
	  * @param	fFuncClass	name of the class implementing the filter function
	  * @return	<tt>true</tt> if the filter function was correctly added, <tt>false</tt> otherwise
	  */
	public boolean addFilterFunction(Atom fFuncName, String fFuncClass)
	{
		FilterFunctionInt f = null;
		
		try
		{
			f = (FilterFunctionInt) (Class.forName(fFuncClass).newInstance());
		}
		catch (Exception e)
		{
			e.printStackTrace();
			return false;
		}
		
		if (filterFunctions.put(fFuncName, f) != null)
			logger.fine("Filter function \"" + fFuncName + "\" has been updated.");
		else
			logger.fine("Filter function \"" + fFuncName + "\" has been registered.");

		return true;
	}

	/**
	  * Removes a filter function previously registered in the MADeM agent.  
	  *
	  * @param	fFuncName	name of the filter function to be removed
	  * @return	<tt>true</tt> if the filter function was correctly removed, <tt>false</tt> otherwise
	  */
	public boolean removeFilterFunction(Atom fFuncName)
	{
		return (filterFunctions.remove(fFuncName) == null);
	}
	
	/**
	  * Gets the names of the filter functions previously registered in the MADeM agent.  
	  *
	  * @return the filter function names in the form of a <tt>Set</tt> 
	  */
	public Set<Atom> getFilterFunctionNames()
	{
		return filterFunctions.keySet();
	}

	/**
	  * Computes if an allocation should be filtered out according to the specified filter functions.  
	  *
	  * @param	allocation	allocation being evaluated
	  * @param	fFuncs	names of filter functions being considered
	  * 
	  * @return	<tt>true</tt> if the allocation has to be filtered out, <tt>false</tt> otherwise
	  */
	public boolean doFilter(Term allocation, ListTerm fFuncs)
	{
		Atom fFuncName;
		boolean valid = true;
		
		Iterator<Term> it_fFuncs = fFuncs.iterator();
		while (valid && it_fFuncs.hasNext())
		{
			fFuncName = (Atom) it_fFuncs.next();
			FilterFunctionInt filterFunction = filterFunctions.get(fFuncName);
			try
			{	
	            logger.fine("Invoking the filter function named " + fFuncName);
	            valid = !filterFunction.doFilter(allocation, this);
			}
			catch (Exception e)
			{
				logger.warning("Error invoking the filter function named " + fFuncName + ", it seems not to be registered");
				valid = false;
			}
		}
		
		return valid;		
	}	
	
	/**
	  * Registers a new utility function in the MADeM agent.
	  *
	  * @param	uFuncName	name of the utility function to be added
	  * @param	uFuncClass	name of the class implementing the utility function
	  * @return	<tt>true</tt> if the utility function was correctly added, <tt>false</tt> otherwise
	  */
	public boolean addUtilityFunction(Atom uFuncName, String uFuncClass)
	{
		UtilityFunctionInt u = null;
		
		try
		{
			u = (UtilityFunctionInt) (Class.forName(uFuncClass).newInstance());
		}
		catch (Exception e)
		{
			e.printStackTrace();
			return false;
		}
		
		if (utilityFunctions.put(uFuncName, u) != null)
			logger.fine("Utility function \"" + uFuncName + "\" has been updated.");
		else
			logger.fine("Utility function \"" + uFuncName + "\" has been registered.");

		return true;
	}

	/**
	  * Removes a utility function previously registered in the MADeM agent.  
	  *
	  * @param	uFuncName	name of the utility function to be removed
	  * @return	<tt>true</tt> if the utility function was correctly removed, <tt>false</tt> otherwise
	  */
	public boolean removeUtilityFunction(Atom uFuncName)
	{
		return (utilityFunctions.remove(uFuncName) == null);
	}

	/**
	  * Gets the names of the utility functions previously registered in the MADeM agent.  
	  *
	  * @return the utility function names in the form of a <tt>Set</tt> 
	  */
	public Set<Atom> getUtilityFunctionNames()
	{
		return utilityFunctions.keySet();
	}

	/**
	  * Starts a MADeM decision using the internal parameters of the agent.  
	  *
	  * @param	ags					names of the agents involved in the decision
	  * @param	allocs				allocations being considered as solutions for the decision problem
	  * @param	uFuncs				names of the different utility functions to pay attention to 
	  * @param	multipleSolutions	ask for all possible winners or just one randomly chosen 
	  * @param	returnWelfare		whether to return the social welfare of winner allocations 
	  *
	  * @return	the identifier of the MADeM decision or <tt>-1</tt> when any error occurs 
	  */
	public int startMADeMDecision(ListTerm ags, ListTerm allocs, ListTerm uFuncs, boolean multipleSolutions, boolean returnWelfare)
	{
		return startMADeMDecision(ags, allocs, uFuncs, this.personalWeights, this.utilityWeights, this.welfare, this.multimodality, this.tout, multipleSolutions, returnWelfare);
	}

	/**
	  * Starts a MADeM decision by specifying all its parameters.  
	  *
	  * @param	ags					names of the agents involved in the decision
	  * @param	allocs				allocations being considered as solutions for the decision problem
	  * @param	uFuncs				names of the different utility functions to pay attention to 
	  * @param	pW					personal weights to be applied over external preferences
	  * @param	uW					utility weights in the multi-modal decision
	  * @param	sw					type of social welfare being considered 
	  * @param	m					type of multimodality used in the Winner Determination Problem
	  * @param	tout				timeout for getting bids (in milliseconds)
	  * @param	multipleSolutions	ask for all possible winners or just one randomly chosen 
	  * @param	returnWelfare		whether to return the social welfare of winner allocations 
	  *
	  * @return	the identifier of the MADeM decision or <tt>-1</tt> when any error occurs 
	  */
	public int startMADeMDecision(ListTerm ags, ListTerm allocs, ListTerm uFuncs,
								  ConcurrentMap<Atom, Float> pW, ConcurrentMap<Atom, Float> uW, 
								  Cuf sw, Multimodality m, int tout, boolean multipleSolutions, boolean returnWelfare)
	{
		// Read the first three parameters into Vectors (Agents, Allocations, Utility Functions)
		List<Atom> v_ags = Arrays.asList(new Atom[ags.size()]);
		int i = 0;
		for (Term ag : ags.getAsList())
		{
			v_ags.set(i, (Atom) ag);
			i++;
		}
		List<Term> v_allocs = allocs.getAsList();
		List<Atom> v_uFuncs = Arrays.asList(new Atom[uFuncs.size()]);
		i = 0;
		for (Term uf : uFuncs.getAsList())
		{
			v_uFuncs.set(i, (Atom) uf);
			i++;
		}

		// Create and fill decision data
		DecisionData decision = new DecisionData(v_ags, v_allocs, v_uFuncs, pW, uW, sw, m, multipleSolutions, returnWelfare);
		mademData.put(new Integer(decision.getId()), decision);

		// Prepare auction message to ask for preferences
		Literal auction = ASSyntax.createLiteral("jmadem_auction");
		auction.addTerm(new NumberTermImpl((double) decision.getId()));
		auction.addTerm(allocs);
		auction.addTerm(uFuncs);
		logger.fine("Auction message: " + auction);

		// Send auctions to bidders
		String agName = this.getAgName();
		Message msg = new Message("tell",
								agName,
								agName,
								auction);
		for (i = 0; i < v_ags.size(); i++)
		{
			if (v_ags.get(i).toString().equals(agName))
			{
				logger.fine("Obtaining personal preferences for " + auction);
				computeUtilities(agName, auction);
			}
			else
			{			
				msg.setReceiver(v_ags.get(i).toString());
				try
				{
					sendMsg(msg);
				}
				catch (Exception e)
				{
					logger.severe("Error sending message \"" + msg + "\" while starting MADeM.");
					e.printStackTrace();
	
					// Remove decision data
					mademData.remove(new Integer(decision.getId()));
					
					return -1;
				}
				
				logger.fine("Auctioned message sent to " + v_ags.get(i));
			}
		}
		
		// Wait for bids
		if (v_ags.size() != 0)
		{
			// Schedule timeout
			decision.setTimeoutTask(timeoutScheduler.schedule(new BidTimeout(decision.getId()), tout, TimeUnit.MILLISECONDS));
		}
		else  // No other agent involved in the decision
			winnerDeterminationProblem(decision.getId(), false);

		return decision.getId();
	}
	
	/**
	  * Handles bid timeout for a specific decision making.  
	  */
	class BidTimeout extends TimerTask
	{
		int id_decision;	
		
		public BidTimeout(int id) {
			id_decision = id;
		}
		
		public void run() {
			logger.fine("Bid timeout reached for decision " + id_decision);
			winnerDeterminationProblem(id_decision, true);
		}
	}	

	/**
	  * Filters MADeM messages out of the agent Mailbox.  
	  */
	@Override
	public void checkMail()
	{
        super.checkMail(); // get the messages
        
        // check the MailBox (at TS) for MADeM messages
        Iterator<Message> i = getTS().getC().getMailBox().iterator();
        while (i.hasNext()) 
		{
            Message m = i.next();
			Object body = m.getPropCont();
			
			//"jmadem_auction"
			if ( (body instanceof Literal) && ((Literal) body).getFunctor().equals("jmadem_auction") )
			{
				i.remove(); // the agent do not receive this message
				computeUtilities(m.getSender(), (Literal) body);
			}
			
			//"jmadem_bid"
			if ( (body instanceof Literal) && ((Literal) body).getFunctor().equals("jmadem_bid") )
			{
				i.remove(); // the agent do not receive this message
				processBid(m.getSender(), (Literal) body);
			}
		}
	}
	
	/**
	  * Changes bid timeout to a new positive value.  
	  *
	  * @param	new_tout	new timeout value (in milliseconds)
	  * @return	<tt>true</tt> if Timeout was correctly changed, <tt>false</tt> otherwise
	  */
	public boolean setTimeout(int new_tout)
	{
		if (new_tout > 0)
		{
			tout = new_tout;
			return true;
		}
		
		return false;
	}
	
	/**
	  * Gets bid timeout (in milliseconds).  
	  *
	  * @return	the bid timeout (in milliseconds) used by the MADeM agent
	  */
	public int getTimeout()
	{
		return tout;
	}

	/**
	  * Changes social welfare of the MADeM agent.  
	  *
	  * @param	new_sw	new social welfare
	  */
	public void setWelfare(Cuf new_sw)
	{
		welfare = new_sw;
	}

	/**
	  * Gets the social welfare of the MADeM agent.  
	  *
	  * @return	the name of the social welfare
	  */
	public String getWelfare()
	{
		return welfare.name();
	}

	/**
	  * Changes the type of multimodality used in the Winner Determination Problem.  
	  *
	  * @param	new_multimodality	new type of multimodality
	  */
	public void setMultimodality(Multimodality new_multimodality)
	{
		multimodality = new_multimodality;
	}

	/**
	  * Gets the type of multimodality used in the Winner Determination Problem.  
	  *
	  * @return	the name of the type of multimodality
	  */
	public String getMultimodality()
	{
		return multimodality.name();
	}

	/**
	  * Changes the personal weight of the MADeM agent towards another agent.  
	  *
	  * @param	agName	name of the agent
	  * @param	new_pw	new personal weights
	  * @return	<tt>true</tt> if the personal weight was correctly changed, <tt>false</tt> otherwise
	  */
	public boolean setPersonalWeight(Atom agName, Float new_pw)
	{
		personalWeights.put(agName, new_pw);
		return true;
	}

	/**
	  * Gets the personal weight of the MADeM agent towards another agent.  
	  *
	  * @param	agName	name of the agent
	  * @return	the personal weight within [0, 1] or 1 when there is no weight explicitly defined 
	  */
	public float getPersonalWeight(Atom agName)
	{
		Float weight = personalWeights.get(agName);
		if (weight != null)
			return weight;
		else
			return 1;
	}

	/**
	  * Changes the personal weights of the MADeM agent towards the other agents.  
	  *
	  * @param	new_pw	new personal weights
	  * @return	<tt>true</tt> if Personal Weights were correctly changed, <tt>false</tt> otherwise
	  */
	public boolean setPersonalWeights(ConcurrentMap<Atom, Float> new_pw)
	{
		personalWeights.putAll(new_pw);
		return true;
	}

	/**
	  * Gets the personal weights of the MADeM agent.  
	  *
	  * @return	the personal weights being used by de MADeM agent in the form of a <tt>ConcurrentMap</tt> 
	  */
	public ConcurrentMap<Atom, Float> getPersonalWeights()
	{
		return personalWeights;
	}

	/**
	  * Gets the weight of the MADeM agent for a certain utility function.  
	  *
	  * @param	utilityName	name of the utility function
	  * @return	the utility weight within [0, 1] or 1 when there is no weight explicitly defined 
	  */
	public float getUtilityWeight(Atom utilityName)
	{
		Float weight = utilityWeights.get(utilityName);
		if (weight != null)
			return weight;
		else
			return 1;
	}

	/**
	  * Changes the utility weight of the MADeM agent for a certain utility function.  
	  *
	  * @param	utilityName	name of the utility function
	  * @param	new_uw	new utility weights
	  * @return	<tt>true</tt> if the utility weight was correctly changed, <tt>false</tt> otherwise
	  */
	public boolean setUtilityWeight(Atom utilityName, Float new_uw)
	{
		utilityWeights.put(utilityName, new_uw);
		return true;
	}

	/**
	  * Changes the utility weights of the MADeM agent.  
	  *
	  * @param	new_uw	new utility weights
	  * @return	<tt>true</tt> if Utility Weights were correctly changed, <tt>false</tt> otherwise
	  */
	public boolean setUtilityWeights(ConcurrentMap<Atom, Float> new_uw)
	{
		utilityWeights.putAll(new_uw);
		return true;
	}

	/**
	  * Gets the utility weights of the MADeM agent.  
	  *
	  * @return	the utility weights being used by de MADeM agent in the form of a <tt>ConcurrentMap</tt> 
	  */
	public ConcurrentMap<Atom, Float> getUtilityWeights()
	{
		return utilityWeights;
	}

	/**
	  * Computes the utility of a set of allocations coming from an auctioneer according
	  * to the specified utility functions.  
	  *
	  * @param	auctioneer	agent asking for preference
	  * @param	madem_auction	allocations and utility functions being considered
	  */
	private void computeUtilities(String auctioneer, Literal madem_auction)
	{
		List<Term> allocs = ((ListTerm) madem_auction.getTerm(1)).getAsList();
		ListTerm uFuncs = (ListTerm) madem_auction.getTerm(2);

		// utilityKey = [auctioneer, mademAuctionId]
		ListTerm utilityKey = new ListTermImpl();
		utilityKey.add(ASSyntax.createAtom(auctioneer));
		utilityKey.add(madem_auction.getTerm(0));
		// |uValues| = NAllocations x NUtilityFunctions
		List<Term> uValues = Arrays.asList(new Term[allocs.size() * uFuncs.size()]);
		
		utilityValues.put(utilityKey, uValues);
		
		// Compute utility values
		float utilityValue = 0;			
		Atom uFuncName;
		int utilityId = 0;
		for (int nAlloc = 0; nAlloc < allocs.size(); nAlloc++)
		{
			Term alloc = allocs.get(nAlloc);
			for (Term t: uFuncs)
			{
				uFuncName = (Atom) t;
				UtilityFunctionInt utilityFunction = utilityFunctions.get(uFuncName);
				if (utilityFunction != null) 
				{	
		            logger.fine("Compute utility by invoking the utility function named " + uFuncName);
					utilityValue = getUtility(auctioneer, alloc, utilityFunction);
					uValues.set(utilityId, ASSyntax.createNumber(utilityValue));
				}
				else
				{
		            logger.fine("Add compute utility event for the utility function named " + uFuncName);
					Literal ufEvent = ASSyntax.createLiteral("jmadem_utility",
							                                 uFuncName,
															 ASSyntax.createAtom(auctioneer),
															 alloc,
															 new VarTerm("Utility")); 
						
					// computeUtilityId == [auctioneer,mademAuctionId,utilityId]
					ListTerm computeUtilityId = new ListTermImpl();
					computeUtilityId.addAll(utilityKey);
					computeUtilityId.add(ASSyntax.createNumber(utilityId));
					Literal jmademUtility = ASSyntax.createLiteral("jmadem_compute_utility",
                            									   computeUtilityId,
							 									   ufEvent);
					
					this.getTS().updateEvents(new Event(new Trigger(TEOperator.add, TEType.achieve, jmademUtility), Intention.EmptyInt));
				}
				
				utilityId++;
			}
		} // for (int nAlloc = 0; nAlloc < allocs.size(); nAlloc++)
		
		if (!uValues.contains(null))
			bid(utilityKey);
	}	
		
	/**
	  * Catches a utility value coming from an AgentSpeak plan and stores it
	  * within the utility values storage.  
	  *
	  * @param	utilityKey	identifier of the utility value being caught
	  * @param	utilityValue	utility value
	  */
	public void catchUtilityValue(ListTerm utilityKey, Term utilityValue)
	{
		Term utilityIdTerm = utilityKey.removeLast();
		int id;
		try {
			id = (int) ((NumberTerm) utilityIdTerm).solve();
		} catch (NoValueException e) {
			id = -1;
			logger.severe("Utility Id \"" + utilityIdTerm + "\" is not numeric while catching utility value.");
			e.printStackTrace();
		}

		List<Term> uValues = utilityValues.get(utilityKey);
		
		if ((uValues == null) || (id == -1))
			return;
		
		synchronized(uValues)
		{
			uValues.set(id, utilityValue);
		
			if (!uValues.contains(null))
				bid(utilityKey);
		}
	}
	
	/**
	  * Bids over a set of allocations coming from an auctioneer.  
	  *
	  * @param	utilityKey	identifier of the utility values storage
	  */
	private void bid(ListTerm utilityKey)
	{
		// Avoid concurrency when bidding
		List<Term> uValues = utilityValues.remove(utilityKey);

		String auctioneer = utilityKey.get(0).toString();
		double mademAuctionId;
		try {
			mademAuctionId = ((NumberTerm) utilityKey.get(1)).solve();
		} catch (NoValueException e1) {
			logger.severe("MADeM auction Id \"" + utilityKey.get(1) + "\" is not numeric while bidding.");
			e1.printStackTrace();
			return;
		}
	
		Literal bidResult = ASSyntax.createLiteral("jmadem_bid");
		bidResult.addTerm(ASSyntax.createNumber(mademAuctionId));
		bidResult.addTerm(ASSyntax.createList(uValues));

		// Send bids to auctioneer
		String agName = getAgName();
		if (agName.equals(auctioneer))
		{
			processBid(agName, bidResult);
		}
		else
		{
			Message msg = new Message("tell", agName, auctioneer, bidResult);
			try
			{
				sendMsg(msg);
			}
			catch (Exception e)
			{
				logger.severe("Error sending message \"" + msg + "\" while bidding.");
				e.printStackTrace();
			}
		}
	}

	/**
	  * Process an external bid related to a certain decision.
	  *
	  * @param	bidder	name of the agent who sent the bid
	  * @param	madem_bid	bid containing bidder's preferences
	  */
	private void processBid(String bidder, Literal madem_bid)
	{
		// Get MADeM decision data
		int id_decision = (int)((NumberTermImpl) madem_bid.getTerm(0)).solve();
		DecisionData decisionData = mademData.get(new Integer(id_decision));
		if ( (decisionData != null) && (!decisionData.isDecisionClosed()) )
		{
		    // Add utility values to MADeM decision data
			decisionData.addBid(bidder, ((ListTermImpl) madem_bid.getTerm(1)).getAsList());
			
			// Call Winner Determination Problem if it is the last bid
			if (decisionData.areAllBidsReceived())
				winnerDeterminationProblem(id_decision, false);
		}
	}

	/**
	  * Obtains the utility value given by a specific utility function.
	  *
	  * @param	auctioneer	agent asking for preference
	  * @param	allocation	allocation being evaluated
	  * @param	utilityFunction	utility function being considered
	  * @return	the utility value assigned to the allocation. 
	  */
	private float getUtility(String auctioneer, Term allocation, UtilityFunctionInt utilityFunction)
	{
		float utility = 0;
		
		try
		{
			utility = utilityFunction.computeUtility(auctioneer, allocation, this);
		}
		catch (UtilityFunctionException e)
		{
			logger.warning("Error computing utility function for the allocation \"" + allocation + "\".");
			e.printStackTrace();
		}
		
		return utility;
	}

	/**
	  * Solves the winner determination problem for a specific decision.
	  *
	  * @param	id_decision	Identifier of the decision being solved
	  */
	private void winnerDeterminationProblem(int id_decision, boolean timeout)
	{
		// Retrieve MADeM decision data
		DecisionData decision = null;
		if (automaticRemoveOfMademData)
			decision = mademData.remove(id_decision);
		else
			decision = mademData.get(id_decision);

		// Avoid concurrency when timeout concurs with the reception of the last bid
		if (decision == null)
			return;
		synchronized(decision)
		{
			decision.getTimeoutTask().cancel(false);
			
			if (decision.isDecisionClosed())
				return;
			else
				decision.setDecisionClosed(true);
		}
		
		// Get MADeM decision data
		List<Float> uValues;
		int i, j, k;
		logger.fine("Starting Winner Determination Problem.");
		List<Atom> agents = decision.getAgents();
		Map<Atom, Float> personalWeights = decision.getPersonalWeights();
		List<Term> allocs = decision.getAllocations();
		List<Atom> uFuncs = decision.getUtilityFunctions();
		Map<Atom, Float> utilityWeights = decision.getUtilityWeights();
		ConcurrentMap<Atom, List<Float>> utilityValues = decision.getUtilityValues();
		Cuf welfare = decision.getWelfare();

		logger.fine("Agents: " + agents);
		logger.fine("Personal Weights: " + personalWeights);
		logger.fine("Allocations: " + allocs);
		logger.fine("Utility Functions: " + uFuncs);
		logger.fine("Utility Weights: " + utilityWeights);
		logger.fine("Welfare: " + welfare);
		logger.fine("Utility Values (agent = [Allocations X UtilityFunctions]):\n" + utilityValues);
		
		// Weight preferences
		Float w;
		for (Map.Entry<Atom, List<Float>> element: utilityValues.entrySet())
		{
			uValues = element.getValue();
			w = personalWeights.get(element.getKey());
			if (w != null)
			{
				for (i = 0; i < uValues.size(); i++)
					if (uValues.get(i) != null)
						uValues.set(i, w * uValues.get(i));
			}
		}
		logger.fine("Weighted Utility Values (agent = [Allocations X UtilityFunctions*PersonalWeights]):\n" + utilityValues);
		
		// Winner Determination Problem
		Set<Integer> mademWinners = new HashSet<Integer>();
		Float mademWinnersWelfare = null;
		List<Float> agentBids = null;
		Float utilityValue, socialWelfare;
		if (decision.getMultimodality() == Multimodality.AGGREGATE)
		{
			// Aggregate social welfare for each utility function
			Float aggregatedWelfare;
			for (j = 0; j < allocs.size(); j++)
			{				
				aggregatedWelfare = null;
				
				for (i = 0; i < uFuncs.size(); i++)
				{
					socialWelfare = null;
					
					for (k = 0; k < agents.size(); k++)
					{		
						agentBids = utilityValues.get(agents.get(k)); 
						if (agentBids != null) {
							utilityValue = agentBids.get(j*uFuncs.size() + i);
							
							if (utilityValue == null) // The agent did not bid for that allocation
								continue;
						}
						else { // The agent did not bid on time
							continue;
						}

						if (socialWelfare == null) {
							socialWelfare = utilityValue;
							continue;
						}
				
						switch (welfare)
						{
							case UTILITARIAN:
							socialWelfare += utilityValue;
							break;
		
							case EGALITARIAN:
							if (socialWelfare > utilityValue)
								socialWelfare = utilityValue;
							break;
		
							case ELITIST:
							if (socialWelfare < utilityValue)
								socialWelfare = utilityValue;
							break;
		
							case NASH:
							socialWelfare *= utilityValue;
							break;
		
							default:
							break;
						}
					}
					
					if (socialWelfare == null)
						continue;
					
					w = utilityWeights.get(uFuncs.get(i));
					if (w == null)
						w = new Float(1);
	
					if (aggregatedWelfare == null)
						aggregatedWelfare = w * socialWelfare;
					else
						aggregatedWelfare += w * socialWelfare;
				}
				
				if (aggregatedWelfare == null)
					continue;
				
				if (mademWinnersWelfare == null)
				{
					mademWinnersWelfare = aggregatedWelfare;
					mademWinners.add(j);
				}
				else if (aggregatedWelfare.equals(mademWinnersWelfare))
				{
					mademWinners.add(j);
				}
				else if (aggregatedWelfare > mademWinnersWelfare)
				{
					mademWinnersWelfare = aggregatedWelfare;
					mademWinners.clear();
					mademWinners.add(j);			
				}
			}	
		}
		else  // decision.getMultimodality() == Multimodality.COMPETITIVE
		{
			// Winner Determination Problem for each utility function
			List<List<Integer>> winnersPerUFunc = new ArrayList<List<Integer>>( (int)(uFuncs.size() * 0.75) + 1 );
			List<Float> winnersPerUFuncWelfare = Arrays.asList(new Float[uFuncs.size()]);
			Float winnerPerUFuncWelfare;
			for (i = 0; i < uFuncs.size(); i++)
			{
				winnersPerUFunc.add(new ArrayList<Integer>());
				
				for (j = 0; j < allocs.size(); j++)
				{
					socialWelfare = null;
					
					for (k = 0; k < agents.size(); k++)
					{		
						agentBids = utilityValues.get(agents.get(k)); 
						if (agentBids != null) {
							utilityValue = agentBids.get(j*uFuncs.size() + i);
							
							if (utilityValue == null) // The agent did not bid for that allocation
								continue;
						}
						else { // The agent did not bid on time
							continue;
						}

						if (socialWelfare == null) {
							socialWelfare = utilityValue;
							continue;
						}
				
						switch (welfare)
						{
							case UTILITARIAN:
							socialWelfare += utilityValue;
							break;
		
							case EGALITARIAN:
							if (socialWelfare > utilityValue)
								socialWelfare = utilityValue;
							break;
		
							case ELITIST:
							if (socialWelfare < utilityValue)
								socialWelfare = utilityValue;
							break;
		
							case NASH:
							socialWelfare *= utilityValue;
							break;
		
							default:
							break;
						}
						
					}
					
					if (socialWelfare == null)
						continue;
					
					winnerPerUFuncWelfare = winnersPerUFuncWelfare.get(i);
					if (winnerPerUFuncWelfare == null) 
					{
						winnersPerUFuncWelfare.set(i, socialWelfare);
						winnersPerUFunc.get(i).add(j);
					}
					else if (socialWelfare.equals(winnerPerUFuncWelfare))
					{
						winnersPerUFunc.get(i).add(j);
					}
					else if (socialWelfare > winnerPerUFuncWelfare)
					{
						winnersPerUFuncWelfare.set(i, socialWelfare);
						winnersPerUFunc.get(i).clear();
						winnersPerUFunc.get(i).add(j);
					}
				}
					
				if (winnersPerUFuncWelfare.get(i) == null)
					logger.fine("Winner(s) of utility " + uFuncs.get(i) + ": None");
				else
				{
					logger.fine("Winner(s) of utility " + uFuncs.get(i) + ": [");
					for (int allocId : winnersPerUFunc.get(i))
						logger.fine(allocs.get(allocId).toString());
					logger.fine("] with Welfare: "+ winnersPerUFuncWelfare.get(i));
				}
			}
			
			// Multi-modal decision making
			for (i = 0; i < uFuncs.size(); i++)
			{
				if (winnersPerUFuncWelfare.get(i) == null)
					continue;
				
				w = utilityWeights.get(uFuncs.get(i));
				if (w == null)
					socialWelfare = winnersPerUFuncWelfare.get(i);
				else
					socialWelfare = winnersPerUFuncWelfare.get(i) * w;
			
				if (mademWinnersWelfare == null)
				{
					mademWinnersWelfare = socialWelfare;
					mademWinners.addAll(winnersPerUFunc.get(i));
				}
				else if (socialWelfare.equals(mademWinnersWelfare))
				{
					mademWinners.addAll(winnersPerUFunc.get(i));
				}
				else if (socialWelfare > mademWinnersWelfare)
				{
					mademWinnersWelfare = socialWelfare;
					mademWinners.clear();
					mademWinners.addAll(winnersPerUFunc.get(i));			
				}
			}
		}		
	
		logger.fine("Madem Winner(s): [");
		for (int allocId : mademWinners)
			logger.fine(allocs.get(allocId).toString());
		if (mademWinnersWelfare == null)
			logger.fine("] with Welfare: None");
		else
			logger.fine("] with Welfare: " + mademWinnersWelfare);
		
		// Publish winner decision
		Literal l = ASSyntax.createLiteral("jmadem_result");
		l.addTerm(new NumberTermImpl((double) id_decision));
		
		if (mademWinnersWelfare == null)
		{
			l.addTerm(ASSyntax.createAtom("none"));
			if (decision.getReturnWelfare())
				l.addTerm(ASSyntax.createAtom("none"));
		}
		else
		{
			if (decision.isMultipleSolution())
			{
				ListTermImpl listOfWinnerAllocations = new ListTermImpl();
				for (int allocId : mademWinners)
					listOfWinnerAllocations.add(allocs.get(allocId));
				l.addTerm(listOfWinnerAllocations);
			}
			else
			{
				if (mademWinners.size() > 1)
				{
					int randomWinner = random.nextInt(mademWinners.size() - 1);
					Iterator<Integer> itWinners = mademWinners.iterator();
					for (i = 0; i < randomWinner - 1; i++)
						itWinners.next();
						
					l.addTerm(allocs.get(itWinners.next()));
				}
				else
					l.addTerm(allocs.get(mademWinners.iterator().next()));
			}
			
			if (decision.getReturnWelfare())
				l.addTerm(new NumberTermImpl((double) mademWinnersWelfare));
		}
		
		if (timeout)
			l.addAnnot(ASSyntax.createAtom("timeout"));
		
		logger.fine("Add believe: " + l);
		try
		{
			getTS().getAg().addBel(l);
		}	
		catch (Exception e)
		{
			logger.warning("Error adding believe " + l);
			e.printStackTrace();
		}
		
		logger.fine("Ending Winner Determination Problem.");
	}

}
