/*
* 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.List;

import com.tyr.DebugMessages;
import com.tyr.EnemyManager;
import com.tyr.Tyr;
import com.tyr.agents.Agent;
import com.tyr.agents.None;

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 harassing a certain base.
 * @author Simon
 *
 */
public class MarineHarassGroup extends UnitGroup
{
	/**
	 * The position of the base we want to harass.
	 */
	private Position target;
	
	/**
	 * Do we retreat after each attack?
	 */
	private boolean retreat = true;
	
	/**
	 * The next enemy we intend to kill.
	 */
	private Unit killEnemy;
	
	/**
	 * List if units from which we flee if we encounter them.
	 */
	public static UnitType[] retreatTypes = new UnitType[] {
			UnitType.Terran_Bunker, 
			UnitType.Protoss_Photon_Cannon, 
			UnitType.Zerg_Sunken_Colony};

	/**
	 * A unit group for harassing a certain base.
	 * @param target The position of the base we want to harass.
	 */
	public MarineHarassGroup(Position target) 
	{
		super(Tyr.bot.hobos);
		
		this.target = target;
	}
	
	/**
	 * A unit group for harassing a certain base.
	 * @param target The position of the base we want to harass.
	 * @param retreat Do we retreat after each attack?
	 */
	public MarineHarassGroup(Position target, boolean retreat) 
	{
		super(Tyr.bot.hobos);
		
		this.target = target;
		this.retreat = false;
	}
	
	/**
	 * Setter for the position of the base we want to harass.
	 */
	public void setTarget(Position target)
	{
		this.target = target;
	}

	@Override
	public void onFrame(Game game, Player self, Tyr bot)
	{
		boolean defended = false;
		
		// If the previously selected enemy is already dead we can select a new one.
		if (killEnemy != null)
			if (killEnemy.getHitPoints() <= 0 || killEnemy.getRemoveTimer() != 0 
					|| !killEnemy.exists() || killEnemy.getPlayer() == game.self())
				killEnemy = null;
		
		if (killEnemy != null)
		{
			boolean inRange = false;
			
			// If the unit is a worker and it is close enough to our target we will continue to attack it.
			if (target != null && killEnemy.getType().isWorker() && killEnemy.getDistance(target) <= 352)
				inRange = true;
			else
			{
				// See if the unit is in weapons range of one of our harassing units.
				for(Agent agent : units)
				{
					int range = 200;
					if (agent.distanceSquared(killEnemy) <= range * range)
					{
						inRange = true;
						break;
					}
				}
			}
			
			if(!inRange)
				killEnemy = null;
		}
		
		double distanceSq = Integer.MAX_VALUE;
		int prio = 100;
		
		if (killEnemy == null
				|| killEnemy.getType().isWorker()
				|| killEnemy.getType().isBuilding()
				|| killEnemy.getType() == UnitType.Zerg_Overlord)
		{
			// See if an enemy is inside weapons range.
			for(Agent agent : units)
			{
				int rangeSq = 300*300; //agent.unit.getType().groundWeapon().maxRange() * agent.unit.getType().groundWeapon().maxRange();
				agent.drawCircle(Color.Purple, 300);
				
				for(Unit unit : EnemyManager.getEnemyUnits())
				{
					
					for (UnitType type : retreatTypes)
					{
						if (unit.getType() == type)
						{
							defended = true;
							break;
						}
					}
					if (defended)
						break;
					
					int newPrio = 100;

					// We do not attack observers either.
					if (unit.getType() == UnitType.Protoss_Observer || unit.isMorphing() || unit.getType() == UnitType.Zerg_Larva)
						continue;

					double dist = agent.distanceSquared(unit);

					// Enemy is too far away to attack.
					if (dist > rangeSq)
						continue;
					
					if (unit.getType().isBuilding())
					{
						if (unit.getType().groundWeapon().maxRange() == 0)
							newPrio = 10;
						else
							newPrio = 8;
					}
					else if (unit.getType().isFlyer() && unit.getType().groundWeapon() == WeaponType.None)
						newPrio = 9;
					else if (unit.getType().isWorker())
						newPrio = 7;
					else if (dist <= 200*200)
						newPrio = 6;
					else newPrio = 101;
					
					if (newPrio > prio)
						continue;
					
					
					// Attack the closest enemy.
					if (newPrio < prio || dist < distanceSq)
					{
						prio = newPrio;
						distanceSq = dist;
						killEnemy = unit;
					}
				}
				if (defended)
					break;
			}
		}
		else
			DebugMessages.addMessage("Already have a target.");
		
		if (defended)
		{
			for(Agent agent : units)
			{
				agent.order(new None(agent));
				rejects.add(agent);
			}
			this.units = new ArrayList<Agent>();
			return;
		}
		
		if (killEnemy == null && target != null)
		{
			// see if there is a worker we want to kill.
			List<Unit> workerTargets = game.getUnitsInRadius(target, 352);
			for(Unit unit : workerTargets)
			{
				if (!unit.getType().isWorker() || !unit.getPlayer().isEnemy(self))
					continue;
				
				for(Agent agent : units)
				{
					double dist = agent.distanceSquared(unit);
					if (unit.getPlayer().isEnemy(self) && dist < distanceSq)
					{
						distanceSq = dist;
						killEnemy = unit;	
					}
				}
			}
		}
		
		// If we have an enemy to kill, we kill the enemy.
		if (killEnemy != null)
		{
			for (Agent agent : units)
			{
				game.drawLineMap(agent.unit.getX(), agent.unit.getY(), killEnemy.getX(), killEnemy.getY(), Color.Red);
				
				if (agent.unit.getGroundWeaponCooldown() > 1 && retreat)
					agent.unit.move(Tyr.tileToPosition(self.getStartLocation()));
				else if(!agent.unit.isStartingAttack()
						&& !agent.unit.isAttackFrame()
						&& ! agent.unit.isAttacking()
						&& (agent.unit.isIdle() || agent.unit.isMoving()))
					agent.attack(killEnemy);
			}
			return;
		}
		
		// If we do not have an enemy to kill, we move to the target.
		if (target != null)
		{
			for (Agent agent : units)
			{
				agent.unit.move(target);

				game.drawLineMap(agent.unit.getX(), agent.unit.getY(), target.getX(), target.getY(), Color.Blue);
			}
		}
	}
	
	/**
	 * Adds an agent to the list of agents.
	 */
	@Override public void add(Agent agent)
	{
		super.add(agent);
		agent.order(new None(agent));
		agent.unit.stop();
	}
	
	/**
	 * Clears the list of units, so that the group can be removed.
	 */
	public void clear() 
	{
		for(int i = units.size() - 1; i >= 0; i--)
		{
			this.rejects.add(units.get(i));
			units.remove(i);
		}
	}

}
