#include "NovaStdAfx.h"
#include "SquadManager.h"
#include "WorkerManager.h"
#include "Search/AbstractLayer.h"

using namespace BWAPI;

#define MERGE_DISTANCE 300
#define AUTO_MERGE_DISTANCE 385

SquadManager::SquadManager()
	: _creatingSquad(0),
	_leadSquad(0),
	_searchSquad(0),
	_detectorSquad(0),
	_logger(log4cxx::Logger::getLogger("SquadManager"))
{
	
};

void SquadManager::newSquad(const UnitSet &units) 
{
	SquadAgent *newSquad = new SquadAgent();
	for(UnitSet::const_iterator i=units.begin();i!=units.end();++i) {
		newSquad->addUnit(*i);
		_unitToSquadMap[*i] = newSquad;
	}
	//Position targetPosition = getBestTarget();
	Position targetPosition = informationManager->_enemyStartPosition;
	newSquad->orderGetPosition(targetPosition);
	_squads.insert(newSquad);
	// if it is first squad, check _seenEnemies
	if (_squads.size()==1) {
		if ( !informationManager->_seenEnemies.empty()) {
			for(UnitSet::const_iterator enemyIter=informationManager->_seenEnemies.begin();enemyIter!=informationManager->_seenEnemies.end();) {
				Unit* enemy = *enemyIter;
				++enemyIter;
				if (enemy->isVisible()) {
					newSquad->_enemies.insert(enemy);
					_enemyToSquadMap[enemy] = newSquad;
					newSquad->_enemiesType[enemy] = (enemy)->getType();
					newSquad->insertEnemyThreat(enemy);
					informationManager->deleteSeenEnemy(enemy);
				}
			}
		}
	}
}

SquadAgent* SquadManager::testSquad(const UnitSet &units) 
{
	SquadAgent *newSquad = new SquadAgent();
	for(UnitSet::const_iterator i=units.begin();i!=units.end();++i) {
		newSquad->addUnit(*i);
		_unitToSquadMap[*i] = newSquad;
	}
	_squads.insert(newSquad);
	return newSquad;
}

void SquadManager::newSquad(Unit* unit) 
{
	// check if we have a creating squad
	if (_creatingSquad == 0) {
        //LOG("Creating new squas");
		SquadAgent *newSquad = new SquadAgent();
		_squads.insert(newSquad);
		_creatingSquad = newSquad;
		if (_leadSquad == 0) {
			_leadSquad = newSquad;
		}
	}

	// add unit to squad
	_creatingSquad->addUnit(unit);
	_unitToSquadMap[unit] = _creatingSquad;

	// if it is first squad, check _seenEnemies
	if (_squads.size()==1) {
		if ( !informationManager->_seenEnemies.empty()) {
			for(UnitSet::const_iterator enemyIter=informationManager->_seenEnemies.begin();enemyIter!=informationManager->_seenEnemies.end();) {
				Unit* enemy = *enemyIter;
				++enemyIter;
				if (enemy->isVisible()) {
					_creatingSquad->_enemies.insert(enemy);
					_enemyToSquadMap[enemy] = _creatingSquad;
					_creatingSquad->_enemiesType[enemy] = enemy->getType();
					_creatingSquad->insertEnemyThreat(enemy);
					informationManager->deleteSeenEnemy(enemy);
				}
			}
		}
	}

	// check squad complete
	if ((int)_creatingSquad->_squadUnits.size() >= informationManager->_minSquadSize || normalSquads() > 1 ) { 
        //LOG("Squad completed wiht " << _creatingSquad->_squadUnits.size() << " units");
		_creatingSquad->_waitingNewUnits = false;
		Position targetPosition = getBestTarget();
		_creatingSquad->orderGetPosition(targetPosition);
		_creatingSquad = 0;
	}
}

int SquadManager::normalSquads()
{
	int squadsSize = 0;
	for(SquadSet::const_iterator checkSquad=_squads.begin();checkSquad!=_squads.end();++checkSquad) {
		if ( !isSpecialSquad(*checkSquad) ) {
			squadsSize++;
		}
	}
	return squadsSize;
}

void SquadManager::newBunkerSquad(Unit* bunker)
{
	// create new squad
	SquadAgent *bunkerSquad = new SquadAgent();

	// add unit to squad
	bunkerSquad->addUnit(bunker);
	_unitToSquadMap[bunker] = bunkerSquad;

	// search units to load bunker
	for(SquadSet::const_iterator squad=_squads.begin();squad!=_squads.end();) {
		CombatUnitSet squadUnits = (*squad)->_squadUnits;
		++squad;
		
		for(CombatUnitSet::const_iterator unit=squadUnits.begin();unit!=squadUnits.end();) {
			Unit* marine = (*unit)->_unit;
			++unit;
			if (marine->getType() == UnitTypes::Terran_Marine) {
				onUnitDestroy(marine);
				bunkerSquad->addUnit(marine);
				_unitToSquadMap[marine] = bunkerSquad;
				marine->load(bunker);
			}
			if (bunkerSquad->_squadUnits.size() == 5) break;
		}
		if (bunkerSquad->_squadUnits.size() == 5) break;
	}

	bunkerSquad->_state = SquadAgent::Bunker;
	_squads.insert(bunkerSquad);
}

void SquadManager::newScoutSquad(BWAPI::Unit* unit)
{
	SquadAgent *scoutSquad = new SquadAgent();

	// add unit to squad
	scoutSquad->addUnit(unit);
	_unitToSquadMap[unit] = scoutSquad;

	scoutSquad->_state = SquadAgent::Scout;
	_squads.insert(scoutSquad);

	scoutSquad->_positionTarget = getScoutTarget();
}

void SquadManager::newDetectorSquad(BWAPI::Unit* unit)
{
	SquadAgent *detectorSquad = new SquadAgent();

	// add unit to squad
	detectorSquad->addUnit(unit);
	_unitToSquadMap[unit] = detectorSquad;

	detectorSquad->_state = SquadAgent::Detector;
	_squads.insert(detectorSquad);

	_detectorSquad = detectorSquad;

	//detectorSquad->_positionTarget = getScoutTarget();
}

void SquadManager::unitTraining(Unit* unit)
{
	_unitsTraning.insert(unit);
}

void SquadManager::onFrame() 
{
	// sanitize squad
	_leadSquad = getBestSquadLead();

	if (_leadSquad != NULL && _leadSquad->squadNeedSCV() ) {
		Unit* scv = workerManager->getWorkerForTask(_leadSquad->_center);
		if (scv != NULL) {
			_leadSquad->addUnit(scv);
			_unitToSquadMap[scv] = _leadSquad;
			workerManager->onUnitDestroy(scv);
		}
	}

	// check if wee need a search squad
	if (informationManager->_searchAndDestroy && _searchSquad == 0) {
		// search for a Wraith
		for(SquadSet::const_iterator squad=_squads.begin();squad!=_squads.end();) {
			CombatUnitSet squadUnits = (*squad)->_squadUnits;
			++squad;

			for(CombatUnitSet::const_iterator unit=squadUnits.begin();unit!=squadUnits.end();) {
				Unit* wraith = (*unit)->_unit;
				++unit;
				if (wraith->getType() == UnitTypes::Terran_Wraith) {
					onUnitDestroy(wraith);
					SquadAgent *searchSquad = new SquadAgent();
					searchSquad->addUnit(wraith);
					_unitToSquadMap[wraith] = searchSquad;
					searchSquad->_state = SquadAgent::Search;
					_squads.insert(searchSquad);
					Position corner = Position(192,192);
					searchSquad->_positionTarget = corner;
					wraith->attack(corner);
					_searchSquad = searchSquad;
					break;
				}
			}
		}
	}

	if (!ONLY_MICRO) {
		// check for units training ready
		for(UnitSet::const_iterator unit=_unitsTraning.begin();unit!=_unitsTraning.end();) {
			LOG4CXX_DEBUG(_logger, "[TRAINING] Unit " << (*unit)->getType().getName() << " order: " << (*unit)->getOrder().getName());
			if ((*unit)->getOrder() != Orders::Nothing) {
				if ((*unit)->getType() == UnitTypes::Terran_Science_Vessel) {
					if ( _detectorSquad != NULL ) {
						// add unit to squad
						_detectorSquad->addUnit(*unit);
						_unitToSquadMap[*unit] = _detectorSquad;
					} else {
						newDetectorSquad(*unit);
					}
				} else {
					newSquad(*unit);
				}
				_unitsTraning.erase( unit++ );
			} else {
				++unit;
			}
		}
	}

	// TODO: only use this in DEBUG MODE
	int squadsMerging = 0;
	for(SquadSet::const_iterator checkSquad=_squads.begin();checkSquad!=_squads.end();++checkSquad) {
		if ( (*checkSquad)->_state == SquadAgent::MergeSquads) {
			++squadsMerging;
			if ( *checkSquad != (*checkSquad)->_squadToMerge->_squadToMerge)
				LOG4CXX_WARN(_logger, "BAD LINK " << (*checkSquad)->_squadToMerge << " <-> " << *checkSquad);
		}
	}

	// auto-merge
	if (_squads.size() > 1) {
		LOG4CXX_TRACE(_logger, "CHECK AUTO-MERGING (START)");
		checkAutoMerge();
		LOG4CXX_TRACE(_logger, "CHECK AUTO-MERGING (END)");
	}

	// merge squads
	if (_squadsToMerge.size() > 0) {
		LOG4CXX_TRACE(_logger, "CHECK MERGING (START)");
		checkMerge();
		LOG4CXX_TRACE(_logger, "CHECK MERGING (END)");
	}

	// update each squad
#ifndef TOURNAMENT
	int line = 0;
	char squadColor;
#endif

    if (HIGH_LEVEL_SEARCH) {
        if (informationManager->mapAnalyzed && (Broodwar->getFrameCount() % HIGH_LEVEL_REFRESH == 0)&& !Broodwar->isPaused()) {
            LOG4CXX_TRACE(_logger, "CHECK HIGH LEVEL ORDER (START)");
            checkHighLevelOrder();
            LOG4CXX_TRACE(_logger, "CHECK HIGH LEVEL ORDER (END)");
        }
    }


	LOG4CXX_TRACE(_logger, "UPDATE SQUADS (START)");
	for(SquadSet::const_iterator squad=_squads.begin();squad!=_squads.end();++squad) {
		LOG4CXX_TRACE(_logger, "UPDATE squad (" << *squad << ")");
		(*squad)->onFrame();

		// Draw stats
		#ifndef TOURNAMENT
			squadColor = '\x02'; // Light Blue
			if (isSpecialSquad(*squad)) squadColor = '\x0F'; //Teal
			if ((*squad) == _leadSquad) squadColor = '\x11'; //Orange
			Broodwar->drawTextScreen(5,13*line,"%cunits(%d) enemies(%d) [%s] %s", squadColor,
									(*squad)->_squadUnits.size(), (*squad)->_enemies.size(), (*squad)->getState().c_str(), (*squad)->_waitingReason.c_str() );

			if ((*squad)->_enemyStats.airHP) {
				++line;
				Broodwar->drawTextScreen(10,13*line,"EnemyAirHP %2.f vs SquadAirDPS %2.f",(*squad)->_enemyStats.airHP,(*squad)->_squadStats.airDPS);
			}
			if ((*squad)->_enemyStats.groundHP) {
				++line;
				Broodwar->drawTextScreen(10,13*line,"EnemyGroundHP %2.f vs SquadGroundDPS %2.f",(*squad)->_enemyStats.groundHP,(*squad)->_squadStats.groundDPS);
			}
			if ((*squad)->_squadStats.airHP) {
				++line;
				Broodwar->drawTextScreen(10,13*line,"SquadAirHP %2.f vs EnemyAirDPS %2.f",(*squad)->_squadStats.airHP,(*squad)->_enemyStats.airDPS);
			}
			if ((*squad)->_squadStats.groundHP) {
				++line;
				Broodwar->drawTextScreen(10,13*line,"SquadGroundHP %2.f vs EnemyGroundDPS %2.f",(*squad)->_squadStats.groundHP,(*squad)->_enemyStats.groundDPS);
			}

			
			++line;
			for(UnitSet::const_iterator enemy=(*squad)->_enemies.begin();enemy!=(*squad)->_enemies.end();++enemy) {
				//Broodwar->drawCircleMap((*enemy)->getPosition().x(), (*enemy)->getPosition().y(), (*enemy)->getType().airWeapon().maxRange(), Colors::Red, false);
				//if ((*enemy)->getType() == UnitTypes::Unknown || (*squad) == squadManager->_leadSquad ) {
				if ((*enemy)->getType() == UnitTypes::Unknown) {
					Broodwar->drawTextScreen(10,13*line,"Enemy: %s <-> %s", (*enemy)->getType().getName().c_str(), (*squad)->_enemiesType[*enemy].getName().c_str() );
					++line;
				}
			}
		#endif
	}
	LOG4CXX_TRACE(_logger, "UPDATE SQUADS (END)");
}

void SquadManager::checkHighLevelOrder()
{
#ifdef NOVA_GUI
    WaitForSingleObject(gameStateMutex,INFINITE); // wait for ownership
#endif

    // clean and add enemies in the game state
    AbstractLayer search;

    // translate squads to high level squads into game state
    for(SquadSet::const_iterator squad=_squads.begin();squad!=_squads.end();++squad) {
        // ignore scout
        if ( (*squad)->_state == SquadAgent::Scout ) continue;
        search.addSquadToGameState(*squad);
    }

    //search.printBranchingStats();

    if (!search.hasFriendlyUnits()) return;

    // perform high level search
    std::map<SquadAgent*, BWAPI::Position> bestOrders = search.searchBestOrders();

    // check if we have new orders
    Position bestPosition;
    Position squadtPosition;
    for(SquadSet::const_iterator squad=_squads.begin();squad!=_squads.end();++squad) {
        stats.orders++;
        bestPosition = bestOrders[*squad];
        squadtPosition = (*squad)->_positionTarget;
        if (bestPosition != squadtPosition) {
            stats.ordersOverwritten++;
            // if the new position is closer to home it's a FORCE move

            // otherwise it's an attack move
            //LOG(*squad << " Attack move to new region from " << squadtPosition.x() << "," << squadtPosition.y() << " to " << bestPosition.x() << "," << bestPosition.y());
            (*squad)->orderGetPosition(bestPosition);
        }
    }
    //LOG("EvaluationCurrentState: " << search.getEvaluation() << " myKillScore: " << Broodwar->self()->getKillScore() << " enemyKillScore: " << Broodwar->enemy()->getKillScore());

#ifdef NOVA_GUI
    ReleaseMutex(gameStateMutex);
    informationManager->_GUIsignal->emitGameStateChanged();
#endif
}

void SquadManager::checkAutoMerge()
{
	SquadAgent *squad1;
	SquadAgent *squad2;
	for(SquadSet::const_iterator checkSquad=_squads.begin();checkSquad!=_squads.end();) {
		squad2 = *checkSquad;
		if (_creatingSquad == squad2) { ++checkSquad; continue; }
		if ( squad2->_state == SquadAgent::GetPosition) { 
			squad1 = getClosestSquad(squad2->_center, squad2, true);
			if ( squad1 != 0 && _creatingSquad != squad1 && squad1->_positionTarget == squad2->_positionTarget) { 
				// TODO improve automerge conditions
				int distance = squad2->_center.getApproxDistance(squad1->_center);
				BWTA::Region* squad2Region = BWTA::getRegion(TilePosition(squad2->_center));
				if (distance < AUTO_MERGE_DISTANCE) {
				//if (distance < AUTO_MERGE_DISTANCE || (!squad1->isSquadBio() && !squad2->isSquadBio()) ) {
					// do unobtrusive merge
					LOG4CXX_DEBUG(_logger, "Unobtrusive Merge " << squad1 << " <-> " << squad2);
					if (squad1->_state == SquadAgent::MergeSquads) {
						for(CombatUnitSet::const_iterator i=squad2->_squadUnits.begin();i!=squad2->_squadUnits.end();++i) {
							if ((*i)->_unit->getType() == UnitTypes::Terran_Dropship || (*i)->_unit->getType() == UnitTypes::Terran_Science_Vessel) continue; //ignore special micro
							(*i)->_unit->move(squad1->_positionToMerge);
						}
					} else if (squad1->_state == SquadAgent::HoldCenter) {
						// if we auto-merge with a squad holding center, order to move to hold position
						for(CombatUnitSet::const_iterator i=squad2->_squadUnits.begin();i!=squad2->_squadUnits.end();++i) {
							(*i)->_unit->move(squad1->_holdCenterPosition);
						}
					}
					mergeUnitsAndEnemies(squad1, squad2);
					// delete old squad
					LOG4CXX_DEBUG(_logger, "Delete squad " << squad2);
					_squads.erase( checkSquad++ );
					if (_leadSquad == squad2) {
						// find another candidate for squad lead
						_leadSquad = getBestSquadLead();
					}
					delete squad2; squad2 = 0;
					continue;
				}
			}
		}
		++checkSquad;
	}
}

void SquadManager::checkMerge()
{
	SquadAgent *squad1;
	SquadAgent *squad2;
	int distance;
	
	for(UnitPairSet::const_iterator squadPair=_squadsToMerge.begin();squadPair!=_squadsToMerge.end();) {
		squad1 = squadPair->first;
		squad2 = squadPair->second;
		LOG4CXX_TRACE(_logger, "Merge " << squad1 << " <-> " << squad2);
		distance = squad1->_center.getApproxDistance(squad2->_center);
		//distance = BWTA::getGroundDistance(TilePosition(squad1->_center.x(),squad1->_center.y()), TilePosition(squad2->_center.x(),squad2->_center.y()));
		//Broodwar->drawTextScreen(290,16,"Merge distance: %d", distance);
		Broodwar->drawLine(CoordinateType::Map, squad1->_center.x(), squad1->_center.y(), squad2->_center.x(), squad2->_center.y(), Colors::Orange);
		if (distance < MERGE_DISTANCE) {
			mergeUnitsAndEnemies(squad1, squad2);
			//check if any other squad want to merge to squad2 
			bool newOrder = true;
			for(UnitPairSet::iterator oldSquadPair=_squadsToMerge.begin();oldSquadPair!=_squadsToMerge.end();++oldSquadPair) {
				if (oldSquadPair->first  == squad1) continue;
				if (oldSquadPair->second == squad2) {
					oldSquadPair->second = squad1;
					newOrder = false;
					oldSquadPair->first->inMerge(squad1);
					squad1->inMerge(oldSquadPair->first);
				} else if (oldSquadPair->first == squad2) {
					oldSquadPair->first = squad1;
					newOrder = false;
					oldSquadPair->second->inMerge(squad1);
					squad1->inMerge(oldSquadPair->second);
				}
			}
			// TODO: only use this in DEBUG MODE
			for(SquadSet::const_iterator checkSquad=_squads.begin();checkSquad!=_squads.end();++checkSquad) {
				if (*checkSquad == squad1) continue;
				if ((*checkSquad)->_squadToMerge == squad2) {
					LOG4CXX_ERROR(_logger, "Squad " << *checkSquad << " want a merge with deleted " << squad2);
				}
			}
			// delete old squad
			LOG4CXX_TRACE(_logger, "Delete squad " << squad2);
			_squads.erase(squad2);
			if (_leadSquad == squad2) {
				// find anohter candidate for squad lead
				_leadSquad = getBestSquadLead();
			}
			delete squad2; squad2 = 0;
			//check if any other squad want to merge to squad1
			for(SquadSet::const_iterator checkSquad=_squads.begin();checkSquad!=_squads.end();++checkSquad) {
				if (*checkSquad == squad1) continue;
				if ((*checkSquad)->_squadToMerge == squad1) {
					LOG4CXX_TRACE(_logger, "Readjust Merge " << squad1 << " <-> " << *checkSquad);
					newOrder = false;
					squad1->inMerge(*checkSquad);
				}
			}
			if (newOrder) { // else default behavior
				squad1->_state = SquadAgent::Idle;
				squad1->_squadToMerge = 0;
                //LOG("Target after merge (" << squad1->_positionTarget.x() << "," << squad1->_positionTarget.y() << ")");
				squad1->orderGetPosition(squad1->_positionTarget); // new order TODO default behavior?
			}
			// delete merge operation
			_squadsToMerge.erase( squadPair++ );
		} else {
			++squadPair;
		}
	}
}

void SquadManager::mergeUnitsAndEnemies(SquadAgent* squad1, SquadAgent* squad2)
{
	// merge units
	for(CombatUnitSet::const_iterator unit=squad2->_squadUnits.begin();unit!=squad2->_squadUnits.end();++unit) {
		squad1->addUnit((*unit)->_unit);
		_unitToSquadMap.erase((*unit)->_unit);
		_unitToSquadMap.insert(UnitToSquadMap::value_type((*unit)->_unit, squad1));
	}
	// merge enemies
	for(UnitSet::const_iterator enemy=squad2->_enemies.begin();enemy!=squad2->_enemies.end();++enemy) {
		squad1->_enemies.insert(*enemy);
		squad1->insertEnemyThreat(*enemy);
		_enemyToSquadMap[*enemy] = squad1;
		squad1->_enemiesType[*enemy] = (*enemy)->getType();
	}
}

// void SquadManager::orderGetPosition(Position positionTarget) 
// {
// 	_positionTarget = positionTarget;
// 	for(SquadSet::const_iterator squad=_squads.begin();squad!=_squads.end();++squad) {
// 		(*squad)->orderGetPosition(positionTarget);
// 	}
// }

void SquadManager::onUnitDestroy(Unit* unit)
{
	UnitToSquadMap::iterator found = _unitToSquadMap.find(unit);
	if(found != _unitToSquadMap.end()) {
		SquadAgent *squad = found->second;
		if (squad == 0) {
			LOG4CXX_ERROR(_logger, "SQUAD in _unitToSquadMap entry was null");
			return;
		}
		squad->onUnitDestroy(unit);
		_unitToSquadMap.erase(found);
		// if no more units on that squad, remove squad
		if (squad->getAgents() == 0) {
			if (_creatingSquad == squad) _creatingSquad = 0;
			if (_searchSquad == squad) _searchSquad = 0;
			UnitSet enemiesToReassign = squad->_enemies;
			for(UnitSet::const_iterator enemy=enemiesToReassign.begin();enemy!=enemiesToReassign.end();++enemy) {
				if ((*enemy)->isVisible() ) {
					onEnemyEvade(*enemy);
 					newEnemy(*enemy, squad);
				} else {
					LOG4CXX_ERROR(_logger, "REASSIGN ENEMY LOST !!!!");
				}
			}
			removeMergingSquads(squad);
			_squads.erase(squad);
			LOG4CXX_TRACE(_logger, "Squad deleted (" << squad << ")");
			//DEBUG("Squad deleted (" << squad << ")");
			if (_leadSquad == squad) {
				// find anohter candidate for squad lead
				_leadSquad = getBestSquadLead();
				//informationManager->_minSquadSize += 2; // TODO use an evaluation function to do this
				informationManager->_minSquadSize = squad->_maxEnemyUnitsSeen + std::abs((int)squad->_maxEnemyUnitsSeen - (int)informationManager->_minSquadSize);
			}
			delete squad; squad = 0;
		}
	} else {
		// remove from _unitsTraning
		UnitSet::iterator found = _unitsTraning.find(unit);
		if(found != _unitsTraning.end()) {
			LOG4CXX_TRACE(_logger, "Unit (" << unit << ") removed from _unitsTraning");
			_unitsTraning.erase(unit);
		} else {
			//Broodwar->printf("[ERROR] Unit not found");
			LOG4CXX_ERROR(_logger, "Squad Unit (" << unit << ") not found");
			//DEBUG("   MAP UNITS");
			//for(UnitToSquadMap::const_iterator unitMap=_unitToSquadMap.begin();unitMap!=_unitToSquadMap.end();++unitMap) {
				//DEBUG("    Unit (" << unitMap->first << ") Squad (" << unitMap->second << ")");
			//}
		}
	}
}

SquadAgent* SquadManager::getBestSquadLead()
{
	int distance = 9999999;
	unsigned int size = 0;
	int newDistance;
	SquadAgent *bestSquad = 0;
	for(SquadSet::const_iterator squadIter=_squads.begin();squadIter!=_squads.end();++squadIter) {
		if ( isSpecialSquad(*squadIter) ) continue;
		newDistance = (*squadIter)->_center.getApproxDistance(informationManager->_enemyStartPosition);
		if (newDistance < distance || (*squadIter)->_squadUnits.size() > size) {
			distance = newDistance;
			size = (*squadIter)->_squadUnits.size();
			bestSquad = *squadIter;
		}
	}
	return bestSquad;
}

bool SquadManager::isSpecialSquad(SquadAgent* squad)
{
	if ( isSquadNoMergeable(squad) || isSquadNoEnemyAssignable(squad) )
		return true;
	return false;
}

bool SquadManager::isSquadNoMergeable(SquadAgent* squad)
{
	if (squad->_state == SquadAgent::Bunker ||
		squad->_state == SquadAgent::Search ||
		squad->_state == SquadAgent::Scout ||
		squad->_state == SquadAgent::Detector) return true;
	return false;
}

bool SquadManager::isSquadNoEnemyAssignable(SquadAgent* squad)
{
	if (squad->_state == SquadAgent::Bunker ||
		squad->_state == SquadAgent::Detector) return true;
	return false;
}


int SquadManager::getUnitFrameCreated(Unit* unit)
{
	UnitToSquadMap::iterator found = _unitToSquadMap.find(unit);
	if(found != _unitToSquadMap.end()) {
		SquadAgent *squad = found->second;
		if (squad == 0) {
			LOG4CXX_ERROR(_logger, "SQUAD in _unitToSquadMap entry was null");
			return Broodwar->getFrameCount();
		}
		 return squad->getUnitFrameCreated(unit);
		_unitToSquadMap.erase(found);
	} else {
		LOG4CXX_ERROR(_logger, "Unit not found in _unitToSquadMap");
		return Broodwar->getFrameCount();
	}
}

void SquadManager::newEnemy(Unit* enemy)
{
	newEnemy(enemy, 0);
}

void SquadManager::newEnemy(Unit* enemy, SquadAgent* oldSquad)
{
	// look into seen enemies to delete
	UnitSet::iterator found = informationManager->_seenEnemies.find(enemy);
	if(found != informationManager->_seenEnemies.end()) {
		informationManager->deleteSeenEnemy(enemy);
	}

	// try to assign enemy to a squad
	if ( _squads.size() == 0 ) { //we are in troubles
		informationManager->addSeenEnemy(enemy);
		LOG4CXX_INFO(_logger, "Unit discover but we don't have squads...");
	} else {
		// Search the closest squad
		SquadAgent *bestSquad = getClosestSquad(enemy->getPosition(), oldSquad, false, true);
		if (bestSquad != 0) {
			bestSquad->_enemies.insert(enemy);
			if (bestSquad->_maxEnemyUnitsSeen < bestSquad->_enemies.size()) {
				bestSquad->_maxEnemyUnitsSeen = bestSquad->_enemies.size();
			}
			bestSquad->insertEnemyThreat(enemy);
			_enemyToSquadMap.insert(UnitToSquadMap::value_type(enemy, bestSquad));
			bestSquad->_enemiesType[enemy] = (enemy)->getType();
		} else {
			informationManager->addSeenEnemy(enemy);
		}
	}
}

void SquadManager::onEnemyEvade(Unit* enemy)
{
// 	DEBUG("[EVADE] Enemy (" << enemy << ") type:" << enemy->getType().getName() );
// 	DEBUG("   SEEN ENEMIES");
// 	for(UnitSet::const_iterator enemyMap=informationManager->_seenEnemies.begin();enemyMap!=informationManager->_seenEnemies.end();++enemyMap) {
// 		DEBUG("    Enemy (" << *enemyMap << ") type (" << (*enemyMap)->getType().getName() << ")");
// 	}
// 	DEBUG("   MAP ENEMIES");
// 	for(UnitToSquadMap::const_iterator enemyMap=_enemyToSquadMap.begin();enemyMap!=_enemyToSquadMap.end();++enemyMap) {
// 		DEBUG("    Enemy (" << enemyMap->first << ") type (" << enemyMap->first->getType().getName() << ") Squad (" << enemyMap->second << ")");
// 	}
// 	DEBUG("   SQUAD ENEMIES");
// 	for(SquadSet::const_iterator checkSquad=_squads.begin();checkSquad!=_squads.end();++checkSquad) {
// 		for(UnitSet::const_iterator enemySquad=(*checkSquad)->_enemies.begin();enemySquad!=(*checkSquad)->_enemies.end();++enemySquad) {
// 			DEBUG("    Enemy (" << *enemySquad << ") type (" << (*enemySquad)->getType().getName() << ") Squad (" << *checkSquad << ")");
// 		}
// 	}

	// mark as seen unit
	informationManager->addSeenEnemy(enemy);

	// remove from squad target
	UnitToSquadMap::iterator found = _enemyToSquadMap.find(enemy);
	if(found != _enemyToSquadMap.end()) {
		SquadAgent *squad = found->second;
		_enemyToSquadMap.erase(found);	
		if (squad != 0) {
			squad->_enemies.erase(enemy);
			squad->removeEnemyThreat(enemy);
			squad->_enemiesType.erase(enemy);
			//if (squad->_enemies.size() == 0 && squad->_state == SquadAgent::Fight)
			if (squad->_enemies.size() == 0 && squad->_waitingNewUnits == false)
				squad->orderGetPosition(squad->_positionTarget); // new order. TODO: default behavior?
// 			else { //wait for more units
// 				squad->_state = SquadAgent::Idle;
// 			}
		} else {
			LOG4CXX_ERROR(_logger, "SQUAD in enemyToSquadMap entry was null");
		}
	} else {
		//Broodwar->printf("[ERROR] Enemy not found");
		LOG4CXX_ERROR(_logger, "Enemy (" << enemy << ") type:" << enemy->getType().getName() << " not found");
		//DEBUG("   MAP ENEMIES");
		//for(UnitToSquadMap::const_iterator enemyMap=_enemyToSquadMap.begin();enemyMap!=_enemyToSquadMap.end();++enemyMap) {
			//DEBUG("    Enemy (" << enemyMap->first << ") type (" << enemyMap->first->getType().getName() << ") Squad (" << enemyMap->second << ")");
		//}
		//DEBUG("   SEEN ENEMIES");
		//for(UnitSet::const_iterator enemyMap=informationManager->_seenEnemies.begin();enemyMap!=informationManager->_seenEnemies.end();++enemyMap) {
			//DEBUG("    Enemy (" << *enemyMap << ") type (" << (*enemyMap)->getType().getName() << ")");
		//}
	}
}

void SquadManager::onEnemyDestroy(Unit* enemy)
{
	// remove from seen unit
	UnitSet::iterator found = informationManager->_seenEnemies.find(enemy);
	if(found != informationManager->_seenEnemies.end()) {
		informationManager->deleteSeenEnemy(enemy);
	} else {
		LOG4CXX_ERROR(_logger, "Enemy (" << enemy << ") type:" << enemy->getType().getName() << " not found");
		//DEBUG("   SEEN ENEMIES");
		//for(UnitSet::const_iterator enemyMap=informationManager->_seenEnemies.begin();enemyMap!=informationManager->_seenEnemies.end();++enemyMap) {
			//DEBUG("    Enemy (" << *enemyMap << ") type (" << (*enemyMap)->getType().getName() << ")");
		//}
	}
}

void SquadManager::requestRetreat(SquadAgent* squad)
{
	//DEBUG("REQUEST RETREAT");
	 //if no more squads deny retreat
	if (_squads.size() == 1) {
		LOG4CXX_TRACE(_logger,"[Squad Retreat] deny (only 1 squad)");
		squad->inCombat(); return;
	}

	 //if there are tanks deny retreat
	if (squad->hasUnitOfType(UnitTypes::Terran_Siege_Tank_Tank_Mode) || squad->hasUnitOfType(UnitTypes::Terran_Siege_Tank_Siege_Mode) ) {
		LOG4CXX_TRACE(_logger,"[Squad Retreat] deny (tank present)");
		squad->inCombat(); return;
	}
		
	 //merge with closest squad
	SquadAgent *bestSquad = getClosestSquad(squad->_center, squad, true);
	if (bestSquad != 0) {
		LOG4CXX_TRACE(_logger,"[Squad Retreat] OK");
		squad->inMerge(bestSquad);
		LOG4CXX_TRACE(_logger,"[Squad Retreat] Squad 1 done");
		bestSquad->inMerge(squad, squad->_positionToMerge);
		LOG4CXX_TRACE(_logger,"[Squad Retreat] Squad 2 done");
		_squadsToMerge.insert(std::make_pair(squad, bestSquad));
		LOG4CXX_TRACE(_logger,"[Squad Retreat] Pair updated");
		if (_creatingSquad == bestSquad)
			_creatingSquad = 0;
	}
	LOG4CXX_TRACE(_logger,"[Squad Retreat] deny (any squad near)");
}

void SquadManager::removeMergingSquads(SquadAgent *squad) {
	SquadAgent *squad1;
	SquadAgent *squad2;
	for(UnitPairSet::const_iterator squadPair=_squadsToMerge.begin();squadPair!=_squadsToMerge.end();) {
		squad1 = squadPair->first;
		squad2 = squadPair->second;
		if (squad == squad1) {
			squad2->_state = SquadAgent::Idle;
			squad2->_squadToMerge = 0;
			squad2->orderGetPosition(squad2->_positionTarget); //TODO: default behavior?
			_squadsToMerge.erase(squadPair++);
		} else if (squad == squad2) {
			squad1->_state = SquadAgent::Idle;
			squad1->_squadToMerge = 0;
			squad1->orderGetPosition(squad1->_positionTarget); //TODO: default behavior?
			_squadsToMerge.erase(squadPair++);
		} else {
			++squadPair;
		}
	}
}

SquadAgent* SquadManager::getClosestSquad(Position toPosition, SquadAgent* ignoreSquad, bool ignoreNoMergeable, bool ignoreNoAssignable)
{
	// TODO int distance = std::numeric_limits<int>::max();
	int distance = 9999999;
	int newDistance;
	SquadAgent *bestSquad = 0;
	for(SquadSet::const_iterator squadIter=_squads.begin();squadIter!=_squads.end();++squadIter) {
		if (*squadIter == ignoreSquad) continue;
		if (ignoreNoMergeable && isSquadNoMergeable(*squadIter) ) continue;
		if (ignoreNoAssignable && isSquadNoEnemyAssignable(*squadIter) ) continue;
		newDistance = (*squadIter)->_center.getApproxDistance(toPosition);
		if (newDistance < distance) {
			distance = newDistance;
			bestSquad = *squadIter;
		}
	}
	return bestSquad;
}

Position SquadManager::getBestTarget()
{
// 	if (!informationManager->_firstPush) {
// 		return informationManager->_initialRallyPosition;
// 	}

	if (informationManager->_enemyBases.size() == 0) {
		return informationManager->_enemyStartPosition;
	}

	if (informationManager->_enemyBases.size() == 1) {
		return (*informationManager->_enemyBases.begin())->getPosition();
	}

	// Ratio units per possible target
	std::map<Position, int> unitsPerTarget;

	// add enemy bases
	for(std::set<BWTA::BaseLocation*>::const_iterator i=informationManager->_enemyBases.begin();i!=informationManager->_enemyBases.end();++i) {
		Position p=(*i)->getPosition();
		unitsPerTarget[p] = 0;
		// Prioritize main base
		if ( p == informationManager->_enemyStartPosition ) {
			unitsPerTarget[p] -= informationManager->_minSquadSize*2;
		}
	}
	// add our bases under attack
	for(std::map<BWTA::BaseLocation*, BWAPI::TilePosition>::const_iterator i=informationManager->_ourBases.begin();i!=informationManager->_ourBases.end();++i) {
		Position p=i->first->getPosition();
		UnitSet UnitsInRange = Broodwar->getUnitsInRadius(p, 350);
		for(UnitSet::const_iterator j=UnitsInRange.begin();j!=UnitsInRange.end();++j) {
			if ( Broodwar->self()->isEnemy((*j)->getPlayer()) && (*j)->getType().canAttack() ) {
				//Broodwar->printf("Addeed defense point");
				unitsPerTarget[(*j)->getPosition()] = informationManager->_minSquadSize*2;
				break;
			}
		}

	}

	for(SquadSet::const_iterator squadIter=_squads.begin();squadIter!=_squads.end();++squadIter) {
		unitsPerTarget[(*squadIter)->_positionTarget] += (*squadIter)->_squadUnits.size();
	}

	// Get target with less units
	std::pair<Position, int> min = *min_element(unitsPerTarget.begin(), unitsPerTarget.end(), &SquadManager::compare);


	return min.first;
}

Position SquadManager::getScoutTarget()
{
	if (informationManager->_scoutedAnEnemyBase) {
		return informationManager->_enemyStartPosition;
	} else {
		std::set<BWTA::BaseLocation*>::iterator enemyBase = informationManager->startLocationCouldContainEnemy.begin();
		//TODO sanitize check
		return (*enemyBase)->getPosition();
	}
}

bool SquadManager::compare( std::pair<Position, int> i, std::pair<Position, int> j)
{
	return i.second < j.second;
}