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

import java.util.HashSet;

import com.tyr.BWTAProxy;
import com.tyr.DebugMessages;
import com.tyr.EnemyManager;
import com.tyr.EnemyPosition;
import com.tyr.PositionUtil;
import com.tyr.ScannerListener;
import com.tyr.Settings;
import com.tyr.Tyr;
import com.tyr.agents.Agent;
import com.tyr.buildingplacement.SpaceManager;

import bwapi.Color;
import bwapi.Game;
import bwapi.Player;
import bwapi.Position;
import bwapi.UnitType;
import bwta.BaseLocation;


/**
 * A task that constantly pushes the opponent.
 */
public class ConstantPushTask extends Task implements ScannerListener
{
	/**
	 * The target position where we want the army to attack.
	 */
	private Position target;
	
	/**
	 * The type of unit we will accept for the push. If null, we accept all unit types.
	 */
	private UnitType acceptedUnit = null;
	
	/**
	 * Have the reinforcements been paused?
	 */
	public static HashSet<UnitType> paused = new HashSet<UnitType>();
	
	/**
	 * Has the constant push task been stopped completely?
	 */
	public boolean stop = false;
	
	/**
	 * The mode with which we attack the opponent.
	 */
	private int mode;
	
	/**
	 * Do we prioritize attacking enemy expansions?
	 */
	public static boolean prioritizeExpands = false;
	
	/**
	 * Do we return home to defend when we are under attack?
	 */
	public static boolean returnForDefense = false;
	
	/**
	 * How many units do we need to have as reinforcements before we send them?
	 */
	public static int reinforcementSize = 1;
	
	/**
	 * A task that constantly pushes the opponent.
	 */
	public ConstantPushTask()
	{
		this(ConstantPushSolution.ATTACK);
	}
	
	/**
	 * A task that constantly pushes the opponent.
	 * @param mode The mode with which we attack the opponent.
	 */
	public ConstantPushTask(int mode)
	{
		Settings.addScannerListener(this);
		this.mode = mode;
	}
	
	/**
	 * A task that determines whether we are ready to attack the opponent.
	 * @param acceptedUnit The type of unit we will accept for the push. If null, we accept all unit types.
	 */
	public ConstantPushTask(UnitType acceptedUnit)
	{
		this(acceptedUnit, ConstantPushSolution.ATTACK);
	}
	
	/**
	 * A task that determines whether we are ready to attack the opponent.
	 * @param acceptedUnit The type of unit we will accept for the push. If null, we accept all unit types.
	 * @param mode The mode with which we attack the opponent.
	 */
	public ConstantPushTask(UnitType acceptedUnit, int mode)
	{
		Settings.addScannerListener(this);
		this.acceptedUnit = acceptedUnit;
		this.mode = mode;
	}
	
	@Override
	public boolean isRequired(Game game, Player self, Tyr bot) 
	{
		return true;
	}
	
	/**
	 * Adds the attacking units to the scanner to start finding the remaining enemy buildings.
	 */
	public void addToScanner()
	{
		if (solution != null)
			((ConstantPushSolution)solution).addToScanner();
	}
	
	@Override
	public void solve(Game game, Player self, Tyr bot)
	{
		// If we find a new target to attack, we set that new target.
		if (!BWTAProxy.initialized)
			return;
		boolean newTarget = acquireTarget(bot); 
		if(newTarget)
			((ConstantPushSolution)solution).setTarget(target);
		
		for (UnitType type : paused)
			DebugMessages.addMessage("Push paused for: " + type.toString());

		if (!stop && bot.homeGroup.units.size() >= reinforcementSize)
		{
			for(int i = bot.homeGroup.units.size() - 1; i >= 0; i--)
			{
				Agent agent = bot.homeGroup.units.get(i);
				
				if (agent.unit.getType() == UnitType.Protoss_Observer)
					continue;
	
				if (paused.contains(agent.unit.getType()))
				{
					agent.drawCircle(Color.White, 6);
					continue;
				}
				 
				if (acceptedUnit == null || agent.unit.getType() == acceptedUnit)
				{
					((ConstantPushSolution)solution).add(agent);
					bot.homeGroup.units.remove(i);
				}
			}
		}
		
		// We call the super class, which will send the units out to attack.
		super.solve(game, self, bot);
	}

	@Override
	public void findSolution(Game game, Player self, Tyr bot)
	{
		if (BWTAProxy.initialized)
			acquireTarget(bot);
		solution = new ConstantPushSolution(this, target, mode);
	}
	
	/**
	 * See if we need to find a new target to attack.
	 * If so we return true, if the current target is already fine, we return false.
	 */
	public boolean acquireTarget(Tyr bot)
	{
		if (returnForDefense && EnemyManager.getManager().getInvaderCount() >= 4)
		{
			target = EnemyManager.getManager().getInvader().getPosition();
			return true;
		}
		
		if (prioritizeExpands)
		{
			boolean enemyHasExpands = EnemyManager.getManager().getAllCount(UnitType.Protoss_Nexus)
					+ EnemyManager.getManager().getAllCount(UnitType.Terran_Command_Center)
					+ EnemyManager.getManager().getAllCount(UnitType.Zerg_Hatchery)
					+ EnemyManager.getManager().getAllCount(UnitType.Zerg_Hive)
					+ EnemyManager.getManager().getAllCount(UnitType.Zerg_Lair)
					>= 3;
			
			final Position enemyNatural;
			if (Tyr.bot.suspectedEnemy.size() == 1)
			{
				final BaseLocation enemyBase = SpaceManager.getEnemyNatural();
				if (enemyBase != null)
					enemyNatural = enemyBase.getPosition();
				else
					enemyNatural = null;
			}
			else enemyNatural = null;
			if (!enemyHasExpands && Tyr.bot.suspectedEnemy.size() == 1)
			{
		    	for(EnemyPosition p : EnemyManager.getManager().enemyBuildingMemory)
		    	{
		    		if (!p.type.isResourceDepot())
		    			continue;

		    		if (PositionUtil.distanceSq(Tyr.bot.suspectedEnemy.get(0).getPosition(), p.pos) <= 100 * 100)
		    			continue;
		    		
		    		if (PositionUtil.distanceSq(enemyNatural, p.pos) <= 100 * 100)
		    			continue;
		    		
		    		enemyHasExpands = true;
		    		break;
		    	}
				
			}


    		// We skip the enemy natural and main if he has other expands.
			if (target != null && enemyHasExpands && isNaturalOrMain(target, enemyNatural))
				target = null;
			
			if (target != null)
			{
		    	for(EnemyPosition p : EnemyManager.getManager().enemyBuildingMemory)
		    	{
		    		if (!p.type.isResourceDepot())
		    			continue;

		    		if (EnemyManager.getManager().getAllCount(p.type) > 1
		    				&& bot.suspectedEnemy.size() == 1
		    				&& PositionUtil.distanceSq(p.pos, bot.suspectedEnemy.get(0).getPosition()) <= 10000)
		    			continue;
		    		
		    		if (p.pos.getX() == target.getX() && p.pos.getY() == target.getY())
		    		{
		    			return false;
		    		}
		    	}
			}
	    	for(EnemyPosition p : EnemyManager.getManager().enemyBuildingMemory)
	    	{
	    		if (!p.type.isResourceDepot())
	    			continue;
	    		
	    		if (EnemyManager.getManager().getAllCount(p.type) > 1
	    				&& bot.suspectedEnemy.size() == 1
	    				&& PositionUtil.distanceSq(p.pos, bot.suspectedEnemy.get(0).getPosition()) <= 10000)
	    			continue;
	    		
	    		// We skip the enemy natural and main if he has other expands.
	    		if (enemyHasExpands && isNaturalOrMain(p.pos, enemyNatural))
	    			continue;
	    		
	    		target = p.pos;
	    		return true;
	    	}
		}
		
		if (target != null)
		{
	    	for(EnemyPosition p : EnemyManager.getManager().enemyBuildingMemory)
	    	{
	    		if (p.pos.getX() == target.getX() && p.pos.getY() == target.getY())
	    		{
	    			return false;
	    		}
	    	}
		}
		
		Position newTarget = null;
    	for(EnemyPosition p : EnemyManager.getManager().enemyBuildingMemory)
    	{
    		newTarget = p.pos;
			break;
    	}

    	if (newTarget == null)
    		for (BaseLocation b : bot.suspectedEnemy) 
    		{
    			newTarget = b.getPosition();
    			break;
    		}
    	
    	if (newTarget == null)
    		return false;
    	
    	if (target == null)
    	{
    		target = newTarget;
    		return true;
    	}
    	
    	
    	if (target.getX() == newTarget.getX() && target.getY() == newTarget.getY())
    		return false;
    	
    	target = newTarget;
    	return true;
	}
	
	private boolean isNaturalOrMain(Position pos, Position enemyNatural)
	{
		if (Tyr.bot.suspectedEnemy.size() != 1)
			return false;
		if (enemyNatural != null && PositionUtil.distanceSq(pos, enemyNatural) <= 100 * 100)
			return true;
		
		return (PositionUtil.distanceSq(Tyr.bot.suspectedEnemy.get(0).getPosition(), pos) <= 100 * 100);
	}

	@Override
	public void onScannerEliminating()
	{
		if (solution != null)
			((ConstantPushSolution)solution).clear();
		solution = null;
	}
	
	public void remove()
	{
		stop = true;
		if (solution != null)
			((ConstantPushSolution)solution).done(Tyr.bot.hobos);
	}

	public void stop()
	{
		stop = true;
		clear();
	}
	
	public void go()
	{
		stop = false;
	}
	
	public void clear()
	{
		((ConstantPushSolution)solution).clear();
	}

	public int size()
	{
		return ((ConstantPushSolution)solution).size();
	}
}
