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

import cz.cuni.amis.pogamut.base.agent.worldview.IWorldView;
import cz.cuni.amis.pogamut.base.agent.worldview.WorldEventListener;
import cz.cuni.amis.pogamut.ut2004.agent.module.AgentModule;
import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.GameInfo;
import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.BeginMessage;
import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.InitedMessage;

import java.util.logging.Logger;

/**
 * Memory module specialized on general info about the game.
 *
 * @author Juraj 'Loque' Simlovic
 */
public class Game extends AgentModule
{
	public enum GameType
	{
		/** Classic death-match: Kill or get killed. You're on you own! */
		BotDeathMatch,
		/** Team death-match: Strategic team killing. Shoot opponents only. */
		BotTeamGame,
		/** Capture the Flag! Raid the enemy base, steal their flag. */
		BotCTFGame,
		/** Bombing run. Play soccer in UT2004, either kick ball or shoot. */
		BotBombingRun,
		/** Double domination. Take control of specific spots on the map. */
		BotDoubleDomination,
		/** This type of game is not supported. */
		Unknown;

		/**
		 * Tedious work this is.. Let's do it once, shall we?
		 *
		 * @param type Name of the type of the game type.
		 * @return Game type associated with given name.
		 */
		public static GameType getType(String type)
		{
			if (type.equals("BotDeathMatch")) return BotDeathMatch;
			if (type.equals("BotTeamGame")) return BotTeamGame;
			if (type.equals("BotCTFGame")) return BotCTFGame;
			if (type.equals("BotBombingRun")) return BotBombingRun;
			if (type.equals("BotDoubleDomination")) return BotDoubleDomination;
			return Unknown;
		}
	}

	/**
	 * Retreives the type of the game.
	 *
	 * @return Type of the game.
	 */
	public GameType getGameType()
	{
		// retreive from GameInfo object and translate
		return GameType.getType(lastGameInfo.getGametype());
	}

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

	/**
	 * Retreives the name of current map.
	 *
	 * @return Name of the current map.
	 */
	public String getMapName()
	{
		// retreive from GameInfo object
		return lastGameInfo.getLevel();
	}

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

	/**
	 * Retreives current game time, since the game started.
	 *
	 * @return Current game timestamp.
	 *
	 * @todo Test, whether it is correct..
	 */
	public double getTime()
	{
		// retreive from last BeginMessage object
		return lastBeginMessage.getTime();
	}

	/**
	 * Retreives time limit for the game.
	 *
	 * <p>Note: Then the time limit is reached and the game is tie, special
	 * game modes might be turned on, e.g. <i>sudden death overtime</i>.
	 * Depends on the game type and game settings.
	 *
	 * @return Time limit of the game.
	 *
	 * @see getRemainingTime()
	 */
	public double getTimeLimit()
	{
		// retreive from GameInfo object
		return lastGameInfo.getTimeLimit();
	}

	/**
	 * Retreives time remaining for the game.
	 *
	 * <p>Note: Then the time limit is reached and the game is tie, special
	 * game modes might be turned on, e.g. <i>sudden death overtime</i>.
	 * Depends on the game type and game settings.
	 *
	 * @return Time limit of the game.
	 *
	 * @see getTime()
	 * @see getTimeLimit()
	 *
	 * @todo Test, whether it is correct..
	 */
	public double getRemainingTime()
	{
		// derive from the time limit and current time
		return getTimeLimit() - getTime();
	}

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

	/**
	 * BotDeathMatch only:
	 * Number of points (e.g. kills) needed to win the game.
	 *
	 * @return Frag limit of the game.
	 *
	 * @see getTeamScoreLimit()
	 */
	public int getFragLimit()
	{
		// retreive from GameInfo object
		return lastGameInfo.getFragLimit();
	}

	/**
	 * BotTeamGame, BotCTFGame, BotBombingRun, BotDoubleDomination only:
	 * Number of points a team needs to win the game.
	 *
	 * @return Team score limit of the game.
	 *
	 * @see getFragLimit()
	 */
	public int getTeamScoreLimit()
	{
		// retreive from GameInfo object
		// FIXME[js]: Return type!
		return (int)lastGameInfo.getGoalTeamScore();
	}

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

	/**
	 * Retreives number of teams in the game.
	 *
	 * <p> Team numbers start from 0. Only in BotTeamGame, BotCTFGame,
	 * BotDoubleDomination. Usually, there are just two teams: 0 and 1.
	 *
	 * @return Number of teams in the game.
	 */
	public int getMaxTeams()
	{
		// retreive from GameInfo object
		return lastGameInfo.getMaxTeams();
	}

	/**
	 * Retreives maximum number of players per team.
	 *
	 * <p>BotTeamGame, BotCTFGame, BotDoubleDomination only.
	 *
	 * @return Maximum number of players per team.
	 */
	public int getMaxTeamSize()
	{
		// retreive from GameInfo object
		return lastGameInfo.getMaxTeamSize();
	}

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

	/**
	 * Retreives starting level of health. This is the level of health the
	 * players spawn with into the game.
	 *
	 * @return Starting level of health.
	 *
	 * @see getMaxHealth()
	 * @see getFullHealth()
	 */
	public int getStartHealth()
	{
		// retreive from InitedMessage object
		return lastInitedMessage.getHealthStart();
	}

	/**
	 * Retreives maximum level of <i>non-boosted</i> health. This is the level
	 * achievable by foraging standard health kits.
	 *
	 * @return Maximum level of <i>non-boosted</i> health.
	 *
	 * @see getStartHealth()
	 * @see getMaxHealth()
	 */
	public int getFullHealth()
	{
		// retreive from InitedMessage object
		return lastInitedMessage.getHealthFull();
	}

	/**
	 * Retreives maximum level of <i>boosted</i> health. This is the total
	 * maximum health achievable by any means of health kits, super health,
	 * or health vials.
	 *
	 * @return Maximum level of <i>boosted</i> health.
	 *
	 * @see getStartHealth()
	 * @see getFullHealth()
	 */
	public int getMaxHealth()
	{
		// retreive from InitedMessage object
		return lastInitedMessage.getHealthMax();
	}

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

	/**
	 * Retreives maximum level of combined armor. The armor consist of two
	 * parts, which are summed together into combined armor value. However,
	 * each part is powered-up by different item (either by <i>small shield</i>
	 * or by <i>super-shield</i>).
	 *
	 * @return Maximum level of combined armor.
	 *
	 * @see getMaxLowArmor()
	 * @see getMaxHighArmor()
	 */
	public int getMaxArmor()
	{
		// retreive from InitedMessage object
		return lastInitedMessage.getShieldStrengthMax();
	}

	/**
	 * Retreives maximum level of low armor. The armor consist of two
	 * parts, which are summed together into combined armor value. However,
	 * each part is powered-up by different item (either by <i>small shield</i>
	 * or by <i>super-shield</i>).
	 *
	 * <p>Low armor is powered-up by <i>small shield</i>.
	 *
	 * @return Maximum level of low armor.
	 *
	 * @see getMaxArmor()
	 * @see getMaxHighArmor()
	 */
	public int getMaxLowArmor()
	{
		// FIXME[js]: Where do we retreive the max low-armor info?
		return 50;
	}

	/**
	 * Retreives maximum level of high armor. The armor consist of two
	 * parts, which are summed together into combined armor value. However,
	 * each part is powered-up by different item (either by <i>small shield</i>
	 * or by <i>super-shield</i>).
	 *
	 * <p>High armor is powered-up by <i>super-shield</i>.
	 *
	 * @return Maximum level of high armor.
	 *
	 * @see getMaxArmor()
	 * @see getMaxLowArmor()
	 */
	public int getMaxHighArmor()
	{
		// FIXME[js]: Where do we retreive the max high-armor info?
		return 100;
	}

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

	/**
	 * Retreives starting level of adrenaline. This is the level of adrenaline
	 * the players spawn with into the game.
	 *
	 * @return Starting level of adrenaline.
	 */
	public int getStartAdrenaline()
	{
		// retreive from InitedMessage object
		// FIXME[js]: Return type!
		return (int)lastInitedMessage.getAdrenalineStart();
	}

	/**
	 * Retreives target level of adrenaline that need to be gained to start
	 * special bonus actions.
	 *
	 * <p>Once the agent's adrenaline reaches this designated level, it can be
	 * used to start special bonus booster-actions like <i>invisibility</i>,
	 * <i>speed</i>, <i>booster</i>, etc. The adrenaline is then spent on the
	 * invoked action.
	 *
	 * @return Maximum level of adrenaline that can be gained.
	 */
	public int getTargetAdrenaline()
	{
		// retreive from InitedMessage object
		// FIXME[js]: Return type!
		return (int)lastInitedMessage.getAdrenalineMax();
	}

	/**
	 * Retreives maximum level of adrenaline that can be gained.
	 *
	 * @return Maximum level of adrenaline that can be gained.
	 */
	public int getMaxAdrenaline()
	{
		// retreive from InitedMessage object
		// FIXME[js]: Return type!
		return (int)lastInitedMessage.getAdrenalineMax();
	}

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

	/**
	 * Tells, whether the weapons stay on pick-up points, even when they are
	 * picked-up by players.
	 *
	 * <p>If so, each weapon type can be picked up from pick-up points only
	 * once. If the player already has the weapon the pick-up point offers, he
	 * can not pick it up. Also, each weapon pick-up point always contains its
	 * associated weapon.
	 *
	 * <p>If not, weapons can be picked up from pick-up points repeatedly.
	 * If the player already has the weapon the pick-up point offers, the
	 * pick-up will simply replenish ammo for that weapon. Also, upon each
	 * pick-up by a player, the offered weapon disappears (it is "taken" by
	 * that player). Weapons respawn on empty pick-up points after a while.
	 *
	 * @return True, if weapons stay on pick-up points.
	 */
	public boolean getWeaponsStay()
	{
		// retreive from GameInfo object
		return lastGameInfo.isWeaponStay();
	}

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

	/**
	 * Retreives the maximum number of multi-jumping combos.
	 * 
	 * <p>Note: Multi-jump combos are currently limited to double-jumps for
	 * bots.
	 * 
	 * @return Maximum number of multi-jumping combos.
	 */
	public int getMaxMultiJump()
	{
		// retreive from InitedMessage object
		return lastInitedMessage.getMaxMultiJump();
	}

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

	public void getMutators()
	{
		// FIXME[js]: Where do we retreive mutators info?
	}

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

	/**
	 * Tells, whether the game is paused or running. When the game is paused,
	 * nobody can move or do anything (usually except posting text messages).
	 *
	 * @return True, if the game is paused. False otherwise.
	 *
	 * @see areBotsPaused()
	 */
	public boolean isPaused()
	{
		// retreive from GameInfo object
		return lastGameInfo.isGamePaused();
	}

	/**
	 * Tells, whether the bots are paused or running. When the bots are paused,
	 * but the game is not paused as well, human controlled players can move.
	 * The bots are standing still and can do nothing  (usually except posting
	 * text messages).
	 *
	 * @return True, if the bots are paused. False otherwise.
	 *
	 * @see isPaused()
	 */
	public boolean isBotsPaused()
	{
		// retreive from GameInfo object
		return lastGameInfo.isBotsPaused();
	}

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

	/** Most rescent message containing info about the game. */
	GameInfo lastGameInfo = null;

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

	/** Most rescent message containing info about the game frame. */
	BeginMessage lastBeginMessage = null;

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

	/**
	 * GameInfo listener.
	 */
	private class GameInfoListener implements WorldEventListener<GameInfo>
	{
		@Override
		public void notify(GameInfo event)
		{
			lastGameInfo = event;
		}

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

	/** GameInfo listener */
	GameInfoListener gameInfoListener;

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

	/**
	 * 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 */
	InitedMessageListener initedMessageListener;

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

	/**
	 * BeginMessage listener.
	 */
	private class BeginMessageListener implements WorldEventListener<BeginMessage>
	{
		@Override
		public void notify(BeginMessage event)
		{
			lastBeginMessage = event;
		}

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

	/** BeginMessage listener */
	BeginMessageListener beginMessageListener;

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

	/**
	 * Constructor. Setups the memory module based on given WorldView.
	 * @param worldView WorldView object to read the info from.
	 * @param log Logger to be used for logging runtime/debug info.
	 */
	public Game(IWorldView worldView, Logger log)
	{
		super(log);

		// create listeners
		gameInfoListener = new GameInfoListener(worldView);
		beginMessageListener = new BeginMessageListener(worldView);
		initedMessageListener = new InitedMessageListener(worldView);
	}
}
