#include "ActionGenerator.h"
#include "InformationManager.h"
#include <cstdlib>

using namespace BWAPI;

ActionGenerator::ActionGenerator() {}

ActionGenerator::ActionGenerator(GameState gs, bool player)
{
	_gs = gs;
	_size = 1;
	_player = player;

	// set myUnitsList and enemyUnitList depends on the player
	if (_player) {
		_myUnitsList = _gs.friendlyUnits;
		_enemyUnitsList = _gs.enemyUnits;
	} else {
		_myUnitsList = _gs.enemyUnits;
		_enemyUnitsList = _gs.friendlyUnits;
	}
	
	//cleanActions(); // TODO temporal for debuging

	// Generate all possible choices
	_choices.clear();
	for (unsigned int i=0; i < _myUnitsList.size(); i++) {
		if (_myUnitsList[i].endFrame <= _gs._time) {
			GameState::unitState_t unit = _myUnitsList[i];
			std::vector<action_t> actions = getUnitActions(unit);
			_choices.push_back(std::make_pair(i, actions));
			_size *= actions.size();
		}
	}
	if (_choices.size()>0) {
		_moreActions = true;
		_choiceSizes.resize(_choices.size());
		_currentChoice.resize(_choices.size(),0);
		for (unsigned int i=0; i < _choices.size(); i++) {
			_choiceSizes[i] = _choices[i].second.size();
		}
	} else {
		_moreActions = false;
	}
}

void ActionGenerator::cleanActions()
{
	for (unsigned int i=0; i < _myUnitsList.size(); i++) {
		_myUnitsList[i].endFrame = -1;
		_myUnitsList[i].orderId = abstractOrder::Unknown;;
	}
}

std::vector<action_t> ActionGenerator::getUnitActions(GameState::unitState_t unit)
{
	std::vector<action_t> possibleActions;
	
    BWAPI::UnitType unitType(unit.unitTypeId);

    if (unitType.isBuilding()) {
        possibleActions.push_back(std::make_pair(abstractOrder::Idle,unit.regionId));
        return possibleActions;
    }

	// Move
	// --------------------------------
	// get neighbors from a region or chokepoint
    if (unitType.canMove()) {
	    BWTA::Region* region = informationManager->_regionFromID[unit.regionId];
	    if (region != NULL) {
		    const std::set<BWTA::Chokepoint*> chokePoints = region->getChokepoints();
            if (!informationManager->_onlyRegions) {
		        for(std::set<BWTA::Chokepoint*>::const_iterator c=chokePoints.begin();c!=chokePoints.end();c++) {
			        possibleActions.push_back(std::make_pair(abstractOrder::Move,informationManager->_chokePointID[*c]));
		        }
            } else {
                for(std::set<BWTA::Chokepoint*>::const_iterator c=chokePoints.begin();c!=chokePoints.end();c++) {
                    const std::pair<BWTA::Region*,BWTA::Region*> regions = (*c)->getRegions();
                    if (informationManager->_regionID[regions.first] == unit.regionId) {
			            possibleActions.push_back(std::make_pair(abstractOrder::Move,informationManager->_regionID[regions.second]));
                    } else {
                        possibleActions.push_back(std::make_pair(abstractOrder::Move,informationManager->_regionID[regions.first]));
                    }
		        }
            }
	    } else {
		    BWTA::Chokepoint* cp = informationManager->_chokePointFromID[unit.regionId];
		    if (cp != NULL) {
			    const std::pair<BWTA::Region*,BWTA::Region*> regions = cp->getRegions();
			    possibleActions.push_back(std::make_pair(abstractOrder::Move,informationManager->_regionID[regions.first]));
			    possibleActions.push_back(std::make_pair(abstractOrder::Move,informationManager->_regionID[regions.second]));
		    }
	    }
    }

	// Attack
	// --------------------------------
	// only if there are enemies in the region
    if (unitType.canAttack()) {
	    for (unsigned int i=0; i < _enemyUnitsList.size(); i++) {
		    if (_enemyUnitsList[i].regionId == unit.regionId) {
			    possibleActions.push_back(std::make_pair(abstractOrder::Attack, unit.regionId));
			    break;
		    }
	    }
    }

    // Nothing
    // --------------------------------
    possibleActions.push_back(std::make_pair(abstractOrder::Idle,unit.regionId));

	return possibleActions;
}

void ActionGenerator::incrementCurrentChoice(unsigned int startPosition) {
    for(unsigned int i = 0;i<startPosition;i++) _currentChoice[i] = 0;
    _currentChoice[startPosition]++;
    if (_currentChoice[startPosition] >= _choiceSizes[startPosition]) {
        if (startPosition < _currentChoice.size()-1) {
            incrementCurrentChoice(startPosition+1);
        } else {
            _moreActions = false;
        }
    }
}

playerActions_t ActionGenerator::getNextAction() {
	playerActions_t playerActions;
	if(_moreActions) {
        int i = _choices.size();
        playerActions.reserve(i);
        if (i == 0) {
            DEBUG("This should not have happened...");
        }
        while(i>0) {
            i--;
			choice_t unitChoices = _choices[i];
            int choice = _currentChoice[i];
            // add unit and action to perform
			//playerActions.push_back(std::make_pair(unitChoices.first, unitChoices.second[choice]));
            // Packing as 00000000 TTTTTTTT AAAAAAAA GGGGGGGG
            //LOG("Coding Next");
            playerActions.push_back( (unitChoices.second[choice].second << 16) | 
                                     (unitChoices.second[choice].first << 8) | unitChoices.first );
        }
        incrementCurrentChoice(i);
    }
	if (!playerActions.empty()) {
		_lastAction = playerActions;
	}
	return playerActions;
}

playerActions_t ActionGenerator::getRandomAction() {
    int i = _choices.size();
    playerActions_t playerActions(i);
    while(i>0) {
        i--;
		choice_t unitChoices = _choices[i];
        // get random choice
        int choice = std::rand() % (unitChoices.second.size()-1);
        // add unit and action to perform
		//playerActions[i] = std::make_pair(unitChoices.first, unitChoices.second[choice]);
        // Packing as 00000000 TTTTTTTT AAAAAAAA GGGGGGGG
        //LOG("Coding Random");
        playerActions[i] = (unitChoices.second[choice].second << 16) | 
                           (unitChoices.second[choice].first << 8) | unitChoices.first;
    }
	if (!playerActions.empty()) {
		_lastAction = playerActions;
	}
	return playerActions;
}

std::string ActionGenerator::toString(playerActions_t playerActions)
{
	std::string tmp = "";
    for (unsigned int i=0; i < playerActions.size(); i++) {
//         GameState::unitState_t unit = _myUnitsList[playerActions[i].first];
// 		tmp += BWAPI::UnitType(unit.unitTypeId).getName() + " (" + intToString(unit.regionId) + "): ";
// 		tmp += _gs.getAbstractOrderName(playerActions[i].second.first) + " (" + intToString(playerActions[i].second.second) + ")\n";
        // 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;
        GameState::unitState_t unit = _myUnitsList[j];
		tmp += BWAPI::UnitType(unit.unitTypeId).getName() + " (" + intToString(unit.regionId) + "): ";
		tmp += _gs.getAbstractOrderName(orderId) + " (" + intToString(targetRegionId) + ")\n";
		
    }
	return tmp;
}

std::string ActionGenerator::toString()
{
    std::ostringstream tmp;
	//std::string tmp = "Posible Actions:\n";
	tmp << "Posible Actions:\n";
	for (unsigned int i=0; i < _choices.size(); i++) {
		GameState::unitState_t unit = _myUnitsList[_choices[i].first];
		tmp << BWAPI::UnitType(unit.unitTypeId).getName() << " (";
		tmp << intToString(unit.regionId) << ")\n";

		std::vector<action_t> actionsList = _choices[i].second;
		for (unsigned int j=0; j < actionsList.size(); j++) {
			tmp << "  - " << _gs.getAbstractOrderName(actionsList[j].first) << " (" << intToString(actionsList[j].second) << ")\n";
		}
	}
	tmp << "Number of children: " << _size << "\n";

	return tmp.str();
}

double ActionGenerator::getHighLevelFriendlyActions()
{
    return getHighLevelActions(_gs.friendlyUnits);
}

double ActionGenerator::getHighLevelEnemyActions()
{
    return getHighLevelActions(_gs.enemyUnits);
}

double ActionGenerator::getHighLevelActions(std::vector<GameState::unitState_t> unitList)
{
    double size = 1;
    for (unsigned int i=0; i < unitList.size(); i++) {
	    GameState::unitState_t unit = unitList[i];
	    std::vector<action_t> actions = getUnitActions(unit);
	    size *= actions.size();
    }
    return size;
}

double ActionGenerator::getLowLevelFriendlyActions()
{
    return getLowLevelActions(Broodwar->self()->getUnits());
}

double ActionGenerator::getLowLevelEnemyActions()
{
    return getLowLevelActions(Broodwar->enemy()->getUnits());
}

double ActionGenerator::getLowLevelActions(UnitSet units)
{
    double size = 1;
	for (UnitSet::iterator it = units.begin(); it != units.end(); it++) {
        int unitActions = 1;
        Unit* unit = *it;
		
        // building case
        if (unit->getType().isBuilding()) {
            // train
            if (unit->getType().canProduce() && !unit->isTraining()) {
                unitActions++;
            } 

            if (unit->isTraining())
                unitActions++; // cancel order

        // worker case
        } else if (unit->getType().isWorker()) {
            UnitSet unitsInSeekRange = Broodwar->getUnitsInRadius(unit->getPosition(), unit->getType().seekRange());
            // move
            unitActions += 8; // move directions
            unitActions++; // cancel order
            unitActions++; // halt

            // attack on units on range
            for (UnitSet::iterator it2 = unitsInSeekRange.begin(); it2 != unitsInSeekRange.end(); it2++) {
                if (unit->isInWeaponRange(*it2)) unitActions++;
            }

            // harvest
            for (UnitSet::iterator it2 = unitsInSeekRange.begin(); it2 != unitsInSeekRange.end(); it2++) {
                if ((*it2)->getType().isResourceContainer()) unitActions++;
            }

            // repair
            for (UnitSet::iterator it2 = unitsInSeekRange.begin(); it2 != unitsInSeekRange.end(); it2++) {
                if ((*it2)->getType().isMechanical()) unitActions++;
            }

            // build
            unitActions += 5; // considering only the 5 basic Terran buildings

            unitActions++; // cancel order
        // combat unit case
        } else {
            UnitSet unitsInSeekRange = Broodwar->getUnitsInRadius(unit->getPosition(), unit->getType().seekRange());
            if (unit->getType().canMove()) {
                unitActions += 8; // move directions
                unitActions++; // cancel order
                unitActions++; // halt
                unitActions += 8; // patrol

            }
            // abilities on units on range
            if (!unit->getType().abilities().empty()) {
                std::set<TechType> abilities = unit->getType().abilities();
                for (std::set<TechType>::iterator it2 = abilities.begin(); it2 != abilities.end(); it2++) {
                    // some abilities affect target units
                    if ((*it2).targetsUnit() || (*it2).targetsPosition()) {
                        unitActions += (unit->getType().abilities().size()*unitsInSeekRange.size());
                    // other abilites affect only yourself
                    } else {
                        unitActions++;
                    }
                }
            }

            if (unit->getType().canAttack()) {
                // attack on units on range
                for (UnitSet::iterator it2 = unitsInSeekRange.begin(); it2 != unitsInSeekRange.end(); it2++) {
                    if (unit->isInWeaponRange(*it2)) unitActions++;
                }
            }
        }

        // update total actions
        size *= unitActions;
    }
    return size;
}

double ActionGenerator::getSparcraftFriendlyActions()
{
    return getSparcraftActions(Broodwar->self()->getUnits());
}
double ActionGenerator::getSparcraftEnemyActions()
{
    return getSparcraftActions(Broodwar->enemy()->getUnits());
}

double ActionGenerator::getSparcraftActions(UnitSet units)
{
    double size = 1;
	for (UnitSet::iterator it = units.begin(); it != units.end(); it++) {
        int unitActions = 1; // skip
        Unit* unit = *it;

        // skip non combat units
        if (unit->getType().canAttack()) {
            UnitSet unitsInSeekRange = Broodwar->getUnitsInRadius(unit->getPosition(), unit->getType().seekRange());
            // attack on units on range
            for (UnitSet::iterator it2 = unitsInSeekRange.begin(); it2 != unitsInSeekRange.end(); it2++) {
                if (unit->isInWeaponRange(*it2)) unitActions++;
            }
        }

        if (unit->getType().canMove()) {
            unitActions += 4; // move directions
        }

        if (unit->getType() == UnitTypes::Terran_Medic) {
            UnitSet unitsInSeekRange = Broodwar->getUnitsInRadius(unit->getPosition(), unit->getType().seekRange());
            // attack on units on range
            for (UnitSet::iterator it2 = unitsInSeekRange.begin(); it2 != unitsInSeekRange.end(); it2++) {
                if ((*it2)->getType().isOrganic()) unitActions++;
            }
        }

        size *= unitActions;
    }
    return size;

}