/*
* Tyr is an AI for StarCraft: Broodwar, 
* 
* Please visit https://github.com/SimonPrins/Tyr for further information.
* 
* Copyright 2015 Simon Prins
*
* This file is part of Tyr.
* Tyr is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
* Tyr is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with Tyr.  If not, see http://www.gnu.org/licenses/.
*/


package com.tyr.buildingplacement;

import com.tyr.BWTAProxy;
import com.tyr.DebugMessages;
import com.tyr.EnemyManager;
import com.tyr.EnemyPosition;
import com.tyr.Tyr;
import com.tyr.agents.Agent;
import com.tyr.unitgroups.MineralWorkers;

import bwapi.TilePosition;
import bwapi.Unit;
import bwapi.UnitType;
import bwta.BaseLocation;

/**
 * Default build site locator for finding a building placement.
 * @author Simon
 *
 */
public class DefaultBuildSite extends BuildSiteLocator 
{
	private int stopDist = 40;
	private boolean keepExpandsFree = true;
	private boolean checkMap = true;
	private boolean checkUnits = true;

	/**
	 * Default build site locator for finding a building placement.
	 */
	public DefaultBuildSite()
	{ }
	
	public DefaultBuildSite(boolean checkMap, boolean keepExpandsFree, boolean checkUnits)
	{
		this.checkMap = checkMap;
		this.keepExpandsFree = keepExpandsFree;
		this.checkUnits = checkUnits;
	}
	
	public DefaultBuildSite(boolean checkMap, boolean keepExpandsFree, boolean checkUnits, int stopDist)
	{
		this.checkMap = checkMap;
		this.keepExpandsFree = keepExpandsFree;
		this.checkUnits = checkUnits;
		this.stopDist = stopDist;
	}
	
	@Override
	public TilePosition findPlacement(UnitType building, TilePosition preferredTile, Agent worker)
	{
		// Find a suitable tile position for placing the building.
		if(preferredTile == null)
		{
			if(Tyr.game.isVisible(Tyr.self.getStartLocation()))
				preferredTile = Tyr.self.getStartLocation();
			else
			{
				if(Tyr.bot.workForce.mineralWorkers.size() > 0)
					preferredTile = Tyr.bot.workForce.mineralWorkers.get(0).resourceDepot.getTilePosition();
				else
					preferredTile = worker.unit.getTilePosition();
			}
		}
		
		TilePosition buildTile;
		if (!Tyr.game.isVisible(preferredTile))
			buildTile = getBuildTileNoVision(worker.unit, building, preferredTile);
		else
			buildTile = getBuildTile(worker.unit, building, preferredTile);
		
  		return buildTile;
	}
	
    /**
     * Gets a suitable build position for the building.
     * @param builder The worker who will build the building.
     * @param buildingType The type of building to be built.
     * @param aroundTile The tile around which we look for a suitable position.
     * @return The TilePosition where the building can be built.
     */
	public TilePosition getBuildTileNoVision(Unit builder, UnitType buildingType, TilePosition aroundTile)
	{
		int maxDist = 1;
		// Just look in an increasing area around the desired position until we find something.
		while (maxDist < stopDist)
		{
			for (int i=aroundTile.getX()-maxDist; i<=aroundTile.getX()+maxDist; i++)
			{
				if (buildingType == UnitType.Protoss_Pylon && (i <= 3 || i >= Tyr.game.mapWidth() - 3))
					continue;
				for (int j=aroundTile.getY()-maxDist; j<=aroundTile.getY()+maxDist; j++)
				{
					if (buildingType == UnitType.Protoss_Pylon && (j <= 3 || j >= Tyr.game.mapHeight() - 3))
						continue;
					if(SpaceManager.canBuildHere(builder, i, j, buildingType, checkMap, keepExpandsFree, checkUnits))
						return new TilePosition(i, j);
				}
			}
			
			maxDist += 2;
		}
		DebugMessages.addMessage("No placement found for " + buildingType + ".");
		return null;
	}
	
    /**
     * Gets a suitable build poition for the building.
     * @param builder The worker who will build the building.
     * @param buildingType The type of building to be built.
     * @param aroundTile The tile around which we look for a suitable position.
     * @return The TilePosition where the building can be built.
     */
	public TilePosition getBuildTile(Unit builder, UnitType buildingType, TilePosition aroundTile) 
	{
		// Refinery, Assimilator, Extractor
		if (buildingType.isRefinery())
		{
			for (Unit n : Tyr.game.neutral().getUnits())
				if ((n.getType() == UnitType.Resource_Vespene_Geyser) && canConstruct(n))
	 					return n.getTilePosition();
			return null;
		}
	 	
		// Building a new base.
	 	if (buildingType.isResourceDepot())
	 	{
	 		BaseLocation loc = null;
	 		for (BaseLocation b : Tyr.bot.expands)
	 		{
	 			if (Tyr.game.canBuildHere(b.getTilePosition(), buildingType, builder, false))
	 			{
	 				// See if the enemy has already taken this base.
	 				boolean enemyBase = false;
	 				for(EnemyPosition p : EnemyManager.getManager().enemyBuildingMemory)
	 				{
	 					enemyBase = b.getPosition().getDistance(p.pos.getX(), p.pos.getY()) < 256;
	 					if(enemyBase)
	 						break;
	 				}
	 				
	 				if (enemyBase)
	 					continue;
	 				
	 				// See if there are units in the way.
	 				boolean unitsInWay = false;
					for(BuildCommand cmd : Tyr.bot.buildCommands)
					{
						if (cmd.position.getDistance(b.getTilePosition()) <= 1)
						{
							unitsInWay = true;
							break;
						}
					}
					if (unitsInWay)
						continue;
	 				
					// We want the closest base to our own.
					if (BWTAProxy.initialized)
					{
		 				if (loc == null || BWTAProxy.getGroundDistance(aroundTile, b.getTilePosition()) < BWTAProxy.getGroundDistance(aroundTile, loc.getTilePosition()))
		 					loc = b;
					}
					else
		 				if (loc == null || aroundTile.getDistance(b.getTilePosition()) < aroundTile.getDistance(loc.getTilePosition()))
		 					loc = b;
	 			}
	 		}
	
	 	 	return loc==null?null:loc.getTilePosition();
	 	}
	 	
	 	if (!Tyr.game.isVisible(aroundTile))
	 		return aroundTile;
	 	
	 	return getBuildTileNoVision(builder, buildingType, aroundTile);
 	}
	
	private boolean canConstruct(Unit geyser)
	{
		// Do not build the same refinery twice.
		boolean alreadyConstructing = false;
	
		for (BuildCommand com : Tyr.bot.buildCommands)
		{
			if (com.building.isRefinery())
			{
				if (com.position.getX() == geyser.getTilePosition().getX() && com.position.getY() == geyser.getTilePosition().getY())
				{
					alreadyConstructing = true;
					break;
				}
			}
		}
		if (alreadyConstructing)
			return false;
		
		// See if we have built a base near the gas geyser, otherwise we do not need to build the gas geyser there.
		boolean hasBase = false;
		for(MineralWorkers base : Tyr.bot.workForce.mineralWorkers)
		{
			if (base.gasWorkers != null 
					&& Math.abs(base.gasWorkers.geyser.getX() - geyser.getX())  <= 10
					&& Math.abs(base.gasWorkers.geyser.getY() - geyser.getY())  <= 10
					&& !base.resourceDepot.isBeingConstructed())
			{
				hasBase = true;
				break;
			}
		}
		return hasBase;
	}
}
