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

import java.util.ArrayList;

import bwapi.Game;
import bwapi.Player;
import bwapi.Position;
import bwapi.Race;
import bwapi.Unit;
import bwapi.UnitType;
import bwapi.UpgradeType;
import bwta.BWTA;

import com.tyr.DebugMessages;
import com.tyr.EnemyManager;
import com.tyr.EnemyPosition;
import com.tyr.PositionUtil;
import com.tyr.Settings;
import com.tyr.Tyr;
import com.tyr.UnitTracker;
import com.tyr.agents.Agent;
import com.tyr.agents.Attack;
import com.tyr.buildingplacement.SpaceManager;
import com.tyr.requirements.ConjRequirement;
import com.tyr.requirements.CostRequirement;
import com.tyr.requirements.UnitRequirement;
import com.tyr.tasks.BuildAtLocationSolution;
import com.tyr.tasks.BuildAtLocationTask;
import com.tyr.tasks.ClearExpandsTask;
import com.tyr.tasks.ConstantPushSolution;
import com.tyr.tasks.ConstantPushTask;
import com.tyr.tasks.DefendMainTask;
import com.tyr.tasks.LocalDefenseTask;
import com.tyr.tasks.SmeltingTask;
import com.tyr.unitgroups.AttackGroup;
import com.tyr.unitgroups.DistributeAttackGroup;
import com.tyr.unitgroups.PrioritizedAttackGroup;


public class SafeTwoGate extends CompositeBuildOrder
{
	private ConstantPushTask pressureTask;
	private LocalDefenseTask localDefenseTask;
	private LocalDefenseTask naturalDefenseTask;
	private ConstantPushTask constantPushTask;
	private SmeltingTask smeltingTask;
	private int armySize;
	boolean defensesInitialized = false;
	private Position natural = null;
	boolean dragoonsFirst;
	boolean buildCannons;
	boolean detectionNeeded;
	boolean takeExpands;
	boolean prioritizeExpands = false;
	boolean attackStarted = false;
	private int mode = ConstantPushSolution.ATTACK;
	private boolean vIron = false;
	private boolean zealotOnly = false;
	
	public SafeTwoGate()
	{
		this(20, false, true, false, false, false);
	}
	
	public SafeTwoGate(boolean vIron)
	{
		this(vIron?20:20,
				vIron,
				!vIron,
				false,
				false,
				false);
		
		this.vIron = vIron;
	}
	
	public SafeTwoGate(int armySize, boolean dragoonsFirst, boolean buildCannons, boolean detectionNeeded, boolean takeExpands, boolean zealotOnly)
	{
		this.armySize = armySize;
		this.dragoonsFirst = dragoonsFirst;
		this.buildCannons = buildCannons;
		this.detectionNeeded = detectionNeeded;
		this.takeExpands = takeExpands;
		this.zealotOnly = zealotOnly;
	}
	
	@Override
	public void initialize(Game game, Player self, Tyr bot)
	{
		if (vIron)
		{
			DefendMainTask.keepZealotsBack = true;
			this.mode = ConstantPushSolution.DISTRIBUTE;
			//smeltingTask = new SmeltingTask();

			localDefenseTask = new LocalDefenseTask(BWTA.getRegion(Tyr.getStartLocation()), 0);
			
			naturalDefenseTask = new LocalDefenseTask(null, 0);
		}
		
		if (game.enemy().getRace() == Race.Zerg)
		{
			pressureTask = new ConstantPushTask(ConstantPushSolution.PRIORITIZE);
			pressureTask.stop = true;
			Tyr.bot.taskManager.potentialTasks.add(pressureTask);
			PrioritizedAttackGroup.allowRetreat = true;
			PrioritizedAttackGroup.ignoreAfterNonAggressive = Integer.MAX_VALUE;
		}
		
		DistributeAttackGroup.waitDistance = 800;
		
		if (smeltingTask != null)
			Tyr.bot.taskManager.potentialTasks.add(smeltingTask);
		if (localDefenseTask != null)
			Tyr.bot.taskManager.potentialTasks.add(localDefenseTask);
		if (naturalDefenseTask != null)
			Tyr.bot.taskManager.potentialTasks.add(naturalDefenseTask);
		
		Settings.setRequiredSize(100);
		Settings.setMaximumSize(100);
		Settings.setMaximumWorkers(7);
		Settings.setWorkersPerGas(2);
		Settings.setLargeInvasionDist(768);
		ExpandPart.maximumCcs = 1;
		ClearExpandsTask.allowFullAssault = false;
		AttackGroup.hunting = true;

		if (game.mapFileName().contains("Alchemist") || vIron)
			this.add(new WorkerScoutPart(100));
		else
			this.add(new WorkerScoutPart(1600));
		this.add(new ExpandPart(true));
		
		Attack.requiredAtCannon = 0;
		Attack.dontWaitAtCannon = true;
		
		constantPushTask = new ConstantPushTask(null, mode);
		constantPushTask.stop = true;
		bot.taskManager.potentialTasks.add(constantPushTask);
		
		ConstantPushTask.prioritizeExpands = true;
		
		
		super.initialize(game, self, bot);
	}
	
	private static int armySize()
	{
		return completed(ZEALOT) + completed(DRAGOON);
	}
	
	private Position getDefensivePosition()
	{
		boolean expand = false;
		if (!expand)
		{
			Position startLocation = Tyr.tileToPosition(Tyr.self.getStartLocation());
			Position mainExit = SpaceManager.getMainExit();
			return new Position((startLocation.getX() + mainExit.getX())/2, (startLocation.getY() + mainExit.getY())/2);
		}
	
		Position location = SpaceManager.getNatural();
	
		ArrayList<Position> minerals = new ArrayList<Position>();
		// Find all the mineral patches close to the base.
		for (EnemyPosition neutralUnit : EnemyManager.getManager().neutralBuildingMemory)
			if (neutralUnit.type.isMineralField() && neutralUnit.pos.getDistance(location) <= 270)
				minerals.add(neutralUnit.pos);

		return SpaceManager.getDefensePosPositions(location, minerals);
	}

	@Override
	public void onFrame(Game game, Player self, Tyr bot) 
	{
		DebugMessages.addMessage("Army size: " + armySize());
		
		
		if (pressureTask != null)
		{
			if (pressureTask.stop && completed(ZEALOT) + completed(DRAGOON) >= 4)
				pressureTask.stop = false;
			if (EnemyManager.getManager().getAllCount(UnitType.Zerg_Sunken_Colony) >= 2
					|| EnemyManager.getManager().getAllCount(UnitType.Zerg_Zergling) > 6)
			{
				pressureTask.remove();
				pressureTask = null;
			}
		}
		
		boolean takeExpands = this.takeExpands && attackStarted;
		if (UnitTracker.getCcCount() <= 1)
		{
			if (natural == null)
				natural = SpaceManager.getNatural();
			if (natural != null)
			{
				for (Unit enemy : EnemyManager.getEnemyUnits())
				{
					if (PositionUtil.distanceSq(enemy, natural) <= Settings.getSmallInvasionDist() * Settings.getSmallInvasionDist())
					{
						takeExpands = false;
						break;
					}
				}
			}
		}
		if (takeExpands)
		{
			if (armySize() >= armySize + 3)
				prioritizeExpands = true;
			if (armySize() >= 28 && completed(NEXUS) >= 2)
				ExpandPart.maximumCcs = 3;
			else
				ExpandPart.maximumCcs = 2;
		}
		else
			ExpandPart.maximumCcs = 1;
		
		if (completed(NEXUS) > 1
				&& count(ZEALOT) + count(DRAGOON) >= 30)
			detectionNeeded = true;
		
		if (localDefenseTask != null && armySize() > 25)
			localDefenseTask.setDefendersNeeded(3);
		

		if (vIron && count(DRAGOON) + count(ZEALOT) >= 18)
		{
			this.takeExpands = true;
			prioritizeExpands = true;
		}

		if (vIron && armySize() >= 23)
		{
			detectionNeeded = true;
			DistributeAttackGroup.requiredForAttack = 20;
			DistributeAttackGroup.requiredForRetreat = 15;
		}
		if (vIron && armySize() >= 16 && localDefenseTask.getDefendersNeeded() == 0)
		{
			localDefenseTask.setDefendersNeeded(1);
		}
		
		if (naturalDefenseTask != null && naturalDefenseTask.getDefendersNeeded() == 0 && Tyr.bot.workForce.mineralWorkers.size() >= 2)
		{
			naturalDefenseTask.setDefendersNeeded(2);
			naturalDefenseTask.setDefendedPosition(Tyr.bot.workForce.mineralWorkers.get(1).resourceDepot.getPosition());
		}
		
		if (buildCannons && !defensesInitialized && game.getFrameCount() >= 100 && self.completedUnitCount(UnitType.Protoss_Zealot) + self.completedUnitCount(UnitType.Protoss_Dragoon) >= 17)
		{
			final Position defensivePosition = getDefensivePosition();
			if (defensivePosition != null)
				initializeDefenses(defensivePosition);
		}
		
		if (zealotOnly && UnitTracker.count(ZEALOT) >= 6)
			zealotOnly = false;
		
		super.onFrame(game, self, bot);

		if (gas() >= 400)
			Settings.setWorkersPerGas(2);
		//else if (vIron && gas() <= 75)
		//		Settings.setWorkersPerGas(3);
		else if ((gas() < 250 && count(GATEWAY) >= 12) 
				|| gas() + 400 < minerals())
			Settings.setWorkersPerGas(3);

		if (armySize() >= 23)
			SmeltingTask.disabled = true;
		
		if (constantPushTask != null && completed(ZEALOT) + completed(DRAGOON) <= 10)
			constantPushTask.stop = true;
		if (constantPushTask != null && completed(ZEALOT) + completed(DRAGOON) >= armySize)
		{
			attackStarted = true;
			constantPushTask.stop = false;
		}
		
		if (UnitTracker.count(UnitType.Protoss_Cybernetics_Core) > 0)
			Settings.setMaximumWorkers(30);
		else if (UnitTracker.count(UnitType.Protoss_Gateway) >= 4)
			Settings.setMaximumWorkers(25);
		else if (UnitTracker.count(UnitType.Protoss_Gateway) >= 2)
			Settings.setMaximumWorkers(9);
		if (armySize() >= 2 && UnitTracker.count(UnitType.Protoss_Gateway) < 4)
			Settings.setMaximumWorkers(20);
		
		if (UnitTracker.count(UnitType.Protoss_Probe) >= 7 && UnitTracker.count(UnitType.Protoss_Pylon) == 0 && bot.getAvailableMinerals() >= 100)
		{
			bot.spaceManager.build(UnitType.Protoss_Pylon);
		}
		
		if (self.completedUnitCount(UnitType.Protoss_Pylon) > 0 && bot.getAvailableMinerals() >= 250 && UnitTracker.count(UnitType.Protoss_Gateway) == 0)
		{
			bot.spaceManager.build(UnitType.Protoss_Gateway);
		}
			
		if (UnitTracker.count(UnitType.Protoss_Gateway) == 1 
				&& bot.getAvailableMinerals() >= 100
				&& (!dragoonsFirst || (count(CYBERNETICS_CORE) > 0) && count(ASSIMILATOR) > 0))
		{
			bot.spaceManager.build(UnitType.Protoss_Gateway);
		}
		
		if (!pauseProduction())
		{
			if (UnitTracker.count(UnitType.Protoss_Gateway) < 4
					&& bot.getAvailableMinerals() >= 300
					&& UnitTracker.count(UnitType.Protoss_Cybernetics_Core) > 0)
			{
				bot.spaceManager.build(UnitType.Protoss_Gateway);
			}
			
			if (UnitTracker.count(UnitType.Protoss_Gateway) >= 3
					&& UnitTracker.count(UnitType.Protoss_Gateway) < 7
					&& bot.getAvailableMinerals() >= 300
					&& count(ZEALOT) + count(DRAGOON) >= 7
					&& (!vIron || attackStarted))
			{
				bot.spaceManager.build(UnitType.Protoss_Gateway);
			}
		}
		
		//if we're running out of supply and have enough minerals ...
		if ((self.supplyTotal() + UnitTracker.getSupplyConstructing() - self.supplyUsed() 
					<= UnitTracker.count(UnitType.Protoss_Gateway)*3 + UnitTracker.getCcCount() * 3)
				&& (bot.getAvailableMinerals() >= 100)
				&& self.supplyTotal() + UnitTracker.getSupplyConstructing() < 400
				&& UnitTracker.count(UnitType.Protoss_Pylon) >= 1
				&& (armySize() >= 2 || self.supplyTotal() + UnitTracker.getSupplyConstructing() - self.supplyUsed() < 4))
		{
			bot.spaceManager.build(UnitType.Protoss_Pylon);
		}
		
		if(UnitTracker.count(UnitType.Protoss_Assimilator) < 2
				&& bot.getAvailableMinerals() >= 100 
				&& UnitTracker.getGeyserCount() > 0
				&& (count(CYBERNETICS_CORE) > 0 || (dragoonsFirst && minerals() >= 300))
				&& (UnitTracker.count(UnitType.Protoss_Zealot) >= 3 || dragoonsFirst)
				&& !zealotOnly
				)
		{
			bot.spaceManager.build(UnitType.Protoss_Assimilator);
		}
		
		if (bot.getAvailableMinerals() >= 200 
				&& UnitTracker.count(UnitType.Protoss_Cybernetics_Core) < 1
				&& completed(GATEWAY) > 0
				&& (UnitTracker.count(UnitType.Protoss_Zealot) >= 3 || dragoonsFirst)
				&& !zealotOnly
				)
		{
			bot.spaceManager.build(UnitType.Protoss_Cybernetics_Core);
		}
		
		if (bot.getAvailableMinerals() >= 150
				&& UnitTracker.count(UnitType.Protoss_Dragoon) + UnitTracker.count(UnitType.Protoss_Zealot) >= 15
				&& UnitTracker.count(UnitType.Protoss_Forge) == 0
				&& (!vIron || armySize() >= 20))
		{
			bot.spaceManager.build(UnitType.Protoss_Forge);
		}
		
		if (bot.getAvailableMinerals() >= 300
				&& UnitTracker.count(UnitType.Protoss_Nexus) >= 2
				&& UnitTracker.count(UnitType.Protoss_Cybernetics_Core) > 0
				&& UnitTracker.count(UnitType.Protoss_Forge) < 2)
		{
			bot.spaceManager.build(UnitType.Protoss_Forge);
		}
		
		if (bot.getAvailableMinerals() >= 150
				&& bot.getAvailableGas() >= 100
				&& UnitTracker.count(UnitType.Protoss_Citadel_of_Adun) == 0
				&& self.completedUnitCount(UnitType.Protoss_Cybernetics_Core) > 0
				&& UnitTracker.count(UnitType.Protoss_Gateway) >= 4
				&& count(ZEALOT) + count(DRAGOON) >= 20)
		{
			bot.spaceManager.build(UnitType.Protoss_Citadel_of_Adun);
		}
		
		if (gas() >= 200 && minerals() >= 200
				&& (count(GATEWAY) >= 7 || detectionNeeded)
				&& count(ROBOTICS_FACILITY) == 0
				&& count(GATEWAY) >= 2)
		{
			bot.spaceManager.build(ROBOTICS_FACILITY);
		}
		
		if (gas() >= 50 && minerals() >= 100
				&& completed(ROBOTICS_FACILITY) > 0
				&& count(OBSERVATORY) == 0)
		{
			bot.spaceManager.build(OBSERVATORY);
		}
	}
	
	private void initializeDefenses(Position defensivePosition)
	{
		defensesInitialized = true;
		
		BuildAtLocationSolution.buildingDistance = 200;
		
		BuildAtLocationTask buildTask = new BuildAtLocationTask(defensivePosition, true);
		buildTask.addBuilding(UnitType.Protoss_Pylon, new UnitRequirement(UnitType.Protoss_Probe, 7));
		buildTask.addBuilding(UnitType.Protoss_Photon_Cannon, new ConjRequirement()
		.addRequirement(new UnitRequirement(UnitType.Protoss_Forge, 1, true))
		.addRequirement(new CostRequirement(150, 0)));
		buildTask.addBuilding(UnitType.Protoss_Photon_Cannon, new ConjRequirement()
		.addRequirement(new UnitRequirement(UnitType.Protoss_Forge, 1, true))
		.addRequirement(new CostRequirement(150, 0)));
		buildTask.addBuilding(UnitType.Protoss_Photon_Cannon, new ConjRequirement()
		.addRequirement(new UnitRequirement(UnitType.Protoss_Forge, 1, true))
		.addRequirement(new CostRequirement(150, 0)));
		buildTask.addBuilding(UnitType.Protoss_Photon_Cannon, new ConjRequirement()
		.addRequirement(new UnitRequirement(UnitType.Protoss_Forge, 1, true))
		.addRequirement(new CostRequirement(150, 0)));
		
		Tyr.bot.taskManager.potentialTasks.add(buildTask);
	}
	
	private boolean pauseProduction()
	{
		if (gas() > 250 && minerals() > 400)
			return false;

		if (detectionNeeded
				&& armySize() >= 2
				&& count(GATEWAY) > 2
				&& completed(CYBERNETICS_CORE) > 0
				&& count(ROBOTICS_FACILITY) == 0)
			return true;
		if (detectionNeeded
				&& armySize() >= 2
				&& completed(CYBERNETICS_CORE) > 0
				&& completed(ROBOTICS_FACILITY) == 1
				&& count(OBSERVATORY) == 0)
			return true;
		if (detectionNeeded
				&& armySize() >= 2
				&& completed(CYBERNETICS_CORE) > 0
				&& completed(OBSERVATORY) == 1
				&& count(OBSERVER) == 0)
			return true;
		
		if (completed(CITADEL) > 0
				&& Tyr.self.getUpgradeLevel(UpgradeType.Leg_Enhancements) == 0
				&& !Tyr.self.isUpgrading(UpgradeType.Leg_Enhancements))
			return true;
		
		if (UnitTracker.count(CITADEL) == 0
				&& completed(CYBERNETICS_CORE) > 0
				&& count(GATEWAY) >= 4
				&& count(ZEALOT) + count(DRAGOON) >= 20)
			return true;
		
		if (prioritizeExpands
				&& minerals() < 400
				&& count(NEXUS) <= 1)
			return true;
		
		if (minerals() >= 300)
			return false;
		if (count(ZEALOT) >= 2 && count(CYBERNETICS_CORE) == 0)
			return true;
		if (count(ZEALOT) >= 2 && count(ASSIMILATOR) == 0)
			return true;
		return false;
	}
	
	@Override
	public boolean overrideStructureOrder(Game game, Player self, Tyr bot, Agent agent)
	{
		if (agent.unit.getType() == UnitType.Protoss_Gateway && !agent.unit.isTraining())
		{
			if (vIron && count(DRAGOON) <= 6 && count(ZEALOT) >= 5 && !pauseProduction())
			{
				if (bot.getAvailableMinerals() >= 125 && bot.getAvailableGas() >= 50)
					agent.unit.build(DRAGOON);
				return true;
			}
			if ((!pauseProduction() || minerals() >= 300)
					&&(count(ZEALOT) + (dragoonsFirst ? 3 : 0) <= count(DRAGOON) || (!dragoonsFirst && completed(CYBERNETICS_CORE) == 0))
					&& (!dragoonsFirst || count(DRAGOON) >= 10)
					&& bot.getAvailableMinerals() >= 100)
				agent.unit.build(ZEALOT);
			else if (!pauseProduction()
					&& (count(ZEALOT)  + (dragoonsFirst ? 3 : 0) > count(DRAGOON) || (dragoonsFirst && count(DRAGOON) < 10))
					&& bot.getAvailableMinerals() >= 125 && bot.getAvailableGas() >= 50)
				agent.unit.build(DRAGOON);
			return true;
		}
		else if (agent.unit.getType() == UnitType.Protoss_Cybernetics_Core && !agent.unit.isUpgrading())
		{
			if(bot.getAvailableMinerals() >= UpgradeType.Singularity_Charge.mineralPrice()
					&& count(DRAGOON) >= 3
					&& bot.getAvailableGas() >= UpgradeType.Singularity_Charge.gasPrice())
				agent.unit.upgrade(UpgradeType.Singularity_Charge);
		}
		else if (agent.unit.getType() == UnitType.Protoss_Forge && !agent.unit.isUpgrading())
		{
			if(bot.getAvailableMinerals() >= UpgradeType.Protoss_Ground_Weapons.mineralPrice()
					&& bot.getAvailableGas() >= UpgradeType.Protoss_Ground_Weapons.gasPrice())
				agent.unit.upgrade(UpgradeType.Protoss_Ground_Weapons);
			
			if(bot.getAvailableMinerals() >= UpgradeType.Protoss_Ground_Armor.mineralPrice()
					&& bot.getAvailableGas() >= UpgradeType.Protoss_Ground_Armor.gasPrice())
				agent.unit.upgrade(UpgradeType.Protoss_Ground_Armor);
		}
		else if (agent.unit.getType() == CITADEL && !agent.unit.isUpgrading())
		{
			if (gas() >= UpgradeType.Leg_Enhancements.gasPrice()
					&& minerals() >= UpgradeType.Leg_Enhancements.mineralPrice())
				agent.unit.upgrade(UpgradeType.Leg_Enhancements);
		}
		else if (agent.unit.getType() == ROBOTICS_FACILITY)
		{
			if ((count(OBSERVER) < 2)
					&& minerals() >= 25
					&& gas() >= 75
					&& !agent.unit.isTraining())
				agent.unit.build(OBSERVER);
			return true;
		}
		return false;
	}
}
