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

import com.tyr.EnemyManager;
import com.tyr.EnemyPosition;
import com.tyr.PositionUtil;
import com.tyr.Tyr;
import com.tyr.agents.Agent;
import com.tyr.agents.None;
import com.tyr.unitgroups.UnitGroup;

import bwapi.Game;
import bwapi.Player;
import bwapi.Position;
import bwapi.Race;
import bwapi.Unit;
import bwta.BaseLocation;


/**
 * This solution simple send the entire group of units to attack the enemy.
 * AttackRegroupSolution was created to replace this.
 * @author Simon
 *
 */
public class DefendWorkerRushSolution extends TargetableSolution 
{
	/**
	 * The defending units.
	 */
	private List<Agent> units = new ArrayList<>();
	
	/**
	 * The attacking units.
	 */
	private List<Agent> attackUnits = new ArrayList<>();
	
	/**
	 * The target where we are defending.
	 */
	private Position target;
	
	/**
	 * The target where we are attacking.
	 */
	private Position attackTarget;
	
	/**
	 * Position where we retreat to.
	 */
	private Position retreatPos;
	
	/**
	 * This solution simple send the entire group of units to attack the enemy.
	 * AttackRegroupSolution was created to replace this.
	 * 
	 * @param task The task that is using this solution.
	 * @param target The initial target where we attack.
	 */
	public DefendWorkerRushSolution(Task task, Position target) 
	{
		super(task);
		
		this.target = target;
	}
	
	/**
	 * Set the new target for the attack.
	 * @param target The target where we are going to attack.
	 */
	public void setTarget(Position target)
	{
		this.target = target;
	}

	@Override
	public void onFrame(Game game, Player self, Tyr bot)
	{
		for (int i = units.size() - 1; i >= 0; i--)
			if (units.get(i).isDead())
				units.remove(i);
		for (int i = attackUnits.size() - 1; i >= 0; i--)
			if (attackUnits.get(i).isDead())
				attackUnits.remove(i);
		
		for (int i=0; i < units.size(); i++)
		{
			Agent agent = units.get(i);
			
			if (agent.distanceSquared(target) >= 200 * 200)
				agent.unit.move(target);
			else if (retreat(agent))
				agent.unit.move(getRetreatPos());
			else
				agent.attack(target);
		}
		
		acquireTarget(bot);
		for (int i=0; i < attackUnits.size(); i++)
		{
			Agent agent = attackUnits.get(i);
			
			if (attackTarget == null)
			{
				if (agent.distanceSquared(target) >= 200 * 200)
					agent.unit.move(target);
				else
					agent.attack(target);
			}
			else
			{
				if (agent.distanceSquared(attackTarget) >= 200 * 200)
					agent.unit.move(attackTarget);
				else
					agent.attack(attackTarget);
			}
		}
	}
	
	private boolean retreat(Agent agent)
	{
		if (!Agent.isRanged(agent.unit))
			return false;
		if (agent.unit.getGroundWeaponCooldown() > 1 && agent.unit.getAirWeaponCooldown() > 1)
			return true;
		if (agent.distanceSquared(getRetreatPos()) <= 10000)
			return false;
		for (Unit enemy : EnemyManager.getEnemyUnits())
			if (agent.distanceSquared(enemy) <= 2500)
				return true;
		return false;
	}

	private Position getRetreatPos() 
	{
		if (retreatPos == null)
		{
			Position start = Tyr.tileToPosition(Tyr.self.getStartLocation());
			int dist = 100000000;
			for (EnemyPosition mineral : EnemyManager.getManager().neutralBuildingMemory)
			{
				if (!mineral.type.isMineralField())
					continue;
				final int newDist = PositionUtil.distanceSq(mineral.pos, start);
				if (newDist < dist)
				{
					dist = newDist;
					retreatPos = mineral.pos;
				}
			}
		}
		return retreatPos;
	}

	/**
	 * Add an agent who will join the attack.
	 * @param unit The agent who will join the attack.
	 */
	public void add(Agent unit)
	{
		if (units.size() < 4 || Tyr.self.getRace() == Race.Terran)
			units.add(unit);
		else attackUnits.add(unit);
	}
	
	/**
	 * The number of units sent by this solution.
	 * @return The number of units sent by this solution.
	 */
	public int getCount()
	{
		return units.size() + attackUnits.size();
	}
	
	/**
	 * This method is called when this solution is no longer needed.
	 * @param unitGroup The unit group to which we will add all the units.
	 */
	public void done(UnitGroup unitGroup) 
	{
		for(Agent unit : units)
		{
			unitGroup.add(unit);
			unit.order(new None(unit));
		}
		for(Agent unit : attackUnits)
		{
			unitGroup.add(unit);
			unit.order(new None(unit));
		}
		units = new ArrayList<Agent>();
		attackUnits = new ArrayList<Agent>();
	}
	
	/**
	 * Have we added our units to the scanner previously.
	 */
	private boolean addedToScanner = false;	

	/**
	 * Adds the attacking units to the scanner to start finding the remaining enemy buildings.
	 */
	public void addToScanner() 
	{
		// We do not want to add the units twice.
		if (addedToScanner)
			return;
		addedToScanner = true;
		

		for(Agent agent : units)
			Tyr.bot.scanner.add(agent);
		for(Agent agent : attackUnits)
			Tyr.bot.scanner.add(agent);
	}

	/**
	 * Get the closest squared distance between any of the agents and this position.
	 * @param target The target position for which we want to know the distance.
	 * @return The minimum squared distance.
	 */
	public int distanceSq(Position target)
	{
		int distance = Integer.MAX_VALUE;
		for (Agent agent : units)
			distance = Math.min(distance, agent.distanceSquared(target));
		for (Agent agent : attackUnits)
			distance = Math.min(distance, agent.distanceSquared(target));
		return distance;
	}
	
	/**
	 * See if we need to find a new target to attack.
	 * If so we return true, if the current target is already fine, we return false.
	 */
	private boolean acquireTarget(Tyr bot)
	{
		if (attackTarget != null)
		{
	    	for(EnemyPosition p : EnemyManager.getManager().enemyBuildingMemory)
	    	{
	    		if (p.pos.getX() == attackTarget.getX() && p.pos.getY() == attackTarget.getY())
	    			return false;
	    	}
		}
		
		Position newTarget = null;
    	for(EnemyPosition p : EnemyManager.getManager().enemyBuildingMemory)
    	{
    		newTarget = p.pos;
			break;
    	}

    	if (newTarget == null)
    		for (BaseLocation b : bot.suspectedEnemy) 
    		{
    			newTarget = b.getPosition();
    			break;
    		}
    	
    	if (newTarget == null)
    		return false;
    	
    	if (attackTarget == null)
    	{
    		attackTarget = newTarget;
    		return true;
    	}
    	
    	
    	if (attackTarget.getX() == newTarget.getX() && attackTarget.getY() == newTarget.getY())
    		return false;
    	
    	attackTarget = newTarget;
    	return true;
	}

}
