/*
* 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.unitgroups;

import java.util.List;

import com.tyr.EnemyManager;
import com.tyr.Tyr;
import com.tyr.agents.WorkerAgent;
import com.tyr.buildingplacement.BuildCommand;

import bwapi.Color;
import bwapi.Game;
import bwapi.Order;
import bwapi.Player;
import bwapi.Position;
import bwapi.TilePosition;
import bwapi.Unit;
import bwapi.UnitType;


/**
 * This class manages all workers that are currently going to construct buildings.
 * Note that, in case of an SCV, when it is actually constructing a building it will no longer be managed by this group.
 * @author Simon
 *
 */
public class BuilderGroup extends UnitGroup
{
	public BuilderGroup(OutOfJob rejects) 
	{
		super(rejects);
	}

	@Override
	public void onFrame(Game game, Player self, Tyr bot) 
	{
		for(BuildCommand buildCommand : bot.buildCommands)
		{
			if(buildCommand.removing)
				continue;
			
			// Clean up buildCommands for which the constructing worker has died.
			if (((WorkerAgent)buildCommand.worker).isBlocked || buildCommand.worker.isDead()
				|| buildCommand.worker.unit.getBuildUnit() != null || buildCommand.worker.unit.isMorphing())
			{
				buildCommand.remove();
				continue;
			}
			
			if (buildCommand.building.isResourceDepot())
			{
				int dist = 100*100;
				if (buildCommand.worker.unit.isUnderAttack())
					dist = 400*400;
				
				boolean underAttack = false;
				for (Unit enemy : EnemyManager.getEnemyUnits())
				{
					if (enemy.getType().isWorker() || enemy.getType() == UnitType.Zerg_Overlord)
						continue;
					if (buildCommand.worker.distanceSquared(enemy) <= dist)
					{
						underAttack = true;
						break;
					}
				}
				
				if (underAttack)
				{
					buildCommand.remove();
					continue;
				}
			}
			
			// Clean up buildComands for which the construction has already started.
			for(Unit unit : self.getUnits())
			{
				if (unit.getTilePosition().getX() == buildCommand.position.getX()
						&& unit.getTilePosition().getY() == buildCommand.position.getY()
					&& unit.getType() == buildCommand.building
						)
				{
					buildCommand.remove();
					break;
				}
			}
		}
		
		for(BuildCommand buildCommand : bot.buildCommands)
		{
			if(buildCommand.removing)
				continue;
			
			// Draw some debug information.
			buildCommand.worker.drawCircle(Color.Blue);
			game.drawLineMap(buildCommand.getCenter().getX(), buildCommand.getCenter().getY(),
					buildCommand.worker.unit.getPosition().getX(), buildCommand.worker.unit.getPosition().getY(), Color.Blue);
        	game.drawTextMap(buildCommand.worker.unit.getPosition().getX(), buildCommand.worker.unit.getPosition().getY(),
        			buildCommand.worker.unit.getOrder().toString());
			game.drawBoxMap(buildCommand.position.getX()*32, buildCommand.position.getY()*32,
					buildCommand.position.getX()*32 + 32*buildCommand.building.tileWidth(), buildCommand.position.getY()*32 + 32*buildCommand.building.tileHeight(),
					Color.Orange);
			
			// See if something went wrong and we need to give the worker a new command.
        	if (!buildCommand.worker.unit.isConstructing() &&  
        			(buildCommand.worker.unit.isGatheringMinerals() ||
        			(buildCommand.worker.unit.getOrder() != Order.PlaceBuilding 
        			&& (!buildCommand.worker.unit.isMoving() || buildCommand.worker.unit.getOrder() == Order.PlayerGuard))))
			{
				if (game.canBuildHere(buildCommand.position, buildCommand.building, buildCommand.worker.unit, false))
				{
					// If the unit is close enough it can start building immediately.
					if (buildCommand.worker.unit.getTilePosition().getDistance(buildCommand.position) <= 60)
						buildCommand.worker.unit.build(buildCommand.building, buildCommand.position);
					else // Otherwise we give a move command first. This is mainly in case the region hasn't been explored yet.
						buildCommand.worker.unit.move(buildCommand.getCenter());
					continue;
				}
				
				TilePosition newpos = null;
				// Try to get a new position for building the building, since our current one might have been blocked.
				if(!buildCommand.fixed)
					newpos = bot.spaceManager.getBuildTile(buildCommand.worker.unit, buildCommand.building, buildCommand.position);
				
				if (newpos == null)
				{
					// We can't find a correct placement, so we have to cancel the build command.
					buildCommand.remove();
					game.printf("No suitable placement found.");
					continue;
				}
				
				buildCommand.position = newpos;
				if (buildCommand.worker.unit.getTilePosition().getDistance(newpos) <= 20)
				{
					// If the unit is close enough it can build the building.
					buildCommand.worker.unit.build(buildCommand.building, newpos);
					continue;
				}
				
				if (!(buildCommand.building.isResourceDepot() && buildCommand.worker.unit.getDistance(Tyr.tileToPosition(newpos)) > 300))
				{
					buildCommand.worker.unit.move(buildCommand.getCenter());
					continue;
				}
				
				// In case of new bases, if there is a visible mineral patch near to the expand location,
				//  we send the worker to mine there, so it can "mineral walk" to the location.
				Unit mineral = getMineralInRange(Tyr.tileToPosition(newpos), 270, false);
				if (mineral != null)
					buildCommand.worker.unit.gather(mineral);
				else
					buildCommand.worker.unit.move(buildCommand.getCenter());
			}
		}
		
		// Remove all the buildCommands that have been rejected at some earlier point. 
		for(int i=0; i<bot.buildCommands.size(); i++)
		{
			BuildCommand com = bot.buildCommands.get(i);
			if(com.removing)
			{
				bot.buildCommands.set(i, bot.buildCommands.get(bot.buildCommands.size()-1));
				bot.buildCommands.remove(bot.buildCommands.size()-1);
				
				rejects.add(com.worker);
				remove(com.worker);
			}
		}
	}
	
	/**
	 * This method returns a mineral within a certain range of a location.
	 * @param pos The position around which we search for mineral patches.
	 * @param range The maximum range at which the mineral patch is allowed to be.
	 * @param blocking If this is true we only look for empty mineral patches that might be blocking an expand.
	 * @return A mineral patch within range. If no mineral patch is found we return null.
	 */
	private Unit getMineralInRange(Position pos, int range, boolean blocking)
	{
		List<Unit> inRange = Tyr.game.getUnitsInRadius(pos, 270);
		for(Unit mineral : inRange)
			if (mineral.getType().isMineralField() && (!blocking || mineral.getResources() == 0))
				return mineral;
		return null;
	}

}
