package cz.cuni.amis.pogamut.ut2004.agent.module.sensor;

import java.util.Map;
import java.util.logging.Logger;

import cz.cuni.amis.pogamut.base.agent.worldview.IWorldView;
import cz.cuni.amis.pogamut.base.agent.worldview.WorldEventListener;
import cz.cuni.amis.pogamut.base.agent.worldview.WorldObjectEventListener;
import cz.cuni.amis.pogamut.base.agent.worldview.objects.IWorldObjectId;
import cz.cuni.amis.pogamut.base.agent.worldview.objects.WorldObjectFirstEncounteredEvent;
import cz.cuni.amis.pogamut.base3d.worldview.objects.Location;
import cz.cuni.amis.pogamut.base3d.worldview.objects.Rotation;
import cz.cuni.amis.pogamut.base3d.worldview.objects.Velocity;
import cz.cuni.amis.pogamut.ut2004.agent.module.AgentModule;
import cz.cuni.amis.pogamut.ut2004.communication.messages.UnrealId;
import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.InitedMessage;
import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.Player;
import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.PlayerScore;
import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.Self;
import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.TeamScore;
import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.VolumeChanged;

/**
 * Memory module specialized on general info about the agent whereabouts.
 *
 * @author Juraj 'Loque' Simlovic
 */
public class AgentInfo extends AgentModule
{
	/**
	 * Retreives a unique ID of the agent in the game.
	 *
	 * <p>Note: This ID does not change and can be relied upon during entire
	 * match. However, be aware that the ID may change between different matches
	 * and/or sessions.
	 *
	 * @return ID of the agent in the game.
	 */
	public UnrealId getId()
	{
		// retreive from self object
		return self.getId();
	}

	/**
	 * Retreives current name of the agent in the game.
	 *
	 * <p>Note: The agent may choose and change it's name during a match and it
	 * does not need to be unique among players. Even an empty string might be
	 * a valid name.
	 *
	 * @return Name of the agent in the game.
	 */
	public String getName()
	{
		// retreive from self object
		return self.getName();
	}

	/*========================================================================*/

	/** Red team number. */
	public static final int TEAM_RED = 0;
	/** Blue team number. */
	public static final int TEAM_BLUE = 1;
	/** Green team number. */
	public static final int TEAM_GREEN = 2;
	/** Gold team number. */
	public static final int TEAM_GOLD = 3;
	/** No-team number. */
	public static final int TEAM_NONE = 255;

	/**
	 * Retreives team number the agent is on.
	 *
	 * @return Team number the player is on.
	 *
	 * @see #TEAM_RED
	 * @see #TEAM_BLUE
	 * @see #TEAM_GREEN
	 * @see #TEAM_GOLD
	 * @see #TEAM_NONE
	 *
	 * @see isEnemy(int)
	 * @see isEnemy(Player)
	 * @see isFriend(int)
	 * @see isFriend(Player)
	 */
	public int getTeam()
	{
		// retreive from self object
		return self.getTeam();
	}

	/**
	 * Tells, whether a given team is an enemy team to the agent.
	 *
	 * @param team Team number to be tested.
	 * @return True, if the given team is an enemy team.
	 *
	 * @see getTeam()
	 * @see isFriend(int)
	 */
	public boolean isEnemy(int team)
	{
		// freelancers' team or different team
		return (team == TEAM_NONE) || (team != getTeam());
	}

	/**
	 * Tells, whether a given player is an enemy to the agent.
	 *
	 * @param player Player to be tested.
	 * @return True, if the given player is an enemy.
	 *
	 * @see getTeam()
	 * @see isFriend(Player)
	 */
	public boolean isEnemy(Player player)
	{
		// test the enemy team number
		return isEnemy(player.getTeam());
	}

	/**
	 * Tells, whether a given team is a friend team to the agent.
	 *
	 * @param team Team number to be tested.
	 * @return True, if the given team is a friend team.
	 *
	 * @see getTeam()
	 * @see isEnemy(int)
	 */
	public boolean isFriend(int team)
	{
		// same team only
		return (team == getTeam());
	}

	/**
	 * Tells, whether a given player is a friend to the agent.
	 *
	 * @param player Player to be tested.
	 * @return True, if the given player is a friend.
	 *
	 * @see getTeam()
	 * @see isEnemy(Player)
	 */
	public boolean isFriend(Player player)
	{
		// test the friend team number
		return isFriend(player.getTeam());
	}

	/*========================================================================*/
	
	/** 
	 * Which distance to a location is considered the same as specified location. Note
	 * that UT units are rather small. 
	 */
	public static final double AT_LOCATION_EPSILON = 120;

	/**
	 * Retreives absolute location of the agent within the map.
	 *
	 * @return Location of the agent within the map.
	 *
	 * @see getDistance(Location)
	 * @see Location#getDistance(Location)
	 * @see Location#getDistanceL1(Location)
	 * @see Location#getDistanceLinf(Location)
	 * @see Location#getDistancePlane(Location)
	 * @see Location#getDistanceSquare(Location)
	 */
	public Location getLocation()
	{
		// retreive from self object
		return self.getLocation();
	}
	
	public boolean atLocation(Location location) {
		return atLocation(location, AT_LOCATION_EPSILON);
	}
	
	public boolean atLocation(Location location, double epsilon) {
		if (location == null) return false;
		return getLocation().getPoint3d().distance(location.getPoint3d()) < epsilon;
	}

	/**
	 * Computes crow-fly distance of the agent from given location.
	 *
	 * @param location Location within the map.
	 * @return Crow-fly distance of the agent and the location.
	 *
	 * @see getLocation()
	 */
	public double getDistance(Location location)
	{
		// retreive from self object
		return self.getLocation().getDistance(location);
	}

	/*========================================================================*/

	/**
	 * Retreives absolute rotation of the agent within the map.
	 *
	 * @return Rotation of the agent within the map.
	 */
	public Rotation getRotation()
	{
		// retreive from self object
		return self.getRotation();
	}

	/*========================================================================*/

	/**
	 * Retreives current velocity of the agent as a vector of movement.
	 *
	 * @return Current velocity of the agent in the map.
	 *
	 * @see isMoving()
	 */
	public Velocity getVelocity()
	{
		// retreive from self object
		return self.getVelocity();
	}

	/**
	 * Tells, whether the agent is moving. The agent is moving, when his
	 * actual velocity is non-zero.
	 *
	 * @return True, if the agent is moving.
	 *
	 * @see getVelocity()
	 */
	public boolean isMoving()
	{
		// check the size of the velocity
		return !getVelocity().isZero();
	}

	/*========================================================================*/

	/**
	 * Tells, whether the agent is crouched. When crouched, the height of the
	 * agent is smaller and thus harder to spot/hit.
	 *
	 * @return True, if the agent is crouched.
	 */
	public boolean isCrouched()
	{
		// retreive from self object
		return self.isCrouched();
	}

	/**
	 * Tells, whether the agent is walking. When walking, the agent does not
	 * fall off the edges before notification about such edge can be sent to
	 * the agent. The agent's movement is, however, much slower.
	 *
	 * @return True, if the agent is walking.
	 */
	public boolean isWalking()
	{
		// retreive from self object
		return self.isWalking();
	}

	/*========================================================================*/

	/**
	 * Retreives location of the nearest map geometry directly beneath the
	 * agent. This can be used to determine how far the agent is above the
	 * ground, etc.
	 *
	 * @return Location of <i>the ground</i> beneath the agent.
	 */
	public Location getFloorLocation()
	{
		// retreive from self object
		return self.getFloorLocation();
	}

	/**
	 * Tells, whether the agent is currently touching the groud with his feets.
	 * When not touching ground, the agent might be either jumping, or falling,
	 * or hanging from a ledge, or otherwise flying above the ground.
	 *
	 * @return True, if the agent is touching ground with his feets.
	 */
	public boolean isTouchingGround()
	{
		// compare locations of agent and floor (beware of being crouched)
		// FIXME[js]: Test the values..
		return (getLocation().z - getFloorLocation().z)
			   < (isCrouched() ? 50 : 80);
	}

	/*========================================================================*/

	/**
	 * Tells whether the agent has the damage multiplier (UDamage) bonus boost
	 * activated and how long will the UDamage boost remain active.
	 *
	 * <p>When UDamage is activated, the agent is  causing double (or tripple,
	 * or even more) damage to other players. The multiplying factor depends
	 * on game settings and mutators.
	 *
	 * @return Time remaining for UDamage bonus boost. When this value is
	 * positive, the agent has the UDamage bonus boost currently activated.
	 * When this value is negative, the agent does not have UDamage activated.
	 *
	 * @see hasUDamage()
	 */
	public double getRemainingUDamageTime()
	{
		// calculate remaining time by substracting current time
		return self.getUDamageTime() - self.getLastSeenTime();
	}

	/**
	 * Tells whether the agent has the damage multiplier (UDamage) bonus boost
	 * activated.
	 *
	 * <p>When UDamage is activated, the agent is  causing double (or tripple,
	 * or even more) damage to other players. The multiplying factor depends
	 * on game settings and mutators.
	 *
	 * @return True, if the agent has damage multiplier bonus action activated.
	 *
	 * @see getRemainingUDamageTime()
	 */
	public boolean hasUDamage()
	{
		// is there any remaining time?
		return getRemainingUDamageTime() > 0;
	}

	/*========================================================================*/

	/**
	 * Tells, whether the agent has special bonus action activated: the
	 * invisibility. When invisibility is activated, the agent is almost
	 * invisible to other players. When moving, outline of the agent is
	 * glittering a bit. When standing still, he is very hard to spot.
	 *
	 * <p>To learn, for how long the bonus action will remain activated, check
	 * the remaining amount of adrenaline. When level of adrenaline reaches
	 * zero, the bonus action is deactivated. See {@link getAdrenaline()}.
	 *
	 * @return True, if the agent has invisibility bonus action activated.
	 *
	 * @see getAdrenaline()
	 */
	public boolean hasInvisibility()
	{
		// check with the self object
		return self.getCombo().equals("xGame.ComboInvis");
	}

	/**
	 * Tells, whether the agent has special bonus action activated: the
	 * fast firing rate. When fast firing rate is activated, the agent is
	 * firing his weapon at a faster rate, eating more ammo, but launching
	 * more projectiles into air.
	 *
	 * <p>To learn, for how long the bonus action will remain activated, check
	 * the remaining amount of adrenaline. When level of adrenaline reaches
	 * zero, the bonus action is deactivated. See {@link getAdrenaline()}.
	 *
	 * @return True, if the agent has fast firing rate bonus action activated.
	 *
	 * @see getAdrenaline()
	 */
	public boolean hasFastFire()
	{
		// check with the self object
		return self.getCombo().equals("xGame.ComboBerserk");
	}

	/**
	 * Tells, whether the agent has special bonus action activated: the
	 * regenration, which is also called booster. When booster is activated,
	 * the agent regenerates health slowly. Note: The agent's health never
	 * rises above the maximum health level.
	 *
	 * <p>To learn, for how long the bonus action will remain activated, check
	 * the remaining amount of adrenaline. When level of adrenaline reaches
	 * zero, the bonus action is deactivated. See {@link getAdrenaline()}.
	 *
	 * @return True, if the agent has regenration bonus action activated.
	 *
	 * @see getAdrenaline()
	 */
	public boolean hasRegeneration()
	{
		// check with the self object
		return self.getCombo().equals("xGame.ComboDefensive");
	}

	/**
	 * Tells, whether the agent has special bonus action activated: the
	 * speed. When speed is activated, the agent can move much faster than
	 * other players. Note: Firing rate does not change with speed.
	 *
	 * <p>To learn, for how long the bonus action will remain activated, check
	 * the remaining amount of adrenaline. When level of adrenaline reaches
	 * zero, the bonus action is deactivated. See {@link getAdrenaline()}.
	 *
	 * @return True, if the agent has speed bonus action activated.
	 *
	 * @see getAdrenaline()
	 */
	public boolean hasSpeed()
	{
		// check with the self object
		return self.getCombo().equals("xGame.ComboSpeed");
	}

	/*========================================================================*/

	/**
	 * Tells, how much health the agent has.
	 *
	 * <p>The health usually starts at 100, and ranges from 0 to 199. These
	 * values, however, can be changed by various mutators.
	 *
	 * @return Current health status.
	 *
	 * @see isHealthy()
	 * @see isSuperHealthy()
	 */
	public int getHealth()
	{
		// retreive from self object
		return self.getHealth();
	}

	/**
	 * Tells, whether the agent is healthy, i.e. not wounded.
	 *
	 * @return True, if the agent has at least standard amount of health.
	 *
	 * @see getHealth()
	 * @see isSuperHealthy()
	 */
	public boolean isHealthy()
	{
		// compare self object and game info
		return (getHealth() >= game.getFullHealth());
	}

	/**
	 * Tells, whether the agent is healthy to the maximum boostable extent.
	 *
	 * @return True, if the agent has maximum amount of health.
	 *
	 * @see getHealth()
	 * @see isHealthy()
	 */
	public boolean isSuperHealthy()
	{
		// compare self object and game info
		return (getHealth() >= game.getMaxHealth());
	}

	/*========================================================================*/

	/**
	 * Tells, how much of combined armor the agent is wearing.
	 *
	 * <p>The combined armor usually starts at 0, and ranges from 0 to 150.
	 * These values, however, can be changed by various mutators.
	 *
	 * <p>Note: The armor consist of two parts, which are summed together into
	 * combined armor value. However, each part is powered-up by different item
	 * (low armor by <i>small shield</i>; high armor by <i>super-shield</i>).
	 *
	 * @return Current armor status.
	 *
	 * @see hasArmor()
	 * @see getLowArmor()
	 * @see getHighArmor()
	 */
	public int getArmor()
	{
		// retreive from self object
		return self.getArmor();
	}

	/**
	 * Tells, whether the agent is armored to the maximum extent.
	 *
	 * @return True, if the agent has maximum amount of armor.
	 *
	 * @see getArmor()
	 * @see hasLowArmor()
	 * @see hasHighArmor()
	 */
	public boolean  hasArmor()
	{
		// compare self object and game info
		return (getArmor() >= game.getMaxArmor());
	}

	/**
	 * Tells, how much of low armor the agent is wearing.
	 *
	 * <p>The low armor usually starts at 0, and ranges from 0 to 50.
	 * These values, however, can be changed by various mutators.
	 *
	 * <p>Note: The armor consist of two parts, which are summed together into
	 * combined armor value. However, each part is powered-up by different item
	 * (low armor by <i>small shield</i>; high armor by <i>super-shield</i>).
	 *
	 * @return Current low armor status.
	 *
	 * @see hasLowArmor()
	 * @see getArmor()
	 * @see getHighArmor()
	 */
	public int getLowArmor()
	{
		// retreive from self object
		return self.getSmallArmor();
	}

	/**
	 * Tells, whether the agent is armored to the maximum of low-armor extent.
	 *
	 * @return True, if the agent has maximum amount of low-armor.
	 *
	 * @see getLowArmor()
	 * @see hasArmor()
	 * @see hasHighArmor()
	 */
	public boolean  hasLowArmor()
	{
		// compare self object and game info
		return (getLowArmor() >= game.getMaxLowArmor());
	}

	/**
	 * Tells, how much of high armor the agent is wearing.
	 *
	 * <p>The high armor usually starts at 0, and ranges from 0 to 100.
	 * These values, however, can be changed by various mutators.
	 *
	 * <p>Note: The armor consist of two parts, which are summed together into
	 * combined armor value. However, each part is powered-up by different item
	 * (low armor by <i>small shield</i>; high armor by <i>super-shield</i>).
	 *
	 * @return Current high armor status.
	 *
	 * @see hasHighArmor()
	 * @see getArmor()
	 * @see getLowArmor()
	 */
	public int getHighArmor()
	{
		// calculate from armor and small armor in self object
		return self.getArmor() - self.getSmallArmor();
	}

	/**
	 * Tells, whether the agent is armored to the maximum of high-armor extent.
	 *
	 * @return True, if the agent has maximum amount of high-armor.
	 *
	 * @see getHighArmor()
	 * @see hasArmor()
	 * @see hasLowArmor()
	 */
	public boolean  hasHighArmor()
	{
		// compare self object and game info
		return (getHighArmor() >= game.getMaxHighArmor());
	}

	/*========================================================================*/

	/**
	 * Tells, how much adrenaline the agent has.
	 *
	 * <p>Adrenaline can be gained through fulfilling various game tasks, such
	 * as killing opponents, capturing flags, controlling domination points,
	 * picking up adrenaline pills, etc. Note: More adrenaline is gained when
	 * the agent fulfill these tasks in combos (e.g. by scoring a double-kill
	 * the agent receives significantly more adrenaline than by scoring two
	 * single-kills).
	 *
	 * <p>Once the adrenaline reaches a designated level, it can be used to
	 * start special bonus actions like <i>booster</i>, <i>invisibility</i>,
	 * <i>speed</i>, etc. The adrenaline is then spent on the action. The more
	 * adrenaline the agent has, the longer the action lasts. Note: The agent
	 * may gain new adrenaline during the bonus action, which prolongs the
	 * action duration. See {@link isAdrenalineFull() } to determine, when the
	 * necessary adrenaline level is reached.
	 *
	 * <p>The adrenaline usually starts at 0, and ranges from 0 to 100. These
	 * values, however, can be changed by various mutators.
	 *
	 * @return Current armor status.
	 *
	 * @see isAdrenalineFull()
	 */
	public int getAdrenaline()
	{
		// retreive from self object
		return self.getAdrenaline();
	}

	/**
	 * Tells, whether the agent gained enough adrenaline to use it for special
	 * adrenaline-based action, e.g. <i>booster</i>, <i>invisibility</i>, etc.
	 *
	 * @return True, if the adrenaline level is high enough for bonus action.
	 *
	 * @see getAdrenaline()
	 */
	public boolean isAdrenalineSufficient()
	{
		// compare self object and game info
		return (getAdrenaline() >= game.getTargetAdrenaline());
	}

	/**
	 * Tells, whether the agent has full adrenaline.
	 *
	 * @return True, if the adrenaline level is at maximum.
	 *
	 * @see getAdrenaline()
	 */
	public boolean isAdrenalineFull()
	{
		// compare self object and game info
		return (getAdrenaline() >= game.getMaxAdrenaline());
	}

	/*========================================================================*/

	/**
	 * Retreives UnrealId of the weapon the agent is currently holding. This
	 * UnrealId is a unique identifier of weapon from the agent's inventory.
	 * Note that this UnrealId is different from UnrealId of item the agent
	 * seen or picked up from the ground earlier.
	 *
	 * <p>The UnrealId might contains a substring, which identifies the type
	 * of the weapon. However, this is not guaranteed by definition. Therefore,
	 * you shoud use inventory to retreive the appropriate weapon object, to
	 * further retreive correct type of weapon.
	 *
	 * @return UnrealId of the weapon the agent is currently holding in hands.
	 *
	 * @see getCurrentAmmo()
	 * @see getCurrentAlternateAmmo()
	 * @see Inventory#getCurrentWeapon()
	 * @see Inventory#getWeapon(UnrealId)
	 */
	public UnrealId getCurrentWeapon()
	{
		// retreive from self object
		return self.getWeapon();
	}

	/**
	 * Tells, how much ammunition the agent has left for the current weapon
	 * in its primary firing mode.
	 *
	 * @return Amount of ammunition for the primary firing mode.
	 *
	 * @see getCurrentAlternateAmmo()
	 * @see Inventory#getCurrentPrimaryAmmo()
	 */
	public int getCurrentAmmo()
	{
		// retreive from self object
		return self.getPrimaryAmmo();
	}

	/**
	 * Tells, how much ammunition the agent has left for the current weapon
	 * in its alternate firing mode. Note that many weapons use primary ammo
	 * for the alternate firing mode as well. In such cases, the amount of
	 * ammo for primary mode is returned.
	 *
	 * @return Amount of ammunition for the alternate firing mode.
	 *
	 * @see getCurrentAmmo()
	 * @see Inventory#getCurrentAlternateAmmo()
	 */
	public int getCurrentAlternateAmmo()
	{
		// retreive from self object
		return self.getSecondaryAmmo();
	}

	/*========================================================================*/

	/**
	 * Tells, whether the agent is shooting or not.
	 *
	 * <p>This method reports shooting with either primary or alternate fire
	 * mode. To distinguish between the fire modes, see {@link isPriShooting()},
	 * {@link isAltShooting()}.
	 *
	 * @return Returns true, if the agent is shooting his weapon.
	 *
	 * @see isPrimaryShooting()
	 * @see isAlternateShooting()
	 */
	public boolean isShooting()
	{
		// retreive from self object
		return self.isShooting();
	}

	/**
	 * Tells, whether the agent is shooting with primary fire mode.
	 *
	 * <p>This method reports shooting with primary fire mode only. See
	 * {@link isAltShooting()} method to determine, whether the agent shoots
	 * with alternate firing mode. See {@link isShooting()} to determine,
	 * whether the agent shoots with either primary or alternate firing mode.
	 *
	 * @return True, if the agent is shooting weapon in primary firing mode.
	 *
	 * @see isShooting()
	 * @see isAlternateShooting()
	 */
	public boolean isPrimaryShooting()
	{
		// shooting but not in altrenate fire mode
		return isShooting() && !isAlternateShooting();
	}

	/**
	 * Tells, whether the agent is shooting with alternate fire mode.
	 *
	 * <p>This method reports shooting with alternate fire mode only. See
	 * {@link isPriShooting()} method to determine, whether the agent shoots
	 * with primary firing mode. See {@link isShooting()} to determine,
	 * whether the agent shoots with either primary or alternate firing mode.
	 *
	 * @return True, if the agent is shooting his weapon in alternate firing
	 * mode.
	 *
	 * @see isShooting()
	 * @see isPrimaryShooting()
	 */
	public boolean isAlternateShooting()
	{
		// retreive from self object
		return self.isAltFiring();
	}

	/*========================================================================*/

	/**
	 * Retreives number of kills the agent scored.
	 *
	 * <p>A kill is counted, whenever the agent kills an opponent.
	 *
	 * @return Number of kills the agent scored.
	 */
	public int getKills()
	{
		// FIXME[js]: implement when available
		throw new UnsupportedOperationException("Not supported yet");
	}

	/**
	 * Retreives number of deaths the agent took.
	 *
	 * <p>A death is counted, whenever the agent dies.
	 *
	 * @return Number of deaths the agent took.
	 */
	public int getDeaths()
	{
		// retreive from PlayerScore object
		return lastPlayerScore.getDeaths();
	}

	/**
	 * Retreives number of suicides the agent commited.
	 *
	 * <p>A suicide is counted, whenever the agent dies by his own weapon, or
	 * by damaging himself by falling into pits, lava, acid, etc.
	 *
	 * <p>It can also be said that suicide is every agent's death, which could
	 * not be credited to any other player in the map.
	 *
	 * <p>Each suicide is also counted as death. See {@link getDeaths()}.
	 *
	 * @return Number of suicides the agent commited.
	 */
	public int getSuicides()
	{
		// FIXME[js]: implement when available
		throw new UnsupportedOperationException("Not supported yet");
	}

	/**
	 * Retreives current agent score.
	 *
	 * <p>Agent score is usually rising by achieving some goals, e.g. killing
	 * opponents, capturing flags, controlling domination points, etc. Note:
	 * Agent score might decrease upon suicides, based on map, game type and
	 * game settings.
	 *
	 * @return Current agent score.
	 */
	public int getScore()
	{
		// retreive from PlayerScore object
		return lastPlayerScore.getScore();
	}

	/**
	 * Retreives current agent's team score.
	 *
	 * <p>Agent's team score is usually rising by achieving team goals, e.g.
	 * killing opponents, capturing flags, controlling domination points, etc.
	 * Note: Agent's team score might decrease, when oposing teams score points
	 * themselves, based on map, game type and game settings.
	 *
	 * @return Current agent's team score.
	 */
	public int getTeamScore()
	{
		// retreive from PlayerScore object
		return lastTeamScore.getScore();
	}

	/*========================================================================*/

	/**
	 * Pulling velocity in this map zone. Such pulling velocity effectively
	 * draws the player towards a specific direction or even lifts him upwards.
	 * 
	 * @return Pulling velocity in this zone.
	 */
	public Velocity getCurrentZoneVelocity()
	{
		// retreive from VolumeChanged object
		return lastVolumeChanged.getZoneVelocity();
	}

	/**
	 * Gravity in this map zone. Gravity might differ throughout different
	 * parts of the map. The gravity is expressed as a velocity vector. This
	 * vector is used an acceleration. The fall speed may ramp up, to as much
	 * as {@link getFallSpeed()}.
	 * 
	 * @return Gravity in this zone.
	 */
	public Velocity getCurrentZoneGravity()
	{
		// retreive from VolumeChanged object
		return lastVolumeChanged.getZoneGravity();
	}

	/**
	 * Friction of the floor in this map volume. Friction of the floor works
	 * towards movement, slowing down the acceleration and speed of the agent
	 * in any direction.
	 * 
	 * @return Friction of the floor.
	 */
	public double getCurrentVolumeGroundFriction()
	{
		// retreive from VolumeChanged object
		return lastVolumeChanged.getGroundFriction();
	}

	/**
	 * Friction of the fluid in this map volume. Friction of the fluid works
	 * towards movement, slowing down the acceleration and speed of the agent
	 * in any direction.
	 * 
	 * @return Friction of the fluid.
	 */
	public double getCurrentVolumeFluidFriction()
	{
		// retreive from VolumeChanged object
		return lastVolumeChanged.getFluidFriction();
	}

	/**
	 * FIXME[js]: What the hell is this good for?
	 * 
	 * @return TerminalVelocity of the CurrentVolume.
	 */
	public double _getCurrentVolumeTerminalVelocity()
	{
		// retreive from VolumeChanged object
		return lastVolumeChanged.getTerminalVelocity();
	}

	/**
	 * Tells, whether the current volume is water. When the agent is in water,
	 * {@link getCurrentVolumeFluidFriction()} and {@link getWaterSpeed()} can
	 * help to determine changes to movement and speed of the agent. Also note
	 * that {@link getCurrentZoneVelocity()}, {@link getCurrentZoneGravity()},
	 * and others may change (and usually does) in water.
	 * 
	 * @return True, if the current volume is water.
	 */
	public boolean isCurrentVolumeWater()
	{
		// retreive from VolumeChanged object
		return lastVolumeChanged.isWaterVolume();
	}

	/**
	 * Tells, whether the current volume is causing damage. Such damage is
	 * applied to the agent's health every second. The amount of damage taken
	 * per each second spent in this volume can be determined by
	 * {@link getCurrentVolumeDamagePerSec()}. When the volume damages the
	 * agent to the death, the death is counted as a suicide.
	 * 
	 * @return True, if the current volume is causing damage.
	 * 
	 * @see isCurrentVolumeDestructive()
	 * @see getCurrentVolumeDamagePerSec()
	 */
	public boolean isCurrentVolumePainCausing()
	{
		// retreive from VolumeChanged object
		return lastVolumeChanged.isPainCausing();
	}

	/**
	 * Amount of damage taken for spending time in the current volume. Such
	 * damage is applied to the agent's health every second. When the volume
	 * damages the agent to the death, the death is counted as a suicide.
	 * 
	 * @return Amount of damage taken for spending time in the current volume.
	 * 
	 * @see isCurrentVolumePainCausing()
	 */
	public double getCurrentVolumeDamagePerSec()
	{
		// retreive from VolumeChanged object
		return lastVolumeChanged.getDamagePerSec();
	}

	/**
	 * Tells, whether the current volume kills the actors (almost) instantly.
	 * Death in such destructive volume is counted as a suicide.
	 * 
	 * @return True, if the current volume kills (almost) instantly.
	 * 
	 * @see isCurrentVolumePainCausing()
	 */
	public boolean isCurrentVolumeDestructive()
	{
		// retreive from VolumeChanged object
		return lastVolumeChanged.isDestructive();
	}

	/**
	 * Retreives type of damage the current volume inflicts to the agent while
	 * he spends time in this volume.
	 * 
	 * <p>FIXME[js]: Is is possible to provide an enum here?
	 * 
	 * @return Type of the damage the current volume inflicts to the agent.
	 */
	public String getCurrentVolumeDamageType()
	{
		// retreive from VolumeChanged object
		return lastVolumeChanged.getDamageType();
	}

	/**
	 * Tells, whether the current volume (the one the agent is within) forbids
	 * usage of the inventory. If so, no weapons or items can be used, changed,
	 * or picked up.
	 * 
	 * @return True, if the current volume forbids usage of the inventory.
	 */
	public boolean isCurrentVolumeBanningInventory()
	{
		// retreive from VolumeChanged object
		return lastVolumeChanged.isNoInventory();
	}

	/**
	 * Tells, whether the current volume imparts its velocity to projectiles.
	 * E.g. A volume might impart velocity to players to emulate <i>wind</i>.
	 * This settings tells, whether the same applies to projectiels. If so,
	 * Their trajectory will be affected by this volume velocity.
	 * 
	 * @return True, if the current volume imparts its velocity to projectiles.
	 */
	public boolean isCurrentVolumeAffectingProjectiles()
	{
		// retreive from VolumeChanged object
		return lastVolumeChanged.isMoveProjectiles();
	}

	/**
	 * Tells, whether the current zone is a neutral zone. In neutral zone,
	 * players can't take damage.
	 * 
	 * @return True, if the current zone is a neutral zone.
	 */
	public boolean isCurrentZoneNeutral()
	{
		// retreive from VolumeChanged object
		return lastVolumeChanged.isNeutralZone();
	}

	/*========================================================================*/

	/**
	 * Retreives scaling factor for damage dealt by the agent. All damage
	 * dealt by the agent is reduced (or increased) by this value.
	 *
	 * @return Scaling factor for damage dealt by the agent.
	 */
	public double getDamageScaling()
	{
		// retreive from InitedMessage object
		return lastInitedMessage.getDamageScaling();
	}

	/*========================================================================*/

	/**
	 * Retreives maximum base speed of the agent.
	 *
	 * @return Maximum base speed of the agent.
	 */
	public double getBaseSpeed()
	{
		// retreive from InitedMessage object
		return lastInitedMessage.getGroundSpeed();
	}

	/**
	 * Retreives maximum speed of the agent while moving in the air.
	 *
	 * @return Maximum speed of the agent while moving in the air.
	 */
	public double getAirSpeed()
	{
		// retreive from InitedMessage object
		return lastInitedMessage.getAirSpeed();
	}

	/**
	 * Retreives maximum speed of the agent while moving on a ladder.
	 *
	 * @return Maximum speed of the agent while moving on a ladder.
	 */
	public double getLadderSpeed()
	{
		// retreive from InitedMessage object
		return lastInitedMessage.getLadderSpeed();
	}

	/**
	 * Retreives maximum speed of the agent while moving in water.
	 *
	 * @return Maximum speed of the agent while moving in water.
	 */
	public double getWaterSpeed()
	{
		// retreive from InitedMessage object
		return lastInitedMessage.getWaterSpeed();
	}

	/**
	 * Retreives maximum speed of the agent while falling.
	 *
	 * @return Maximum speed of the agent while falling.
	 */
	public double getFallSpeed()
	{
		// retreive from InitedMessage object
		return lastInitedMessage.getMaxFallSpeed();
	}

	/**
	 * Retreives maximum speed of the agent while using dodge.
	 * 
	 * <p>FIXME[js]: Check about the name depending on the meaning/value.
	 *
	 * @return Maximum speed of the agent while using dodge.
	 */
	public double getDodgeSpeedFactor()
	{
		// retreive from InitedMessage object
		return lastInitedMessage.getDodgeSpeedFactor();
	}

	/**
	 * Retreives acceleration rate of the agent.
	 *
	 * @return Acceleration rate of the agent.
	 */
	public double getAccelerationRate()
	{
		// retreive from InitedMessage object
		return lastInitedMessage.getAccelRate();
	}

	/**
	 * Retreives agent's control of movement while in the air.
	 * This value ranges from 0 (none) to 1 (full control).
	 *
	 * @return Agent's control of movement while in the air.
	 */
	public double getAirControl()
	{
		// retreive from InitedMessage object
		return lastInitedMessage.getAirControl();
	}

	/**
	 * Retreives boost of the agent in the Z axis while jumping.
	 *
	 * @return Jumping boost of the agent in the Z axis.
	 */
	public double getJumpZBoost()
	{
		// retreive from InitedMessage object
		return lastInitedMessage.getJumpZ();
	}

	/**
	 * Retreives boost of the agent in the Z axis while using dodge.
	 *
	 * @return Dodge boost of the agent in the Z axis.
	 */
	public double getDodgeZBoost()
	{
		// retreive from InitedMessage object
		return lastInitedMessage.getDodgeSpeedZ();
	}

	/*========================================================================*/

	/**
	 * Retreives current game time, since the game started.
	 *
	 * @return Current game timestamp.
	 */
	protected double getTime()
	{
		// retreive from Game memory module
		return game.getTime();
	}

	/*========================================================================*/

	/** Most rescent message containing info about the agent. */
	Self self = null;

	/** Most rescent message containing info about the game frame. */
	InitedMessage lastInitedMessage = null;

	/** Most rescent message containing info about the player's score. */
	PlayerScore lastPlayerScore = null;

	/** Most rescent message containing info about the player team's score. */
	TeamScore lastTeamScore = null;

	/** Most rescent message containing info about the volume the player is in. */
	VolumeChanged lastVolumeChanged = null;

	/*========================================================================*/

	/**
	 * InitedMessage listener.
	 */
	private class InitedMessageListener implements WorldEventListener<InitedMessage>
	{
		@Override
		public void notify(InitedMessage event)
		{
			lastInitedMessage = event;
		}

		/**
		 * Constructor. Registers itself on the given WorldView object.
		 * @param worldView WorldView object to listent to.
		 */
		public InitedMessageListener(IWorldView worldView)
		{
			worldView.addListener(InitedMessage.class, this);
		}
	}

	/** InitedMessage listener */
	private InitedMessageListener initedMessageListener;

	/*========================================================================*/

	/**
	 * PlayerScore listener.
	 */
	private class PlayerScoreListener implements WorldEventListener<PlayerScore>
	{
		@Override
		public void notify(PlayerScore event)
		{
			lastPlayerScore = event;
		}

		/**
		 * Constructor. Registers itself on the given WorldView object.
		 * @param worldView WorldView object to listent to.
		 */
		public PlayerScoreListener(IWorldView worldView)
		{
			worldView.addListener(PlayerScore.class, this);
		}
	}

	/** PlayerScore listener */
	private PlayerScoreListener playerScoreListener;

	/*========================================================================*/

	/**
	 * VolumeChanged listener.
	 */
	private class VolumeChangedListener implements WorldEventListener<VolumeChanged>
	{
		@Override
		public void notify(VolumeChanged event)
		{
			lastVolumeChanged = event;
		}

		/**
		 * Constructor. Registers itself on the given WorldView object.
		 * @param worldView WorldView object to listent to.
		 */
		public VolumeChangedListener(IWorldView worldView)
		{
			worldView.addListener(VolumeChanged.class, this);
		}
	}

	/** VolumeChanged listener */
	private VolumeChangedListener volumeChangedListener;
	
	/*========================================================================*/

	/**
	 * VolumeChanged listener.
	 */
	private class SelfListener implements WorldEventListener<WorldObjectFirstEncounteredEvent>
	{
		private IWorldView worldView;

	
		/**
		 * Constructor. Registers itself on the given WorldView object.
		 * @param worldView WorldView object to listent to.
		 */
		public SelfListener(IWorldView worldView)
		{			
			worldView.addListener(WorldObjectFirstEncounteredEvent.class, this);
			this.worldView = worldView;
		}

		@Override
		public void notify(WorldObjectFirstEncounteredEvent event) {
			if (event.getObject() instanceof Self) {
				self = (Self) event.getObject();
				worldView.removeListener(WorldObjectFirstEncounteredEvent.class, this);
			}
			
		}
	}

	/** VolumeChanged listener */
	private SelfListener selfListener;


	/*========================================================================*/
	
	public Self getSelf() {
		return self;
	}

	/** Game memory module. */
	protected Game game;

	/**
	 * Constructor. Setups the memory module based on given WorldView.
	 * @param worldView WorldView object to read the info from.
	 * @param game Game memory module. Note: If <i>null</i> is provided, this
	 * memory module creates its own Game memory module. Provide shared Game
	 * memory module to economize CPU time and other resources.
	 * @param log Logger to be used for logging runtime/debug info.
	 */
	public AgentInfo(IWorldView worldView, Game game, Logger log)
	{
		super(log);

		// set or create Game memory module
		if (game != null) this.game = game;
		else this.game = new Game(worldView, log);

		// create listeners
		selfListener = new SelfListener(worldView);		
		initedMessageListener = new InitedMessageListener(worldView);
		playerScoreListener = new PlayerScoreListener(worldView);
		// TODO teamScoreListener = new TeamScoreListener(worldView);
		volumeChangedListener = new VolumeChangedListener(worldView);
	}
}
