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

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.tyr.Tyr;
import com.tyr.agents.Agent;
import com.tyr.agents.None;
import com.tyr.agents.WorkerAgent;
import com.tyr.buildingplacement.BuildCommand;
import com.tyr.buildingplacement.DefaultBuildSite;
import com.tyr.requirements.Requirement;

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

/**
 * This class implements a solution for building at a specific location.
 */
public class BuildAtLocationSolution extends Solution
{
	/**
	 * The target position where we want to build the buildings.
	 */
	private Position target;
	
	/**
	 * The unit who will build the buildings.
	 */
	private Agent builder;
	
	/**
	 * The types of buildings that we will build.
	 */
	private List<UnitType> types = new ArrayList<UnitType>();
	
	/**
	 * Requirements for the units that we will build.
	 */
	private List<Requirement> requirements = new ArrayList<Requirement>();
	
	private BuildCommand command;
	
	private int next;
	
	private int nextUpdatedFrame = -1;
	
	private boolean keepExpandsFree;
	
	public static int buildingDistance = 500;
	
	public static boolean removeExcess = true;
	
	/**
	 * This class implements a solution for building at a specific location.
	 * @param task The task that started this solution.
	 */
	public BuildAtLocationSolution(Task task, Position target, List<UnitType>types, List<Requirement>requirements, boolean keepExpandsFree) 
	{
		super(task);
		this.target = target;
		this.types = types;
		this.requirements = requirements;
		this.keepExpandsFree = keepExpandsFree;
	}

	@Override
	public void onFrame(Game game, Player self, Tyr bot)
	{
		if (builder != null && builder.isDead())
		{
			builder = null;
			command = null;
		}
		if (builder != null && ((WorkerAgent)builder).isBlocked)
		{
			bot.hobos.add(builder);
			builder = null;
			command = null;
		}
		if (builder != null && command != null)
		{
			if (builder.unit.getBuildUnit() != null)
			{
				bot.reservedMinerals -= command.mineralCost();
				bot.reservedGas -= command.gasCost();
				command = null;
				builder.order(new None(builder));
				((WorkerAgent)builder).blockedTimer = -1;
				((WorkerAgent)builder).isBlocked = false;
			}
		}

		if (builder != null && command != null)
		{
			// Clean up buildComands for which the construction has already started.
			for(Unit unit : self.getUnits())
			{
				if (unit.getTilePosition().getX() == command.position.getX()
						&& unit.getTilePosition().getY() == command.position.getY()
					&& unit.getType() == command.building
						)
				{
					bot.reservedMinerals -= command.mineralCost();
					bot.reservedGas -= command.gasCost();
					command = null;
					builder.order(new None(builder));
					((WorkerAgent)builder).blockedTimer = -1;
					((WorkerAgent)builder).isBlocked = false;
					break;
				}
			}
		}

		if (builder != null && command != null)
		{

			// Draw some debug information.
			builder.drawCircle(Color.Blue);
			game.drawLineMap(command.getCenter().getX(), command.getCenter().getY(),
					builder.unit.getPosition().getX(), builder.unit.getPosition().getY(), Color.Blue);
        	game.drawTextMap(command.worker.unit.getPosition().getX(), builder.unit.getPosition().getY(),
        			builder.unit.getOrder().toString());
			game.drawBoxMap(command.position.getX()*32, command.position.getY()*32,
					command.position.getX()*32 + 32*command.building.tileWidth(), command.position.getY()*32 + 32*command.building.tileHeight(),
					Color.Orange);
			
			// See if something went wrong and we need to give the worker a new command.
        	if (!builder.unit.isConstructing() &&  
        			(builder.unit.isGatheringMinerals() ||
        			(builder.unit.getOrder() != Order.PlaceBuilding 
        			&& ((!builder.unit.isMoving() || builder.distanceSquared(command.getCenter()) >= 300*300) || builder.unit.getOrder() == Order.PlayerGuard))))
			{
				if (game.canBuildHere(command.position, command.building, command.worker.unit, false))
				{
					// If the unit is close enough it can start building immediately.
					if (builder.unit.getTilePosition().getDistance(command.position) <= 60)
						builder.unit.build(command.building, command.position);
					else // Otherwise we give a move command first. This is mainly in case the region hasn't been explored yet.
						builder.unit.move(command.getCenter());
					return;
				}
								
				builder.unit.move(command.getCenter());
			}
		}
		
		updateNext();
		if (builder != null && !builder.unit.isConstructing() && needToBuild() && next < types.size())
		{
			command = bot.spaceManager.build(types.get(next), target, new DefaultBuildSite(keepExpandsFree, keepExpandsFree, false, 10), builder);
			if (command == null)
			{
				if (removeExcess)
				{
					types.remove(next);
					next--;
				}
			}
			else if (types.get(next) == UnitType.Protoss_Pylon)
				target = command.getCenter();
		}
		if (command == null && builder != null && builder.distanceSquared(target) >= 300*300)
		{
			builder.order(new None(builder));
			builder.unit.move(target);
		}
	}
	
	public boolean needToBuild()
	{
		updateNext();
		if (next >= requirements.size() || command != null)
			return false;
		return requirements.get(next).met();
	}

	public void setBuilder(Agent builder)
	{
		this.builder = builder;
	}

	public boolean needBuilder()
	{
		boolean needToBuild = needToBuild();
		
		return builder == null && needToBuild;
	}
	
	public void updateNext()
	{
		int frame = Tyr.game.getFrameCount();
		if (frame <= nextUpdatedFrame)
			return;
		
		nextUpdatedFrame = frame;

		
		// Count all units close to our build location.
		Map<UnitType, Integer> counts = new HashMap<UnitType, Integer>();
		for (Unit unit : Tyr.self.getUnits())
		{
			if (unit.getDistance(target) <= buildingDistance)
			{
				if (!counts.containsKey(unit.getType()))
					counts.put(unit.getType(), 0);
				counts.put(unit.getType(), counts.get(unit.getType())+ 1);
			}
		}
		for (next = 0; next < types.size(); next++)
		{
			if (!counts.containsKey(types.get(next)))
				break;
			if (counts.get(types.get(next)) == 0)
				break;
			counts.put(types.get(next), counts.get(types.get(next)) - 1);
		}
	}

	public void clear()
	{
		if (builder != null)
		{
			Tyr.bot.hobos.add(builder);
			builder = null;
		}
	}

	public void addBuilding(UnitType buildingType, Requirement requirement)
	{
		types.add(buildingType);
		requirements.add(requirement);
	}
}
