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

import com.tyr.BWTAProxy;
import com.tyr.Tyr;
import com.tyr.agents.Agent;
import com.tyr.agents.None;
import com.tyr.agents.PlaceMine;
import com.tyr.agents.VultureAgent;
import com.tyr.buildingplacement.DefensiveStructures;

import bwapi.Color;
import bwapi.Game;
import bwapi.Player;
import bwapi.Position;
import bwapi.TilePosition;
import bwta.BaseLocation;
import bwta.Region;

/**
 * This class implements a solution for placing spider mines in a grid.
 */
public class MineGridSolution extends Solution 
{
	/**
	 * The current vulture that is supposed to lay down its mines.
	 */
	VultureAgent vulture;
	
	/**
	 * A list of all potential positions at which we want to place a spider mine.
	 */
	ArrayList<Position> potentialMinePlacements = new ArrayList<Position>();
	
	/**
	 * This class implements a solution for placing spider mines in a grid.
	 * @param task The calling task.
	 */
	public MineGridSolution(Task task) 
	{
		super(task);
	}
	
	@Override
	public void onFrame(Game game, Player self, Tyr bot)
	{
		// If the vulture is dead, or if it is blocked in, we need a new one.
		if (vulture != null && vulture.isDead())
			vulture = null;
		
		if (vulture != null && vulture.isBlocked)
			return;
		
		// If there are no (more) potential mine placements left, we re-initialize the array.
		if (this.potentialMinePlacements.size() == 0)
			initializeMinePlacements();
		
		if(vulture == null)
			return;
		
		vulture.drawCircle(Color.Blue);
		
		// If the vulture is already busy placing the mine, we do not give it a new order.
		if (vulture.getCommand() instanceof PlaceMine)
			return;
		
		// Sort the list of mines again, in case a new expand has been taken.
		potentialMinePlacements.sort(new MineDistanceComparator());
		
		for(int i=this.potentialMinePlacements.size()-1; i >= 0; i--)
		{
			// Get a new potential location for placing a mine.
			Position placement = potentialMinePlacements.get(i);
			potentialMinePlacements.remove(i);
			
			TilePosition tilePos = Tyr.positionToTile(placement);
			
			// See if the mine can actually be placed there.
			if (Tyr.bot.spaceManager.isFree(tilePos) && game.isWalkable(tilePos.getX(), tilePos.getY()))
			{
				// If so, order the vulture to place the mine.
				vulture.placeMine(vulture.unit.getPosition());
				break;
			}
		}
	}
	
	/**
	 * Set the current vulture.
	 * @param vulture The vulture that is to be set.
	 */
	public void setVulture(VultureAgent vulture)
	{
		this.vulture = vulture;
	}
	
	/**
	 * Returns the current vulture and removes it from this solution.
	 * @return The current vulture or null if no vulture is present.
	 */
	public Agent pop()
	{
		Agent result = vulture;
		vulture = null;
		if (result != null)
			result.order(new None(result));
		return result;
	}
	
	/**
	 * Returns true if a vulture is needed.
	 * @return Returns true if a vulture is needed.
	 */
	public boolean vultureNeeded()
	{
		return vulture == null || vulture.unit.getSpiderMineCount() == 0 || vulture.isBlocked;
	}
	
	/**
	 * This method initializes the list of mine placements.
	 * It puts the list in reverse order in which we would want to place the mines.
	 */
	public void initializeMinePlacements()
	{
		if (!BWTAProxy.initialized)
			return;
		
		// The starting region for our bot.
		Region startRegion = BWTAProxy.getRegion(Tyr.self.getStartLocation());
		
		int maxDist = 1536;
		int minDist = 512;
		int clearExpandDist = 256;
		
		Position startLocation = Tyr.tileToPosition(Tyr.self.getStartLocation()); 
		
		for (int x = Math.min(startLocation.getX() - maxDist, 32); 
				x <= startLocation.getX() + maxDist && x < Tyr.game.mapWidth()*32 - 32; x += 64)
		{
			for (int y = Math.min(startLocation.getY() - maxDist, 32); 
					y <= startLocation.getY() + maxDist && y < Tyr.game.mapHeight()*32 - 32; y += 64)
			{
				// Mines are not allowed in our base.
				if (BWTAProxy.getRegion(new Position(x, y)) == startRegion)
					continue;
				
				// Determine the distance from the potential mine position to the start location.
				int dx = x - startLocation.getX();
				int dy = y - startLocation.getY();
				int distSq = dx*dx + dy*dy;
				
				// The mine should not be too close to, or to far from the main base.
				if (distSq > maxDist * maxDist || distSq < minDist * minDist)
					continue;
				
				boolean expandTooClose = false;
				
				// The mine should not be too close to an expand.
				if (Tyr.bot.expands != null)
					for(BaseLocation expand : Tyr.bot.expands)
					{
						Position expandPos = expand.getPosition();
						
						// Determine the distance from the potentiaSl mine position to this expand.
						dx = x - expandPos.getX();
						dy = y - expandPos.getY();
						int expandDistSq = dx*dx + dy*dy;
						
						// The mine should not be too close to the expand.
						if (expandDistSq <= clearExpandDist * clearExpandDist)
						{
							expandTooClose = true;
							break;
						}
					}
				
				if (expandTooClose)
					continue;
				
				// The position is accepted, add it to the list.
				this.potentialMinePlacements.add(new Position(x, y));
			}
			
		}
		
		potentialMinePlacements.sort(new MineDistanceComparator());
	}
	
	/**
	 * Comparator used to sort positions on ascending distance the closest defensivePosition.
	 * @author Simon
	 *
	 */
	public class MineDistanceComparator implements Comparator<Position>
	{
		@Override
		public int compare(Position a, Position b) 
		{
			return (int)Math.signum(getDistance(b) - getDistance(a));
		}
		
		/**
		 * Computes the distance from a position to the closest defensivePosition.
		 * @param pos The position for which we want to determine the distance.
		 * @return The distance from the position to the closest defensivePosition. 
		 */
		private int getDistance(Position pos)
		{
			int result = Integer.MAX_VALUE;
			if (Tyr.bot.defensiveStructures != null && Tyr.bot.defensiveStructures.size() > 0)
			{
				for(DefensiveStructures defenses: Tyr.bot.defensiveStructures)
				{
					Position defensePos = defenses.getDefensePos();
					int dx = pos.getX() - defensePos.getX();
					int dy = pos.getY() - defensePos.getY();
					result = Math.min(result, dx*dx+dy*dy);
				}
			}
			else
			{
				Position defensePos = Tyr.tileToPosition(Tyr.self.getStartLocation());
				int dx = pos.getX() - defensePos.getX();
				int dy = pos.getY() - defensePos.getY();
				return dx*dx+dy*dy;
			}
			return result;
		}
	}
}
