package waiter;

import jason.asSemantics.*;
import jason.asSyntax.*;

import java.util.*;
import java.awt.Point;

import jmadem.Allocation;
import jmadem.MADeMAgArch;
import jmadem.UtilityFunctionException;

public class WaiterAgArch extends MADeMAgArch 
{
	/** Agent act function */
	public void act(ActionExec action, List<ActionExec> feedback) 
	{
		// get the action to be performed
        Structure taction = action.getActionTerm();
		if (taction.getFunctor().equals("getUtility"))
		{
			Literal auction = Literal.parseLiteral(taction.getTerm(0).toString());
			Literal auctionedAction = Literal.parseLiteral(auction.getTerm(1).toString());
			String resource = auctionedAction.getTerm(0).toString();
			String auctioneer = taction.getTerm(1).toString();
			double utility = -1;
			Unifier un = new Unifier();
			Literal l, res;
		    double remaining_tasks = 0;
			int pos_queue = 0;
			double twait = 0;
			int goThru = 0;
			String atom1,atom2;
			
			// Performance utility
			if (auctionedAction.getFunctor().equals("use"))
			{
				l = Literal.parseLiteral("state(executeAction,_,use(" + resource + "),use)");
				res = getTS().getAg().findBel(l, un);
				boolean is_using = (res != null);
				l = Literal.parseLiteral("using(_,_)");
				Iterator<Literal> it = getTS().getAg().getBB().getRelevant(l);
				int n_uses = 0;
				if (it != null)
				{
					while (it.hasNext())
					{
						res = it.next();
						n_uses++;
					}
				}
				
				// Agent is already using the resource and the resource is not complete
				if ( is_using && (n_uses < 4) ) 
					utility = 1;
				else
				{
					// Get remaining and bidding tasks
					l = Literal.parseLiteral("l_actions(_,_,_,_,_)");
					it = getTS().getAg().getBB().getRelevant(l);
					if (it != null)
					{
						while (it.hasNext())
						{
							res = it.next();
							atom1 = res.getTerm(3).toString();
							atom2 = res.getTerm(4).toString();
							if ( (!atom1.equals("bidding")) && 
								 (atom2.equals("self") || atom2.equals("none")) )
								remaining_tasks++;
						}
					}
					
					// Get time to wait to access the resouce 
					// (position-queue+1) or (size-queue+1)
					l = Literal.parseLiteral("pos_in_queue(" + resource + ",_)");
					res = getTS().getAg().findBel(l, un);
					if (res != null)
					{
						pos_queue = Integer.parseInt(res.getTerm(1).toString());
					}
					else
					{
						l = Literal.parseLiteral("queue_size(" + resource + ",_)");
						res = getTS().getAg().findBel(l, un);
						if (res != null)
							pos_queue = Integer.parseInt(res.getTerm(1).toString());
					}
					twait = pos_queue + 1;					
					
					double divisor = remaining_tasks + twait;
					if (divisor == 0) 
						divisor = 1;
					
					utility = 1 / divisor;
				}
			}
			else if (auctionedAction.getFunctor().equals("give"))
			{ 
				l = Literal.parseLiteral("state(executeAction,_,give(" + resource + ",_),_)");
				res = getTS().getAg().findBel(l, un);
				boolean is_giving = (res != null);
				l = Literal.parseLiteral("hands_busy(_)");
				res = getTS().getAg().findBel(l, un);
				boolean has_hand_free = ( (res != null) && (Integer.parseInt(res.getTerm(0).toString()) < 2) );

				// Agent is already giving and has one hand free
				if (is_giving && has_hand_free) 
					utility = 1;
				else
				{
					// Get remaining and bidding tasks
					l = Literal.parseLiteral("l_actions(_,_,_,_,_)");
					Iterator<Literal> it = getTS().getAg().getBB().getRelevant(l);
					if (it != null)
					{
						while (it.hasNext())
						{
							res = it.next();
							atom1 = res.getTerm(3).toString();
							atom2 = res.getTerm(4).toString();
							if ( (!atom1.equals("bidding")) && 
								 (atom2.equals("self") || atom2.equals("none")) )
								remaining_tasks++;
						}
					}
				
					double divisor = remaining_tasks + twait;
					if (divisor == 0) 
						divisor = 1;
					
					utility = 1 / divisor;
				}
			}
			else
				System.out.println("--> Unable to get utility: Unknown action " + auctionedAction.getFunctor());
			
			// Social utilities
			double socUtilityInt = 0;
			double socUtilityExt = 0;
			int distX, distY;
			l = Literal.parseLiteral("friend(" + auctioneer + ")");
			res = getTS().getAg().findBel(l, un);
				goThru = 2;
			if (res != null)  // Only for friends
			{
				goThru = 1;
				
				// Internal social utility
				Literal nextAction = Literal.parseLiteral(auction.getTerm(2).toString());
				long tEndAction = 0;
				l = Literal.parseLiteral("t_init_action(_,_,_,_)");
				res = getTS().getAg().findBel(l, un);
				if (res != null)
				{
					long execTime = executionTime((Literal)res.getTerm(0));
					long t_init = (Integer.parseInt(res.getTerm(3).toString()) +              // seconds
								   Integer.parseInt(res.getTerm(2).toString())*60 +           // minutes
								   Integer.parseInt(res.getTerm(1).toString())*60*60 )*1000;  // hours
					long t_now = new GregorianCalendar().getTimeInMillis();
					long t_passed = (t_now - t_init) % execTime;				
					tEndAction = execTime - t_passed;				
				}
				long[] intervalA = {tEndAction, tEndAction + executionTime(auctionedAction)};
				long[] intervalB = {0,executionTime(nextAction)};
				if ( (intervalB[0] <= intervalA[1] && intervalB[0] >= intervalA[0]) ||
					 (intervalA[0] <= intervalB[1] && intervalA[0] >= intervalB[0]) )   // If intersection
				{
					boolean near = false;
					
					if (nextAction.getFunctor().equals("none"))
						near = computeNear(resource, auctioneer);
					else if ( auctionedAction.getFunctor().equals("give") && nextAction.getFunctor().equals("give") )
						near = true;
					else 
						near = computeNear(resource, nextAction.getTerm(0).toString());
						
					if (near)
						socUtilityInt = 1;
				}
				
				// External social utility
				l = Literal.parseLiteral("state(executeAction,_,_,_)");
				res = getTS().getAg().findBel(l, un);
				Literal currentAction = Literal.parseLiteral("none");
				if (res != null) 
					currentAction = Literal.parseLiteral(res.getTerm(2).toString());
				tEndAction = 0;
				l = Literal.parseLiteral("t_init_action(_,_,_,_)");
				res = getTS().getAg().findBel(l, un);
				if (res != null)
				{
					long execTime = executionTime((Literal)res.getTerm(0));
					long t_init = (Integer.parseInt(res.getTerm(3).toString()) +             // seconds
								  Integer.parseInt(res.getTerm(2).toString())*60 +           // minutes
								  Integer.parseInt(res.getTerm(1).toString())*60*60 )*1000;  // hours
					long t_now = new GregorianCalendar().getTimeInMillis();
					long t_passed = (t_now - t_init) % execTime;				
					tEndAction = execTime - t_passed;				
				}
				intervalA[0] = 0; intervalA[1] = tEndAction;
				intervalB[0] = 0; intervalB[1] = executionTime(auctionedAction);
				if ( (intervalB[0] <= intervalA[1] && intervalB[0] >= intervalA[0]) ||
					 (intervalA[0] <= intervalB[1] && intervalA[0] >= intervalB[0]) )   // If intersection
				{
					boolean near = false;
					
					if (currentAction.getFunctor().equals("none"))
						near = computeNear(resource, auctioneer);
					else if ( auctionedAction.getFunctor().equals("give") && currentAction.getFunctor().equals("give") )
						near = true;
					else 
						near = computeNear(resource, currentAction.getTerm(0).toString());
						
					if (near)
						socUtilityExt = 1;
				}
			}
			
		    // Tiredness utility
			double tirUtility = 0;
			double n_use = 0;
			double n_give = 0;
			double nb_orders = 0;
			//if (auctioneer.equals("self"))
			//{
				/*
				l = Literal.parseLiteral("n_actions_tired(_)");
				res = getTS().getAg().findBel(l, un);
				if (res != null)
					n_actions_tired = Double.parseDouble(res.getTerm(0).toString());

				l = Literal.parseLiteral("max_actions_tired(_)");
				res = getTS().getAg().findBel(l, un);
				if (res != null)
					max_actions_tired = Double.parseDouble(res.getTerm(0).toString());
				else
					max_actions_tired = 1;
				
				tirUtility = 1 - n_actions_tired/max_actions_tired;
				*/
				
				l = Literal.parseLiteral("n_use(_)");
				res = getTS().getAg().findBel(l, un);
				if (res != null)
					n_use = Double.parseDouble(res.getTerm(0).toString());

				l = Literal.parseLiteral("n_give(_)");
				res = getTS().getAg().findBel(l, un);
				if (res != null)
					n_give = Double.parseDouble(res.getTerm(0).toString());

				l = Literal.parseLiteral("nb_orders(_)");
				res = getTS().getAg().findBel(l, un);
				if (res != null)
					nb_orders = Double.parseDouble(res.getTerm(0).toString());
				
				tirUtility = 1 - (n_use + n_give) / (2 * nb_orders);
				
			//}

			Literal utilities = ASSyntax.createLiteral("utility");
			try{
			utilities = Literal.parseLiteral("utility(" + taction.getTerm(0).toString() + 
                    "," + utility + "," + socUtilityInt + "," + socUtilityExt + 
					   "," + tirUtility + 
					   ", debug(" + goThru + "," + auctioneer + "))");
			getTS().getAg().addBel(utilities);
			}
			catch(jason.RevisionFailedException e)
			{
				e.printStackTrace();
			}
			
			
			// Test separate utility functions
			// -------------------------------
			/*Performance performanceUF = new Performance();
			Tiredness tirednessUF = new Tiredness();
			Sociability sociabilityUF = new Sociability();
			ListTermImpl lt = new ListTermImpl();
			lt.add(auction);
			float newPerformanceUtility = -1;
			float newTirednessUtility = -1;
			float newSociabilityUtility = -1;
			try{
				newPerformanceUtility = performanceUF.computeUtility(getAgName(), 
															  new Allocation(lt),
															  this);
				newTirednessUtility = tirednessUF.computeUtility(getAgName(), 
						  										new Allocation(lt),
						  										this);
				newSociabilityUtility = sociabilityUF.computeUtility(getAgName(), 
																	new Allocation(lt),
																	this);
			}catch(UtilityFunctionException ex)
			{
				System.out.println("Error computing utility function");
				ex.printStackTrace();
			}
			Literal newUtilities = Literal.parseLiteral("newUtility(" + taction.getTerm(0).toString() + 
                    "," + newPerformanceUtility + "," + newSociabilityUtility +  
					"," + newTirednessUtility + 
					", debug(" + goThru + "," + auctioneer + "))");
			System.out.println(utilities + " ?== " + newUtilities);
			*/
			
			action.setResult(true);
			feedback.add(action);
		}
		else
            super.act(action,feedback);
	}
	
	/** Agent sense function */ 
	public List<Literal> perceive()
	{
		List<Literal> p = super.perceive();
		
		if (p != null) 
		{
			Literal l;
			Hashtable<String, Point> positions = new Hashtable<String, Point>();
			Point pos;
		  
			// Get all agent positions
			Iterator<Literal> it = p.iterator();
			while (it.hasNext())
			{
				l = it.next();
				if (l.getFunctor().equals("pos"))
				{
					pos = new Point(Integer.parseInt(l.getTerm(1).toString()), Integer.parseInt(l.getTerm(2).toString()));
					positions.put(l.getTerm(0).toString(), pos);
				}
			}
			
            // Check whether the agent is getting eny service from a resource
			Unifier un = new Unifier();
			l = Literal.parseLiteral("state(executeAction,_,_,use)");
			boolean isGettingService = (getTS().getAg().findBel(l, un) != null);

			// Detect near friends
			String agName = getAgName();
			Point posMe = positions.get(agName);
			int distX = Integer.MAX_VALUE, distY = Integer.MAX_VALUE;
			String friend;
			l = Literal.parseLiteral("friend(_)");
			it = getTS().getAg().getBB().getRelevant(l);
			if (it != null)
				while(it.hasNext())
		  	  	{
					l = it.next();
					friend = l.getTerm(0).toString();
					if (!friend.equals(agName))
					{
						pos = positions.get(friend);
						if (pos != null)
						{
							distX = Math.abs((int)posMe.getX() - (int)pos.getX());
							distY = Math.abs((int)posMe.getY() - (int)pos.getY());
						}
					
						if ( ( isGettingService && (distX <= 4) && (distY <= 4) ) ||
						     ( (distX <= 1) && (distY <= 1) ) )
							p.add(Literal.parseLiteral("near(" + friend + ")"));
					}
				}
		}
		
		return p;
	}
	
	/* ------------------------ ADDITIONAL FUNCTIONS -----------------------*/

    /** Returns the estimated execution time for an action */	
	private long executionTime(Literal action)
	{
		if (action.getFunctor().equals("none"))
			//return Long.MAX_VALUE;
			return 0;
		
		Unifier un = new Unifier();
		Literal l, res;
		String resource = action.getTerm(0).toString();
		long execTime = 0;
		
		if (action.getFunctor().equals("use"))
		{
			l = Literal.parseLiteral("t_use(" + resource + ",_)");
			res = getTS().getAg().findBel(l, un);
		    execTime = Integer.parseInt(res.getTerm(1).toString());
		}
        else // give(R)
		{
			l = Literal.parseLiteral("delay(_)");
			res = getTS().getAg().findBel(l, un);
			int delay = Integer.parseInt(res.getTerm(0).toString());

			l = Literal.parseLiteral("size(_,_)");
			res = getTS().getAg().findBel(l, un);
			int size = Integer.parseInt(res.getTerm(0).toString());
			execTime = 2 * delay * size;
		}
		
		return execTime; 
	}

	private boolean computeNear(String resourceA, String resourceB)
	{
		Unifier un = new Unifier();
		Literal l = Literal.parseLiteral("pos(" + resourceA + ",_,_)");
		Literal res = getTS().getAg().findBel(l, un);
		if (res == null)
			return false;
		Point posMe = new Point(Integer.parseInt(res.getTerm(1).toString()), Integer.parseInt(res.getTerm(2).toString()));
		l = Literal.parseLiteral("pos(" + resourceB + ",_,_)");
		res = getTS().getAg().findBel(l, un);
		if (res == null)
			return false;
		Point pos = new Point(Integer.parseInt(res.getTerm(1).toString()), Integer.parseInt(res.getTerm(2).toString()));
						
		int distX = Math.abs((int)posMe.getX() - (int)pos.getX());
		int distY = Math.abs((int)posMe.getY() - (int)pos.getY());
		if ( (distX <= 4) && (distY <= 4) )
			return true;
		else
			return false;
	}
	
}
