package agent;
import reservationManager.ReservationManager;
import jade.core.behaviours.*;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;


class NegotiationBehaviour extends CyclicBehaviour {
	private ResourceManager myAgent;
	
	public NegotiationBehaviour(ResourceManager a) {
		super(a);
		myAgent = a;
	}
	
	/**
	 * Sends a message to the asker that rejects his proposal
	 * and frees the resources that were allocated to this 
	 * reservation and asker
	 * @param msg the proposal in reply to which we reject
	 */
	private void rejectProposal(ACLMessage msg){
		ACLMessage reply = msg.createReply();
		String content[] = msg.getContent().split(",");		
		int reservationId=Integer.parseInt(content[0]);
		reply.setPerformative(ACLMessage.REJECT_PROPOSAL);
		reply.setContent(Integer.toString(reservationId));
		
		
		//free the resources that were allocated to this 
		//reservation and asker
		myAgent.getReservator().retractReservation(reservationId);
		try{
			myAgent.getAskersDB().remove(new Integer(reservationId));
		}
		catch (Exception e){
			//recieved false reservation id, no asker for this one
			//we dont care about the error
		}
		
		//send the reply
		System.out.println("send REJECT_PROPOSAL("+reservationId+") to "+ msg.getSender().getName());
		myAgent.send(reply);
	}
	
	/**
	 * Send a message to the asker, informing him that his proposal
	 * was accepted, and commits the reservation.
	 * If the reservation couldnt be commited (because it is 
	 * expired) the rejectProposal is called
	 * @param msg the proposal in reply to which we accept
	 */
	private void acceptProposal(ACLMessage msg){
		ACLMessage reply = msg.createReply();
		String content[] = msg.getContent().split(",");		
		int reservationId=Integer.parseInt(content[0]);
		AskerInfo asker = (AskerInfo)myAgent.getAskersDB().get(new Integer(reservationId));
		
		reply.setContent(Integer.toString(reservationId));
		
		if (myAgent.getReservator().commitReservation(reservationId,asker.time)){
			//if could commit the reservation in the reservator
			reply.setPerformative(ACLMessage.ACCEPT_PROPOSAL);
			//send the reply
			System.out.println("send ACCEPT_PROPOSAL("+reservationId+") to "+ msg.getSender().getName());
			myAgent.send(reply);
		}
		else
			this.rejectProposal(msg);
	}	

	/**
	 * This implements the fuctionality of the agent
	 * when it recieves an REJECT-PROPOSAL msg
	 * The agent will send a message to the asker that rejects his proposal
	 * aka, confirming that we received his REJECT
	 * and frees the resources that were allocated to this 
	 * reservation and asker
	 */
	private void rejectHandler(ACLMessage msg){
		String content[] = msg.getContent().split(",");		
		int reservationID=Integer.parseInt(content[0]);
		System.out.println("got REJECT_PROPOSAL("+msg.getContent()+") from "+ msg.getSender().getName());
		this.rejectProposal(msg);
	}
	
	/**
	 * This implements the fuctionality of the agent
	 * when it recieves any message that is not part
	 * of the negotiation protocol
	 * The agent will send a message 
	 */
	private void defaultHandler(ACLMessage msg){
		ACLMessage reply = msg.createReply();
		reply.setPerformative(ACLMessage.NOT_UNDERSTOOD);
		System.out.println("got TYPE="+ msg.getPerformative()+" content='"+msg.getContent()+"' from "+ msg.getSender().getName());
		System.out.println("send NOT_UNDERSTOOD to "+ msg.getSender().getName());
		myAgent.send(reply);
	}
	
	/**
	 * This implements the fuctionality of the agent
	 * when it recieves an ACCEPT-PROPOSAL msg
	 * the agent will Send a message to the asker, confirming that his proposal
	 * was accepted, and commits the reservation.
	 * If the reservation couldnt be commited (because it is 
	 * expired) the rejectProposal is called
	 */
	private void acceptHandler(ACLMessage msg){
		String content[] = msg.getContent().split(",");		
		int reservationID=Integer.parseInt(content[0]);

		System.out.println("got ACCEPT_PROPOSAL("+msg.getContent()+") from "+ msg.getSender().getName());
		this.acceptProposal(msg);
	}
	
	/**
	 * This implements the behavior of the agent
	 * when he receives a REQUEST msg
	 * which asks us to reserve a certain ammount of the resource for the negotiation 
	 * about the future price, and asks for my initial price.
	 *
	 * If i have the asked amount of resource available to reserve
	 * i will reserve it and send back an INFORM msg with the reservationId
	 * and the asked price.
	 * 
	 * If i didnt have enough resource t reserve, i will send a
	 * REFUSE msg.
	 * 
	 * The initial price i ask for, in case of INFORM msg is a 
	 * factor of few things: the asked amount, number of my current reservations,
	 * amount of available resources, my minimum price.
	 */
	private void requestHandler(ACLMessage msg){
		int amount=0;
		int howLong=0;
		try{
			String content[] = msg.getContent().split(",");
			amount=Integer.parseInt(content[0]);
			howLong=Integer.parseInt(content[1]);
		}catch(Exception e){
			defaultHandler(msg);
			return;
		}
		ACLMessage reply = msg.createReply();
		
		System.out.println("got REQUEST("+msg.getContent()+") from "+ msg.getSender().getName());
		
		ReservationManager reservator = myAgent.getReservator();
		int nReservations = reservator.getNumReservations();
		int availableResource = reservator.getAvailableResource();
		int reservationID = reservator.reserveResource(amount);
		
		if (reservationID > -1){
			reply.setPerformative(ACLMessage.INFORM);
			float basePrice = myAgent.getMinPricePerUnit() * amount;
			basePrice*=1.3; //raise 30% of the price so we will have what to burgain about
			
			//raise the price if we have many reservations
			if (nReservations > 3)
				basePrice*=1.03;
			if (nReservations > 5)
				basePrice*=1.03;
			if (nReservations > 10)
				basePrice*=1.03;
			
			//if we have less than 30% of the resource we had in the beginning, we are popular
			//so we can raise the price
			if ((float)availableResource/(float)myAgent.managedResorcesAmount < 0.3){
				//System.out.println("availableResource="+availableResource);
				//System.out.println("managedResorcesAmount="+myAgent.managedResorcesAmount);
				basePrice*=1.05;
			}
			
			AskerInfo asker = new AskerInfo(msg.getSender(),
					reservationID,
					howLong,
					Math.round(basePrice));
			reply.setContent(
					Integer.toString(asker.reservationId)+","+
					Integer.toString(asker.myLastPrice));
			
			//save the asker and the info in the DB
			myAgent.getAskersDB().put(new Integer(reservationID),asker);
		}
		else{
			//we dont have enough resource to give
			reply.setPerformative(ACLMessage.REFUSE);
			System.out.println("sending REFUSE to "+ msg.getSender().getName());
		}
		System.out.println("sending INFORM("+reply.getContent()+") to "+ msg.getSender().getName());
		myAgent.send(reply);
	}
	
	/**
	 * This func sends the proposal with the new price 
	 * back to the sender, and remembers the last prices
	 * for this negotiation, and increments internal counters for
	 * this negotiation
	 * @param msg the message we reply to
	 * @param asker negotiation record from the AskersDB
	 * @param hisPrice the last price (what he gave in the current msg) 
	 * @param myNewPrice my current price that will be sent to the asker
	 */
	protected void sendProposal(ACLMessage msg, AskerInfo asker,int hisPrice,int myNewPrice){
		ACLMessage reply = msg.createReply();
		//save the last prices for the next round
		asker.myLastPrice=myNewPrice;
		asker.hisLastPrice = hisPrice;
		asker.myNumProposals++;
		
		reply.setPerformative(ACLMessage.PROPOSE);
		reply.setContent(Integer.toString(asker.reservationId)+","+
				Integer.toString(myNewPrice));
		System.out.println("sent PROPOSE("+ reply.getContent()+") to "+ msg.getSender().getName());
		myAgent.send(reply);
	}
	
	private void proposeHandler(ACLMessage msg){
		int reservationId=0;
		int currPrice=0;
		try{
			String content[] = msg.getContent().split(",");		
			reservationId=Integer.parseInt(content[0]);
			currPrice=Integer.parseInt(content[1]);
		}catch(Exception e){
			defaultHandler(msg);
			return;
		}
		System.out.println("got PROPOSE("+msg.getContent()+") from "+ msg.getSender().getName());
		
		AskerInfo asker = (AskerInfo)myAgent.getAskersDB().get(new Integer(reservationId));
		float myNewPrice=0;
		
		//If we dont have info about this asker
		//we reject his proposal
		//probably sent us already a reject (or i rejected him)
		//or its a fraud
		if (asker==null){
			this.rejectProposal(msg);
			return;
		}
			
		//His proposal automaticly renews the reservation, if couldnt renew
		//and it expired already, i reject his proposal
		if (!myAgent.getReservator().renewReservation(reservationId)){
			this.rejectProposal(msg);
			return;
		}
		
		//if he offers me now less than before
		//i quit the negotiation
		if (currPrice < asker.hisLastPrice){
			this.rejectProposal(msg);
			return;
		}
			
		//if its his first proposal
		//i dont know yet where we are going in
		//this negotiation, so i will take off 5%
		//of the price to keep the ball rolling
		if (asker.hisLastPrice == -1){
			if (currPrice >= asker.myLastPrice * (float)0.93){
				System.out.println("His first proposal is high enough");
				this.acceptProposal(msg);
			}
			else{
				myNewPrice = asker.myLastPrice * (float)0.95;
				sendProposal(msg,asker,currPrice,Math.round(myNewPrice));
			}
			return;
		}
		float agreePoint = (asker.myLastPrice + asker.hisLastPrice) /2;
		
		//i am aiming on the highest between my lowest price
		//and the price we seem to agree about
		float aiming;
		float minimalPrice = myAgent.getReservator().getReservation(reservationId)*
		myAgent.getMinPricePerUnit();
		try{
			aiming= Math.max(minimalPrice,agreePoint);
		}
		catch(Exception ex){
			//The reservation expired already
			this.rejectProposal(msg);
			return;
		}
		System.out.println("agreePoint="+agreePoint +". aiming="+aiming);
		//if his offer is close enough to the aiming point
		//where i wish to end the negotiation, i will agree
		//but only after 2 proposals that i made
		if (
				(currPrice > aiming *0.95) && 
				(currPrice >= minimalPrice) &&
				(asker.myNumProposals > 1 )
				){
			System.out.println("price is close enough to the aiming");
			acceptProposal(msg);
			return;
		}
		
		//The negotiation cycle can not last more than 10 cycles.
		//After 10 proposals that we gave, we check his price, if its 
		//more than my minimal accepted price, i will agree,
		//if its less, than this negotiation converges too slowly
		//and we quit it
		if (asker.myNumProposals > 10){
			System.out.println("more than 10 proposals");
			try{
				if (currPrice >= myAgent.getReservator().getReservation(reservationId)*
					myAgent.getMinPricePerUnit()){
					System.out.println("price is more than the minimal");
					acceptProposal(msg);
					return;
				}
				else{
					//System.out.println("nWanted="+myAgent.getReservator().getReservation(reservationId));
					//System.out.println("minimalPerUnit="+myAgent.getMinPricePerUnit());
					System.out.println("price is less than the minimal");
					rejectProposal(msg);
					return;
				}	
			}
			catch(Exception ex){
				//The reservation expired already
				rejectProposal(msg);
				return;
			}
		}
		
		
		//step is the proportion between the increase in the price he made
		//and the distance between his old price and my aiming point
		//for example if the price was 20, the gareement point 40
		//and he said 30 - the step is 50%
		float hisStep = (currPrice - asker.hisLastPrice)/(aiming - asker.hisLastPrice);
		//i do the same step he did towards the aiming point
		System.out.println("hisStep="+hisStep);
		myNewPrice = asker.myLastPrice - (asker.myLastPrice - aiming)*hisStep;
		
		
		sendProposal(msg,asker,currPrice,Math.round(myNewPrice));
	}
	
	/**
	 * activates the right handler according to the message type
	 * that was received
	 */
	public void action() {	
		MessageTemplate t1 = MessageTemplate.or(MessageTemplate.MatchPerformative(ACLMessage.REQUEST),MessageTemplate.MatchPerformative(ACLMessage.PROPOSE));
		MessageTemplate t2 = MessageTemplate.or(MessageTemplate.MatchPerformative(ACLMessage.ACCEPT_PROPOSAL),MessageTemplate.MatchPerformative(ACLMessage.REJECT_PROPOSAL));
		MessageTemplate template = MessageTemplate.or(t1,t2);	
		ACLMessage msg = this.myAgent.blockingReceive(template);
		
		switch (msg.getPerformative()){
			case ACLMessage.ACCEPT_PROPOSAL: {this.acceptHandler(msg); break;}
			case ACLMessage.REJECT_PROPOSAL: {this.rejectHandler(msg); break;}
			case ACLMessage.PROPOSE: {this.proposeHandler(msg); break;}
			case ACLMessage.REQUEST: {this.requestHandler(msg); break;}
			default: {this.defaultHandler(msg); break;}
		}
		System.out.println("availableResource = "+myAgent.getReservator().getAvailableResource()+"\n");
	}	
};