#include "GameState.h"
#include "InformationManager.h"

#define IDLE_TIME 400 //in frames
#define NOTHING_TIME 24*60*30 //in frames (30 minutes)

using namespace BWAPI;

// player = true  = friendly player
// player = false = enemy player

GameState::GameState()
{
}

void GameState::initFileVariables()
{
    std::string iniBuilding = LoadConfigString("high_level_search", "buildings", "RESOURCE_DEPOT");
    if (iniBuilding == "NONE") {
        _buildingTypes = NoneBuilding;
    } else if (iniBuilding == "ALL") {
        _buildingTypes = AllBuildings;
    } else {
        _buildingTypes = ResourceDepot;
    }
}

void GameState::updateGameState()
{
	PlayerSet players = Broodwar->getPlayers();
	std::vector<unitState_t>* listToSave;
	Unit* unit;
	Position targetPosition;
	_time = BWAPI::Broodwar->getFrameCount();

	for (PlayerSet::iterator it = players.begin(); it != players.end(); it++) {
		Player* player = *it;
		UnitSet units = player->getUnits();
		
		if ( player == Broodwar->self() ) listToSave = &friendlyUnits;
		else if (Broodwar->self()->isEnemy(player)) listToSave = &enemyUnits;
		else continue;
		
		listToSave->clear();

		for (UnitSet::iterator it2 = units.begin(); it2 != units.end(); it2++) {
			unit = *it2;
			// ignore buildings (except _buildingTypes)
			if ((_buildingTypes == ResourceDepot) && unit->getType().isBuilding() && !unit->getType().isResourceDepot() ) continue;
			// ignore units training
			if (!unit->isCompleted()) continue;
            // ignore spells
            if (unit->getType().isSpell()) continue;

			unitState_t unitState;
			unitState.unitTypeId = unit->getType().getID();
			unitState.numUnits = 1;
			unitState.regionId = informationManager->_regionIdMap[unit->getTilePosition().x()][unit->getTilePosition().y()];
			targetPosition = unit->getTargetPosition();
			if (targetPosition != Positions::None) {
				TilePosition tile = (TilePosition)targetPosition;
				unitState.targetRegionId = informationManager->_regionIdMap[tile.x()][tile.y()];
			} else {
				unitState.targetRegionId = 0;
			}

            if ( unit->getType().isBuilding() ) {
                //LOG("Base order: " << unit->getOrder().getName() << " id: " << unit->getOrder().getID());
                // TODO: warning, possible collision with BWAPI::Orders::Nothing
                unitState.orderId = abstractOrder::Nothing;
            } else {
			    unitState.orderId = getAbstractOrderID(unit->getOrder().getID(), unitState.regionId, unitState.targetRegionId);
            }

			addUnit(unitState, *listToSave);
		}
	}

	expectedEndFrame();
}

void GameState::addAllEnemyUnits()
{
	Unit* unit;
	Position targetPosition;
	_time = BWAPI::Broodwar->getFrameCount();

	UnitSet units = Broodwar->enemy()->getUnits();

	for (UnitSet::iterator it2 = units.begin(); it2 != units.end(); it2++) {
		unit = *it2;
		// ignore buildings (except _buildingTypes)
        if ((_buildingTypes == ResourceDepot) && unit->getType().isBuilding() && !unit->getType().isResourceDepot() ) continue;
		// ignore units training
		if (!unit->isCompleted()) continue;
		if (unit->getType().isWorker()) continue;
        // ignore spells
        if (unit->getType().isSpell()) continue;

		unitState_t unitState;
		unitState.unitTypeId = unit->getType().getID();
		unitState.numUnits = 1;
		unitState.regionId = informationManager->_regionIdMap[unit->getTilePosition().x()][unit->getTilePosition().y()];
// 		targetPosition = unit->getTargetPosition();
// 		if (targetPosition != Positions::None) {
// 			TilePosition tile = (TilePosition)targetPosition;
// 			unitState.targetRegionId = informationManager->_regionIdMap[tile.x()][tile.y()];
// 		} else {
// 			unitState.targetRegionId = 0;
// 		}
// 
//         if ( unit->getType().isResourceDepot() ) {
//             //LOG("Base order: " << unit->getOrder().getName() << " id: " << unit->getOrder().getID());
//             // TODO: warning, possible collision with BWAPI::Orders::Nothing
//             unitState.orderId = abstractOrder::Nothing;
//         } else {
// 		    unitState.orderId = getAbstractOrderID(unit->getOrder().getID(), unitState.regionId, unitState.targetRegionId);
//         }

        // simplify enemy orders
        unitState.targetRegionId = 0;
        unitState.orderId = abstractOrder::Unknown;

        if ( unit->getType().isBuilding() ) unitState.orderId = abstractOrder::Nothing;

		addUnit(unitState, enemyUnits);
	}
}

void GameState::addSelfBuildings()
{
    UnitSet units = Broodwar->self()->getUnits();
    Unit* unit;

    for (UnitSet::iterator it2 = units.begin(); it2 != units.end(); it2++) {
		unit = *it2;
		// ignore all (except _buildingTypes)
        if ( !unit->getType().isBuilding() ) continue;
        if ( _buildingTypes == ResourceDepot && !unit->getType().isResourceDepot() ) continue;

		unitState_t unitState;
		unitState.unitTypeId = unit->getType().getID();
		unitState.numUnits = 1;
		unitState.regionId = informationManager->_regionIdMap[unit->getTilePosition().x()][unit->getTilePosition().y()];
        unitState.targetRegionId = 0;
        unitState.orderId = abstractOrder::Nothing;

        addUnit(unitState, friendlyUnits);
    }


}

unsigned int GameState::addFriendlyUnit(Unit *unit)
{
		unitState_t unitState;
		unitState.unitTypeId = unit->getType().getID();
		unitState.numUnits = 1;
		unitState.regionId = informationManager->_regionIdMap[unit->getTilePosition().x()][unit->getTilePosition().y()];
// 		Position targetPosition = unit->getTargetPosition();
// 		if (targetPosition != Positions::None) {
// 			TilePosition tile = (TilePosition)targetPosition;
// 			unitState.targetRegionId = informationManager->_regionIdMap[tile.x()][tile.y()];
// 		} else {
// 			unitState.targetRegionId = 0;
// 		}
// 
//         unitState.orderId = getAbstractOrderID(unit->getOrder().getID(), unitState.regionId, unitState.targetRegionId);

        // simplify orders
        unitState.targetRegionId = 0;
        unitState.orderId = abstractOrder::Unknown;

		return addUnit(unitState, friendlyUnits);
}

void GameState::addGroup(int unitId, int numUnits, int regionId, int listID)
{
    unitState_t unitState;
    unitState.unitTypeId = unitId;
    unitState.numUnits = numUnits;
    unitState.regionId = regionId;
    unitState.targetRegionId = 0;
    unitState.orderId = abstractOrder::Unknown;

    BWAPI::UnitType unitType(unitId);
    //LOG("Adding unit group: "<<unitType.getName()<<" isAddon? "<<unitType.isAddon());
    if ( unitType.isBuilding() ) unitState.orderId = abstractOrder::Nothing;
    //if ( unitType.isResourceDepot() ) unitState.orderId = abstractOrder::Nothing;
    //if ( !unitType.isResourceDepot() && unitType.isBuilding() && !unitType.canAttack() ) return; // skip buildings
    if ( unitType.isAddon() ) return;
    if ( unitType.isSpell() ) return;

    if (listID == 1) {
        friendlyUnits.push_back(unitState);
    } else if (listID == 2) {
        enemyUnits.push_back(unitState);
    }
}

unsigned int GameState::addUnit(unitState_t unit, std::vector<unitState_t>& vectorState)
{
    // simplify unitTypeId
    if (unit.unitTypeId == UnitTypes::Terran_Siege_Tank_Siege_Mode) {
        unit.unitTypeId = UnitTypes::Terran_Siege_Tank_Tank_Mode;
    }

    // check for wrong unit
    BWAPI::UnitType unitType(unit.unitTypeId);
    if ( unitType.isSpell() ) {
        DEBUG("Trying to add a spell");
    }

	// search if unit is already in the vector
	for (unsigned int i=0; i < vectorState.size(); i++) {
		if (unit.unitTypeId == vectorState[i].unitTypeId && 
			unit.regionId == vectorState[i].regionId &&
			unit.orderId == vectorState[i].orderId &&
			unit.targetRegionId == vectorState[i].targetRegionId) {
			vectorState[i].numUnits++;
			return i;
		}
	}
	// if not, add it to the end
	vectorState.push_back(unit);
    return vectorState.size()-1;
}

// Calculate the expected end frame for each order
void GameState::expectedEndFrame()
{
	combatList.clear();
	regionsInCombat.clear();
	std::vector<unitState_t> *vectorState;
	for (int i=0; i<2; i++) {
		if (i==0) vectorState = &friendlyUnits;
		else vectorState = &enemyUnits;
		for (unsigned int j=0; j < vectorState->size(); j++) {
			vectorState->at(j).endFrame = _time + IDLE_TIME;
			if (vectorState->at(j).orderId == abstractOrder::Unknown) {
				vectorState->at(j).endFrame = _time + IDLE_TIME;
			} else if (vectorState->at(j).orderId == abstractOrder::Move) {
				vectorState->at(j).endFrame = getMoveTime(vectorState->at(j).unitTypeId, vectorState->at(j).regionId, vectorState->at(j).targetRegionId);
			} else if (vectorState->at(j).orderId == abstractOrder::Attack) {
				// keep track of the regions with units in attack state
				regionsInCombat.insert(vectorState->at(j).regionId);
            } else if (vectorState->at(j).orderId == abstractOrder::Nothing) { // it's a building
                vectorState->at(j).endFrame = _time + NOTHING_TIME;
			}
		}
	}
	// TODO some units aren't attacking in the same region!!!
    if (!regionsInCombat.empty()) {
		calculateCombatTimes(); // update combatList too
	}
}

int GameState::getMoveTime(int unitTypeId, int regionId, int targetRegionId)
{
	if (regionId == targetRegionId) return _time;

	BWAPI::UnitType unitType(unitTypeId);
    //LOG("Moving unit: " << unitType.getName());
	double speed = unitType.topSpeed(); //non-upgraded top speed in pixels per frame

	// distance in pixels
    int distance = getRegionDistance(regionId, targetRegionId);

	return _time + (int)((double)distance/speed);
}

// TODO move this to BWTA
BWAPI::Position GameState::getCenterRegionId(int regionId)
{
    // TODO use informationManager->_chokepointIdOffset
	BWTA::Region* region = informationManager->_regionFromID[regionId];
	if (region != NULL) {
		return region->getCenter();
	} else {
		BWTA::Chokepoint* cp = informationManager->_chokePointFromID[regionId];
		if (cp != NULL) {
			return cp->getCenter();
		} else {
			return BWAPI::Positions::None;
		}
	}
}

// TODO move this to BWTA
int GameState::getRegionDistance(int regId1, int regId2)
{
    if (!informationManager->_onlyRegions) {
        if (regId1 < informationManager->_chokepointIdOffset) {
            // regId1 is a region, and regId2 a chokepoint
            return informationManager->_distanceBetweenRegAndChoke[regId1][regId2];
        } else {
            // regId1 is a chokepoint, and regId2 a region
            return informationManager->_distanceBetweenRegAndChoke[regId2][regId1];
        }
    } else {
        //LOG("Distance from " << regId1 << " to " << regId2 << " = " << informationManager->_distanceBetweenRegions[regId1][regId2]);
        return informationManager->_distanceBetweenRegions[regId1][regId2];
    }

}

void GameState::getCombatTime()
{
	//for (combatList_t::iterator it=combatList.begin(); it!=combatList.end(); ++it) {
	for (std::set<int>::iterator it=regionsInCombat.begin(); it!=regionsInCombat.end(); ++it) {
		// get friendly stats
		combatStats_t friendStats;
		friendStats.airDPF = friendStats.airHP = friendStats.groundDPF = friendStats.groundHP = 0;
		//std::set<unitState_t*> friendSet = (*it).second.first;
		std::set<unitState_t*> friendSet = combatList[(*it)].first;
		for (std::set<unitState_t*>::iterator it2=friendSet.begin(); it2!=friendSet.end(); ++it2) {
			BWAPI::UnitType unitType( (*it2)->unitTypeId );
			uint8_t numUnits = (*it2)->numUnits;
			GameState::getCombatStats(friendStats, unitType, numUnits);
		}
		// get enemy stats
		combatStats_t enemyStats;
		enemyStats.airDPF = enemyStats.airHP = enemyStats.groundDPF = enemyStats.groundHP = 0;
		//std::set<unitState_t*> enemySet = (*it).second.second;
		std::set<unitState_t*> enemySet = combatList[(*it)].second;
		for (std::set<unitState_t*>::iterator it2=enemySet.begin(); it2!=enemySet.end(); ++it2) {
			BWAPI::UnitType unitType( (*it2)->unitTypeId );
			uint8_t numUnits = (*it2)->numUnits;
			GameState::getCombatStats(enemyStats, unitType, numUnits);
		}
		// calculate end combat time
		double timeToKillEnemyAir = (enemyStats.airHP>0)? (friendStats.airDPF == 0)? 99999 : enemyStats.airHP/friendStats.airDPF : 0;
		double timeToKillEnemyGround = (enemyStats.groundHP>0)? (friendStats.groundDPF == 0)? 99999 : enemyStats.groundHP/friendStats.groundDPF : 0;
		double timeToKillFriendAir = (friendStats.airHP>0)? (enemyStats.airDPF == 0 )? 99999 : friendStats.airHP/enemyStats.airDPF : 0;
		double timeToKillFriendGround = (friendStats.groundHP>0)? (enemyStats.groundDPF == 0)? 99999 : friendStats.groundHP/enemyStats.groundDPF : 0;

		double timeToKillEnemy = (std::max)(timeToKillEnemyAir,timeToKillEnemyGround);
		double timeToKillFriend = (std::max)(timeToKillFriendAir,timeToKillFriendGround);
		int combatEnd = (int)(std::min)(timeToKillEnemy,timeToKillFriend) + _time;

		// update end combat time
		for (std::set<unitState_t*>::iterator it2=friendSet.begin(); it2!=friendSet.end(); ++it2) {
			if ((*it2)->orderId == abstractOrder::Attack) (*it2)->endFrame = combatEnd;
		}
		for (std::set<unitState_t*>::iterator it2=enemySet.begin(); it2!=enemySet.end(); ++it2) {
			if ((*it2)->orderId == abstractOrder::Attack) (*it2)->endFrame = combatEnd;
		}
	}
}

void GameState::getCombatStats(combatStats_t & combatStats, BWAPI::UnitType unitType, uint8_t numUnits )
{
	if (unitType.airWeapon().damageAmount() > 0 )
		combatStats.airDPF += numUnits * ((double)unitType.airWeapon().damageAmount() / unitType.airWeapon().damageCooldown());
	if (unitType.groundWeapon().damageAmount() > 0 )
		combatStats.groundDPF += numUnits * ((double)unitType.groundWeapon().damageAmount() / unitType.groundWeapon().damageCooldown());
	// In the case of Firebats and Zealots, the damage returned by BWAPI is not right, since they have two weapons:
	if (unitType == UnitTypes::Terran_Firebat || unitType == UnitTypes::Protoss_Zealot)
		combatStats.groundDPF += numUnits * ((double)unitType.groundWeapon().damageAmount() / unitType.groundWeapon().damageCooldown());
	if (unitType.isFlyer()) // TODO we are omitting individual HP!!!
		combatStats.airHP += numUnits * ((double)(unitType.maxShields() + unitType.maxHitPoints()));
	else
		combatStats.groundHP += numUnits * ((double)(unitType.maxShields() + unitType.maxHitPoints()));
}

int GameState::getAbstractOrderID(int orderId, int currentRegion, int targetRegion)
{
 	if ( orderId == BWAPI::Orders::MoveToMinerals ||
		 orderId == BWAPI::Orders::WaitForMinerals ||
		 orderId == BWAPI::Orders::MiningMinerals ||
		 orderId == BWAPI::Orders::Harvest3 ||
		 orderId == BWAPI::Orders::Harvest4 ||
 		 orderId == BWAPI::Orders::ReturnMinerals )
		//return abstractOrder::Mineral;
		return abstractOrder::Unknown;
 	else if ( orderId == BWAPI::Orders::MoveToGas ||
			  orderId == BWAPI::Orders::Harvest1 ||
			  orderId == BWAPI::Orders::Harvest2 ||
			  orderId == BWAPI::Orders::WaitForGas ||
			  orderId == BWAPI::Orders::HarvestGas ||
 			  orderId == BWAPI::Orders::ReturnGas )
 		//return abstractOrder::Gas;
		return abstractOrder::Unknown;
	else if ( orderId == BWAPI::Orders::Move ||
			  orderId == BWAPI::Orders::Follow ||
			  orderId == BWAPI::Orders::ComputerReturn ) 
		return abstractOrder::Move;
	else if ( orderId == BWAPI::Orders::AttackUnit ||
			  orderId == BWAPI::Orders::AttackMove ||
			  orderId == BWAPI::Orders::AttackTile)
		  if (currentRegion == targetRegion) return abstractOrder::Attack;
		  else return abstractOrder::Move;
	else if ( orderId == BWAPI::Orders::Repair || 
			  orderId == BWAPI::Orders::MedicHeal1 )
			return abstractOrder::Heal;
    else if ( orderId == BWAPI::Orders::Nothing )
			return abstractOrder::Nothing;
    else {
        //LOG("Not detected action " << BWAPI::Order(orderId).getName() << " id: " << orderId);
		return abstractOrder::Unknown;
    }
}

const std::string GameState::getAbstractOrderName(BWAPI::Order order, int currentRegion, int targetRegion)
{
	int abstractId = getAbstractOrderID(order.getID(), currentRegion, targetRegion);
	return abstractOrder::name[abstractId];
}

const std::string GameState::getAbstractOrderName(int abstractId)
{
	return abstractOrder::name[abstractId];
}

std::string GameState::toString()
{
    std::stringstream tmp;
	tmp << "GameState: time " << _time << " ====================================\n";
	tmp << "Pointer \tUnit type \tNumber \tRegion \tOrder \tEnd frame \tUnit name\n";
	for (unsigned int i=0; i < friendlyUnits.size(); i++) {
        tmp << &(friendlyUnits[i]) << "\t";
		tmp << intToString(friendlyUnits[i].unitTypeId) << "\t";
		tmp << intToString(friendlyUnits[i].numUnits) << "\t";
		tmp << intToString(friendlyUnits[i].regionId) << "\t";
		tmp << getAbstractOrderName(friendlyUnits[i].orderId) << "\t";
		tmp << intToString(friendlyUnits[i].endFrame - _time) << "\t";
        tmp << BWAPI::UnitType(friendlyUnits[i].unitTypeId).getName() << "\n";
	}
	tmp << "(up)FRIENDS---------------ENEMIES(down)\n";
	for (unsigned int i=0; i < enemyUnits.size(); i++) {
        tmp << &(enemyUnits[i]) << "\t";
		tmp << intToString(enemyUnits[i].unitTypeId) << "\t";
		tmp << intToString(enemyUnits[i].numUnits) << "\t";
		tmp << intToString(enemyUnits[i].regionId) << "\t";
		tmp << getAbstractOrderName(enemyUnits[i].orderId) << "\t";
		tmp << intToString(enemyUnits[i].endFrame - _time) << "\t";
        tmp << BWAPI::UnitType(enemyUnits[i].unitTypeId).getName() << "\n";
	}
/*    if (!combatList.empty()) {
        tmp << "CombatList: =========================================================\n";
	        tmp << "Pointer \tUnit type \tNumber \tRegion \tOrder \tEnd frame\n";
        for (unsigned int i=0; i < combatList.size(); i++) {
            std::set<unitState_t*> friendSet = combatList[i].first;
	        for (std::set<unitState_t*>::iterator it2=friendSet.begin(); it2!=friendSet.end(); ++it2) {
                tmp << (*it2) << "\t";
		        tmp << BWAPI::UnitType((*it2)->unitTypeId).getName() << "\t";
		        tmp << intToString((*it2)->numUnits) << "\t";
		        tmp << intToString((*it2)->regionId) << "\t";
		        tmp << getAbstractOrderName((*it2)->orderId) << "\t";
		        tmp << intToString((*it2)->endFrame - _time) << "\n";
	        }
        }
        tmp << "(up)FRIENDS---------------ENEMIES(down)\n";
        for (unsigned int i=0; i < combatList.size(); i++) {
            std::set<unitState_t*> enemySet = combatList[i].second;
	        for (std::set<unitState_t*>::iterator it2=enemySet.begin(); it2!=enemySet.end(); ++it2) {
                tmp << (*it2) << "\t";
		        tmp << BWAPI::UnitType((*it2)->unitTypeId).getName() << "\t";
		        tmp << intToString((*it2)->numUnits) << "\t";
		        tmp << intToString((*it2)->regionId) << "\t";
		        tmp << getAbstractOrderName((*it2)->orderId) << "\t";
		        tmp << intToString((*it2)->endFrame - _time) << "\n";
	        }
        }
    }*/

    return tmp.str();
}

void GameState::execute(playerActions_t playerActions, bool player)
{
    //LOG(toString());
    //LOG("Executing actions for player: " << player);
	std::vector<unitState_t> *vectorState;
	if (player) vectorState = &friendlyUnits;
	else vectorState = &enemyUnits;

    //LOG("START move and idle time");
	for (unsigned int i=0; i < playerActions.size(); i++) {
// 		int j = playerActions[i].first;
// 		uint8_t orderId = playerActions[i].second.first;
// 		uint8_t targetRegionId = playerActions[i].second.second;
        // Unpacking playerActions_t (00000000 TTTTTTTT AAAAAAAA GGGGGGGG)
        int j = playerActions[i] & 0xFF; // preserve the 7 righmost bits
        uint8_t orderId = (playerActions[i] >> 8) & 0xFF;
        uint8_t targetRegionId = playerActions[i] >> 16;
        //LOG("Group position: " << j << " order: " << getAbstractOrderName(orderId) << " targetID: " << intToString(targetRegionId));
		vectorState->at(j).orderId = orderId;
		vectorState->at(j).targetRegionId = targetRegionId;
		if (orderId == abstractOrder::Move) {
			vectorState->at(j).endFrame = getMoveTime(vectorState->at(j).unitTypeId, vectorState->at(j).regionId, targetRegionId);
		} else if (orderId == abstractOrder::Attack) {
			// keep track of the regions with units in attack state
			regionsInCombat.insert(vectorState->at(j).regionId);
		} else { // Unknown or Idle
			vectorState->at(j).endFrame = _time + IDLE_TIME;
		}
	}
    //LOG("END move and idle time");

	if (!regionsInCombat.empty()) {
        //LOG("START calculateCombatTimes");
		calculateCombatTimes(); // update combatList too
        //LOG("END calculateCombatTimes");
	}
}

void GameState::calculateCombatTimes() {
		combatList.clear();
        // add all units to the combat list since some units can attack units that they aren't in attack state
		std::vector<unitState_t> *vectorState;
		for (int i=0; i<2; i++) {
			if (i==0) vectorState = &friendlyUnits;
			else vectorState = &enemyUnits;
			for (unsigned int j=0; j < vectorState->size(); j++) {
                unitState_t* unitState = &(vectorState->at(j));
				if (i==0) combatList[vectorState->at(j).regionId].first.insert(unitState);
				else combatList[vectorState->at(j).regionId].second.insert(unitState);
			}
		}
        //LOG("Combat List populated");
		getCombatTime();
        //LOG("Combat Time calculated");
        //LOG(toString());
}

bool GameState::canExecuteAnyAction(bool player) 
{
    std::vector<unitState_t> *vectorState;
	if (player) vectorState = &friendlyUnits;
	else vectorState = &enemyUnits;

	for (unsigned int i=0; i < vectorState->size(); i++) {
		if (vectorState->at(i).endFrame <= _time) {
			return true;
		}
	}

    return false;
}

// Move forward the game state until the first unit finish its action
void GameState::moveForward(int forwardTime)
{
    bool unlimitedTime = true;
    std::vector<unitState_t> *vectorState;
    if (forwardTime == 0) {
	    forwardTime = std::numeric_limits<int>::max();
	    // search the first action to finish
        //LOG("search the first action to finish");
	    for (int i=0; i<2; i++) {
		    if (i==0) vectorState = &friendlyUnits;
		    else vectorState = &enemyUnits;
		    for (unsigned int j=0; j < vectorState->size(); j++) {
			    forwardTime = (std::min)(forwardTime, vectorState->at(j).endFrame - _time);
		    }
	    }
    } else {
        unlimitedTime = false;
    }

	// forward time
	_time += forwardTime;

	// update finished actions
    //LOG("update finished actions");
	std::set<int> simulateCombatIn;
	for (int i=0; i<2; i++) {
		if (i==0) vectorState = &friendlyUnits;
		else vectorState = &enemyUnits;
		for (unsigned int j=0; j < vectorState->size(); j++) {
			if (vectorState->at(j).endFrame <= _time) {
				if (vectorState->at(j).orderId == abstractOrder::Move) {
					vectorState->at(j).regionId = vectorState->at(j).targetRegionId;
                    // notice that we are only updating the position and we are
                    // skiping any "merge group" operation!
				} else if (vectorState->at(j).orderId == abstractOrder::Attack) {
					// keep track of the regions with units in attack state
					simulateCombatIn.insert(vectorState->at(j).regionId);
				}
			}
		}
	}

	// simulate combat in regions
    if ( !simulateCombatIn.empty() ) {
        //LOG("simulate combat in regions");
        if (friendlyUnits.empty() || enemyUnits.empty()) {
            return;
        }
        
		for (std::set<int>::iterator it=simulateCombatIn.begin(); it!=simulateCombatIn.end(); ++it) {
			// get friendly stats
            //LOG("get friendly stats");
			combatStats_t friendStats;
			friendStats.airDPF = friendStats.airHP = friendStats.groundDPF = friendStats.groundHP = 0;
			std::set<unitState_t*> friendSetInCombatRegion = combatList[(*it)].first;
			for (std::set<unitState_t*>::iterator it2=friendSetInCombatRegion.begin(); it2!=friendSetInCombatRegion.end(); ++it2) {
				BWAPI::UnitType unitType( (*it2)->unitTypeId );
				uint8_t numUnits = (*it2)->numUnits;
				getCombatStats(friendStats, unitType, numUnits);
			}
			// get enemy stats
            //LOG("get enemy stats");
			combatStats_t enemyStats;
			enemyStats.airDPF = enemyStats.airHP = enemyStats.groundDPF = enemyStats.groundHP = 0;
			std::set<unitState_t*> enemySetInCombatRegion = combatList[(*it)].second;
			for (std::set<unitState_t*>::iterator it2=enemySetInCombatRegion.begin(); it2!=enemySetInCombatRegion.end(); ++it2) {
				BWAPI::UnitType unitType( (*it2)->unitTypeId );
				uint8_t numUnits = (*it2)->numUnits;
				getCombatStats(enemyStats, unitType, numUnits);
			}
			// calculate end combat time
            //LOG("calculate end combat time");
			double timeToKillEnemyAir = (enemyStats.airHP>0)? (friendStats.airDPF == 0)? 99999 : enemyStats.airHP/friendStats.airDPF : 0;
			double timeToKillEnemyGround = (enemyStats.groundHP>0)? (friendStats.groundDPF == 0)? 99999 : enemyStats.groundHP/friendStats.groundDPF : 0;
			double timeToKillFriendAir = (friendStats.airHP>0)? (enemyStats.airDPF == 0 )? 99999 : friendStats.airHP/enemyStats.airDPF : 0;
			double timeToKillFriendGround = (friendStats.groundHP>0)? (enemyStats.groundDPF == 0)? 99999 : friendStats.groundHP/enemyStats.groundDPF : 0;

			double timeToKillEnemy = (std::max)(timeToKillEnemyAir,timeToKillEnemyGround);
			double timeToKillFriend = (std::max)(timeToKillFriendAir,timeToKillFriendGround);
			int combatLength = (int)(std::min)(timeToKillEnemy,timeToKillFriend);

			if (combatLength == timeToKillEnemy) {
				// Remove enemy units from list
                //LOG("Remove enemy units from list");
				for (std::vector<unitState_t>::iterator it2=enemyUnits.begin(); it2!=enemyUnits.end(); ) {
					unitState_t *unitToFind = &(*it2);
					std::set<unitState_t*>::iterator unitFound = enemySetInCombatRegion.find(unitToFind);
					if( unitFound != enemySetInCombatRegion.end() ) {
                        BWAPI::UnitType unitType( (*it2).unitTypeId );
						it2 = enemyUnits.erase(it2);
					} else {
						++it2;
					}
				}
				// Calculate friendly losses
                //LOG("Calculate friendly losses");
				int overDamage = removeAirUnits(&friendlyUnits, *it, (int)enemyStats.airDPF*combatLength);
				overDamage = removeGroundUnits(&friendlyUnits, *it, ((int)enemyStats.groundDPF*combatLength)+overDamage);
			} else {
				// Remove friendly units from list
                //LOG("Remove friendly units from list");
				for (std::vector<unitState_t>::iterator it2=friendlyUnits.begin(); it2!=friendlyUnits.end(); ) {
					unitState_t *unitToFind = &(*it2);
					std::set<unitState_t*>::iterator unitFound = friendSetInCombatRegion.find(unitToFind);
					if( unitFound != friendSetInCombatRegion.end() ) {
                        BWAPI::UnitType unitType( (*it2).unitTypeId );
						it2 = friendlyUnits.erase(it2);
					} else {
						++it2;
					}
				}
				// Calculate enemy losses
                //LOG("Calculate enemy losses - removeAirUnits");
				int overDamage = removeAirUnits(&enemyUnits, *it, (int)friendStats.airDPF*combatLength);
                //LOG("Calculate enemy losses - removeGroundUnits. overDamage: " << overDamage);
				overDamage = removeGroundUnits(&enemyUnits, *it, ((int)friendStats.groundDPF*combatLength)+overDamage);
			}

            // change attack order to idle
            //LOG("change attack order to idle");
            std::vector<unitState_t> *vectorState;
	        for (int i=0; i<2; i++) {
		        if (i==0) vectorState = &friendlyUnits;
		        else vectorState = &enemyUnits;
		        for (unsigned int j=0; j < vectorState->size(); j++) {
                    unitState_t* unitState = &(vectorState->at(j));
                    if (unitState->regionId == *it && unitState->orderId == abstractOrder::Attack) {
                        unitState->orderId = abstractOrder::Idle;
                    }
		        }
	        }

            //LOG("cleaning stuff");
			// remove region from combatList
			combatList.erase(*it);

			// remove region from regionsInCombat
			regionsInCombat.erase(*it);
		}
		// after simulating the combat we may need to move forward
        //LOG("moveForward again");
        if (unlimitedTime) {
		    moveForward();
        }
	}
}


int GameState::removeAirUnits(std::vector<unitState_t> *unitsList, int regionId, int totalDamage)
{
	if (totalDamage==0) return 0;

	for (std::vector<unitState_t>::iterator it=unitsList->begin(); it!=unitsList->end(); ) {
		unitState_t *unit = &(*it);
        if (unit->regionId != regionId) {
            ++it;
            continue;
        }
		BWAPI::UnitType unitType( unit->unitTypeId );
		uint8_t numUnits = unit->numUnits;
		
		if (unitType.isFlyer()) {
			int totalHP = numUnits * (unitType.maxShields() + unitType.maxHitPoints());
			if (totalDamage >= totalHP) {
				// remove all unit
				it = unitsList->erase(it);
				totalDamage -= totalHP;
			} else {
				// remove numUnits lost
				int unitsToRemove = (int)totalDamage/(int)(unitType.maxShields() + unitType.maxHitPoints());
				unit->numUnits -= unitsToRemove;
				totalDamage -= unitsToRemove * (unitType.maxShields() + unitType.maxHitPoints());
				++it;
			}
		} else {
			++it;
		}
	}

	// return over damage (useful to use it on ground damage)
	return totalDamage;
}

int GameState::removeGroundUnits(std::vector<unitState_t> *unitsList, int regionId, int totalDamage)
{
    //LOG("Starting ground damage: "<<totalDamage);
	if (totalDamage==0) return 0;

	for (std::vector<unitState_t>::iterator it=unitsList->begin(); it!=unitsList->end(); ) {
		unitState_t *unit = &(*it);
        if (unit->regionId != regionId) {
            ++it;
            continue;
        }
		BWAPI::UnitType unitType( unit->unitTypeId );
		uint8_t numUnits = unit->numUnits;
		
		if (!unitType.isFlyer()) {
			int totalHP = numUnits * (unitType.maxShields() + unitType.maxHitPoints());
            //LOG("Damaging "<<intToString(numUnits)<<" "<<unitType.getName()<<" damage: "<<totalDamage<<" health: "<<totalHP);
			if (totalDamage >= totalHP) {
				// remove all unit
				it = unitsList->erase(it);
				totalDamage -= totalHP;
			} else {
				// remove numUnits lost
				int unitsToRemove = (int)totalDamage/(int)(unitType.maxShields() + unitType.maxHitPoints());
				unit->numUnits -= unitsToRemove;
				totalDamage -= unitsToRemove * (unitType.maxShields() + unitType.maxHitPoints());
				++it;
			}
		} else {
			++it;
		}
	}

	// return over damage (useful to use it on ground damage)
	return totalDamage;
}

void GameState::mergeGroups() {
    for (std::vector<unitState_t>::iterator it = friendlyUnits.begin(); it != friendlyUnits.end(); ++it) {
        for (std::vector<unitState_t>::iterator it2 = friendlyUnits.begin(); it2 != friendlyUnits.end();) {
            if ( it != it2 &&
                 (*it).unitTypeId == (*it2).unitTypeId &&
                 (*it).regionId == (*it2).regionId ) {
                (*it).numUnits += (*it2).numUnits;
                it2 = friendlyUnits.erase(it2);
            } else {
                ++it2;
            }
        }
    }
}

int GameState::winner() {
    int winner = -1;
	if (friendlyUnits.empty()) {
		winner = 0;
	} else if (enemyUnits.empty()) {
		winner = 1;
	}
	return winner;
}

bool GameState::gameover() {
    if ( friendlyUnits.empty() || enemyUnits.empty() ) {
        return true;
    }
    // only buildings is also "gameover" (nothing to do)
    if (hasOnlyBuildings(friendlyUnits) && hasOnlyBuildings(enemyUnits)) {
        return true;
	}

    return false;
}

bool GameState::hasOnlyBuildings(std::vector<unitState_t> unitsVector) {
    for (unsigned int i=0; i < unitsVector.size(); i++) {
        BWAPI::UnitType unitType(unitsVector[i].unitTypeId);
        if (!unitType.isBuilding()) return false;
	}
    return true;
}

GameState GameState::cloneIssue(playerActions_t playerActions, bool player) {
	GameState nextGameState = *this;
    //LOG("execute");
	nextGameState.execute(playerActions, player);
    //LOG("moveForward");
	nextGameState.moveForward();
    //LOG("end cloneIssue");
	return nextGameState;
}

void GameState::resetFriendlyActions()
{
    for (unsigned int i=0; i < friendlyUnits.size(); i++) {
        // skip buildings
        BWAPI::UnitType unitType(friendlyUnits[i].unitTypeId);
        if (unitType.isBuilding()) continue;

        friendlyUnits[i].endFrame = _time;
	}
}