package nl.tudelft.jpacman;

import java.awt.event.KeyEvent;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;

import nl.tudelft.jpacman.board.BoardFactory;
import nl.tudelft.jpacman.board.Direction;
import nl.tudelft.jpacman.game.Game;
import nl.tudelft.jpacman.game.GameFactory;
import nl.tudelft.jpacman.level.Level;
import nl.tudelft.jpacman.level.LevelFactory;
import nl.tudelft.jpacman.level.MapParser;
import nl.tudelft.jpacman.level.Player;
import nl.tudelft.jpacman.level.PlayerFactory;
import nl.tudelft.jpacman.npc.ghost.GhostFactory;
import nl.tudelft.jpacman.sprite.PacManSprites;
import nl.tudelft.jpacman.ui.Action;
import nl.tudelft.jpacman.ui.PacManUI;
import nl.tudelft.jpacman.ui.PacManUiBuilder;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;

/**
 * Creates and launches the JPacMan UI.
 * 
 * @author Jeroen Roosen
 */
@SuppressWarnings("PMD.TooManyMethods")
public class Launcher {

	private static final PacManSprites SPRITE_STORE = new PacManSprites();

	public static final String DEFAULT_MAP = "/board.txt";
	private String levelMap = DEFAULT_MAP;

	@MonotonicNonNull private PacManUI pacManUI;
	@MonotonicNonNull private Game game;

	/**
	 * @return The game object this launcher will start when {@link #launch()}
	 *         is called.
	 */
	public Game getGame() {
		assert game != null;
		return game;
	}

	/**
	 * The map file used to populate the level.
	 * 
	 * @return The name of the map file.
	 */
	protected String getLevelMap() {
		return levelMap;
	}

	/**
	 * Set the name of the file containing this level's map.
	 * 
	 * @param fileName
	 *            Map to be used.
	 * @return Level corresponding to the given map.
	 */
	public Launcher withMapFile(String fileName) {
		levelMap = fileName;
		return this;
	}

	/**
	 * Creates a new game using the level from {@link #makeLevel()}.
	 * 
	 * @return a new Game.
	 */
	public Game makeGame() {
		GameFactory gf = getGameFactory();
		Level level = makeLevel();
		game = gf.createSinglePlayerGame(level);
		return game;
	}

	/**
	 * Creates a new level. By default this method will use the map parser to
	 * parse the default board stored in the <code>board.txt</code> resource.
	 * 
	 * @return A new level.
	 */
	public Level makeLevel() {
		MapParser parser = getMapParser();
		try (InputStream boardStream = Launcher.class.getResourceAsStream(getLevelMap())) {
			return parser.parseMap(boardStream);
		} catch (IOException e) {
			throw new PacmanConfigurationException("Unable to create level.", e);
		}
	}

	/**
	 * @return A new map parser object using the factories from
	 *         {@link #getLevelFactory()} and {@link #getBoardFactory()}.
	 */
	protected MapParser getMapParser() {
		return new MapParser(getLevelFactory(), getBoardFactory());
	}

	/**
	 * @return A new board factory using the sprite store from
	 *         {@link #getSpriteStore()}.
	 */
	protected BoardFactory getBoardFactory() {
		return new BoardFactory(getSpriteStore());
	}

	/**
	 * @return The default {@link PacManSprites}.
	 */
	protected PacManSprites getSpriteStore() {
		return SPRITE_STORE;
	}

	/**
	 * @return A new factory using the sprites from {@link #getSpriteStore()}
	 *         and the ghosts from {@link #getGhostFactory()}.
	 */
	protected LevelFactory getLevelFactory() {
		return new LevelFactory(getSpriteStore(), getGhostFactory());
	}

	/**
	 * @return A new factory using the sprites from {@link #getSpriteStore()}.
	 */
	protected GhostFactory getGhostFactory() {
		return new GhostFactory(getSpriteStore());
	}

	/**
	 * @return A new factory using the players from {@link #getPlayerFactory()}.
	 */
	protected GameFactory getGameFactory() {
		return new GameFactory(getPlayerFactory());
	}

	/**
	 * @return A new factory using the sprites from {@link #getSpriteStore()}.
	 */
	protected PlayerFactory getPlayerFactory() {
		return new PlayerFactory(getSpriteStore());
	}

	/**
	 * Adds key events UP, DOWN, LEFT and RIGHT to a game.
	 * 
	 * @param builder
	 *            The {@link PacManUiBuilder} that will provide the UI.
	 */
	protected void addSinglePlayerKeys(final PacManUiBuilder builder) {
		builder.addKey(KeyEvent.VK_UP, moveTowardsDirection(Direction.NORTH))
				.addKey(KeyEvent.VK_DOWN, moveTowardsDirection(Direction.SOUTH))
				.addKey(KeyEvent.VK_LEFT, moveTowardsDirection(Direction.WEST))
				.addKey(KeyEvent.VK_RIGHT, moveTowardsDirection(Direction.EAST));
	}

	private Action moveTowardsDirection(Direction direction) {
		return new Action() {

			@Override
			public void doAction() {
				getGame().move(getSinglePlayer(getGame()), direction);
			}
		};
	}

	private Player getSinglePlayer(final Game game) {
		List<Player> players = game.getPlayers();
		if (players.isEmpty()) {
			throw new IllegalArgumentException("Game has 0 players.");
		}
		return players.get(0);
	}

	/**
	 * Creates and starts a JPac-Man game.
	 */
	public void launch() {
		makeGame();
		PacManUiBuilder builder = new PacManUiBuilder().withDefaultButtons();
		addSinglePlayerKeys(builder);
		pacManUI = builder.build(getGame());
		pacManUI.start();
	}

	/**
	 * Disposes of the UI. For more information see
	 * {@link javax.swing.JFrame#dispose()}.
	 *
	 * Precondition: The game was launched first.
	 */
	public void dispose() {
		assert pacManUI != null;
		pacManUI.dispose();
	}

	/**
	 * Main execution method for the Launcher.
	 * 
	 * @param args
	 *            The command line arguments - which are ignored.
	 * @throws IOException
	 *             When a resource could not be read.
	 */
	public static void main(String[] args) throws IOException {
		new Launcher().launch();
	}
}

@@ -2314,16 +2314,50 @@
 rser();%0A
+%09%09String mapName = getLevelMap();%0A
 %09%09try (I
@@ -2420,26 +2420,51 @@
 eam(
-getLevelMap())
+mapName)) %7B%0A%09%09%09if (boardStream != null
 ) %7B%0A
+%09
 %09%09%09r
@@ -2499,16 +2499,119 @@
 tream);%0A
+%09%09%09%7D else %7B%0A%09%09%09%09throw new PacmanConfigurationException(%22Could not get resource for: %22 + mapName);%0A%09%09%09%7D%0A
 %09%09%7D catc
@@ -2695,18 +2695,36 @@
 te level
-.%22
+, name = %22 + mapName
 , e);%0A%09%09

Check if map resource returned is null.