#include "IDABot.h"
#include "Util.h"


IDABot::IDABot()
	: m_map(*this)
	, m_bases(*this)
	, m_unitInfo(*this)
	, m_techTree(*this)
    , m_buildingPlacer(*this)
{
}

void IDABot::OnGameStart()
{
	// ----------------------------------------------------------------- 
	// Initialize all start (base) locations.
	// -----------------------------------------------------------------
	for (auto & loc : Observation()->GetGameInfo().enemy_start_locations)
	{
		m_baseLocations.push_back(loc);
	}
	m_baseLocations.push_back(Observation()->GetStartLocation());

	// -----------------------------------------------------------------
	// Initialize all info, units, etc.
	// -----------------------------------------------------------------
	setUnits();
	m_techTree.onStart();
	m_map.onStart();
	m_unitInfo.onStart();
	m_bases.onStart();
    m_buildingPlacer.onStart();

    /*
    UnitType target { sc2::UNIT_TYPEID::TERRAN_FUSIONCORE, *this };
    std::vector<UnitType> needed = { target };
    build_order.clear();

    std::cout << "Creating plan: ";

    while (!needed.empty())
    {
        UnitType target = needed.back();
	    TypeData target_data = m_techTree.getData(target);
        build_order.push_back(target);
        std::cout << target.getName();
        needed.pop_back();
        
        if (!target_data.requiredUnits.empty())
        {
            // We only need one of the required units, so we pick the first.
            // TODO: Pick in a smarter way.
            UnitType subtarget = target_data.requiredUnits.front();
            needed.push_back(subtarget);

            std::cout << ", ";
        }
    }

    std::cout << "." << std::endl;
    std::cout << "Created build plan of " << build_order.size() << " steps." << std::endl;
    */
}

void IDABot::OnStep()
{
	// -----------------------------------------------------------------
	// Update units, map info, unit info, and base info.
	// -----------------------------------------------------------------
	setUnits();
	m_map.onFrame();
	m_unitInfo.onFrame();
	m_bases.onFrame();

	// -----------------------------------------------------------------
	// Draw debug interface, and send debug interface to the Sc2 client.
	// -----------------------------------------------------------------
	Debug()->SendDebug();
    m_buildingPlacer.drawReservedTiles();
}

void IDABot::setUnits()
{
	m_allUnits.clear();
	Control()->GetObservation();
	for (auto & unit : Observation()->GetUnits())
	{
		m_allUnits.push_back(Unit(unit, *this));
	}
}

CCRace IDABot::GetPlayerRace(int player) const
{
	auto playerID = Observation()->GetPlayerID();
	for (auto & playerInfo : Observation()->GetGameInfo().player_info)
	{
		if (playerInfo.player_id == playerID)
		{
			return playerInfo.race_actual;
		}
	}

	BOT_ASSERT(false, "Failed to find the player's race!");
	return sc2::Race::Random;
}

const MapTools & IDABot::Map() const
{
	return m_map;
}

const BaseLocationManager & IDABot::Bases() const
{
	return m_bases;
}

const UnitInfoManager & IDABot::UnitInfo() const
{
	return m_unitInfo;
}

int IDABot::GetCurrentFrame() const
{
	return (int)Observation()->GetGameLoop();
}

const TechTree & IDABot::GetTechTree() const
{
    return m_techTree;
}

int IDABot::GetCurrentSupply() const
{
	return Observation()->GetFoodUsed();
}

int IDABot::GetMaxSupply() const
{
	return Observation()->GetFoodCap();
}

int IDABot::GetMinerals() const
{
	return Observation()->GetMinerals();
}

int IDABot::GetGas() const
{
	return Observation()->GetVespene();
}

Unit IDABot::GetUnit(const CCUnitID & tag) const
{
	return Unit(Observation()->GetUnit(tag), *(IDABot *)this);
}

const std::vector<Unit> & IDABot::GetAllUnits() const
{
	return m_allUnits;
}

const std::vector<Unit> & IDABot::GetMyUnits() const
{
	return UnitInfo().getUnits(Players::Self);
}

const std::vector<Unit> IDABot::GetUnits(const UnitType & type, int player) const
{
    std::vector<Unit> units;
    for (const Unit & unit : GetAllUnits()) {
        if (unit.getPlayer() == player)
        {
            if (!type.isValid() || (type.isValid() && unit.getType() == type))
            {
                units.push_back(unit);
            }
        }
    }
    return units;
}

CCPosition IDABot::GetStartLocation() const
{
	return Observation()->GetStartLocation();
}

const std::vector<CCPosition> & IDABot::GetStartLocations() const
{
	return m_baseLocations;
}

void IDABot::OnError(const std::vector<sc2::ClientError> & client_errors, const std::vector<std::string> & protocol_errors)
{
	// This is called when the sc2api (Google's API) has an error.
}

BuildingPlacer & IDABot::GetBuildingPlacer()
{
    return m_buildingPlacer;
}


void IDABot::SendChat(const std::string & message)
{
    Actions()->SendChat(message);
}

const TypeData & IDABot::Data(const UnitType & type) const
{
	return m_techTree.getData(type);
}

const TypeData & IDABot::Data(const Unit & unit) const
{
	return m_techTree.getData(unit.getType());
}

const TypeData & IDABot::Data(const CCUpgrade & type) const
{
	return m_techTree.getData(type);
}

const TypeData & IDABot::Data(const MetaType & type) const
{
	return m_techTree.getData(type);
}

/*
	API extended summer 2020
*/

void IDABot::DebugCreateUnit(UnitTypeID unit_type, const CCPosition& p, uint32_t player_id, uint32_t count) 
{
	Debug()->DebugCreateUnit(unit_type, p, player_id, count);
}

void IDABot::DebugKillUnit(const Unit unit) 
{
	Debug()->DebugKillUnit(unit.getUnitPtr());
}

void IDABot::DebugShowMap() 
{
	Debug()->DebugShowMap();
}

void IDABot::DebugFastBuild() 
{
	Debug()->DebugFastBuild();
}

void IDABot::DebugEnemyControl() 
{
	Debug()->DebugEnemyControl();
}

void IDABot::DebugIgnoreFood() 
{
	Debug()->DebugIgnoreFood();
}

void IDABot::DebugIgnoreResourceCost() 
{
	Debug()->DebugIgnoreResourceCost();
}

void IDABot::DebugGiveAllResources() 
{
	Debug()->DebugGiveAllResources();
}

void IDABot::DebugGodMode() 
{
	Debug()->DebugGodMode();
}

void IDABot::DebugIgnoreMineral() 
{
	Debug()->DebugIgnoreMineral();
}

void IDABot::DebugNoCooldowns() 
{
	Debug()->DebugIgnoreMineral();
}

void IDABot::DebugGiveAllTech()
{
	Debug()->DebugGiveAllTech();
}

void IDABot::DebugGiveAllUpgrades() 
{
	Debug()->DebugGiveAllUpgrades();
}

void IDABot::DebugSetScore(float score) 
{
	Debug()->DebugSetScore(score);
}

void IDABot::DebugEndGame(bool victory)
{
	Debug()->DebugEndGame(victory);
}

void IDABot::DebugSetEnergy(float value, const Unit unit)
{
	Debug()->DebugSetEnergy(value, unit.getUnitPtr());
}

void IDABot::DebugSetLife(float value, const Unit unit)
{
	Debug()->DebugSetLife(value, unit.getUnitPtr());
}

void IDABot::DebugSetShields(float value, const Unit unit)
{
	Debug()->DebugSetShields(value, unit.getUnitPtr());
}

// There is a bug in the latest SC2 (if using Blizzard API with game >=4.10)
// This a function to get the enemy base instead of using build location manager
// Switched over to other API where this is solved
// Leaving function incase of it breaking
const std::vector<Point2D> IDABot::GetEnemyBaseLocations()
{
	return Observation()->GetGameInfo().enemy_start_locations;
}

bool IDABot::HasCreep(Point2D p) const 
{
	return Observation()->HasCreep(p);
}

void IDABot::CameraMove(Point2DI p) 
{
	ActionsFeatureLayer()->CameraMove(p);
}

const char* IDABot::abilityForUpgrade(sc2::UpgradeID upgrade_id) const
{
	return sc2::AbilityTypeToName(Observation()->GetUpgradeData()[upgrade_id].ability_id);
}

uint32_t IDABot::UpgradeMineralCost(sc2::UpgradeID upgrade_id) const
{
	return Observation()->GetUpgradeData()[upgrade_id].mineral_cost;
}

uint32_t IDABot::UpgradeGasCost(sc2::UpgradeID upgrade_id) const
{
	return Observation()->GetUpgradeData()[upgrade_id].vespene_cost;
}

float IDABot::UpgradeResearchTime(sc2::UpgradeID upgrade_id) const
{
	return Observation()->GetUpgradeData()[upgrade_id].research_time;
}

float IDABot::RadiusEffect(sc2::EffectID effect_id) const
{
	return Observation()->GetEffectData()[effect_id].radius;
}