/*
* 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.ArrayList;
import java.util.HashSet;

import com.tyr.DebugMessages;
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.buildingplacement.SpaceManager;
import com.tyr.tasks.RunbyTask;

import bwapi.Color;
import bwapi.Game;
import bwapi.Player;
import bwapi.Position;
import bwapi.Race;
import bwapi.Unit;
import bwapi.UnitType;
import bwapi.WeaponType;
import bwta.Chokepoint;
import bwta.Region;


/**
 * A unit group for sending units to attack a certain location.
 * @author Simon
 *
 */
public class PrioritizedAttackGroup extends IAttackGroup
{
	/**
	 * The maximum range at which we will target enemy units.
	 */
	private double maxTargetRange = 300;
	
	/**
	 * After the enemy attacks, if this many frames have elapsed we ignore him.
	 */
	public static int ignoreAfterNonAggressive = 300;
	
	/**
	 * Range at which an enemy is considered a threat.
	 */
	public static int threatRange = 450;
	
	/**
	 * Do we allow our units to retreat against large enemy forces?
	 */
	public static boolean allowRetreat = false;
	
	/**
	 * Do we allow our units to retreat against large enemy forces?
	 */
	public static boolean isAttacking = true;
	
	/**
	 * The enemies natural expansion.
	 */
	private static Region enemyNatural = null;
	
	/**
	 * A unit group for sending units to attack a certain location.
	 * @param target The target where we want the units to attack.
	 */
	public PrioritizedAttackGroup(Position target) 
	{
		super(target);
	}
	
	@Override
	public void onFrame(Game game, Player self, Tyr bot)
	{
		for (Agent agent : units)
		{
			agent.drawCircle(Color.Green, 6);
			if (agent.unit.getType() == UnitType.Protoss_Reaver)
			{
				agent.drawCircle(Color.Purple);
				if (!agent.unit.isTraining())
					agent.unit.train(UnitType.Protoss_Scarab);
			}
		}
		boolean retreat = false;
		HashSet<Agent> closeAgents = new HashSet<Agent>();
		HashSet<EnemyPosition> enemyStructures = new HashSet<EnemyPosition>();
		
		final boolean overwhelming = units.size() >= 30;
		
		int minArmy = EnemyManager.getManager().enemyDefensiveStructures.size() > 0?36:0;
		minArmy += EnemyManager.getManager().enemyDefensiveStructures.size() * 8;
		minArmy = 0;
		int defensesThreatRange = threatRange;
		if (allowRetreat)
		{
			int playerStrength = 0;
			int enemyStrength = 0;
			agentLoop: for (Agent agent : units)
			{
				if (agent.unit.getType().groundWeapon() == WeaponType.None)
					continue;
				for (EnemyPosition enemyStructure : EnemyManager.getManager().enemyDefensiveStructures)
				{
					if (agent.distanceSquared(enemyStructure.pos) <= defensesThreatRange * defensesThreatRange)
					{
						if (!enemyStructures.contains(enemyStructure))
						{
							enemyStructures.add(enemyStructure);
							if (overwhelming)
								enemyStrength += 8;
							else if (RunbyTask.isCreated())
								enemyStrength += 80;
							else if (enemyStructure.type == UnitType.Zerg_Sunken_Colony)
								enemyStrength += 30;
							else
								enemyStrength += 20;
							defensesThreatRange = threatRange + 320;
						}
					}
					if (agent.distanceSquared(enemyStructure.pos) <= (threatRange + 80) * (threatRange + 80))
					{
						if (!closeAgents.contains(agent))
						{
							playerStrength += agent.unit.getType().supplyRequired();
							closeAgents.add(agent);
							agent.drawCircle(Color.Purple);
						}
					}
				}
				
				if (closeAgents.contains(agent))
					continue;
				
				for (Unit unit : EnemyManager.getEnemyUnits())
				{
					if (unit.getType().isFlyer() || unit.getType().isWorker())
						continue;
					if (agent.distanceSquared(unit) <= threatRange * threatRange)
					{
						if (!closeAgents.contains(agent))
						{
							playerStrength += agent.unit.getType().supplyRequired();
							closeAgents.add(agent);
						}
						continue agentLoop;
					}
				}
			}
			for (EnemyPosition pos : enemyStructures)
				Tyr.drawCircle(pos.pos, Color.Orange, threatRange);
			for (UnitType type : EnemyManager.getManager().getEnemyTypes())
			{
				if (type.isFlyer())
					continue;
				if (!type.isBuilding() && type.groundWeapon() != null && type.groundWeapon() != WeaponType.None && !type.isWorker())
					enemyStrength += type.supplyRequired() * EnemyManager.getManager().getAllCount(type);
			}
			
			DebugMessages.addMessage("Player Strength: " + playerStrength + " enemyStrength: " + enemyStrength + " minArmy: " + minArmy);
			
			if (RunbyTask.isCreated() && enemyStructures.size() > 0)
			{
				if (units.size() >= (EnemyManager.getManager().enemyDefensiveStructures.size() > 1?10:5))
				{
					RunbyTask.getTask().addRunby(units);
					units = new ArrayList<Agent>();
					return;
				}
			}
			
			int modifiedPlayerStrength = playerStrength;
			if (game.enemy().getRace() != Race.Zerg)
				modifiedPlayerStrength += (playerStrength / 20) * 2;
			
			if (modifiedPlayerStrength < Math.max(enemyStrength, minArmy) / 2 || (game.enemy().getRace() == Race.Zerg && playerStrength <= 4))
			{
				retreat = true;
				isAttacking = false;
			}
			else if (modifiedPlayerStrength >= Math.max(enemyStrength, minArmy) + (EnemyManager.getManager().enemyDefensiveStructures.size() > 1 ? 8 : 0))
			{
				retreat = false;
				isAttacking = true;
			}
			else
			{
				retreat = !isAttacking;
			}
			
		}
		// Order all units to attack the target.
		for (Agent agent : units)
		{
			if (agent.unit.getType() == UnitType.Protoss_Reaver)
			{
				boolean closeEnemy = false;
				if (agent.unit.isMoving())
				{
					for (Unit enemy : EnemyManager.getEnemyUnits())
						if (!enemy.isLifted() && ! enemy.getType().isFlyer() && agent.distanceSquared(enemy) <= 250*250)
						{
							closeEnemy = true;
							agent.unit.stop();
							break;
						}
				}
				if (!closeEnemy)
					agent.attack(target);
				continue;
			}
			if (agent.unit.getType().groundWeapon() == WeaponType.None && agent.unit.getType().airWeapon() != WeaponType.None)
			{
				agent.attack(target);
				continue;
			}
			if (agent.unit.getType() != UnitType.Protoss_Dark_Templar && retreat && closeAgents.contains(agent)
					&& agent.distanceSquared(Tyr.tileToPosition(self.getStartLocation())) >= 480 * 480)
			{
				agent.unit.move(Tyr.tileToPosition(self.getStartLocation()));
				agent.drawCircle(Color.Blue);
				continue;
			}
			Unit killTarget = null;
			double distance = Double.MAX_VALUE;
			
			Unit closeAttackTarget = null;
			int closePriority = 0;
			// Priority for attacking the chosen unit.
			// 0 means don't care
			// 1 is for regular buildings (i.e. not defensive structures)
			// 2 is for workers
			// 3 is for defensive structures
			// 4 is for enemy units which can attack back
			// 5 is for high priority targets, such as reavers
			int priority = 0;
			for (Unit enemy : EnemyManager.getEnemyUnits())
			{
				if (!agent.unit.canAttack(enemy))
					continue;
				if ((enemy.getType().isFlyer() || enemy.isLifted()) 
						&& (agent.unit.getType().airWeapon() == WeaponType.None || agent.unit.getType().airWeapon() == null))
					continue;
				
				int timeSinceAttack = game.getFrameCount() - EnemyManager.getManager().getLastAttackFrame(enemy);
				
				boolean wall = false;
				if (enemy.getType() == UnitType.Terran_Barracks && !enemy.isLifted())
				{
					if (enemyNatural == null)
						enemyNatural = SpaceManager.getEnemyNatural().getRegion();
					if (enemyNatural != null)
						for (Chokepoint choke : enemyNatural.getChokepoints())
						{
							if (PositionUtil.distanceSq(enemy, choke.getCenter()) <= 200 * 200)
							{
								wall = true;
							}
						}
				}
				
				int newPriority = 0;
				if (enemy.getType() == UnitType.Protoss_Reaver || wall)
					newPriority = 5;
				else if (enemy.getType().isBuilding())
					newPriority = enemy.getType().groundWeapon() != null && enemy.getType().groundWeapon() != WeaponType.None?4:1;
				else if (enemy.getType().isWorker())
					newPriority = 2;
				else if (timeSinceAttack <= ignoreAfterNonAggressive && enemy.getType().groundWeapon() != null && enemy.getType().groundWeapon() != WeaponType.None)
					newPriority = 3;
				else
					newPriority = 0;
				
				if (agent.unit.getType() == UnitType.Protoss_Dark_Templar && enemy.getType() == UnitType.Terran_Missile_Turret)
					newPriority = 5;
				
				if (newPriority > closePriority && agent.unit.isInWeaponRange(enemy) && newPriority > 1)
				{
					closePriority = newPriority;
					closeAttackTarget = enemy;
				}
				
				if (newPriority < priority || newPriority == 0)
					continue;

				double newDistance = agent.distanceSquared(enemy);
				if (newDistance >= maxTargetRange * maxTargetRange)
					continue;
				if (newPriority == priority && newDistance >= distance)
					continue;
				
				killTarget = enemy;
				distance = newDistance;
				priority = newPriority;
			}
			
			if (killTarget != null && agent.unit.isInWeaponRange(killTarget))
				agent.attack(killTarget);
			else if (closeAttackTarget != null && agent.unit.canAttack())
				agent.attack(closeAttackTarget);
			else if (killTarget != null && !agent.unit.isAttackFrame())
			{
				agent.order(new None(agent));
				agent.move(killTarget.getPosition());
			}
			else if (!agent.unit.isAttackFrame())
			{
				agent.order(new None(agent));
				agent.move(target);
			}
		}
	}
}
