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

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.Attack;
import com.tyr.agents.MedicAttack;
import com.tyr.agents.None;
import com.tyr.buildingplacement.SpaceManager;

import bwapi.Color;
import bwapi.Game;
import bwapi.Order;
import bwapi.Player;
import bwapi.Position;
import bwapi.TechType;
import bwapi.Unit;
import bwapi.UnitType;
import bwta.Chokepoint;
import bwta.Region;


/**
 * A unit group for sending units to attack a certain location.
 * @author Simon
 *
 */
public class WallAttackGroup extends IAttackGroup
{
	/**
	 * A possible target to kill.
	 */
	private Unit killTarget;
	
	/**
	 * Do we hunt reavers and tanks with zealots?
	 */
	public static boolean hunting = false;
	
	/**
	 * Do zealots fear barracks?
	 */
	public static boolean fearBarracks = false;
	
	/**
	 * The reaver or tank we intend to kill.
	 */
	private Unit prey;
	
	/**
	 * The zealot which will hunt the reaver.
	 */
	private Agent hunter;
	
	/**
	 * High templar are treated seperately from the rest of the army.
	 */
	private List<Agent> highTemplar = new ArrayList<>();
	
	/**
	 * The enemies natural expansion.
	 */
	private static Region enemyNatural = null;

	/**
	 * Do we kill the opponents wall?
	 */
	public static boolean killWall;
	
	/**
	 * Do we attack the opponents wall with zealots?
	 */
	private boolean attackWall = false;

	private List<Unit> enemyTanks = new ArrayList<>();
	
	private List<ZealotDropGroup> drops = new ArrayList<>();
	
	/**
	 * A unit group for sending units to attack a certain location.
	 * @param target The target where we want the units to attack.
	 */
	public WallAttackGroup(Position target) 
	{
		super(target);
	}
	
	/**
	 * Setter for the target unit to kill.
	 */
	public void setKillTarget(Unit killTarget)
	{
		this.killTarget = killTarget;
	}

	@Override
	public void onFrame(Game game, Player self, Tyr bot)
	{
		if (killTarget != null)
			Tyr.drawCircle(killTarget.getPosition(), Color.Purple, 16);

		for (ZealotDropGroup drop : drops)
			drop.cleanup();
		
		armyLoop : for (int i = units.size() - 1; i >= 0; i--)
		{
			final Agent agent = units.get(i);
			if (agent.unit.getType() == UnitType.Protoss_Shuttle)
			{
				for (ZealotDropGroup drop : drops)
				{
					if (drop.needsShuttle())
					{
						units.remove(i);
						drop.add(agent);
						continue armyLoop;
					}
				}
				ZealotDropGroup drop = new ZealotDropGroup();
				units.remove(i);
				drop.add(agent);
				drops.add(drop);
			}
		}
		
		for (ZealotDropGroup drop : drops)
		{
			if (!drop.needsShuttle())
			{
				for (int i = units.size() - 1; i >= 0; i--)
				{
					if (drop.getZealots() >= 4)
						break;
					final Agent agent = units.get(i);
					if (agent.unit.getType() == UnitType.Protoss_Zealot)
					{
						units.remove(i);
						drop.add(agent);
					}
				}
			}
			
			drop.setTarget(getDropTarget());
			drop.onFrame(game, self, bot);
		}
		
		Position medicTarget = target;
		for (Agent agent : units)
			if (agent.unit.getGroundWeaponCooldown() > 0 )
			{
				medicTarget = agent.unit.getPosition();
				break;
			}
		
		for (int i = enemyTanks.size() - 1; i >= 0; i--)
		{
			final Unit tank = enemyTanks.get(i);
			final Position pos = EnemyManager.getManager().getLastPosition(tank);
			for (Unit unit : self.getUnits())
			{
				if (unit.getType().isBuilding() || unit.getType().isWorker())
					continue;
				if (pos == null || PositionUtil.distanceSq(pos, unit) <= 200 * 200)
				{
					enemyTanks.remove(i);
					break;
				}
			}
		}

		for (Unit unit : EnemyManager.getEnemyUnits())
		{
			if (unit.getType() == UnitType.Terran_Siege_Tank_Siege_Mode
					|| unit.getType() == UnitType.Terran_Siege_Tank_Tank_Mode)
			{
				if (!enemyTanks.contains(unit))
					enemyTanks.add(unit);
			}
		}
		
		if (hunting)
		{
			
			if (prey == null || Agent.isDead(prey) || hunter == null || hunter.isDead())
			{
				prey = null;
				hunter = null;
			}
			
			if (prey == null)
			{
				ArrayList<Unit> enemyTargets = new ArrayList<Unit>();

				for (Unit enemy : EnemyManager.getEnemyUnits())
					if(enemy.getType() == UnitType.Protoss_Reaver
					|| enemy.getType() == UnitType.Terran_Siege_Tank_Siege_Mode
					|| enemy.getType() == UnitType.Terran_Siege_Tank_Tank_Mode)
						enemyTargets.add(enemy);
				
				agentLoop : for (Agent agent : units)
				{
					if (agent.unit.getType() != UnitType.Protoss_Zealot)
						continue;
					
					for (Unit unit : enemyTargets)
					{
						if (agent.distanceSquared(unit) <= 300 * 300)
						{
							prey = unit;
							hunter = agent;
							break agentLoop;
						}
					}
				}
			}
		}
		
		if (!highTemplar.isEmpty())
			manageHighTemplar();

		if (enemyNatural == null && bot.suspectedEnemy.size() == 1)
			enemyNatural = SpaceManager.getEnemyNatural().getRegion();
		Unit wall = null;
		if (enemyNatural != null)
		{
			for (Chokepoint choke : enemyNatural.getChokepoints())
				Tyr.drawCircle(choke.getCenter(), Color.Blue, 200);
			for (Unit enemy : EnemyManager.getEnemyUnits())
			{
				if (enemy.getType() != UnitType.Terran_Barracks || enemy.isLifted())
					continue;
				for (Chokepoint choke : enemyNatural.getChokepoints())
					if (PositionUtil.distanceSq(enemy, choke.getCenter()) <= 200 * 200)
						wall = enemy;
			}
		}
		Position wallPos = wall == null ? null : wall.getPosition();
		if (wallPos == null && enemyNatural != null)
		{
			for (EnemyPosition pos : EnemyManager.getManager().enemyBuildingMemory)
			{
				if (pos.type != UnitType.Terran_Barracks)
					continue;

				for (Chokepoint choke : enemyNatural.getChokepoints())
					if (PositionUtil.distanceSq(pos.pos, choke.getCenter()) <= 200 * 200)
						wallPos = pos.pos;
			}
		}
		
		boolean dragoonAtWall = false;
		int zealotsAtWall = 0;
		DebugMessages.addMessage("Wall: " + (wall != null));
		
		
		/*
		Unit wallDepot = null;
		if (wall != null)
		{
			for (Unit enemy : EnemyManager.getEnemyUnits())
			{
				if (enemy.getType() != UnitType.Terran_Supply_Depot)
					continue;
				Tyr.drawCircle(enemy.getPosition(), Color.Orange, 16);
				if (PositionUtil.distanceSq(wall, enemy) <= 100)
					wallDepot = enemy;
			}
		}
		*/
		
		if (wallPos != null)
		{
			Tyr.drawCircle(wallPos, Color.Red, 300);
			for (Agent agent : units)
			{
				if (agent.unit.getType() == UnitType.Protoss_Dragoon && agent.distanceSquared(wallPos) <= 300 * 300)
					dragoonAtWall = true;
				if (agent.unit.getType() == UnitType.Protoss_Zealot && agent.distanceSquared(wallPos) <= 400 * 400)
					zealotsAtWall++;
			}
		}
		attackWall = zealotsAtWall >= 1 && killWall; //(attackWall ? 3 : 5);
		DebugMessages.addMessage("Zealots at wall: " + zealotsAtWall);
		
		final List<Unit> repairingSCVs = new ArrayList<>();
		for (Unit unit : EnemyManager.getEnemyUnits())
		{
			if (wall == null) 
				break;
			if (!unit.getType().isWorker() || !unit.isRepairing())
				continue;
			
			if (PositionUtil.distanceSq(unit, wall) <= 200 * 200)
				repairingSCVs.add(unit);
		}
		
		// Order all units to attack the target.
		agentLoop : for (Agent agent : units)
		{
			if (fearBarracks
					&& !dragoonAtWall 
					&& !attackWall 
					&& agent.unit.getType() == UnitType.Protoss_Zealot 
					&& wallPos != null
					&& agent.distanceSquared(wallPos) <= 300 * 300)
			{
				agent.move(Tyr.getStartLocation());
				continue agentLoop;
			}
			
			for (Unit tank : enemyTanks)
			{
				Position pos = EnemyManager.getManager().getLastPosition(tank);
				if (pos != null && agent.distanceSquared(pos) <= 550 * 550)
				{
					agent.move(Tyr.getStartLocation());
					continue agentLoop;
				}
			}
			
			/*
			if (agent.unit.getType() == UnitType.Protoss_Dragoon && attackWall)
			{
				for (Unit scv : repairingSCVs)
				{
					if (agent.distanceSquared(scv) <= 300 * 300)
					{
						agent.attack(scv);
						game.drawLineMap(agent.unit.getPosition(), scv.getPosition(), Color.Red);
						agent.drawCircle(Color.Red, 6);
						continue agentLoop;
						
					}
				}
			}
			*/
			
			
			if (wall != null 
					&& (agent.unit.getType() == UnitType.Protoss_Dragoon || agent.unit.getType() == UnitType.Protoss_Zealot)
					&& attackWall
					&& agent.distanceSquared(wall) <= 300 * 300)
			{
				agent.attack(wall);
				agent.drawCircle(Color.Yellow, 6);
				continue;
			}
			if (agent == hunter)
			{
				agent.attack(prey);
				game.drawLineMap(agent.unit.getPosition(), prey.getPosition(), Color.Red);
				continue;
			}
			
			if (agent.unit.getType() == UnitType.Protoss_Reaver)
			{
				if (!agent.unit.isTraining() && agent.unit.getScarabCount() < 5)
					agent.unit.train(UnitType.Protoss_Scarab);
				
			}
			else if (agent.unit.getType() == UnitType.Protoss_Carrier)
			{
				if (!agent.unit.isTraining() && agent.unit.getInterceptorCount() < 8)
					agent.unit.train(UnitType.Protoss_Interceptor);

				if (agent.unit.isTraining() && agent.unit.getInterceptorCount() < 4)
					agent.unit.move(Tyr.getStartLocation());
				if (agent.unit.isTraining() && agent.unit.getInterceptorCount() < 8)
					agent.attack(target);
				
				if (agent.unit.isUnderAttack())
				{
					agent.unit.move(Tyr.getStartLocation());
					continue;
				}
			}
			
			if (agent.unit.getType() == UnitType.Terran_Vulture)
			{
				if (!self.hasResearched(TechType.Spider_Mines))
					agent.drawCircle(Color.Green, 6);
				else if (agent.unit.getSpiderMineCount() <= 0)
					agent.drawCircle(Color.Blue, 6);
			}
			if (agent.unit.getType() == UnitType.Terran_Medic)
				agent.order(new MedicAttack(agent, medicTarget));

			
			if (agent.unit.getType() == UnitType.Protoss_Dark_Templar)
			{
				boolean retreat = false;
				for (Unit enemy : EnemyManager.getEnemyUnits())
				{
					if (!enemy.getType().isDetector())
						continue;
					if (agent.distanceSquared(enemy) <= 400 * 400)
						retreat = true;
				}
				if (retreat)
				{
					agent.move(Tyr.getStartLocation());
					continue;
				}
			}
			
			else if (killTarget != null)
			{
				agent.order(new None(agent));
				if (agent.distanceSquared(killTarget) <= 
						agent.unit.getType().groundWeapon().maxRange() * agent.unit.getType().groundWeapon().maxRange())
					agent.attack(killTarget);
				else
					agent.unit.move(killTarget.getPosition());
			}
			else
				agent.order(new Attack(agent, target));
		}
	}
	
	private void manageHighTemplar() 
	{
		final Set<Unit> stormTargets = new HashSet<>();
		
		for (Unit enemy : EnemyManager.getEnemyUnits())
		{
			if (enemy.getType().isBuilding() || enemy.getType() == UnitType.Zerg_Overlord)
				continue;
			
			int count = 1;
			for (Unit enemy2 : EnemyManager.getEnemyUnits())
			{
				if (enemy.getType().isBuilding() || enemy.getType() == UnitType.Zerg_Overlord)
					continue;
				
				if (Math.abs(enemy.getX() - enemy2.getX()) <= 48 && Math.abs(enemy.getY() - enemy2.getY()) <= 48)
					count++;
			}
			
			if (count >= 6)
				stormTargets.add(enemy);
		}
		
		templar : for (Agent agent : highTemplar)
		{
			if (agent.unit.getOrder() == Order.CastPsionicStorm)
				continue;
			
			final boolean canStorm = Tyr.self.hasResearched(TechType.Psionic_Storm)
					&& agent.unit.getEnergy() >= 75;
			
			for (Unit enemy : EnemyManager.getEnemyUnits())
			{
				final int dist = agent.distanceSquared(enemy);
				if (!enemy.getType().isBuilding() 
						&& enemy.getType() != UnitType.Zerg_Overlord
						&& dist <= 300 * 300 
						&& canStorm
						&& stormTargets.contains(enemy))
					agent.unit.useTech(TechType.Psionic_Storm, enemy.getPosition());
				
				if (dist <= 150 * 150)
				{
					int dx = agent.unit.getX() - enemy.getX();
					int dy = agent.unit.getX() - enemy.getX();
					final double length = Math.sqrt(dx * dx + dy * dy);
					final Position fleePos = new Position(
							(int)(agent.unit.getX() + dx * 64 / length),
							(int)(agent.unit.getY() + dy * 64 / length));
					agent.move(fleePos);
				}
				else if (dist <= 250 * 250)
				{
					agent.move(Tyr.getStartLocation());
					continue templar;
				}
			}
			agent.unit.move(target);
		}
		
	}
	
	private Position getDropTarget()
	{
		int dist = 1000000000;
		Position result = Tyr.getStartLocation();
		for (Agent agent : units)
		{
			int newDist = agent.distanceSquared(target); 
			if (newDist < dist)
			{
				result = agent.unit.getPosition();
				dist = newDist;
			}
		}
		return result;
	}

	/**
	 * Adds an agent to the list of agents.
	 */
	@Override
	public void add(Agent agent)
	{
		if (agent.unit.getType() == UnitType.Protoss_High_Templar)
		{
			highTemplar.add(agent);
			agent.order(new None(agent));
			agent.unit.stop();
		}
		super.add(agent);
	}

	
	/**
	 * Removes all dead agents from this unit group.
	 */
	public void cleanup()
	{
		super.cleanup();
		Agent.clean(highTemplar);
	}
}
