/*
* 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.HashMap;
import java.util.List;
import java.util.Map;

import com.tyr.EnemyManager;
import com.tyr.Tyr;
import com.tyr.agents.Agent;
import bwapi.Color;
import bwapi.Game;
import bwapi.Player;
import bwapi.Position;
import bwapi.Unit;
import bwapi.UnitType;
import bwapi.WeaponType;


/**
 * A unit group for sending units to attack a certain location.
 * @author Simon
 *
 */
public class DistributeAttackGroup extends IAttackGroup
{
	/**
	 * The maximum range at which we will target enemy units.
	 */
	private double maxTargetRange = 300;
	
	/**
	 * Range at which an enemy is considered a threat.
	 */
	public static int threatRange = 450;
	
	/**
	 * Distance from which to wait before attacking.
	 */
	public static int waitDistance = -200;
	
	private Map<Integer, EnemyUnit> enemyMap = new HashMap<Integer, EnemyUnit>();
	
	private Map<Integer, Integer> attackMap = new HashMap<Integer, Integer>();
	
	/**
	 * A unit group for sending units to attack a certain location.
	 * @param target The target where we want the units to attack.
	 */
	public DistributeAttackGroup(Position target) 
	{
		super(target);
	}
	
	@Override
	public void onFrame(Game game, Player self, Tyr bot)
	{
		Tyr.drawCircle(target, Color.Orange, 64);
		for (Agent agent : units)
			if (agent.unit.getType() == UnitType.Protoss_Reaver)
			{
				agent.drawCircle(Color.Purple);
				if (!agent.unit.isTraining())
					agent.unit.train(UnitType.Protoss_Scarab);
			}
		
		int waiting = 0;
		for (Agent agent : units)
			if (agent.distanceSquared(target) < (waitDistance + 200) * (waitDistance + 200))
			{
				agent.drawCircle(Color.Blue);
				waiting++;
			}
		
		List<Integer> removeIDs = new ArrayList<>();
		
		for (Integer id : enemyMap.keySet())
		{
			if (enemyMap.get(id).isDead())
				removeIDs.add(id);
			else
				Agent.clean(enemyMap.get(id).attackingAgents);
		}
		for (Integer id : removeIDs)
			enemyMap.remove(id);
		
		removeIDs = new ArrayList<>();
		for (Integer id : attackMap.keySet())
			if (!enemyMap.containsKey(attackMap.get(id)))
				removeIDs.add(id);
		for (Integer id : removeIDs)
			attackMap.remove(id);
		
		for (Agent agent : units)
		{
			if (attackMap.containsKey(agent.unit.getID()))
				continue;
			
			EnemyUnit target = null;
			double enemyDistance = maxTargetRange * maxTargetRange;
			
			for (Unit enemy : EnemyManager.getEnemyUnits())
			{
				if (enemy.getType().isWorker())
					continue;
				
				if (enemy.getType().isBuilding())
					continue;
				
				if (enemy.getType() == UnitType.Protoss_Observer || enemy.getType() == UnitType.Terran_Vulture_Spider_Mine)
					continue;
				
				if (!enemy.isVisible())
					continue;
				
				if (enemy.getType() == UnitType.Terran_Vulture && agent.unit.getType() == UnitType.Protoss_Zealot)
					continue;

				if (agent.unit.getType().airWeapon() == WeaponType.None && enemy.getType().isFlyer())
					continue;
				
				if (agent.unit.getType().groundWeapon() == WeaponType.None && !enemy.getType().isFlyer())
					continue;
				
				final int distanceSq = agent.distanceSquared(enemy);
				final int maxDist;
				if (enemy.getType() == UnitType.Terran_Siege_Tank_Siege_Mode || enemy.getType() == UnitType.Terran_Siege_Tank_Tank_Mode)
					maxDist = 450 * 450;
				else
					maxDist = (int) (maxTargetRange * maxTargetRange);
					
				if (distanceSq >= maxDist)
					continue;
				
				final EnemyUnit unit;
				if (!enemyMap.containsKey(enemy.getID()))
				{
					unit = new EnemyUnit(enemy);
					enemyMap.put(enemy.getID(), unit);
				}
				else
					unit = enemyMap.get(enemy.getID());
				
				if (enemy.getType().isWorker()
						|| enemy.getType() == UnitType.Terran_Vulture
						|| enemy.getType() == UnitType.Terran_Marine
						|| enemy.getType() == UnitType.Terran_Firebat)
				{
					if (unit.attackingAgents.size() > 0)
						continue;
				}
				
				if (target == null)
				{
					target = unit;
					enemyDistance = distanceSq;
					continue;
				}
				
				if (target.enemy.getType().isWorker() && !enemy.getType().isWorker())
					continue;
				
				if (!target.enemy.getType().isWorker() && enemy.getType().isWorker())
				{
					target = unit;
					enemyDistance = distanceSq;
					continue;
				}
				
				if (unit.attackingAgents.size() < target.attackingAgents.size())
				{
					target = unit;
					enemyDistance = distanceSq;
					continue;
				}
				
				if (unit.attackingAgents.size() > target.attackingAgents.size())
					continue;
				
				if (distanceSq < enemyDistance)
				{
					target = unit;
					enemyDistance = distanceSq;
				}
			}
			
			if (target != null)
			{
				target.attackingAgents.add(agent);
				attackMap.put(agent.unit.getID(), target.enemy.getID());
			}
		}
		
		// Order all units to attack the target.
		for (Agent agent : units)
		{
			if (waiting <= 15 && agent.distanceSquared(target) < waitDistance * waitDistance)
			{
				agent.unit.move(Tyr.tileToPosition(self.getStartLocation()));
				continue;
			}
			
			if (agent.unit.getType() == UnitType.Protoss_Reaver)
			{
				if (agent.unit.isMoving())
				{
					for (Unit enemy : EnemyManager.getEnemyUnits())
						if (!enemy.isLifted() && ! enemy.getType().isFlyer() && agent.distanceSquared(enemy) <= 250*250)
						{
							agent.unit.stop();
							return;
						}
				}
				agent.attack(target);
				continue;
			}
			
			if (attackMap.containsKey(agent.unit.getID()))
			{
				game.drawLineMap(agent.unit.getPosition(), enemyMap.get(attackMap.get(agent.unit.getID())).enemy.getPosition(), Color.Red);
				agent.attack(enemyMap.get(attackMap.get(agent.unit.getID())).enemy);
			}
			else
				agent.attack(target);
		}
	}
	
	private static class EnemyUnit
	{
		public Unit enemy;
		public List<Agent> attackingAgents = new ArrayList<Agent>();
		
		public EnemyUnit(Unit enemy)
		{
			this.enemy = enemy;
		}
		
		public boolean isDead()
		{
			return enemy.getHitPoints() <= 0 || enemy.getRemoveTimer() != 0 || !enemy.exists();
		}
	}
}
