package bar;

// Environment code for project bar.mas2j

import jason.asSyntax.Literal;
import jason.asSyntax.Structure;
import jason.environment.grid.Location;

import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.Vector;
import java.util.Iterator;
import java.util.Timer;
import java.util.TimerTask;

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JScrollBar;

public class BarEnvironment extends jason.environment.Environment {

    private Logger          logger   = Logger.getLogger("bar.mas2j." + BarEnvironment.class.getName());
    WorldModel              model;
    WorldView               view;
	
	JFrame					speechView;  // Conversational console GUI
	JTextArea 				textArea;
	JScrollPane 			scrollPane;

	int 					nbCustomers = 0; // Number of customers
	int						nbOrders    = 0; // Number of orders
	int						contOrders  = 0; // Number of orders generated
	int                     freqOrders  = 0; // Frequency of orders (in ms)
	int 					nbWaiters   = 0; // Number of waiters
    int                     sleep       = 0; // Sleep time (in ms) after each action

	int 					p_coffeeMachine = 0; // Probability of coffeeMachine
	int 					p_juiceMachine  = 0; // Probability of juiceMachine
	int 					p_grill         = 0; // Probability of grill
	int 					p_pastries      = 0; // Probability of pastries
	int 					p_cocktails     = 0; // Probability of cocktails
	int 					p_snacks        = 0; // Probability of snacks
	

	/* ---------------------- WORLD CREATION FUNCTIONS ----------------------*/

	/** Starts the world */
    @Override
	public void init(String[] args) 
	{
        // get the parameters
        nbCustomers = Integer.parseInt(args[0]);
		nbWaiters = Integer.parseInt(args[1]);
        nbOrders = Integer.parseInt(args[2]);
		freqOrders = Integer.parseInt(args[3]);
		p_coffeeMachine = Integer.parseInt(args[4]);
		p_juiceMachine = Integer.parseInt(args[5]);
		p_grill = Integer.parseInt(args[6]);
		p_pastries = Integer.parseInt(args[7]);
		p_cocktails = Integer.parseInt(args[8]);
		p_snacks = Integer.parseInt(args[9]);
        sleep = Integer.parseInt(args[10]);        
		
		// Init world
		model = WorldModel.createWorld(nbCustomers, nbWaiters);
		model.setProbabilities(p_coffeeMachine, p_juiceMachine, p_grill, 
					 		   p_pastries, p_cocktails, p_snacks);

		view = WorldView.create(model);
		speechView = new JFrame("Conversational console");
		speechView.setSize(500, 500);
		textArea = new JTextArea();
		scrollPane = new JScrollPane(textArea);
		speechView.getContentPane().add(scrollPane);
		speechView.setLocation(500,0);
		speechView. setVisible(true);
		
		initPerception();
	}
	
	/** Starts perception of all agents in the new world*/
    private void initPerception() 
	{
		int i;
		
		// Perceptions for the customers
		// None because customers are initially outside the bar
		// When they go into the bar they will call initPerceptionCustomer()

		// Perceptions for the waiters
		addPerceptWaiters(Literal.parseLiteral("size(18,14)"));
		addPerceptWaiters(Literal.parseLiteral("delay(" + sleep + ")"));
		addPerceptWaiters(Literal.parseLiteral("t_use(coffeeMachine," + WorldModel.T_COFFEEMACHINE + ")"));
		addPerceptWaiters(Literal.parseLiteral("t_use(juiceMachine," + WorldModel.T_JUICEMACHINE + ")"));
		addPerceptWaiters(Literal.parseLiteral("t_use(grill," + WorldModel.T_GRILL + ")"));
		addPerceptWaiters(Literal.parseLiteral("t_use(pastries," + WorldModel.T_PASTRIES + ")"));
		addPerceptWaiters(Literal.parseLiteral("t_use(cocktails," + WorldModel.T_COCKTAILS + ")"));
		addPerceptWaiters(Literal.parseLiteral("t_use(snacks," + WorldModel.T_SNACKS + ")"));
		addPerceptWaiters(Literal.parseLiteral("pos(coffeeMachine, 25, 1)"));
		addPerceptWaiters(Literal.parseLiteral("free(coffeeMachine)"));
		addPerceptWaiters(Literal.parseLiteral("pos(juiceMachine, 30, 10)"));
		addPerceptWaiters(Literal.parseLiteral("free(juiceMachine)"));
		addPerceptWaiters(Literal.parseLiteral("pos(grill, 30, 2)"));
		addPerceptWaiters(Literal.parseLiteral("free(grill)"));
		addPerceptWaiters(Literal.parseLiteral("pos(pastries, 13, 1)"));
		addPerceptWaiters(Literal.parseLiteral("free(pastries)"));
		addPerceptWaiters(Literal.parseLiteral("pos(cocktails, 30, 6)"));
		addPerceptWaiters(Literal.parseLiteral("free(cocktails)"));
		addPerceptWaiters(Literal.parseLiteral("pos(snacks, 19, 1)"));
		addPerceptWaiters(Literal.parseLiteral("free(snacks)"));
		Location l;
		String name;
		for (i = 1; i <= WorldModel.NB_REQUESTPOINTS; i++)
		{
            name = "bar" + i;			
			l = model.requestPoints.get(name)[1];
			addPerceptWaiters(Literal.parseLiteral("request_point(" + name + ")"));
			addPerceptWaiters(Literal.parseLiteral("pos(" + name + "," + l.x + "," + l.y + ")"));
		}
		for (i = 0; i < model.getNbWaiters(); i++)
		{
            l = model.getAgPos(i + model.getNbCustomers());
  		    if (model.getNbWaiters() > 1)
				name = "waiter" + (i+1);
		    else
				name = "waiter";
			
			addPerceptWaiters(Literal.parseLiteral("waiter(" + name + ")"));
			addPerceptWaiters(Literal.parseLiteral("pos(" + name + "," + l.x + "," + l.y + ")"));
		}
		addPerceptWaiters(Literal.parseLiteral("nb_waiters(" + model.getNbWaiters() + ")"));
		addPerceptWaiters(Literal.parseLiteral("noise(" + WorldModel.NOISE + ")"));
		addPerceptWaiters(Literal.parseLiteral("threshold(" + WorldModel.THRESHOLD + ")"));
		addPerceptWaiters(Literal.parseLiteral("near_dist(" + WorldModel.NEAR_DIST + ")"));
			
		// Perceptions for the customerGenerator
		addPercept("customerGenerator", Literal.parseLiteral("nb_waiters(" + model.getNbWaiters() + ")"));
		addPercept("customerGenerator", Literal.parseLiteral("nb_customers(" + model.getNbCustomers() + ")"));
		addPercept("customerGenerator", Literal.parseLiteral("nb_orders(" + nbOrders + ")"));
		addPerceptWaiters(Literal.parseLiteral("nb_orders(" + nbOrders + ")"));
		addPercept("customerGenerator", Literal.parseLiteral("order_freq(" + freqOrders + ")"));
		
		// Start all agents
		addPercept(Literal.parseLiteral("start"));
	}
	
	/** Starts perception of a customer in the bar */
    private void initPerceptionCustomer(String agName, int agId)
	{
		clearPercepts(agName);
		addPercept(agName, Literal.parseLiteral("trashPoint(trash1)"));
		addPercept(agName, Literal.parseLiteral("pos(trash1, 21, 30)"));
		addPercept(agName, Literal.parseLiteral("trashPoint(trash2)"));
		addPercept(agName, Literal.parseLiteral("pos(trash2, 30, 20)"));
		addPercept(agName, Literal.parseLiteral("pos(exit, 30, 26)"));

		Location l;
		String name;
		int i, j;
		for (i = 1; i <= WorldModel.NB_REQUESTPOINTS; i++)
		{
            name = "bar" + i;			
			l = model.requestPoints.get(name)[0];
			addPerceptCustomers(Literal.parseLiteral("pos(" + name + "," + l.x + "," + l.y + ")"));
			if (model.rPAvailability.get(name))
				addPerceptCustomers(Literal.parseLiteral("requestPointFree(" + name + ")"));
				
		}
		for (i = 1; i <= WorldModel.NB_CONSUMEPOINTS; i++)
		{
            name = "chair" + i;			
			l = model.consumePoints.get(name);
			addPerceptCustomers(Literal.parseLiteral("pos(" + name + "," + l.x + "," + l.y + ")"));
			if (model.cPAvailability.get(name))
				addPerceptCustomers(Literal.parseLiteral("consumePointFree(" + name + ")"));
				
		}
		Vector<String> v_chairs;
		for (i = 1; i <= WorldModel.NB_TABLES; i++)
		{
            name = "table" + i;			
			v_chairs = model.table2chairs.get(name);
			for (j = 0; j < v_chairs.size(); j++)
				addPerceptCustomers(Literal.parseLiteral("inTable(" + name + "," + v_chairs.get(j) + ")"));
				
		}
		
		l = model.getAgPos(agId);
		addPerceptCustomers(Literal.parseLiteral("customer(" + agName + ")"));
		addPerceptCustomers(Literal.parseLiteral("pos(" + agName + "," + l.x + "," + l.y + ")"));
		addPerceptCustomers(Literal.parseLiteral("type(" + agName + "," + model.getTypeOfCustomer(agId) + ")"));
		for (i = 0; i < model.getNbCustomers(); i++)
		{
			if ( (model.isIntoBar(i)) && (i != agId) )
			{
				l = model.getAgPos(i);
				if (model.getNbCustomers() > 1)
					name = "customer" + (i+1);
				else
					name = "customer";
			
				addPercept(agName, Literal.parseLiteral("customer(" + name + ")"));
				addPercept(agName, Literal.parseLiteral("pos(" + name + "," + l.x + "," + l.y + ")"));
		        addPercept(agName, Literal.parseLiteral("type(" + name + "," + model.getTypeOfCustomer(i) + ")"));
			}
		}
	}
		
    /** Adds a percept to all customers*/	
	private void addPerceptCustomers(Literal l)
	{
	    if (model.getNbCustomers() == 1)
			addPercept("customer", l);
		else
			for (int i = 0; i < model.getNbCustomers(); i++)
				if (model.isIntoBar(i))
					addPercept("customer" + (i+1), l);
	}
    /** Removes a percept to all customers*/	
	private void removePerceptCustomers(Literal l)
	{
	    if (model.getNbCustomers() == 1)
			removePercept("customer", l);
		else
			for (int i = 0; i < model.getNbCustomers(); i++)
				if (model.isIntoBar(i))
					removePercept("customer" + (i+1), l);
	}
    /** Adds a percept to all waiters */	
	private void addPerceptWaiters(Literal l)
	{
	    if (model.getNbWaiters() == 1)
			addPercept("waiter", l);
		else
			for (int i = 0; i < model.getNbWaiters(); i++)
				addPercept("waiter" + (i+1), l);
	}
    /** Removes a percept to all waiters */	
	private void removePerceptWaiters(Literal l)
	{
	    if (model.getNbWaiters() == 1)
			removePercept("waiter", l);
		else
			for (int i = 0; i < model.getNbWaiters(); i++)
				removePercept("waiter" + (i+1), l);
	}
	
    /* ------------------------- ACTION FUNCTIONS ---------------------------*/

	/** Executes the actions requested by the agents */
    @Override
    public boolean executeAction(String ag, Structure action) {
        boolean result = false;
        try {
            if (sleep > 0) {
                Thread.sleep(sleep);
            }

            // get the agent id based on its name
			int agId = 0;
			if (ag.startsWith("customerGenerator"))
				agId = -1;
			else
			{
				if (ag.startsWith("customer"))
					if (model.getNbCustomers() > 1)
						agId = Integer.parseInt(ag.substring(8)) - 1;				
					else
						agId = 0;				
				if (ag.startsWith("waiter"))
					if (model.getNbWaiters() > 1)
						agId = Integer.parseInt(ag.substring(6)) - 1 + model.getNbCustomers();				
					else
						agId = model.getNbCustomers();				
			}
			
			// execute requested action
			if (action.getFunctor().equals("goIntoBar")) {
				model.goIntoBar(agId);
				initPerceptionCustomer(ag, agId);
				result = true;
			}
            else if (action.getFunctor().equals("reserveRequestPoint")) {
				String rqPoint = action.getTerm(0).toString();
				if (model.reserveRequestPoint(rqPoint))
				{
					removePerceptCustomers(Literal.parseLiteral("requestPointFree(" + rqPoint + ")"));
					result = true;
				}
				else
					result = false;
			}
            else if (action.getFunctor().equals("freeRequestPoint")) {
				String rqPoint = action.getTerm(0).toString();
				model.freeRequestPoint(rqPoint);
				addPerceptCustomers(Literal.parseLiteral("requestPointFree(" + rqPoint + ")"));
				result = true;
			}
            else if (action.getFunctor().equals("reserveConsumePoint")) {
				String cPoint = action.getTerm(0).toString();
				if (model.reserveConsumePoint(cPoint))
				{
					addPerceptCustomers(Literal.parseLiteral("inConsumePoint(" + cPoint + "," + ag + ")"));
					removePerceptCustomers(Literal.parseLiteral("consumePointFree(" + cPoint + ")"));
					result = true;
				}
				else
					result = false;
			}
            else if (action.getFunctor().equals("freeConsumePoint")) {
				String cPoint = action.getTerm(0).toString();
				model.freeConsumePoint(cPoint);
				addPerceptCustomers(Literal.parseLiteral("consumePointFree(" + cPoint + ")"));
				removePerceptCustomers(Literal.parseLiteral("inConsumePoint(" + cPoint + "," + ag + ")"));
				result = true;
			}
            else if (action.getFunctor().equals("throwTrash")) {
				result = true;
			}
            else if (action.getFunctor().equals("exitBar")) {
				Location l = model.getAgPos(agId);
				removePerceptCustomers(Literal.parseLiteral("pos(" + ag + "," + l.x + "," + l.y + ")"));
				removePerceptCustomers(Literal.parseLiteral("customer(" + ag + ")"));
				removePerceptCustomers(Literal.parseLiteral("type(" + ag + "," + model.getTypeOfCustomer(agId) + ")"));
				model.exitBar(agId);
				clearPercepts(ag);
				result = true;
			}
            else if (action.getFunctor().equals("getWanderPosition")) {
				Location l = model.getWanderPosition();
				addPercept(ag, Literal.parseLiteral("pos(wanderPosition," + l.x + "," + l.y + ")"));
				result = true;
			}
            else if (action.getFunctor().equals("delWanderPosition")) {
				removePercept(ag, (Literal)action.getTerm(0));
				result = true;
			}
            else if (action.getFunctor().equals("moveTowards")) {
				Location l = model.getAgPos(agId);
				if (agId < model.getNbCustomers())
					removePerceptCustomers(Literal.parseLiteral("pos(" + ag + "," + l.x + "," + l.y + ")"));
				else
					removePerceptWaiters(Literal.parseLiteral("pos(" + ag + "," + l.x + "," + l.y + ")"));
				
				int x = Integer.parseInt(action.getTerm(0).toString());
				int y = Integer.parseInt(action.getTerm(1).toString());
				l = model.moveTowards(agId, x, y);
				
				if (agId < model.getNbCustomers())
					addPerceptCustomers(Literal.parseLiteral("pos(" + ag + "," + l.x + "," + l.y + ")"));
				else
					addPerceptWaiters(Literal.parseLiteral("pos(" + ag + "," + l.x + "," + l.y + ")"));
				
				return true;
            } else if (action.getFunctor().equals("generateOrders")) {
				Double aux = Double.parseDouble(action.getTerm(0).toString());
				String rqPoint = action.getTerm(1).toString();
				int Num = (int)Math.floor(aux.doubleValue());
				Vector<Literal> vOrders = model.generateOrders(ag, Num, rqPoint);
				if (vOrders != null)
				{
					Iterator<Literal> it = vOrders.iterator();
					while (it.hasNext()) 
					{
						contOrders++;
						addPerceptWaiters(it.next());
					}

                    result = true;

					if (contOrders >= nbOrders)
						addPerceptWaiters(Literal.parseLiteral("finish_orders"));
				}
		    } else if (action.getFunctor().equals("attend")) {
				//removePercept(ag, Literal.parseLiteral("attending(" + action.getTerm(0).toString() + ")"));
				Literal order;
				if ( (order = model.attendOrder(ag)) != null )
				{
				    removePerceptWaiters(order);
					addPerceptWaiters(Literal.parseLiteral("attending(" + ag + "," + order.toString() + ")"));
                    result = true;
					if (view != null)
				        view.update(WorldModel.SIZE_X-2,WorldModel.SIZE_Y-1);
				}
				else
					result =  false;
					//addPercept(ag, Literal.parseLiteral("attending(no_order)"));
				
            } else if (action.getFunctor().equals("queue")) {
				String resource = action.getTerm(0).toString();
				int prevQueue = model.getSizeOfQueue(resource);
				int posInQueue = model.queue(resource, ag);
				if (posInQueue == 0) 
				{
					removePerceptWaiters(Literal.parseLiteral("free(" + resource + ")"));
					result = true;
				}
				else
					result = false;
				
				int actQueue = model.getSizeOfQueue(resource);
				if (prevQueue != actQueue)
				{
					removePerceptWaiters(Literal.parseLiteral("queue_size(" + resource + "," + prevQueue + ")"));
					addPerceptWaiters(Literal.parseLiteral("queue_size(" + resource + "," + actQueue + ")"));
					addPercept(ag, Literal.parseLiteral("pos_in_queue(" + resource + "," + posInQueue + ")"));
				}
            } else if (action.getFunctor().equals("unQueue")) {
				String resource = action.getTerm(0).toString();
				int prevQueue = model.getSizeOfQueue(resource);
				removePerceptWaiters(Literal.parseLiteral("queue_size(" + resource + "," + prevQueue + ")"));
				String nameAgQueue;
				for (int i = 0; i < prevQueue; i++)
				{
					nameAgQueue = model.getAgentAtQueue(resource,i);
					if (!nameAgQueue.equals(""))
						removePercept(nameAgQueue, Literal.parseLiteral("pos_in_queue(" + resource + "," + i + ")"));
				}	
				result = model.unQueue(resource, ag);
				int actQueue = model.getSizeOfQueue(resource);
				addPerceptWaiters(Literal.parseLiteral("queue_size(" + resource + "," + actQueue + ")"));
				for (int i = 0; i < actQueue; i++)
				{
					nameAgQueue = model.getAgentAtQueue(resource,i);
					if (!nameAgQueue.equals(""))
						addPercept(nameAgQueue, Literal.parseLiteral("pos_in_queue(" + resource + "," + i + ")"));
				}	
		    } else if (action.getFunctor().equals("getService")) {
				String resource = action.getTerm(1).toString();
				String idAction = action.getTerm(2).toString();			
                long tWait = model.useResource(resource);
				//removePercept(ag, Literal.parseLiteral("startUsing(" + resource + ")"));
				Timer timer = new Timer();
				timer.schedule(new UseTask(ag, resource, idAction), tWait);
				result = true;
            } else if (action.getFunctor().equals("finishUsing")) {
				String resource = action.getTerm(0).toString();
				String idAction = action.getTerm(1).toString();			
     			removePercept(ag, Literal.parseLiteral("finishUsing(" + resource + "," + idAction + ")"));
				int prevQueue = model.getSizeOfQueue(resource);
				model.finishUsing(resource, ag); 
				int actQueue = model.getSizeOfQueue(resource);
				if (prevQueue != actQueue)
				{
					removePerceptWaiters(Literal.parseLiteral("queue_size(" + resource + "," + prevQueue + ")"));
					addPerceptWaiters(Literal.parseLiteral("queue_size(" + resource + "," + actQueue + ")"));
					String nameAgQueue;
					for (int i = 0; i < actQueue; i++)
					{
						nameAgQueue = model.getAgentAtQueue(resource,i);
						if (!nameAgQueue.equals(""))
						{
							removePercept(nameAgQueue, Literal.parseLiteral("pos_in_queue(" + resource + "," + (i+1) + ")"));
							addPercept(nameAgQueue, Literal.parseLiteral("pos_in_queue(" + resource + "," + i + ")"));
						}
					}
				}
				if (actQueue == 0) 
					addPerceptWaiters(Literal.parseLiteral("free(" + resource + ")"));
				result = true;
		    } else if (action.getFunctor().equals("give")) {
				String agName = action.getTerm(0).toString();
				Literal order = (Literal) action.getTerm(1);
                removePerceptWaiters(Literal.parseLiteral("attending(" + agName + "," + order.toString() + ")"));
				result = model.giveOrder(order);
		  
            } else if (action.getFunctor().equals("consume")) {
                result = true;
            } else if (action.getFunctor().equals("say")) {
				String message = action.getTerm(0).toString();
				message = message.substring(1, message.length()-1) + "\n";
				// Determine whether the scrollbar is currently at the very bottom position.
				JScrollBar vbar = scrollPane.getVerticalScrollBar();
				boolean autoScroll = ((vbar.getValue() + vbar.getVisibleAmount()) == vbar.getMaximum());
				textArea.append(message);
				// now scroll if we were already at the bottom.
				if( autoScroll ) textArea.setCaretPosition( textArea.getDocument().getLength() );
				result = true;
            }
			else if (action.getFunctor().equals("endOfSimulation"))
			{
				this.getEnvironmentInfraTier().getRuntimeServices().stopMAS();
			}
			else {
                logger.info("Executing: " + action + ", but not implemented!");
            }
        } catch (Exception e) {
            logger.log(Level.SEVERE, "Error executing " + action + " for " + ag, e);
        }
        return result;
    }

    /** Thread used to determine when a a product is ready and the waiter has finished using the resource */
	class UseTask extends TimerTask
	{
		String ag, resource, idAction;	
		
		public UseTask(String a, String r, String id) {
			ag = a;
			resource = r;
			idAction = id;
		}
		
		public void run() {
			addPercept(ag, Literal.parseLiteral("finishUsing(" + resource + "," + idAction + ")"));
		}
	}
}
