/*
* 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 AttackGroup extends IAttackGroup
{
	/**
	 * A possible target to kill.
	 */
	private Unit killTarget;

	/**
	 * Do we force marines to retreat from lurkers?
	 */
	public static boolean fearLurkers;
	
	/**
	 * Do we place offensive mines?
	 */
	public static boolean offensiveMines = false;
	
	/**
	 * Do we hunt reavers and tanks with zealots?
	 */
	public static boolean hunting = false;
	
	/**
	 * Do zealots fear barracks?
	 */
	public static boolean fearBarracks = false;
	
	/**
	 * Do we target fire a wall when we find one?
	 */
	public static boolean killWall = 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 attack the opponents wall with zealots?
	 */
	private boolean attackWall = false;
	
	/**
	 * A unit group for sending units to attack a certain location.
	 * @param target The target where we want the units to attack.
	 */
	public AttackGroup(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);
		
		Position medicTarget = target;
		for (Agent agent : units)
			if (agent.unit.getGroundWeaponCooldown() > 0 )
			{
				medicTarget = agent.unit.getPosition();
				break;
			}
		
		ArrayList<Unit> enemyLurkers = new ArrayList<Unit>();
		if (fearLurkers)
			for (Unit enemy : EnemyManager.getEnemyUnits())
				if(enemy.getType() == UnitType.Zerg_Lurker)
					enemyLurkers.add(enemy);

		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;
				Tyr.drawCircle(enemy.getPosition(), Color.Purple, 16);
				for (Chokepoint choke : enemyNatural.getChokepoints())
					if (PositionUtil.distanceSq(enemy, choke.getCenter()) <= 200 * 200)
						wall = enemy;
			}
		}
		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 (wall != null)
		{
			Tyr.drawCircle(wall.getPosition(), Color.Red, 300);
			for (Agent agent : units)
			{
				if (agent.unit.getType() == UnitType.Protoss_Dragoon && agent.distanceSquared(wall) <= 300 * 300)
					dragoonAtWall = true;
				if (agent.unit.getType() == UnitType.Protoss_Zealot && agent.distanceSquared(wall) <= 400 * 400)
					zealotsAtWall++;
			}
		}
		attackWall = zealotsAtWall >= 1; //(attackWall ? 3 : 5);
		DebugMessages.addMessage("Zealots at wall: " + zealotsAtWall);
		// Order all units to attack the target.
		agentLoop : for (Agent agent : units)
		{
			if (fearBarracks && !dragoonAtWall && !attackWall && agent.unit.getType() == UnitType.Protoss_Zealot)
			{
				for (Unit enemy : EnemyManager.getEnemyUnits())
				{
					if (enemy.getType() != UnitType.Terran_Barracks)
						continue;
					if (agent.distanceSquared(enemy) <= 300 * 300)
					{
						agent.move(Tyr.getStartLocation());
						continue agentLoop;
					}
				}
			}
			if (killWall && wallDepot != null 
					&& (agent.unit.getType() == UnitType.Protoss_Dragoon || (agent.unit.getType() == UnitType.Protoss_Zealot && attackWall))
					&& agent.distanceSquared(wallDepot) <= 300 * 300)
			{
				agent.attack(wallDepot);
				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_Marine
					|| agent.unit.getType() == UnitType.Terran_Vulture
					|| agent.unit.getType() == UnitType.Terran_Goliath)
			{
				for (Unit lurker : enemyLurkers)
				{
					if (agent.distanceSquared(lurker) < 300 * 300)
					{
						agent.unit.move(Tyr.tileToPosition(self.getStartLocation()));
						agent.drawCircle(Color.Blue);
						continue;
					}
				}
				if (fearLurkers)
				{
					for (EnemyPosition defensive : EnemyManager.getManager().enemyDefensiveStructures)
					{
						if (agent.distanceSquared(defensive.pos) < 300 * 300)
						{
							agent.unit.move(Tyr.tileToPosition(self.getStartLocation()));
							agent.drawCircle(Color.Blue);
							continue;
						}
					}
				}
			}
			if (agent.unit.getType() == UnitType.Terran_Vulture 
					&& self.hasResearched(TechType.Spider_Mines)
					&& agent.unit.getSpiderMineCount() > 0
					&& game.getFrameCount() % 100 == 0
					&& offensiveMines)
			{
				boolean mined = false;
				for (Unit enemy : EnemyManager.getEnemyUnits())
				{
					if (enemy.getType().isFlyer()
							|| enemy.getType().isWorker()
							|| enemy.getType().isBuilding()
							|| enemy.getType() == UnitType.Protoss_Zealot)
						continue;
					if (agent.distanceSquared(enemy) <= 300 * 300)
					{
						agent.unit.useTech(TechType.Spider_Mines, agent.unit.getPosition());
						agent.drawCircle(Color.Red, 300);
						agent.drawCircle(Color.Red, 6);
						mined = true;
						break;
					}
				}
				if (mined)
					continue;
				agent.drawCircle(Color.Yellow, 300);
			}
			else 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);
		}
		
	}

	/**
	 * 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);
	}
}
