package CK.IA;

import java.util.HashSet;
import java.util.Hashtable;
import java.util.LinkedList;

public class SoldierAgent extends Entity {
	protected Hashtable<Integer, Entity> beliefs;
	/**
	 * goal id -> [agent id, cost]
	 */
	private Hashtable<Integer, Hashtable<Integer, Double>> costs;
	protected volatile HashSet<Goal> desires;
	protected volatile Goal intention;
	protected Vector3d last_destination;
	protected boolean living_status;
	protected LinkedList<Vector3d> path;
	protected Vector3d path_position;
	protected int path_position_index;
	protected int team;

	/**
	 * Constructs a new SoldierAgent on the given team, positioned on the given position, with the given radius, starting with the given health and
	 * set on the given environment.
	 * 
	 * @param team
	 *            The agent's team.
	 * @param position
	 *            It's position on the world / environment.
	 * @param radius
	 *            It's radius (used for path cornering and action's radius).
	 * @param health
	 *            It's health. If equal or lower than zero, he's dead.
	 * @param environment
	 *            The environment where the agent lives and carries functions.
	 */
	public SoldierAgent(int team, Vector3d position, float radius, float health, EnvironmentAgent environment) {
		super(health, position, radius, EntityType.SOLDIER_AGENT, environment);
		this.paused = true;
		this.team = team;
		intention = null;
		living_status = !this.isDead();
		desires = new HashSet<Goal>();
		costs = new Hashtable<Integer, Hashtable<Integer, Double>>();
	}

	private void auctionCostsCheck(int goal_id) {
		// System.out.println("auctionCostsCheck()");
		try {
			Thread.sleep(GoalSimulatorConfig.SOLDIER_AUCTION_TIMEOUT);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		if (intention != null)
			// auction aborted, my intention got complete or is not mine now
			if (intention == null || intention.getId() != goal_id || intention.getOwner() != this.getId()) {
				// clear cost
				costs.remove(goal_id);
			} else {
				double my_cost = this.calculateGoalCost(intention);
				// get the cost for my intention
				Hashtable<Integer, Double> hashtable = costs.get(goal_id);
				if (hashtable != null) {
					// get the better suited agent
					double lowest_cost = Double.MAX_VALUE;
					int lowest_cost_agent_id = -1;
					for (int agent_id : hashtable.keySet()) {
						Double cost = hashtable.get(agent_id);
						if (cost < lowest_cost) {
							lowest_cost = cost;
							lowest_cost_agent_id = agent_id;
						}
					}
					// is there anyone better suited for the goal than me?
					if (lowest_cost < my_cost && lowest_cost_agent_id > -1) {
						// abandon my intention and give it to the winner
						leaveIntention();
						Message m = new Message(Performative.INFORM, Subject.GOAL_AUCTION, Property.GOAL_AUCTION,
								-1, lowest_cost_agent_id, this.getId());
						GoalAuction ga = new GoalAuction(intention, -1);
						ga.setWinner(lowest_cost_agent_id);
						m.setContent(ga);
						this.sendMessage(m);
						intention = null;
						chooseGoal();
					}
				}
			}
	}

	/**
	 * Executes a single iteration typical of a 'dead' agent. Currently set's the agent turn down and only senses the environment.
	 */
	private void behaviourIncapacitated() {
		debugStatus("behaviourIncapacitated()");
		senseEnvironment();
	}

	/**
	 * Executes a single iteration typical of a 'living' agent. Sets the agent with a normal posture, senses the environment, executes it's primary
	 * intention and randomly chooses a goal.
	 */
	private void behaviourNormal() {
		debugStatus("behaviourNormal()");
		// let's get loud... hum, old
		float new_health = this.getHealth() - GoalSimulatorConfig.SOLDIER_HEALTH_REDUCTION_RATE;
		this.setHealth(new_health);
		senseEnvironment();
		// if we don't have an intention we're always scanning for available goals, else we do it occasionally
		double random = Math.random();
		if (intention == null || random < GoalSimulatorConfig.GOAL_SCAN_PROBABILITY) {
			// System.out.println(this.getId() + " decided to scan the environment");
			chooseGoal();
		}
		executeIntention();
	}

	/**
	 * Computes the 'cost' (currently a simplified version of traveling distances) of the given goal.
	 * 
	 * @param g
	 *            The goal to calculate the cost from.
	 * @return The cost of the goal or -1 if the cost can't be calculated (because that goal type does not exist).
	 * @throws Exception
	 */
	private double calculateGoalCost(Goal g) {
		if (g.getType() == GoalType.ROAM) {
			// roam's value is just the distance agent to the complete position (traveling distance, agent->roam position)
			return g.getCompletePosition().distanceTo(this.getPosition());
		} else if (g.getType() == GoalType.CARRY) {
			// carry's value is <currently> the sum of traveling distances agent->payload->capture
			Entity payload = g.getEntity();
			if (payload != null) {
				Vector3d payload_position = payload.getPosition();
				return this.getPosition().distanceTo(payload_position)
						+ payload_position.distanceTo(g.getCompletePosition());
			} else {
				g.getCompletePosition().distanceTo(this.getPosition());
			}
		}
		return -1;
	}

	/**
	 * Calculates the best rotation step from the agent's current yaw (rotation xOz), the target yaw and some step as maximum.
	 * 
	 * @param rotate_step
	 *            The step to be used if the rotation is bigger than this.
	 * @param yaw_target
	 *            The needed rotation.
	 * @param yaw_current
	 *            The current rotation.
	 * @return rotate_step or another value if rotate_step rotates too much.
	 */
	private float calculateRotationStep(float rotate_step, float yaw_target, float yaw_current) {
		// how much do we need to rotate?
		float yaw_dif = yaw_target - yaw_current;
		// in case we need to rotate more than PI (180º) it's because we're doing it in the wrong direction
		if (yaw_dif > GoalSimulatorConfig.PI_FLOAT) {
			yaw_dif -= 2.0f * GoalSimulatorConfig.PI_FLOAT;
		} else if (yaw_dif < -GoalSimulatorConfig.PI_FLOAT) {
			yaw_dif += 2.0f * GoalSimulatorConfig.PI_FLOAT;
		}
		// step too big to equal target, shorten it
		if (Math.abs(yaw_dif) < rotate_step) {
			rotate_step = yaw_dif;
		} else {
			if (yaw_dif < 0) {
				rotate_step = -rotate_step;
			}
		}
		// System.out.println(this.getId() + "\t" + radToDegree(yaw_target) + "\t" + radToDegree(yaw_current) + "\t" + radToDegree(yaw_dif) + "\t"
		// + radToDegree(rotate_step));
		return rotate_step;
	}

	/**
	 * Get's the goals from the environment and chooses as it's primary intention the goal of this agent's team with higher priority. In case of goals
	 * with same priority, the 'nearest' one is selected. If there's no better goal, the current intention stays the same. It also informs the
	 * environment of choosing new goals and leaving old ones, as well as the switching between goals..
	 */
	private void chooseGoal() {
		// get goals from environment
		requestGoals();
		// intention chosen from the available goals (not assigned OR shared) as the highest priority incomplete goal
		Goal candidate = getBestGoal();
		// is the candidate goal more important than the current intention?
		// if it is, we'll set it as the current intention
		if (candidate != null && (intention == null || candidate.getPriority() > intention.getPriority())) {
			// if we acquire a new goal and the last one was a carry goal, carrying the payload
			if (intention != null && intention.getType() == GoalType.CARRY
					&& intention.getStatus() == GoalStatus.APPROACHING_CAPTURE_POSITION_WITH_PAYLOAD) {
				leavePayload();
			}
			// if we acquire a new carry goal as intention
			if (candidate.getType() == GoalType.CARRY) {
				// and it's different from the current intention (or the last one is null)
				// we must set it's status to <approach payload>
				if (intention == null || intention.getId() != candidate.getId()) {
					candidate.setStatus(GoalStatus.APROACHING_PAYLOAD_POSITION);
				}
			}
			// set the last intention as without an owner
			if (intention != null) {
				intention.setOwner(-1);
				// warn the environment we're leaving it
				Message m = new Message(Performative.INFORM, Subject.GOAL, Property.GOAL, 1,
						EnvironmentAgent.ENVIROMENT_ID, this.getId());
				m.setContent(intention);
				sendMessage(m);
			}
			intention = candidate;
			// set the goal as assigned to us
			intention.setOwner(this.getId());
			// inform the environment the goal is ours
			Message m = new Message(Performative.INFORM, Subject.GOAL, Property.GOAL, 1,
					EnvironmentAgent.ENVIROMENT_ID, this.getId());
			m.setContent(intention);
			sendMessage(m);
			auctionIntention();
		}
	}

	private void auctionIntention() {
		// System.out.println("auctionIntention()");
		if (intention != null) {
			Message m;
			// contact team's agents and decide if there's an agent better suited (possibly, in general, the nearest one) LAST THING TO BE DONE
			costs.put(intention.getId(), new Hashtable<Integer, Double>());
			// we must send the request to the environment, it will broadcast the message to the team
			GoalAuction ga = new GoalAuction(intention, this.getId());
			GoalAuctionBid b = new GoalAuctionBid(ga, -1, 0);
			m = new Message(Performative.REQUEST, Subject.GOAL_AUCTION_BID, Property.GOAL_AUCTION_BID, -1,
					EnvironmentAgent.ENVIROMENT_ID, this.getId());
			m.setContent(b);
			sendMessage(m);
			// wait a little and check results later
			new Thread("costs checker") {
				public void run() {
					if (intention != null) {
						int intention_id = intention.getId();
						auctionCostsCheck(intention_id);
					}
				}
			}.start();
		}
	}

	/**
	 * Writes to the console some of this agent's details and the given message.
	 * 
	 * @param message
	 *            The message to be written.
	 */
	private void debugStatus(String message) {
		if (GoalSimulatorConfig.DEBUG)
			System.out.println("SoldierAgent:" + this.toString() + " " + message);
	}

	private void died() {
		// if we're incapacitated, let's pretend we are...
		this.setRoll(GoalSimulatorConfig.SOLDIER_DEAD_ROLL);
		// let's move ourselves on the floor
		Vector3d mypos = this.getPosition();
		mypos.add(GoalSimulatorConfig.SOLDIER_DEAD_OFFSET);
		// we must leave our current intention
		if (intention != null) {
			leaveIntention();
			// warn the environment we're leaving it
			Message m = new Message(Performative.INFORM, Subject.GOAL, Property.GOAL, 1,
					EnvironmentAgent.ENVIROMENT_ID, this.getId());
			m.setContent(intention);
			sendMessage(m);
			// we'll need to start all over again
			intention = null;
			// path = null;
		}
	}

	private void leaveIntention() {
		if (intention.getType() == GoalType.CARRY
				&& intention.getStatus() == GoalStatus.APPROACHING_CAPTURE_POSITION_WITH_PAYLOAD) {
			leavePayload();
		}
		// set the intention as without an owner
		intention.setOwner(-1);
	}

	private void leavePayload() {
		// let's re-position the payload
		Entity payload = intention.getEntity();
		Vector3d payload_position = payload.getPosition();
		// leave it on the floor
		payload_position.subtract(GoalSimulatorConfig.CARRY_GOAL_PAYLOAD_OFFSET);
		// somewhere near us
		payload_position.addX(this.getRadius() + payload.getRadius() * (Math.random() * 2 - 1));
		payload_position.addZ(this.getRadius() + payload.getRadius() * (Math.random() * 2 - 1));
		// inform the environment of the payload
		Message m = new Message(Performative.INFORM, Subject.ID_ENTITY, Property.ENTITY, -1,
				EnvironmentAgent.ENVIROMENT_ID, this.getId());
		m.setContent(payload);
		this.sendMessage(m);
	}

	/**
	 * Gives a 'cycle' to the agent. Basically it allows him to do it's life for a single iteration.
	 */
	public void executeCycle() {
		debugStatus("executeCycle()");
		super.executeCycle();
		if (!this.isPaused()) {
			// I'm dead
			if (this.isDead()) {
				// but I was living before
				if (living_status) {
					living_status = false;
					this.died();
				}
				behaviourIncapacitated();
			} else
			// I'm alive
			{
				// but I was dead before
				if (!living_status) {
					living_status = true;
					this.revived();
				}
				behaviourNormal();
			}
		}
	}

	/**
	 * Executes what the agent has chosen as the primary intention. According to it, it may move to somewhere, follow some path, rotate towards
	 * something, carry a payload, etc. It also checks for the completeness of the intention and warns the environment of it's state. If new type of
	 * goals are created this function must be updated.
	 */
	private void executeIntention() {
		Vector3d destination = null;
		// System.out.println("pos "+this.getPosition());
		// do we have an incomplete intention?
		try {
			if (intention != null && intention.getStatus() != GoalStatus.COMPLETE) {
				// we must get movement destination according to intention type
				// is the intention ROAMING?
				if (intention.getType() == GoalType.ROAM) {
					// get the destination position
					destination = intention.getCompletePosition();
				} else {
					Entity payload = intention.getEntity();
					// is the intention CARRYING <something != null>?
					if (intention.getType() == GoalType.CARRY && payload != null) {
						// the destination depends on the state of the goal
						if (intention.getStatus() == GoalStatus.APROACHING_PAYLOAD_POSITION) {
							// the goal status forces us to approach the payload position
							destination = payload.getPosition();
						} else if (intention.getStatus() == GoalStatus.APPROACHING_CAPTURE_POSITION_WITH_PAYLOAD) {
							// the goal status forces us to approach the capture position
							destination = intention.getCompletePosition();
						}
					}
				}
				// if we acquired a destination in the above computation we have somewhere to go
				if (destination != null) {
					// are we at the intention's destination?
					if (destination.distanceTo(this.getPosition()) < this.getRadius()) {
						// if our goal is the roam, it completes when we arrive at it's destination
						if (intention.getType() == GoalType.ROAM) {
							// JAWOHL! <complete>
							intention.setStatus(GoalStatus.COMPLETE);
							// inform the environment of the goal
							Message m = new Message(Performative.INFORM, Subject.GOAL, Property.COMPLETE, -1,
									EnvironmentAgent.ENVIROMENT_ID, this.getId());
							m.setContent(intention);
							sendMessage(m);
							intention = null;
						} else
						// if our goal is the carry, it switches status according to what we are approaching
						if (intention.getType() == GoalType.CARRY) {
							if (intention.getStatus() == GoalStatus.APROACHING_PAYLOAD_POSITION) {
								// we have arrived at the payload's position, now we switch the status to <approaching the capture position>
								intention.setStatus(GoalStatus.APPROACHING_CAPTURE_POSITION_WITH_PAYLOAD);
							} else if (intention.getStatus() == GoalStatus.APPROACHING_CAPTURE_POSITION_WITH_PAYLOAD) {
								// get the payload's position
								Vector3d payload_position = intention.getEntity().getPosition();
								// restore the offset to position it nicely on the floor
								payload_position.subtract(GoalSimulatorConfig.CARRY_GOAL_PAYLOAD_OFFSET);
								// we have arrived at the capture's position, so now we switch the status to <complete>
								intention.setStatus(GoalStatus.COMPLETE);
								// inform the environment of the goal
								Message m = new Message(Performative.INFORM, Subject.GOAL, Property.COMPLETE, -1,
										EnvironmentAgent.ENVIROMENT_ID, this.getId());
								m.setContent(intention);
								sendMessage(m);
								intention = null;
							}
						}
					} else
					// we are not near the destination
					{
						// if the destination changed since the last update (it may be dynamic), get a new path
						if (last_destination == null || !last_destination.equals(destination)) {
							// if it did change, we must get a path
							// set path to null cause this may be assincron
							path = null;
							// path will be executed from the start
							path_position_index = 0;
							// the last path waypoint MUST BE our destination, else the all program explodes!
							requestPathTo(destination);
						}
						// if we have a path
						if (path != null) {
							int last_waypoint_i = path.size() - 1;
							// and it's valid, i.e., it's last point equals (or is very near) our destination
							if (path.get(last_waypoint_i).distanceTo(destination) < GoalSimulatorConfig.WAYPOINT_POSITION_TOLERANCE) {
								// where's the current waypoint?
								path_position = path.get(path_position_index);
								// our distance to it?
								double distance_to_waypoint = path_position.distanceTo(this.getPosition());
								// are we within waypoint's radius? (possibly to change to the next waypoint)
								if (distance_to_waypoint < GoalSimulatorConfig.WAYPOINT_RADIUS) {
									// we are, get next waypoint, if there's one
									if (path_position_index < last_waypoint_i) {
										path_position_index++;
									} else
									// we arrived at the last waypoint
									{
										// nothing to do, except holding on while the destination changes
									}
								}
								// orient myself (rotate) and move towards the destination
								// orientAndMove(path_position, GoalSimulatorConfig.SOLDIER_SPEED);
								orientAndMoveSmooth(path_position, GoalSimulatorConfig.SOLDIER_MOVEMENT_SPEED,
										GoalSimulatorConfig.SOLDIER_ROTATION_SPEED);
								// if our intention is the carry
								if (intention.getType() == GoalType.CARRY) {
									// and it's status is approaching the capture position with the payload
									if (intention.getStatus() == GoalStatus.APPROACHING_CAPTURE_POSITION_WITH_PAYLOAD) {
										// we move the payload along our movement
										Vector3d payload_position = intention.getEntity().getPosition();
										// set the payload position as ours
										payload_position.x = this.getPosition().x;
										payload_position.y = this.getPosition().y;
										payload_position.z = this.getPosition().z;
										// add some offset to position it nicely
										payload_position.add(GoalSimulatorConfig.CARRY_GOAL_PAYLOAD_OFFSET);
										// and set the payload to our position
										intention.getEntity().setYaw(this.getYaw());
									}
								}
								// showMyPosition(destination);
							} else {
								// this means the path's last waypoint isn't (or isn't near) our destination (for some reason)
							}
						} // we don't have a valid path so we do not move...
						else {
							// this warning means we haven't received a path (shouldn't happen, instantaneously
							// communication)
							System.out.println(this.getId() + ": can't move, no path my destination");
						}
					}
				} else {
					{
						// this warning means there is no destination, quite bad
						System.out.println(this.getId() + ": can't move, no destination");
					}
				}
				last_destination = destination;
				if (intention != null) {
					Message m = new Message(Performative.INFORM, Subject.GOAL, Property.GOAL, intention.getId(),
							EnvironmentAgent.ENVIROMENT_ID, this.getId());
					m.setContent(intention);
					this.sendMessage(m);
				}
			} else {
				// System.out.println(this.getId() + ": no intention");
			}
		} catch (NullPointerException e) {
		}

	}

	/**
	 * Gets the best (higher priority and in case of goals of same higher priority, the nearest) goal from the desires.
	 * 
	 * @return the best goal.
	 */
	private Goal getBestGoal() {
		Goal candidate = null;
		double cost_g;
		double cost_candidate = Double.POSITIVE_INFINITY;
		// scan all desires (available goals)
		synchronized (desires) {
			if (desires != null && desires.size() > 0) {
				for (Goal g : desires) {
					synchronized (g) {
						boolean assigned = g.isAssigned();
						boolean shared = g.isShared();
						boolean complete = g.getStatus() == GoalStatus.COMPLETE;
						// the goal is a possible candidate if it's not complete AND (not assigned OR allowed to be shared)
						if (!complete && (!assigned || shared)) {
							// rate the goal according to it's 'cost'
							cost_g = calculateGoalCost(g);
							// if our current candidate is null, any goal is better
							if (candidate == null) {
								candidate = g;
								cost_candidate = cost_g;
							} else {
								// if the analyzed goal has higher priority
								if (g.getPriority() > candidate.getPriority()) {
									candidate = g;
									cost_candidate = cost_g;
								} else
								// the analyzed goal has the same priority as the candidate
								if (g.getPriority() == candidate.getPriority()) {
									// it's chosen if it's cost is lower than the candidate's one
									if (cost_g >= 0 && cost_g < cost_candidate) {
										candidate = g;
										cost_candidate = cost_g;
									}
								}
							}
						}
					}
				}
			}
		}
		return candidate;
	}

	/**
	 * Returns the agent's team.
	 * 
	 * @return it's team identifier
	 */
	public int getTeam() {
		return this.team;
	}

	/**
	 * Simplified version of movement. The agent moves linearly and aims instantly. It has a behavior quite 'mechanic'. The agent moves by some amount
	 * (vector) in the direction of destination.
	 * 
	 * @param destination
	 *            The destination of movement.
	 * @param movement_step
	 *            The movement's step (movement is actually a normalized vector (destination-direction) * step.
	 */
	@SuppressWarnings("unused")
	private void orientAndMove(Vector3d destination, double movement_step) {
		// get my direction (from my position to my destination)
		Vector3d my_position = this.getPosition();
		if (my_position != null) {
			double dx = destination.x - my_position.x;
			double dy = destination.y - my_position.y;
			double dz = destination.z - my_position.z;
			double len = Math.sqrt(dx * dx + dy * dy + dz * dz);
			// do we have movement?
			if (len > 0) {
				// normalize direction
				dx = dx / len;
				dy = dy / len;
				dz = dz / len;
				// rotate
				if (dx != 0.0 && dz != 0.0) {
					// yaw (xOz)
					double yaw_target = Math.atan2(dx, dz);
					this.setYaw((float) yaw_target);
				}
				// move myself
				my_position.add(dx * movement_step, dy * movement_step, dz * movement_step);
			}
		}
	}

	/**
	 * Improved version of orientAndMove(), much more smooth. The agent moves and rotates linearly towards the destination, changing it's facing
	 * direction. The agent moves by some amount (vector) in the direction it's currently facing.
	 * 
	 * @param destination
	 *            The destination of movement.
	 * @param movement_step
	 *            The movement's step (movement is actually the vector current direction multiplied by the step).
	 * @param rotate_step
	 *            The rotation's step. Higher values makes the agent rotate much faster towards the destination. Huge values makes it instant.
	 */
	private void orientAndMoveSmooth(Vector3d destination, double movement_step, float rotate_step) {
		// get my direction (from my position to my destination)
		Vector3d my_position = this.getPosition();
		if (my_position != null) {
			double dx = destination.x - my_position.x;
			double dy = destination.y - my_position.y;
			double dz = destination.z - my_position.z;
			double len = Math.sqrt(dx * dx + dy * dy + dz * dz);
			// do we have movement?
			if (len > 0) {
				// normalize direction
				dx = dx / len;
				dy = dy / len;
				dz = dz / len;
				// rotate
				float yaw_target = 0.0f;
				if (dx != 0.0 && dz != 0.0) {
					// yaw (xOz)
					yaw_target = (float) Math.atan2(dx, dz);
				}
				float yaw_current = this.getYaw();
				// compute rotation step (yaw delta)
				rotate_step = calculateRotationStep(rotate_step, yaw_target, yaw_current);
				// rotate (add angle step to current yaw)
				float new_yaw = yaw_current + rotate_step;
				this.setYaw(new_yaw);
				dx = Math.sin(new_yaw);
				dz = Math.cos(new_yaw);
				// move
				my_position.add(dx * movement_step, dy * movement_step, dz * movement_step);
			}
		}
	}

	/**
	 * Converts radians to degrees...
	 * 
	 * @param rad
	 *            the radians
	 * @return radians converted to degrees...
	 */
	@SuppressWarnings("unused")
	private float radToDegree(float rad) {
		return rad * 180.0f / GoalSimulatorConfig.PI_FLOAT;
	}

	/**
	 * Processes the given message received from somewhere. Basically it updates the agent's internal structures and representation of the world.
	 */
	@SuppressWarnings("unchecked")
	public boolean receiveMessage(Message original) {
		// as we are an entity, first let the base method respond
		boolean message_processed = super.receiveMessage(original);
		// debugStatus("receiveMessage() " + original.toString());
		// the message must be for us
		int destination_id = original.getDestination_id();
		Performative performative = original.getPerformative();
		Subject subject = original.getSubject();
		Property property = original.getProperty();
		Object content = original.getContent();
		int sender_id = original.getSender_id();
		if (destination_id == this.getId()) {
			// is it an information?
			if (performative == Performative.INFORM) {
				// ALL entities
				if (subject == Subject.ALL_ENTITIES) {
					// as HASHTABLE <ID,ENTITY>
					if (property == Property.HASHTABLE_ENTITY_BY_ID) {
						// lets update our beliefs about the environment
						beliefs = (Hashtable<Integer, Entity>) content;
						message_processed = true;
					}
				} else
				// ALL goals
				if (subject == Subject.ALL_GOALS) {
					// as hashset
					if (property == Property.HASHSET) {
						// lets update our desires about the environment (local copy)
						updateDesires((HashSet<Goal>) content);
						message_processed = true;
					}
				} else
				// some goal
				if (subject == Subject.GOAL) {
					if (property == Property.GOAL) {
						// was the message sent from the environment?
						if (sender_id == EnvironmentAgent.ENVIROMENT_ID) {
							// get the goal, update our desires
							Goal g = (Goal) content;
							// the received goal must be of our team...
							if (g.getTeam() == this.getTeam()) {
								synchronized (desires) {
									desires.add(g);
								}
								// if our current intention is the same goal has the given one, but assigned to
								// another agent, or complete, remove it
								if (g.getStatus() == GoalStatus.COMPLETE
										|| (intention != null && intention.getId() == g.getId() && g.isAssigned() && g
												.getOwner() != this.getId())) {
									intention = null;
								}
							}
							message_processed = true;
						}
					}
				} else
				// a path
				if (subject == Subject.PATH) {
					if (property == Property.LINKED_LIST) {
						if (sender_id == EnvironmentAgent.ENVIROMENT_ID) {
							path = (LinkedList<Vector3d>) content;
							message_processed = true;
						}
					}
				} else if (subject == Subject.GOAL_AUCTION_BID) {
					// about someone's bid to a goal auction
					if (property == Property.GOAL_AUCTION_BID) {
						GoalAuctionBid b = (GoalAuctionBid) content;
						double bid = b.getBid();
						int bidder_id = b.getBidder_id();
						int goal_id = b.getGoal_auction().getGoal().getId();
						Hashtable<Integer, Double> goal_fitted_agents = costs.get(goal_id);
						if (goal_fitted_agents != null) {
							goal_fitted_agents.put(bidder_id, bid);
						}
						message_processed = true;
					}
				} else
				// information about a goal auction
				if (subject == Subject.GOAL_AUCTION) {
					if (property == Property.GOAL_AUCTION) {
						GoalAuction ga = (GoalAuction) content;
						// am I the winner?
						if (ga.getWinner() == this.getId()) {
							// I only want it if it's better than mine
							Goal g = ga.getGoal();
							if (intention == null || intention.getPriority() < g.getPriority()) {
								intention = g;
								// set the goal as assigned to us
								intention.setOwner(this.getId());
								// inform the environment the goal is ours
								Message m = new Message(Performative.INFORM, Subject.GOAL, Property.GOAL, 1,
										EnvironmentAgent.ENVIROMENT_ID, this.getId());
								m.setContent(intention);
								sendMessage(m);
							}
						}
						message_processed = true;
					}
				}

			}
			// request
			else if (performative == Performative.REQUEST) {
				// about a goal bid
				if (subject == Subject.GOAL_AUCTION_BID) {
					// hum, someone is requesting us to bid some goal
					if (property == Property.GOAL_AUCTION_BID) {
						// do we know of that goal?
						GoalAuctionBid b = (GoalAuctionBid) content;
						GoalAuction ga = b.getGoal_auction();
						Goal g = (Goal) ga.getGoal();
						// obviously, the goal must be of our team
						if (g.getTeam() == this.getTeam()) {
							// I only bid to goals 'better' than mine
							if (intention == null || intention.getPriority() < g.getPriority()) {
								// if my desires does not contain that goal, add it
								synchronized (desires) {
									if (!desires.contains(g)) {
										// I never heard of that goal, but now I know of it
										desires.add(g);
									}
								}
								Entity goal_entity = g.getEntity();
								if (goal_entity != null) {
									int goal_entity_id = goal_entity.getId();
									// if my beliefs does not contain that goal's entity, add it
									if (!beliefs.containsKey(goal_entity_id)) {
										// I never heard of that goal's entity, but now I know of it
										beliefs.put(goal_entity_id, goal_entity);
									}
								}
								// let's compute our cost to the given goal
								double cost = this.calculateGoalCost(g);
								// and bid to the sender
								Message m = new Message(Performative.INFORM, Subject.GOAL_AUCTION_BID,
										Property.GOAL_AUCTION_BID, -1, sender_id, this.getId());
								b = new GoalAuctionBid(ga, this.getId(), cost);
								m.setContent(b);
								this.sendMessage(m);
							}
						}
						message_processed = true;
					}
				}
			}
		}
		if (!message_processed) {
			System.out.println("SoldierAgent:SOMETHING's MISSING HERE!!!");
		}
		return message_processed;
	}

	/**
	 * Requests all the goals existing on the environment.
	 */
	private void requestGoals() {
		Message m = new Message(Performative.REQUEST, Subject.ALL_GOALS, Property.HASHSET, -1,
				EnvironmentAgent.ENVIROMENT_ID, this.getId());
		sendMessage(m);
	}

	/**
	 * Requests the environment the shortest path from this agent's position to the given destination.
	 * 
	 * @param destination
	 *            Where the path leads to.
	 */
	private void requestPathTo(Vector3d destination) {
		// request the path from our position to the goal's position
		Message m = new Message(Performative.REQUEST, Subject.PATH, Property.LINKED_LIST, 1,
				EnvironmentAgent.ENVIROMENT_ID, this.getId());
		m.setContent(destination);
		sendMessage(m);
	}

	private void revived() {
		// if we're normal, let's have a normal posture
		this.setRoll(0.0f);
		// let's move ourselves on the floor (the roll moves us up & down)
		Vector3d mypos = this.getPosition();
		mypos.subtract(GoalSimulatorConfig.SOLDIER_DEAD_OFFSET);
	}

	/**
	 * Requests the environment all the entities existing on it.
	 */
	private void senseEnvironment() {
		// get entities from environment
		debugStatus("senseEnvironment()");
		// request the environment the hashtable of all entities
		Message m = new Message(Performative.REQUEST, Subject.ALL_ENTITIES, Property.HASHTABLE_ENTITY_BY_ID, -1,
				EnvironmentAgent.ENVIROMENT_ID, this.getId());
		sendMessage(m);
	}

	/**
	 * adds to this agent's desires the goals of his team
	 * 
	 * @param content
	 *            the environment's goals hashset
	 */
	private void updateDesires(HashSet<Goal> content) {
		if (content != null && content.size() > 0) {
			synchronized (desires) {
				desires.clear();
				for (Goal goal : content) {
					if (goal.getTeam() == this.getTeam()) {
						desires.add(goal);
					}
				}
			}
		}
	}

	/**
	 * asked by the spawn agent to check for re-spawns
	 * 
	 * @return if this agents wants to re-spawn (be positioned at the spawn point)
	 */
	public boolean waitingToSpawn() {
		return this.isDead();
	}

}
