// directly imported from Marilyn, with slight modifications to work with qBot.

function Defence(){
	this.defenceRatio = 1.8; // How many defenders we want per attacker.  Need to balance fewer losses vs. lost economy
	// note: the choice should be a no-brainer most of the time: better deflect the attack.
	
	this.totalAttackNb = 0;	// used for attack IDs
	this.attacks = [];
	this.toKill = [];
	
	// keeps a list of targeted enemy at instant T
	this.attackerCache = {};
	this.listOfEnemies = {};
	this.listedEnemyCollection = null;	// entity collection of this.listOfEnemies
	
	// boolean 0/1 that's for optimization
	this.attackerCacheLoopIndicator = 0;
	
	// this is a list of units to kill. They should be gaia animals, or lonely units. Works the same as listOfEnemies, ie an entityColelction which I'll have to cleanup
	this.listOfWantedUnits = {};
	this.WantedUnitsAttacker = {};	// same as attackerCache.
	
	this.defenders = null;
	this.idleDefs = null;
}

// DO NOTE: the Defence manager, when it calls for Defence, makes the military manager go into "Defence mode"... This makes it not update any plan that's started or not.
// This allows the Defence manager to take units from the plans for Defence.

// Defcon levels
// 5: no danger whatsoever detected
// 4: a few enemy units are being dealt with, but nothing too dangerous.
// 3: A reasonnably sized enemy army is being dealt with, but it should not be a problem.
// 2: A big enemy army is in the base, but we are not outnumbered
// 1: Huge army in the base, outnumbering us.


Defence.prototype.update = function(gameState, events, militaryManager){
	
	Engine.ProfileStart("Defence Manager");
			
	// a litlle cache-ing
	if (!this.idleDefs) {
		var filter = Filters.and(Filters.byMetadata("role", "defence"), Filters.isIdle());
		this.idleDefs = gameState.getOwnEntities().filter(filter);
		this.idleDefs.registerUpdates();
	}
	if (!this.defenders) {
		var filter = Filters.byMetadata("role", "defence");
		this.defenders = gameState.getOwnEntities().filter(filter);
		this.defenders.registerUpdates();
	}
	if (!this.listedEnemyCollection) {
		var filter = Filters.byMetadata("listed-enemy", true);
		this.listedEnemyCollection = gameState.getEnemyEntities().filter(filter);
		this.listedEnemyCollection.registerUpdates();
	}
	this.myBuildings = gameState.getOwnEntities().filter(Filters.byClass("Structure")).toEntityArray();
	this.myUnits = gameState.getOwnEntities().filter(Filters.byClass("Unit"));
	
	this.territoryMap = Map.createTerritoryMap(gameState);	// used by many func

	// First step: we deal with enemy armies, those are the highest priority.
	this.defendFromEnemyArmies(gameState, events, militaryManager);
	
	// second step: we loop through messages, and sort things as needed (dangerous buildings, attack by animals, ships, lone units, whatever).
	// TODO
	this.MessageProcess(gameState,events,militaryManager);
	
	this.DealWithWantedUnits(gameState,events,militaryManager);

	// putting unneeded units at rest
	this.idleDefs.forEach(function(ent) {
		if (ent.getMetadata("formerrole"))
			ent.setMetadata("role", ent.getMetadata("formerrole") );
		else
			ent.setMetadata("role", "worker");
		ent.setMetadata("subrole", undefined);
	});

	Engine.ProfileStop();
	
	return;
};
// returns armies that are still seen as dangerous (in the LOS of any of my buildings for now)
Defence.prototype.reevaluateDangerousArmies = function(gameState, armies) {
	var stillDangerousArmies = {};
	for (i in armies) {
		var pos = armies[i].getCentrePosition();
		if (armies[i].getCentrePosition() && +this.territoryMap.point(armies[i].getCentrePosition()) - 64 === +gameState.player) {
			stillDangerousArmies[i] = armies[i];
			continue;
		}
		for (o in this.myBuildings) {
			// if the armies out of my buildings LOS (with a little more, because we're cheating right now and big armies could go undetected)
			if (inRange(pos, this.myBuildings[o].position(),this.myBuildings[o].visionRange()*this.myBuildings[o].visionRange() + 2500)) {
				stillDangerousArmies[i] = armies[i];
				break;
			}
		}		
	}
	return stillDangerousArmies;
}
// returns armies we now see as dangerous, ie in my territory
Defence.prototype.evaluateArmies = function(gameState, armies) {
	var DangerousArmies = {};
	for (i in armies) {
		if (armies[i].getCentrePosition() && +this.territoryMap.point(armies[i].getCentrePosition()) - 64 === +gameState.player) {
			DangerousArmies[i] = armies[i];
		}
	}
	return DangerousArmies;
}
// This deals with incoming enemy armies, setting the defcon if needed. It will take new soldiers, and assign them to attack
// it's still a fair share of dumb, so TODO improve
Defence.prototype.defendFromEnemyArmies = function(gameState, events, militaryManager) {
	
	// The enemy Watchers keep a list of armies. This class here tells them if an army is dangerous, and they manage the merging/splitting/disbanding.
	// With this system, we can get any dangerous armies. Thus, we can know where the danger is, and react.
	// So Defence deals with attacks from animals too (which aren't watched).
	// The attackrs here are dealt with on a per unit basis.
	// We keep a list of idle defenders. For any new attacker, we'll check if we have any idle defender available, and if not, we assign available units.
	// At the end of each turn, if we still have idle defenders, we either assign them to neighboring units, or we release them.
	
	var dangerArmies = {};
	this.enemyUnits = {};
	
	// for now armies are never seen as "no longer dangerous"... TODO
	for (enemyID in militaryManager.enemyWatchers) {
		this.enemyUnits[enemyID] = militaryManager.enemyWatchers[enemyID].getAllEnemySoldiers();
		
		var dangerousArmies = militaryManager.enemyWatchers[enemyID].getDangerousArmies();
		// we check if all the dangerous armies are still dangerous.
		var newDangerArmies = this.reevaluateDangerousArmies(gameState,dangerousArmies);
		
		var safeArmies = militaryManager.enemyWatchers[enemyID].getSafeArmies();
		// we check not dangerous armies, to see if they suddenly became dangerous
		var unsafeArmies = this.evaluateArmies(gameState,safeArmies);
		for (i in unsafeArmies)
			newDangerArmies[i] = unsafeArmies[i];
		
		// and any dangerous armies we push in "dangerArmies"
		militaryManager.enemyWatchers[enemyID].resetDangerousArmies();		
		for (o in newDangerArmies)
			militaryManager.enemyWatchers[enemyID].setAsDangerous(o);
		
		for (i in newDangerArmies)
			dangerArmies[i] = newDangerArmies[i];
	}
	
	var self = this;
	
	var nbOfAttackers = 0;
	
	var newEnemies = [];
	// clean up before adding new units (slight speeding up, since new units can't already be dead)
	for (i in this.listOfEnemies) {
		if (this.listOfEnemies[i].length === 0) {
			// if we had defined the attackerCache, ie if we had tried to attack this unit.
			if (this.attackerCache[i] !== undefined) {
				this.attackerCache[i].forEach(function(ent) { ent.stopMoving(); });
				delete this.attackerCache[i];
			}
			delete this.listOfEnemies[i];
		} else {
			var unit = this.listOfEnemies[i].toEntityArray()[0];
			var enemyWatcher = militaryManager.enemyWatchers[unit.owner()];
			if (enemyWatcher.isPartOfDangerousArmy(unit.id())) {
				nbOfAttackers++;
				if (this.attackerCache[unit.id()].length == 0) {
					newEnemies.push(unit);
				}
			} else {
				// if we had defined the attackerCache, ie if we had tried to attack this unit.
				if (this.attackerCache[unit.id()] != undefined) {
					this.attackerCache[unit.id()].forEach(function(ent) { ent.stopMoving(); });
					delete this.attackerCache[unit.id()];
				}
				this.listOfEnemies[unit.id()].toEntityArray()[0].setMetadata("listed-enemy",undefined);
				delete this.listOfEnemies[unit.id()];
			}
		}
	}
	// okay so now, for every dangerous armies, we loop.
	for (armyID in dangerArmies) {
		// looping through army units
		dangerArmies[armyID].forEach(function(ent) {
			// do we have already registered an entityCollection for it?
			if (self.listOfEnemies[ent.id()] === undefined) {
				// no, we register a new entity collection in listOfEnemies, listing exactly one unit as long as it remains alive and owned by my enemy.
				// can't be bothered to recode everything
				var owner = ent.owner();
				var filter = Filters.and(Filters.byOwner(owner),Filters.byID(ent.id()));
				self.listOfEnemies[ent.id()] = self.enemyUnits[owner].filter(filter);
				self.listOfEnemies[ent.id()].registerUpdates();
				self.listOfEnemies[ent.id()].length;
				self.listOfEnemies[ent.id()].toEntityArray()[0].setMetadata("listed-enemy",true);

				// let's also register an entity collection for units attacking this unit (so we can new if it's attacked)
				filter = Filters.and(Filters.byOwner(gameState.player),Filters.byTargetedEntity(ent.id()));
				self.attackerCache[ent.id()] = self.myUnits.filter(filter);
				self.attackerCache[ent.id()].registerUpdates();
				nbOfAttackers++;
				newEnemies.push(ent);
			}
		});
	}
	// Reordering attack because the pathfinder is for now not dynamically updated
	for (o in this.attackerCache) {
		if ((this.attackerCacheLoopIndicator + o) % 2 === 0) {
			this.attackerCache[o].forEach(function (ent) {
										  ent.attack(+o);
										  });
		}
	}
	this.attackerCacheLoopIndicator++;
	this.attackerCacheLoopIndicator = this.attackerCacheLoopIndicator % 2;

	if (nbOfAttackers === 0) {
		militaryManager.unpauseAllPlans(gameState);
		return;
	}
	
	// If I'm here, I have a list of enemy units, and a list of my units attacking it (in absolute terms, I could use a list of any unit attacking it).
	// now I'll list my idle defenders, then my idle soldiers that could defend.
	// and then I'll assign my units.
	// and then rock on.	
	
	if (nbOfAttackers < 10){
		gameState.setDefcon(4);	// few local units
	} else if (nbOfAttackers >= 10){
		gameState.setDefcon(3);
	}
	
	// we're having too many.
	if (this.myUnits.filter(Filters.byMetadata("role","defence")).length > nbOfAttackers*this.defenceRatio*1.3) {
		this.myUnits.filter(Filters.byMetadata("role","defence")).forEach(function (defender) { //}){
			if (defender.unitAIOrderData() && defender.unitAIOrderData()["target"]) {
				if (self.attackerCache[defender.unitAIOrderData()["target"]].length > 3) {
					// okay release me.
					defender.stopMoving();
					if (defender.getMetadata("formerrole"))
						defender.setMetadata("role", defender.getMetadata("formerrole") );
					else
						defender.setMetadata("role", "worker");
					defender.setMetadata("subrole", undefined);
					
				}
			}
		});
	}
	
	var nonDefenders = this.myUnits.filter(Filters.or(Filters.not(Filters.byMetadata("role","defence")),Filters.isIdle()));
	nonDefenders = nonDefenders.filter(Filters.not(Filters.byClass("Female")));
	nonDefenders = nonDefenders.filter(Filters.not(Filters.byMetadata("subrole","attacking")));
	var defenceRatio = this.defenceRatio;
	if (newEnemies.length * defenceRatio > nonDefenders.length) {
		defenceRatio = 1;
	}
	
	// For each enemy, we'll pick two units.
	for each (enemy in newEnemies) {
		if (nonDefenders.length === 0)
			break;
		// garrisoned.
		if (enemy.position() === undefined)
			continue;
		
		var assigned = self.attackerCache[enemy.id()].length;
		if (assigned >= defenceRatio)
			return;

		// let's check for a counter.
		//debug ("Enemy is a " + uneval(enemy._template.Identity.Classes._string) );
		var potCounters = gameState.ai.templateManager.getCountersToClasses(gameState,enemy.classes(),enemy.templateName());
		//debug ("Counters are" +uneval(potCounters));
		var counters = [];
		for (o in potCounters) {
			var counter = nonDefenders.filter(Filters.and(Filters.byType(potCounters[o][0]), Filters.byStaticDistance(enemy.position(), 150) )).toEntityArray();
			if (counter.length !== 0)
				for (unit in counter)
					counters.push(counter[unit]);
		}
		//debug ("I have " +counters.length +"countering units");
		for (var i = 0; i < defenceRatio && i < counters.length; i++) {
			if (counters[i].getMetadata("plan") !== undefined)
				militaryManager.pausePlan(gameState,counters[i].getMetadata("plan"));
			if (counters[i].getMetadata("role") == "worker" || counters[i].getMetadata("role") == "attack")
				counters[i].setMetadata("formerrole", counters[i].getMetadata("role"));
			counters[i].setMetadata("role","defence");
			counters[i].setMetadata("subrole","defending");
			counters[i].attack(+enemy.id());
			nonDefenders.updateEnt(counters[i]);
			assigned++;
			//debug ("Sending a " +counters[i].templateName() +" to counter a " + enemy.templateName());
		}
		if (assigned !== defenceRatio) {
			// take closest units
			nonDefenders.filter(Filters.byClass("CitizenSoldier")).filterNearest(enemy.position(),defenceRatio-assigned).forEach(function (defender) { //}){
				if (defender.getMetadata("plan") !== undefined)
					militaryManager.pausePlan(gameState,defender.getMetadata("plan"));
				if (defender.getMetadata("role") == "worker" || defender.getMetadata("role") == "attack")
					defender.setMetadata("formerrole", defender.getMetadata("role"));
				defender.setMetadata("role","defence");
				defender.setMetadata("subrole","defending");
				defender.attack(+enemy.id());
				nonDefenders.updateEnt(defender);
				assigned++;
			});
		}
	}
	/*
	// yes. We'll pick new units (pretty randomly for now, todo)
	// first from attack plans, then from workers.
	var newSoldiers = gameState.getOwnEntities().filter(function (ent) {
		if (ent.getMetadata("plan") != undefined && ent.getMetadata("role") != "defence")
			return true;
		return false;
	});
	newSoldiers.forEach(function(ent) {
		if (ent.getMetadata("subrole","attacking"))	// gone with the wind to avenge their brothers.
			return;
		if (nbOfAttackers <= 0)
			return;
		militaryManager.pausePlan(gameState,ent.getMetadata("plan"));
		ent.setMetadata("formerrole", ent.getMetadata("role"));
		ent.setMetadata("role","defence");
		ent.setMetadata("subrole","newdefender");
		nbOfAttackers--;
	});


	if (nbOfAttackers > 0) {
		newSoldiers = gameState.getOwnEntitiesByRole("worker");
		newSoldiers.forEach(function(ent) {
			if (nbOfAttackers <= 0)
				return;
			// If we're not female, we attack
			// and if we're not already assigned from above (might happen, not sure, rather be cautious)
			if (ent.hasClass("CitizenSoldier") && ent.getMetadata("subrole") != "newdefender") {
				ent.setMetadata("formerrole", "worker");
				ent.setMetadata("role","defence");
				ent.setMetadata("subrole","newdefender");
				nbOfAttackers--;
			}
		});
	}
	// okay
	newSoldiers = gameState.getOwnEntitiesByMetadata("subrole","newdefender");
	
	// we're okay, but there's a big amount of units
	// todo: check against total number of soldiers
	if (nbOfAttackers <= 0 && newSoldiers.length > 35)
		gameState.setDefcon(2);
	else if (nbOfAttackers > 0) {
		// we are actually lacking units
		gameState.setDefcon(1);
	}
	// TODO. For now, each unit will pick the closest unit that is attacked by only one/zero guy, or any if there is none.
	// ought to regroup them first for optimization.
	
	newSoldiers.forEach(function(ent) { //}) {
		var enemies = self.listedEnemyCollection.filterNearest(ent.position()).toEntityArray();
		var target = -1;
		var secondaryTarget = enemies[0];	// second best pick
		for (o in enemies) {
			var enemy = enemies[o];
			if (self.attackerCache[enemy.id()].length < 2) {
				target = +enemy.id();
				break;
			}
		}
		ent.setMetadata("subrole","defending");
		ent.attack(+target);
	});
*/
	return;
}

// this processes the attackmessages
// So that a unit that gets attacked will not be completely dumb.
// warning: huge levels of indentation coming.
Defence.prototype.MessageProcess = function(gameState,events, militaryManager) {
	for (var key in events){
		var e = events[key];
		if (e.type === "Attacked" && e.msg){
			if (gameState.isEntityOwn(gameState.getEntityById(e.msg.target))) {
				var attacker = gameState.getEntityById(e.msg.attacker);
				var ourUnit = gameState.getEntityById(e.msg.target);
				// the attacker must not be already dead, and it must not be me (think catapults that miss).
				if (attacker !== undefined && attacker.owner() !== gameState.player && attacker.position() !== undefined) {
					// note: our unit can already by dead by now... We'll then have to rely on the enemy to react.
					// if we're not on enemy territory
					var territory = +this.territoryMap.point(attacker.position())  - 64;
					
					// we do not consider units that are defenders, and we do not consider units that are part of an attacking attack plan
					// (attacking attacking plans are dealing with threats on their own).
					if (ourUnit !== undefined && (ourUnit.getMetadata("role") == "defence" || ourUnit.getMetadata("subrole") == "attacking"))
						continue;
					
					// let's check for animals
					if (attacker.owner() == 0) {
						// if our unit is still alive, we make it react
						// in this case we attack.
						if (ourUnit !== undefined) {
							if (ourUnit.hasClass("Unit") && !ourUnit.hasClass("Support"))
								ourUnit.attack(e.msg.attacker);
							else {
								ourUnit.flee(attacker);
							}
						}
						// anyway we'll register the animal as dangerous, and attack it.
						var filter = Filters.byID(attacker.id());
						this.listOfWantedUnits[attacker.id()] = gameState.getEntities().filter(filter);
						this.listOfWantedUnits[attacker.id()].registerUpdates();
						this.listOfWantedUnits[attacker.id()].length;
						filter = Filters.and(Filters.byOwner(gameState.player),Filters.byTargetedEntity(attacker.id()));
						this.WantedUnitsAttacker[attacker.id()] = this.myUnits.filter(filter);
						this.WantedUnitsAttacker[attacker.id()].registerUpdates();
						this.WantedUnitsAttacker[attacker.id()].length;
					} else if (territory != attacker.owner()) {	// preliminary check: attacks in enemy territory are not counted as attacks
						// Also TODO: this does not differentiate with buildings...
						// These ought to be treated differently.
						// units in attack plans will react independently, but we still list the attacks here.
						if (attacker.hasClass("Structure")) {
							// todo: we ultimately have to check wether it's a danger point or an isolated area, and if it's a danger point, mark it as so.
						} else {
							// TODO: right now a soldier always retaliate... Perhaps it should be set in "Defence" mode.							
							if (!attacker.hasClass("Female") && !attacker.hasClass("Ship")) {
								// This unit is dangerous. We'll ask the enemy manager if it's part of a big army, in which case we'll list it as dangerous (so it'll be treated next turn by the other manager)
								// If it's not part of a big army, depending on our priority we may want to kill it (using the same things as animals for that)
								// TODO (perhaps not any more, but let's mark it anyway)
								var army = militaryManager.enemyWatchers[attacker.owner()].getArmyFromMember(attacker.id());
								if (army !== undefined && army[1].length > 5) {
									militaryManager.enemyWatchers[attacker.owner()].setAsDangerous(army[0]);
								} else if (army !== undefined && !militaryManager.enemyWatchers[attacker.owner()].isDangerous(army[0])) {
									// we register this unit as wanted, TODO register the whole army
									// another function will deal with it.
									var filter = Filters.and(Filters.byOwner(attacker.owner()),Filters.byID(attacker.id()));
									this.listOfWantedUnits[attacker.id()] = this.enemyUnits[attacker.owner()].filter(filter);
									this.listOfWantedUnits[attacker.id()].registerUpdates();
									this.listOfWantedUnits[attacker.id()].length;
									filter = Filters.and(Filters.byOwner(gameState.player),Filters.byTargetedEntity(attacker.id()));
									this.WantedUnitsAttacker[attacker.id()] = this.myUnits.filter(filter);
									this.WantedUnitsAttacker[attacker.id()].registerUpdates();
									this.WantedUnitsAttacker[attacker.id()].length;
								}
								if (ourUnit && ourUnit.hasClass("Unit")) {
									if (ourUnit.hasClass("Support")) {
										// TODO: it's a villager. Garrison it.
										// TODO: make other neighboring villagers garrison

										// Right now we'll flee from the attacker.
										ourUnit.flee(attacker);
									} else {
										// It's a soldier. Right now we'll retaliate
										// TODO: check for stronger units against this type, check for fleeing options, etc.
										ourUnit.attack(e.msg.attacker);
									}
								}
								
							}
						}
					}
				}
			}
		}
	}
};
// At most, this will put defcon to 4
Defence.prototype.DealWithWantedUnits = function(gameState, events, militaryManager) {
	//if (gameState.defcon() < 3)
	//	return;
	
	var self = this;
	
	var nbOfAttackers = 0;
	var nbOfDealtWith = 0;
	
	// clean up before adding new units (slight speeding up, since new units can't already be dead)
	for (i in this.listOfWantedUnits) {
		if (this.listOfWantedUnits[i].length === 0 || this.listOfEnemies[i] !== undefined) {	// unit died/was converted/is already dealt with as part of an army
			delete this.WantedUnitsAttacker[i];
			delete this.listOfWantedUnits[i];
		} else {
			nbOfAttackers++;
			if (this.WantedUnitsAttacker[i].length > 0)
				nbOfDealtWith++;
		}
	}

	// note: we can deal with units the way we want because anyway, the Army Defender has already done its task.
	// If there are still idle defenders here, it's because they aren't needed.
	// I can also call other units: they're not needed.
	// Note however that if the defcon level is too high, this won't do anything because it's low priority.
	// this also won't take units from attack managers
	
	if (nbOfAttackers === 0)
		return;
	
	// at most, we'll deal with 3 enemies at once.
	if (nbOfDealtWith >= 3)
		return;
	
	// dynamic properties are not updated nearly fast enough here so a little caching
	var addedto = {};
	
	// we send 3 units to each target just to be sure. TODO refine.
	// we do not use plan units
	this.idleDefs.forEach(function(ent) {
		if (nbOfDealtWith < 3 && nbOfAttackers > 0 && ent.getMetadata("plan") == undefined)
			for (o in self.listOfWantedUnits) {
				if ( (addedto[o] == undefined && self.WantedUnitsAttacker[o].length < 3) || (addedto[o] && self.WantedUnitsAttacker[o].length + addedto[o] < 3)) {
					if (self.WantedUnitsAttacker[o].length === 0)
						nbOfDealtWith++;
						  
					ent.setMetadata("formerrole", ent.getMetadata("role"));
					ent.setMetadata("role","defence");
					ent.setMetadata("subrole", "defending");
					ent.attack(+o);
					if (addedto[o])
						addedto[o]++; 
					else
						addedto[o] = 1;
					break;
				}
				if (self.WantedUnitsAttacker[o].length == 3)
					 nbOfAttackers--;	// we hav eenough units, mark this one as being OKAY
			}
	});
	
	// still some undealt with attackers, recruit citizen soldiers
	if (nbOfAttackers > 0 && nbOfDealtWith < 2) {
	
	gameState.setDefcon(4);
	
	var newSoldiers = gameState.getOwnEntitiesByRole("worker");
	newSoldiers.forEach(function(ent) {
		// If we're not female, we attack
		if (ent.hasClass("CitizenSoldier"))
			if (nbOfDealtWith < 3 && nbOfAttackers > 0)
				for (o in self.listOfWantedUnits) {
					if ( (addedto[o] == undefined && self.WantedUnitsAttacker[o].length < 3) || (addedto[o] && self.WantedUnitsAttacker[o].length + addedto[o] < 3)) {
						if (self.WantedUnitsAttacker[o].length === 0)
							nbOfDealtWith++;
						ent.setMetadata("formerrole", ent.getMetadata("role"));
						ent.setMetadata("role","defence");
						ent.setMetadata("subrole", "defending");
						ent.attack(+o);
						if (addedto[o])
							addedto[o]++; 
						else
							addedto[o] = 1;
						break;
					}
					if (self.WantedUnitsAttacker[o].length == 3)
						nbOfAttackers--;	// we hav eenough units, mark this one as being OKAY
				}
	});	
	}
	return;
}
