package org.bwapi.proxy.model;

import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.bwapi.proxy.messages.BasicTypes;
import org.bwapi.proxy.messages.GameMessages;
import org.bwapi.proxy.messages.DrawCommands.ShapeType;
import org.bwapi.proxy.messages.GameMessages.StaticGameData;
import org.bwapi.proxy.messages.Messages.FrameCommands;
import org.bwapi.proxy.messages.Messages.FrameMessage;

import org.bwapi.proxy.messages.DrawCommands;

public class Game {
	public boolean isDrawing() {
		return drawing;
	}

	public static final int TILE_SIZE = 32;

	protected static Game game = new Game();

	protected UnitManager unitManager;
	protected PlayerManager playerManager;

	protected CommandManager commandManager;

	// Static data
	Set<Unit> staticMinerals;
	Set<Unit> staticGeysers;
	Set<Unit> staticNeutralUnits;
	Set<TilePosition> startLocations = new HashSet<TilePosition>();
	private Set<Bullet> bullets = new HashSet<Bullet>();

	int mapWidth;
	int mapHeight;
	String mapFilename;
	String mapName;
	int mapHash;

	boolean[][] walkable;
	boolean[][] buildable;
	int[][] groundHeight;

	// dynamic data:
	boolean[][] visible;
	boolean[][] explored;
	boolean[][] hasCreep;

	// Temporary data
	private FrameMessage currentFrameData;
	Set<Unit> selectedUnits;

	private Player self;

	private Set<Unit>[][] unitsOnTile;

	private Set<Unit> pylons;
	private Set<Unit> geysers;
	private Set<Unit> minerals;
	private Set<Unit> neutralUnits;

	private boolean drawing = false;

	private boolean[][] unpackBitmask(List<Integer> list, int dim1, int dim2) {
		boolean[][] res = new boolean[dim1][dim2];
		Iterator<Integer> it = list.iterator();
		int bit = 0;
		int field = 0;
		for (int x = 0; x < dim1; ++x) {
			for (int y = 0; y < dim2; ++y) {
				if (bit == 0) {
					field = it.next();
				}
				res[x][y] = ((1 << bit) & field) != 0;

				if (++bit == 32) {
					bit = 0;
				}
			}
		}

		return res;
	}

	private Set<Unit> buildUnitSet(List<GameMessages.UnitId> list) {
		Set<Unit> set = new HashSet<Unit>();
		for (GameMessages.UnitId u : list) {
			set.add(unitManager.getUnit(u.getId()));
		}
		return set;
	}

	public static Game getInstance() {
		return game;
	}

	public void init() {
		unitManager = new UnitManager();
		playerManager = new PlayerManager();

		commandManager = new CommandManager();
	}

	public Set<Player> getPlayers() {
		return playerManager.getAllPlayers();
	}

	public Set<Unit> getAllUnits() {
		return unitManager.getAllUnits();
	}

	public Set<Unit> unitsOnTile(int x, int y) {
		if (x < 0 || y < 0 || x >= mapWidth || y >= mapHeight)
			return Collections.<Unit> emptySet();
		return unitsOnTile[x][y] == null ? Collections.<Unit> emptySet() : unitsOnTile[x][y];
	}

	public Set<Unit> getGeysers() {
		return geysers;
	}

	public Set<Unit> getMinerals() {
		return minerals;
	}

	public Set<Unit> getNeutralUnits() {
		return neutralUnits;
	}

	public Set<TilePosition> getStartLocations() {
		return startLocations;
	}

	public Set<Unit> getStaticMinerals() {
		return staticMinerals;
	}

	public Set<Unit> getStaticGeysers() {
		return staticGeysers;
	}

	public Set<Unit> getStaticNeutralUnits() {
		return staticNeutralUnits;
	}

	public int getMapWidth() {
		return mapWidth;
	}

	public int getMapHeight() {
		return mapHeight;
	}

	/*
	// Deprecated: Use getMapName() because the filename may change.
	public String getMapFilename() {
		return mapFilename;
	}
	//*/

	public String getMapName() {
		return mapName;
	}

	public int getMapHash() {
		return mapHash;
	}

	public int getLatency() {
		return currentFrameData.getLatency();
	}

	// More dynamic stuff:
	public int getFrameCount() {
		return currentFrameData == null ? 0 : currentFrameData.getFrameCount();
	}

	public int getMouseX() {
		return currentFrameData.getMouseX();
	}

	public int getMouseY() {
		return currentFrameData.getMouseY();
	}

	public int getScreenX() {
		return currentFrameData.getScreenX();
	}

	public int getScreenY() {
		return currentFrameData.getScreenY();
	}

	public int getGroundHeight(int x, int y) {
		if (x >= 0 && y >= 0 && y < mapHeight*4 && x < mapWidth*4)
			return groundHeight[x][y];
		else
			return -1;
	}

	public boolean isWalkable(int x, int y) {
		return x >= 0 && y >= 0 && y < (mapHeight * 4) && x < (mapWidth * 4) && walkable[x][y];
	}

	public boolean isBuildable(int x, int y) {
		return x >= 0 && y >= 0 && y < (mapHeight) && x < (mapWidth) && buildable[x][y];
	}

	public boolean isVisible(int x, int y) {
		return x >= 0 && y >= 0 && y < (mapHeight) && x < (mapWidth) && visible[x][y];
	}

	public boolean isVisible(TilePosition pos) {
		return isVisible(pos.x(), pos.y());
	}

	public boolean isExplored(int x, int y) {
		return x >= 0 && y >= 0 && y < (mapHeight) && x < (mapWidth) && explored[x][y];
	}

	public boolean isExplored(TilePosition here) {
		return isExplored(here.x(), here.y());
	}

	public boolean hasCreep(int x, int y) {
		return x >= 0 && y >= 0 && y < (mapHeight) && x < (mapWidth) && hasCreep[x][y];
	}

	public void readStaticGameData(StaticGameData data) {
		staticMinerals = buildUnitSet(data.getStaticMineralsList());
		staticGeysers = buildUnitSet(data.getStaticGeysersList());
		staticNeutralUnits = buildUnitSet(data.getStaticNeutralUnitsList());

		mapWidth = data.getMapWidth();
		mapHeight = data.getMapHeight();
		mapFilename = data.getMapFilename();
		mapName = data.getMapName();
		mapHash = data.getMapHash();

		walkable = unpackBitmask(data.getWalkableList(), mapWidth * 4, mapHeight * 4);
		buildable = unpackBitmask(data.getBuildableList(), mapWidth, mapHeight);
		
		groundHeight = new int[mapWidth * 4][mapHeight * 4];
		int i = 0;
		for(int x = 0; x < mapWidth * 4; ++x)
			for(int y = 0; y < mapHeight * 4; ++y) {
				groundHeight[x][y] = data.getGroundHeight(i++);
			}

		for (BasicTypes.TilePosition start : data.getStartLocationsList()) {
			startLocations.add(new TilePosition(start));
		}

	}

	public void readFrameMessage(FrameMessage message) {
		this.currentFrameData = message;

		for (GameMessages.Player p : message.getPlayersList()) {
			playerManager.getPlayer(p.getId()).setMessage(p);
			if (p.hasSelf() && p.getSelf()) {
				self = (playerManager.getPlayer(p.getId()));
			}
		}

		unitsOnTile = new Set[mapWidth][mapHeight];
		pylons = new HashSet<Unit>();
		minerals = new HashSet<Unit>();
		geysers = new HashSet<Unit>();
		neutralUnits = new HashSet<Unit>();

		Set<Unit> unitsToWipe = unitManager.getAllUnits();
		unitManager.clearUnits();
		for (GameMessages.Unit mu : message.getUnitsList()) {
			Unit u = unitManager.getUnit(mu.getTheID().getId());
			u.setMessage(mu);
			unitManager.addUnit(u);

			TilePosition tilePosition = u.getTilePosition();
			if (tilePosition.x() >= 0 && u.getTilePosition().x() < mapWidth && u.getTilePosition().y() < mapHeight) {
				if (unitsOnTile[tilePosition.x()][tilePosition.y()] == null) {
					unitsOnTile[tilePosition.x()][tilePosition.y()] = new HashSet<Unit>();
				}
				unitsOnTile[tilePosition.x()][tilePosition.y()].add(u);

			} else {
				// System.out.println("Weird: " + mapHeight + " " + mapWidth +
				// tilePosition);
			}

			if (u.getType().getID() == UnitType.PROTOSS_PYLON.getID()) {
				pylons.add(u);
			} else if (u.getType().getID() == UnitType.RESOURCE_MINERAL_FIELD.getID()) {
				minerals.add(u);
			} else if (u.getType().getID() == UnitType.RESOURCE_VESPENE_GEYSER.getID()) {
				geysers.add(u);
			}

			if (u.getPlayer().isNeutral()) {
				neutralUnits.add(u);
			}
		}

		unitsToWipe.removeAll(unitManager.getAllUnits());
		for (Unit u : unitsToWipe) {
			u.setMessage(null);
		}

		bullets = new HashSet<Bullet>();
		for (GameMessages.Bullet b : message.getBulletsList()) {
			bullets.add(new Bullet(b));
		}

		visible = unpackBitmask(message.getDynamicTerrainInfo().getIsVisibleList(), mapWidth, mapHeight);
		explored = unpackBitmask(message.getDynamicTerrainInfo().getIsExploredList(), mapWidth, mapHeight);
		hasCreep = unpackBitmask(message.getDynamicTerrainInfo().getHasCreepList(), mapWidth, mapHeight);

		selectedUnits = buildUnitSet(message.getSelectedUnitsList());
	}

	public Player getPlayer(int id) {
		return playerManager.getPlayer(id);
	}

	public Unit getUnit(int id) {
		return unitManager.getUnit(id);
	}

	public FrameCommands flushCommands() {
		return commandManager.flushCommands();
	}

	public void drawText(CoordinateType ctype, int x, int y, String text) {
		DrawCommands.DrawCommand cmd = DrawCommands.DrawCommand.newBuilder()
		    .setCoordinateType(ctype.getMessage()).addCoordinates(x).addCoordinates(y).setText(text).setShape(
		        ShapeType.text).build();
		Game.getInstance().commandManager.addDrawCommand(cmd);
	}

	public void drawTextMap(int x, int y, String text) {
		drawText(CoordinateType.MAP, x, y, text);
	}

	public void drawTextMouse(int x, int y, String text) {
		drawText(CoordinateType.MOUSE, x, y, text);
	}

	public void drawTextScreen(int x, int y, String text) {
		drawText(CoordinateType.SCREEN, x, y, text);
	}

	public void drawBox(CoordinateType ctype, int left, int top, int right, int bottom, Color color,
	    boolean isSolid) {
		DrawCommands.DrawCommand cmd = DrawCommands.DrawCommand.newBuilder()
		    .setCoordinateType(ctype.getMessage()).addCoordinates(left).addCoordinates(top).addCoordinates(right)
		    .addCoordinates(bottom).setColor(color.getMessage()).setIsSolid(isSolid).setShape(ShapeType.box)
		    .build();
		Game.getInstance().commandManager.addDrawCommand(cmd);
	}

	public void drawBoxMap(int left, int top, int right, int bottom, Color color, boolean isSolid) {
		drawBox(CoordinateType.MAP, left, top, right, bottom, color, isSolid);
	}

	public void drawBoxMouse(int left, int top, int right, int bottom, Color color, boolean isSolid) {
		drawBox(CoordinateType.MOUSE, left, top, right, bottom, color, isSolid);
	}

	public void drawBoxScreen(int left, int top, int right, int bottom, Color color, boolean isSolid) {
		drawBox(CoordinateType.SCREEN, left, top, right, bottom, color, isSolid);
	}

	public void drawTriangle(CoordinateType ctype, int ax, int ay, int bx, int by, int cx, int cy, Color color,
	    boolean isSolid) {
		DrawCommands.DrawCommand cmd = DrawCommands.DrawCommand.newBuilder()
		    .setCoordinateType(ctype.getMessage()).addCoordinates(ax).addCoordinates(ay).addCoordinates(bx)
		    .addCoordinates(by).addCoordinates(cx).addCoordinates(cy).setColor(color.getMessage()).setIsSolid(
		        isSolid).setShape(ShapeType.triangle).build();
		Game.getInstance().commandManager.addDrawCommand(cmd);
	}

	public void drawTriangleMap(int ax, int ay, int bx, int by, int cx, int cy, Color color, boolean isSolid) {
		drawTriangle(CoordinateType.MAP, ax, ay, bx, by, cx, cy, color, isSolid);
	}

	public void drawTriangleMouse(int ax, int ay, int bx, int by, int cx, int cy, Color color, boolean isSolid) {
		drawTriangle(CoordinateType.MOUSE, ax, ay, bx, by, cx, cy, color, isSolid);
	}

	public void drawTriangleScreen(int ax, int ay, int bx, int by, int cx, int cy, Color color, boolean isSolid) {
		drawTriangle(CoordinateType.SCREEN, ax, ay, bx, by, cx, cy, color, isSolid);
	}

	public void drawCircle(CoordinateType ctype, int x, int y, int radius, Color color, boolean isSolid) {
		DrawCommands.DrawCommand cmd = DrawCommands.DrawCommand.newBuilder()
		    .setCoordinateType(ctype.getMessage()).addCoordinates(x).addCoordinates(y).addCoordinates(radius)
		    .setColor(color.getMessage()).setShape(ShapeType.circle).setIsSolid(isSolid).build();
		Game.getInstance().commandManager.addDrawCommand(cmd);
	}

	public void drawCircleMap(int x, int y, int radius, Color color, boolean isSolid) {
		drawCircle(CoordinateType.MAP, x, y, radius, color, isSolid);
	}

	public void drawCircleMouse(int x, int y, int radius, Color color, boolean isSolid) {
		drawCircle(CoordinateType.MOUSE, x, y, radius, color, isSolid);
	}

	public void drawCircleScreen(int x, int y, int radius, Color color, boolean isSolid) {
		drawCircle(CoordinateType.SCREEN, x, y, radius, color, isSolid);
	}

	public void drawEllipse(CoordinateType ctype, int x, int y, int xrad, int yrad, Color color, boolean isSolid) {
		DrawCommands.DrawCommand cmd = DrawCommands.DrawCommand.newBuilder()
		    .setCoordinateType(ctype.getMessage()).addCoordinates(x).addCoordinates(y).addCoordinates(xrad)
		    .addCoordinates(yrad).setColor(color.getMessage()).setShape(ShapeType.ellipse).setIsSolid(isSolid)
		    .build();
		Game.getInstance().commandManager.addDrawCommand(cmd);
	}

	public void drawEllipseMap(int x, int y, int xrad, int yrad, Color color, boolean isSolid) {
		drawEllipse(CoordinateType.MAP, x, y, xrad, yrad, color, isSolid);
	}

	public void drawEllipseMouse(int x, int y, int xrad, int yrad, Color color, boolean isSolid) {
		drawEllipse(CoordinateType.MOUSE, x, y, xrad, yrad, color, isSolid);
	}

	public void drawEllipseScreen(int x, int y, int xrad, int yrad, Color color, boolean isSolid) {
		drawEllipse(CoordinateType.SCREEN, x, y, xrad, yrad, color, isSolid);
	}

	public void drawDot(CoordinateType ctype, int x, int y, Color color) {
		DrawCommands.DrawCommand cmd = DrawCommands.DrawCommand.newBuilder()
		    .setCoordinateType(ctype.getMessage()).addCoordinates(x).addCoordinates(y).setColor(
		        color.getMessage()).setShape(ShapeType.dot).build();
		Game.getInstance().commandManager.addDrawCommand(cmd);
	}

	public void drawDotMap(int x, int y, Color color) {
		drawDot(CoordinateType.MAP, x, y, color);
	}

	public void drawDotMouse(int x, int y, Color color) {
		drawDot(CoordinateType.MOUSE, x, y, color);
	}

	public void drawDotScreen(int x, int y, Color color) {
		drawDot(CoordinateType.SCREEN, x, y, color);
	}

	public void drawLine(CoordinateType ctype, int x1, int y1, int x2, int y2, Color color) {
		DrawCommands.DrawCommand cmd = DrawCommands.DrawCommand.newBuilder()
		    .setCoordinateType(ctype.getMessage()).addCoordinates(x1).addCoordinates(y1).addCoordinates(x2)
		    .addCoordinates(y2).setColor(color.getMessage()).setShape(ShapeType.line).build();
		Game.getInstance().commandManager.addDrawCommand(cmd);
	}

	public void drawLineMap(int x1, int y1, int x2, int y2, Color color) {
		drawLine(CoordinateType.MAP, x1, y1, x2, y2, color);
	}

	public void drawLineMouse(int x1, int y1, int x2, int y2, Color color) {
		drawLine(CoordinateType.MOUSE, x1, y1, x2, y2, color);
	}

	public void drawLineScreen(int x1, int y1, int x2, int y2, Color color) {
		drawLine(CoordinateType.SCREEN, x1, y1, x2, y2, color);
	}

	public void setScreenPosition(int x, int y) {
		commandManager.setScreenPosition(x, y);
	}

	public void printf(String message) {
		commandManager.printfQueue.add(message);
	}

	public int mapHeight() {
		return mapHeight;
	}

	public int mapWidth() {
		return mapWidth;
	}

	public void setLocalSpeed(int speed) {
		commandManager.setGameSpeed(speed);
	}

	public Set<Unit> getSelectedUnits() {
		return selectedUnits;
	}

	public Player self() {
		return self;
	}

	public void enableFlag(Flag flag) {
		commandManager.addFlag(flag.getMessage());
	}

	/**
	 * Use Model.canBuildHere() instead.
	 * 
	 * @param builder
	 * @param position
	 * @param type
	 * @return
	 */
	// Deprecated
	// public boolean canBuildHere(Unit builder, TilePosition position, UnitType
	// type) {
	// FIXME

	/*
	 * // Original code: if (position.x() < 0) return false; if (position.y() < 0)
	 * return false; int width = type.tileWidth(); int height = type.tileHeight();
	 * if (position.x() + width > Game.getInstance().mapWidth()) return false; if
	 * (position.y() + height >= Game.getInstance().mapHeight()) return false; if
	 * (position.y() + height == Game.getInstance().mapHeight() - 1) { if
	 * (position.x() < 5) return false; if (position.x() + width >
	 * Game.getInstance().mapWidth() - 5) return false; } if (type.isRefinery()) {
	 * for (Unit g : Game.getInstance().getGeysers()) { if
	 * (g.getTilePosition().equals(position)) { return true; } } return false; }
	 * for (int x = position.x()-1; x < position.x() + width+1; x++) { for (int y
	 * = position.y()-1; y < position.y() + height+1; y++) { Set<Unit> groundUnits
	 * = new HashSet<Unit>();
	 * 
	 * if (!isBuildable(x, y)) return false;
	 * 
	 * for (Unit i : unitsOnTile(x, y)) if (!i.getType().isFlyer() &&
	 * !i.isLifted()) { groundUnits.add(i); if(groundUnits.size() > 1) return
	 * false; }
	 * 
	 * if (!groundUnits.isEmpty()) { Unit blocking =
	 * groundUnits.iterator().next(); if (builder == null ||
	 * !builder.equals(blocking)) return false; } } } if
	 * (type.getRace().equals(Race.ZERG)) { if (!type.isResourceDepot()) for (int
	 * x = position.x(); x < position.x() + width; x++) for (int y = position.y();
	 * y < position.y() + height; y++) if (!hasCreep(x, y)) return false; } else {
	 * for (int x = position.x(); x < position.x() + width+1; x++) for (int y =
	 * position.y(); y < position.y() + height+1; y++) if
	 * (Game.getInstance().hasCreep(x, y)) return false; }
	 * 
	 * if (type.requiresPsi()) { if (hasPower(position.x(), position.y(), width,
	 * height)) { return true; } return false; }
	 * 
	 * if (type.isResourceDepot()) { for (Unit m : getStaticMinerals()) { if
	 * (m.getInitialTilePosition().x() > position.x() - 5 &&
	 * m.getInitialTilePosition().y() > position.y() - 4 &&
	 * m.getInitialTilePosition().x() < position.x() + 7 &&
	 * m.getInitialTilePosition().y() < position.y() + 6) { return false; } }
	 * 
	 * for (Unit g : getStaticGeysers()) { if (g.getInitialTilePosition().x() >
	 * position.x() - 7 && g.getInitialTilePosition().y() > position.y() - 5 &&
	 * g.getInitialTilePosition().x() < position.x() + 7 &&
	 * g.getInitialTilePosition().y() < position.y() + 6) { return false; } }
	 * 
	 * } return true; //
	 */

	/*
	 * // Check if the building can be placed within the map borders. if
	 * (position.x() < 0 || position.y() < 0) { return false; } final int width =
	 * type.tileWidth(); final int height = type.tileHeight(); final int upperX =
	 * position.x() + width; final int upperY = position.y() + height; if (upperX
	 * > Game.getInstance().mapWidth()) { return false; } if (upperY >=
	 * Game.getInstance().mapHeight()) { return false; } if (upperY ==
	 * Game.getInstance().mapHeight() - 1) { if (position.x() < 5) { return false;
	 * } if (upperX > Game.getInstance().mapWidth() - 5) { return false; } }
	 * 
	 * // If the unit is a refinery, // we just need to check the set of geysers
	 * to see if the position matches one of them. if (type.isRefinery()) { for
	 * (Unit geyser : Game.getInstance().getGeysers()) { if
	 * (geyser.getTilePosition().equals(position)) { return true; } } return
	 * false; }
	 * 
	 * // Check to see if the construction site is completely buildable and
	 * completely visible. for (int x = position.x(); x < upperX; x++) { for (int
	 * y = position.y(); y < upperY; y++) { if (!isBuildable(x, y) ||
	 * !isVisible(x, y)) { return false; } } }
	 * 
	 * // Check to see if any ground units are blocking the build site. final
	 * Set<Unit> groundUnits = new HashSet<Unit>(); for (int x = position.x(); x <
	 * upperX; x++) { for (int y = position.y(); y < upperY; y++) {
	 * groundUnits.clear();
	 * 
	 * for (Unit unitOnTile : unitsOnTile(x, y)) { if
	 * (!unitOnTile.getType().isFlyer() && !unitOnTile.isLifted()) {
	 * groundUnits.add(unitOnTile); if (groundUnits.size() > 1) return false; } }
	 * 
	 * if (!groundUnits.isEmpty()) { final Unit blockingUnit =
	 * groundUnits.iterator().next(); if (builder == null || builder.getID() !=
	 * blockingUnit.getID()) { return false; } } } }
	 * 
	 * // Check creep/no creep requirements. if (type.getRace().getID() ==
	 * Race.ZERG.getID()) { if (!type.isResourceDepot()) { for (int x =
	 * position.x(); x < upperX; x++) { for (int y = position.y(); y < upperY;
	 * y++) { if (!hasCreep(x, y)) { return false; } } } } } else { for (int x =
	 * position.x(); x < upperX; x++) { for (int y = position.y(); y < upperY;
	 * y++) { if (hasCreep(x, y)) { return false; } } } }
	 * 
	 * // Most Protoss buildings can only be built in a power field. if
	 * (type.requiresPsi()) { if (hasPower(position.x(), position.y(), width,
	 * height)) { return true; } return false; }
	 * 
	 * // Command Centers, Nexuses, and Hatcheries cannot be built too close to
	 * resources. if (type.isResourceDepot()) { for (Unit mineral : getMinerals())
	 * { if (mineral.getInitialTilePosition().x() > position.x() - 5 &&
	 * mineral.getInitialTilePosition().y() > position.y() - 4 &&
	 * mineral.getInitialTilePosition().x() < position.x() + 7 &&
	 * mineral.getInitialTilePosition().y() < position.y() + 6) { return false; }
	 * }
	 * 
	 * for (Unit staticGeyser : getStaticGeysers()) { if
	 * (staticGeyser.getInitialTilePosition().x() > position.x() - 7 &&
	 * staticGeyser.getInitialTilePosition().y() > position.y() - 5 &&
	 * staticGeyser.getInitialTilePosition().x() < position.x() + 7 &&
	 * staticGeyser.getInitialTilePosition().y() < position.y() + 6) { return
	 * false; } }
	 * 
	 * } return true; //
	 */
	// }

	public boolean canUpgrade(Unit unit, UpgradeType type) {

		if (self() == null) {
			return false;
		}
		if (unit != null) {
			if (unit.getPlayer() != self()) {

				return false;
			}
			if (unit.getType() != type.whatUpgrades()) {

				return false;
			}
		}
		if (self().getUpgradeLevel(type) >= type.maxRepeats()) {
			return false;
		}
		if (self().minerals() < type.mineralPriceBase() + type.mineralPriceFactor()
		    * (self().getUpgradeLevel(type))) {
			return false;
		}
		if (self().gas() < type.gasPriceBase() + type.gasPriceFactor() * (self().getUpgradeLevel(type))) {
			return false;
		}
		return true;
	}

	public boolean canResearch(Unit unit, TechType type) {
		if (self() == null) {
			return false;
		}
		if (unit != null) {
			if (unit.getPlayer() != self()) {
				return false;
			}
			if (unit.getType() != type.whatResearches()) {
				return false;
			}
		}
		if (self().hasResearched(type)) {
			return false;
		}
		if (self().minerals() < type.mineralPrice()) {
			return false;
		}
		if (self().gas() < type.gasPrice()) {
			return false;
		}
		return true;
	}

	public boolean canMake(Unit builder, UnitType type) {

		if (self() == null) {

			return false;
		}
		if (builder != null) {
			/* Check if the owner of the unit is you */
			if (builder.getPlayer() != self()) {

				return false;
			}
			/* Check if this unit can actually build the unit type */
			if (!builder.getType().equals(type.whatBuilds().getKey())) {
				return false;
			}
			/* Carrier space */
			if (builder.getType() == UnitType.PROTOSS_CARRIER) {
				int max_amt = 4;
				if (self().getUpgradeLevel(UpgradeType.CARRIER_CAPACITY) > 0)
					max_amt += 4;
				if (builder.getInterceptorCount() + (int) builder.getTrainingQueue().size() >= max_amt) {

					return false;
				}
			}
			/* Reaver Space */
			if (builder.getType() == UnitType.PROTOSS_REAVER) {
				int max_amt = 5;
				if (self().getUpgradeLevel(UpgradeType.REAVER_CAPACITY) > 0)
					max_amt += 5;
				if (builder.getScarabCount() + (int) builder.getTrainingQueue().size() >= max_amt) {

					return false;
				}
			}
		}
		/* Check if player has enough minerals */
		if (self().minerals() < type.mineralPrice()) {

			return false;
		}
		/* Check if player has enough gas */
		if (self().gas() < type.gasPrice()) {

			return false;
		}
		/* Check if player has enough supplies */
		if (type.supplyRequired() > 0)
			if (self().supplyTotal() < self().supplyUsed() + type.supplyRequired()
			    - type.whatBuilds().getKey().supplyRequired()) {

				return false;
			}
		UnitType addon = UnitType.NONE;
		for (Map.Entry<UnitType, Integer> i : type.requiredUnits().entrySet())
			if (i.getKey().isAddon())
				addon = i.getKey();
		for (Map.Entry<UnitType, Integer> i : type.requiredUnits().entrySet()) {
			boolean pass = false;
			if (self().completedUnitCount(i.getKey()) >= i.getValue())
				pass = true;
			if (i.getKey() == UnitType.ZERG_HATCHERY) {
				if (self().completedUnitCount(UnitType.ZERG_LAIR) >= i.getValue())
					pass = true;
				if (self().completedUnitCount(UnitType.ZERG_HIVE) >= i.getValue())
					pass = true;
			}
			if (i.getKey() == UnitType.ZERG_LAIR)
				if (self().completedUnitCount(UnitType.ZERG_HIVE) >= i.getValue())
					pass = true;
			if (!pass) {
				return false;
			}
		}
		if (!type.requiredTech().equals(TechType.NONE))
			if (!self().hasResearched(type.requiredTech())) {
				return false;
			}
		if (builder != null)
			if (addon != UnitType.NONE && addon.whatBuilds().getKey().equals(type.whatBuilds().getKey()))
				if (builder.getAddon() == null || builder.getAddon().getType() != addon) {

					return false;
				}
		return true;
	}

	public boolean hasPower(int x, int y, int tileWidth, int tileHeight) {

		if (!(tileWidth == 2 && tileHeight == 2) && !(tileWidth == 3 && tileHeight == 2)
		    && !(tileWidth == 4 && tileHeight == 3)) {
			return false;
		}
		if (tileWidth == 4) {
			x++;
		}
		/* Loop through all pylons for the current player */
		for (Unit i : pylons) {
			int px = i.getTilePosition().x();
			int py = i.getTilePosition().y();
			int bx = x - px + 7;
			int by = y - py + 4;
			/* Deal with special cases, pylon offset cutoff */
			if (bx >= 0 && by >= 0 && bx <= 14 && by <= 8) {
				switch (by) {
				case 0:
					if (bx >= 1 && bx <= 12)
						return true;
					break;
				case 1:
					if (bx <= 13)
						return true;
					break;
				case 2:
				case 3:
				case 4:
				case 5:
					return true;
				case 6:
					if (bx <= 13)
						return true;
				case 7:
					if (bx >= 1 && bx <= 12)
						return true;
				case 8:
					if (bx >= 4 && bx <= 9)
						return true;
					break;
				}
			}
		}
		return false;
	}

	public Set<Bullet> getAllBullets() {
		return bullets;
	}

	public void setDrawing(boolean master) {
		this.drawing = master;
	}

}
