/*
* 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 com.tyr.DebugMessages;
import com.tyr.EnemyManager;
import com.tyr.Scanner;
import com.tyr.StopWatch;
import com.tyr.Tyr;
import com.tyr.agents.Agent;
import com.tyr.agents.None;
import com.tyr.buildingplacement.SpaceManager;

import bwapi.Color;
import bwapi.Game;
import bwapi.Player;
import bwapi.Unit;
import bwapi.UnitType;


/**
 * This class implements the management of Battlecruisers.
 * It sends the Battlecruisers out, and it also makes sure they retreat when they are damaged.
 * Finally when they are back home, Battlecruisers are automatically repaired.
 * @author Simon
 *
 */
public class BCGroup extends UnitGroup 
{
	/**
	 * The Battlecruisers are divided up into squads. 
	 */
	public ArrayList<BCSquad> squads = new ArrayList<BCSquad>();
	
	/**
	 * A special squad that manages the retreating Battlecruisers.
	 */
	public BCSquad retreatSquad;
	
	/**
	 * A special squad that manages all Battlecruisers that need to be repaired.
	 */
	public BCSquad repairSquad;
	
	/**
	 * The OutOfJob UnitGroup, where all Battlecruisers go to when they are removed from one of the squads.
	 */
	public OutOfJob outOfJob = new OutOfJob();
	
	/**
	 * Is there an enemy close to the base that we need to defend against?
	 */
	boolean enemyThreat = false;
	
	/**
	 * A list of SCVs that are currently repairing the Battlecruisers.
	 */
	public ArrayList<Agent> bcrepair = new ArrayList<Agent>();
	
	/**
	 * The Battlecruiser that is currently being repaired.
	 */
	private Agent damagedbc = null;
	
	/**
	 * A stopwatch for tracking how much time the class is taking.
	 */
	private StopWatch stopWatch = new StopWatch();
	
	/**
	 * This class implements the management of Battkecruisers. 
	 * @param rejects The OutOfJob object where we send all the units we no longer need.
	 */
	public BCGroup(OutOfJob rejects)
	{
		super(rejects);
	}
	
	/**
	 * Adds a squad to the current list of squads.
	 * @return
	 */
	public BCSquad addBCSquad()
	{
		BCSquad s = new BCSquad(outOfJob);
		squads.add(s);
		return s;
	}
	
	@Override
	public void onFrame(Game game, Player self, Tyr bot)
	{
		stopWatch.start();
		
		// Initialize retreatSquad and repairSquad.
		if(retreatSquad == null)
		{
			retreatSquad = new BCSquad(outOfJob);
			repairSquad = new BCSquad(outOfJob);
		}
		
		if (EnemyManager.getManager().enemyBuildingMemory.size() == 0 && EnemyManager.getManager().getInvader() == null)
		{
			// In case the scanner is starting to search for the enemy,
			// Add the Battlecruisers to the scanner to help the search. 
			if (bot.scanner.action != Scanner.Eliminating && bot.suspectedEnemy.size() == 1 && game.isVisible(bot.suspectedEnemy.get(0).getTilePosition()))
			{
				bot.scanner.startElimination();
				for (BCSquad squad : squads)
					for(Agent agent : squad.units)
						bot.scanner.addAir(agent);
			}
			
			// If the scanner is searching, we do not control the Battlecruisers anymore.
			if (bot.scanner.action == Scanner.Eliminating)
			{
				long time = stopWatch.time();
				
				if (time > 50)
					DebugMessages.addMessage("BCGroup is taking too long: " + time + "ms.");
				
				return;
			}
		}
		
		// If an enemy is in attacking range of a building, it is considered a threat.
		enemyThreat = false;
		if (EnemyManager.getManager().getInvader() != null)
		{
			int enemyRange = 4*32 + 10;
			for(Unit enemyUnit : EnemyManager.getEnemyUnits())
			{
				enemyRange = Math.max(enemyRange, enemyUnit.getType().groundWeapon().maxRange() + 10);
			}
			for(Unit unit : self.getUnits())
			{
				if (!unit.getType().isBuilding())
					continue;
				if(unit.getDistance(EnemyManager.getManager().getInvader().getPosition()) <= enemyRange)
				{
					enemyThreat = true;
					break;
				}
			}
		}
		
		int bcsquadPos = 0;
		
		for(BCSquad squad : squads)
		{
			for(int i=0; i < squad.units.size(); i++)
			{
				// Add damaged units to the retreatSquad or repairSquad, depening on whether they are in attack mode or not.
				Agent agent = squad.units.get(i);
				if (squad.attackMode && agent.unit.getHitPoints() <= 125)
				{
					squad.remove(i);
					i--;
					retreatSquad.add(agent);
				}
				if(!squad.attackMode &&  agent.unit.getHitPoints() < agent.unit.getType().maxHitPoints())
				{
					squad.remove(i);
					i--;
					repairSquad.add(agent);
				}
			}
			
			// If there are too few Battlecruisers left in an attacking squad, the whole squad will retreat.
			if ((squad.units.size() < BCSquad.fleeMinimum && squad.attackMode) || squad.retreatMode)
			{
				while(squad.units.size() > 0)
				{
					retreatSquad.add(squad.units.get(squad.units.size()-1));
					squad.units.remove(squad.units.size()-1);
				}
			}
			
			for(int i=0; i<retreatSquad.units.size(); i++)
			{
				Agent agent = retreatSquad.units.get(i);
				// If the retreating Battlecruisers are close enough to our main base, they can be added to the repairSquad.
				if (retreatSquad.target != null && agent.distanceSquared(retreatSquad.target) <= 200*200)
				{
					repairSquad.add(agent);
					retreatSquad.remove(i);
					i--;
				}
			}
			
			for(int i=0; i<repairSquad.units.size(); i++)
			{
				Agent agent = repairSquad.units.get(i);
				// Remove Battlecruisers from the repair squad if they are fully repaired.
				if (agent.unit.getHitPoints() >= agent.unit.getType().maxHitPoints())
				{
					outOfJob.add(agent);
					repairSquad.remove(i);
					i--;
				}
				else if (agent.unit.getHitPoints() <= 125 && retreatSquad.target != null && agent.distanceSquared(retreatSquad.target) > 200*200)
				{
					// If the unit has move too far away from our main, it is added back into the retreat squad, so it will return.
					retreatSquad.add(agent);
					repairSquad.remove(i);
					i--;
				}
			}
			
			// The repair squad is never in attack or retreat mode.
			repairSquad.attackMode = false;
			repairSquad.retreatMode = false;
			
			// The retreat squad is always in retreat mode.
			retreatSquad.retreatMode = true;
			retreatSquad.attackMode = false;
		}
		
		// remove empty squads.
		for(int i=squads.size()-1; i>= 0; i--)
			if(squads.get(i).units.size() == 0)
				squads.remove(i);
		
		// Add new units to the proper squad.
		for(Agent agent = outOfJob.pop();agent != null; agent = outOfJob.pop())
		{
			if (agent.unit.getType() == UnitType.Terran_Battlecruiser)
			{
				for(;bcsquadPos < squads.size() && (squads.get(bcsquadPos).units.size() >= BCSquad.maximum || squads.get(bcsquadPos).attackMode);bcsquadPos++);
				
				// If all squads are filled up or attacking, we create a new squad.
				if (bcsquadPos >= squads.size())
					addBCSquad();
				
				squads.get(bcsquadPos).add(agent);
				continue;
			}
			else rejects.add(agent);
		}
		
		if (repairSquad.units.size() > 0 && EnemyManager.getManager().getInvader() == null)
		{
			// Add SCVs to repair damaged Battlecruisers.
			while (bcrepair.size() < 3)
			{
				Agent scv = bot.workForce.pop(SpaceManager.getMainExit());
				if(scv == null)
					break;
				bcrepair.add(scv);
				
				scv.order(new None(scv));
			}
			
			// Determine which Battlecruiser needs to be repaired.
			if (damagedbc != null && (damagedbc.isDead() || damagedbc.unit.getHitPoints() >= damagedbc.unit.getType().maxHitPoints()))
				damagedbc = null;
			if (damagedbc == null)
			{
				int hp = 500;
				for(Agent agent : repairSquad.units)
				{
					if (agent.unit.getHitPoints() < hp)
					{
						damagedbc = agent;
						hp = agent.unit.getHitPoints();
					}
				}
			}
			
			if(damagedbc != null)
			{
				boolean first = true;
				for(Agent scv : bcrepair)
				{
					if (first)
					{
						// The battlecruiser is sent toward the first repairing SCV.
						damagedbc.unit.attack(scv.unit.getPosition());
						first = false;
					}
					
					// The SCVs are sent to repair the Battlecruiser.
					if (!scv.unit.isRepairing())
						scv.unit.repair(damagedbc.unit);
				}
			}
			
			// Draw some debug information for the repairing SCVs.
			for(Agent scv : bcrepair)
			{
				scv.drawCircle(Color.Yellow);
				if(!scv.unit.isRepairing())
					continue;
				Unit bcTarget = scv.unit.getTarget();
				if(bcTarget != null)
					game.drawLineMap(scv.unit.getX(), scv.unit.getY(), bcTarget.getX(), bcTarget.getY(), Color.Yellow);
			}
		}
		else
		{
			// If there is no Battlecruiser to be repaired,
			// remove all repairing SCVs.
			while (bcrepair.size() > 0)
			{
				Agent scv = bcrepair.get(bcrepair.size() -1);
				bcrepair.remove(bcrepair.size() - 1);
				scv.order(new None(scv));
				rejects.add(scv);
			}
		}
		
		// Let all squads execute their onFrame methods.
		retreatSquad.onFrame(game, self, bot);
		repairSquad.onFrame(game, self, bot);
		for(BCSquad s : squads)
			s.onFrame(game, self, bot);
		
		long time = stopWatch.time();
		
		if (time > 50)
		{
			DebugMessages.addMessage("BCGroup is taking too long: " + time + "ms.");
		}
		DebugMessages.addMessage("Battlecruiser squads: " + squads.size());
	}
	
	@Override
	public void add(Agent agent)
	{
		super.add(agent);
		outOfJob.add(agent);
		if (Tyr.bot.scanner.action == Scanner.Eliminating)
		{
			if (agent.unit.getType().isFlyer())
				Tyr.bot.scanner.addAir(agent);
			else 
				Tyr.bot.scanner.addGround(agent);
		}
	}
	
	@Override
	public Agent pop()
	{
		for(int i=squads.size()-1; i >= 0; i--)
		{
			if(!squads.get(i).attackMode && squads.size()> 0)
			{
				Agent result = squads.get(i).pop();
				units.remove(result);
				if (result != null)
					result.order(new None(result));
				return result;
			}
		}
		
		return null;
	}
	
	@Override
	public void cleanup()
	{
		super.cleanup();
		
		// Cleanup repairSquad and retreatSquad
		if (repairSquad != null)
			repairSquad.cleanup();
		if (retreatSquad != null)
			retreatSquad.cleanup();
		
		// Cleanup repairing SCVs.
		for(int i=0; i<bcrepair.size(); i++)
		{
			if(bcrepair.get(i).isDead())
			{
				bcrepair.remove(i);
				i--;
			}
		}
		
		// Cleanup all other squads.
		for (BCSquad squad : squads)
			squad.cleanup();
	}
}
