/*! \file sc2_client.h
    \brief The base class for Agent and ReplayObserver.

The Client and ClientEvents provides most of the functionality a user would be interested in for examining
game state and scripting bots. A user should prefer to inherit from either Agent or ReplayObserver, those classes
both inherit from Client.
*/

#pragma once

#include <string>
#include <vector>
#include <cassert>

#include "sc2api/sc2_typeenums.h"

namespace sc2 {

class ControlImp;
class Unit;
class ObservationInterface;
class QueryInterface;
class DebugInterface;
class ControlInterface;
class ControlImp;

/*! Errors that the api can encounter, if the OnError event in ClientEvents is overwritten it will contain a list of errors encountered. */
enum class ClientError {
    ErrorSC2,
    InvalidAbilityRemap, /*! An ability was improperly mapped to an ability id that doesn't exist. */
    InvalidResponse,     /*! The response does not contain a field that was expected. */
    NoAbilitiesForTag,   /*! The unit does not have any abilities. */
    ResponseNotConsumed, /*! A request was made without consuming the response from the previous request, that puts this library in an illegal state. */
    ResponseMismatch,    /*! The response received from SC2 does not match the request. */
    ConnectionClosed,    /*! The websocket connection has prematurely closed, this could mean starcraft crashed or a websocket timeout has occurred. */
    SC2UnknownStatus,
    SC2AppFailure,       /*! SC2 has either crashed or been forcibly terminated by this library because it was not responding to requests. */
    SC2ProtocolError,    /*! The response from SC2 contains errors, most likely meaning the API was not used in a correct way. */
    SC2ProtocolTimeout,  /*! A request was made and a response was not received in the amount of time given by the timeout. */
    WrongGameVersion,    /*! A replay was attempted to be loaded in the wrong game version. */
};

//! A set of common events a user can override in their derived bot or replay observer class.
class ClientEvents {
public:
    virtual ~ClientEvents() {}

    //! Called when a game is started after a load. Fast restarting will not call this.
    virtual void OnGameFullStart() {}

    //! Called when a game is started or restarted.
    virtual void OnGameStart() {}

    //! Called when a game has ended.
    virtual void OnGameEnd() {}

    //! In non realtime games this function gets called after each step as indicated by step size.
    //! In realtime this function gets called as often as possible after request/responses are received from the game gathering observation state.
    virtual void OnStep() {}

    //! Called whenever one of the player's units has been destroyed.
    //!< \param unit The destroyed unit.
    virtual void OnUnitDestroyed(const Unit*) {}

    //!  Called when a neutral unit is created. For example, mineral fields observed for the first time
    //!< \param unit The observed unit.
    virtual void OnNeutralUnitCreated(const Unit*) {}

    //! Called when a Unit has been created by the player.
    //!< \param unit The created unit.
    virtual void OnUnitCreated(const Unit*) {}

    //! Called when a unit becomes idle, this will only occur as an event so will only be called when the unit becomes
    //! idle and not a second time. Being idle is defined by having orders in the previous step and not currently having
    //! orders or if it did not exist in the previous step and now does, a unit being created, for instance, will call both
    //! OnUnitCreated and OnUnitIdle if it does not have a rally set.
    //!< \param unit The idle unit.
    virtual void OnUnitIdle(const Unit*) {}

    //! Called when an upgrade is finished, warp gate, ground weapons, baneling speed, etc.
    //!< \param upgrade The completed upgrade.
    virtual void OnUpgradeCompleted(UpgradeID) {}

    //! Called when the unit in the previous step had a build progress less than 1.0 but is greater than or equal to 1.0 in
    // !the current step.
    //!< \param unit The constructed unit.
    virtual void OnBuildingConstructionComplete(const Unit*) {}

    //! Called when the unit in the current observation has lower health or shields than in the previous observation.
    //!< \param unit The damaged unit.
    //!< \param health The change in health (damage is positive)
    //!< \param shields The change in shields (damage is positive)
    virtual void OnUnitDamaged(const Unit*, float /*health*/, float /*shields*/) {}

    //! Called when a nydus is placed.
    virtual void OnNydusDetected() {}

    //! Called when a nuclear launch is detected.
    virtual void OnNuclearLaunchDetected() {}

    //! Called when an enemy unit enters vision from out of fog of war.
    //!< \param unit The unit entering vision.
    virtual void OnUnitEnterVision(const Unit*) {}

    //! Called for various errors the library can encounter. See ClientError enum for possible errors.
    virtual void OnError(const std::vector<ClientError>& /*client_errors*/, const std::vector<std::string>& /*protocol_errors*/ = {}) {}
};

//! The base class for Agent and ReplayObserver.
class Client : public ClientEvents {
public:
    Client();
    virtual ~Client();

    //! The ObservationInterface is used to query game state.
    const ObservationInterface* Observation() const;

    //! The UnitQuery interface is used to issue commands to units.
    QueryInterface* Query();

    //! The DebugInterface allows a derived class to print text, draw primitive shapes and spawn/destroy units.
    DebugInterface* Debug();

    //! The ControlInterface is only meant to be used by the coordinator as it provides functionality for connecting
    //! to Starcraft2, setting up a websocket connection and issuing blocking commands via SC2's protocol.
    ControlInterface* Control();
    const ControlInterface* Control() const;

    void Reset();

private:
    //! Pointer to the control interface.
    ControlImp* control_imp_;
};

//! Helper function used to discover whether a unit is carrying minerals or not. You could use this function in GetUnits
//! to get all units carrying minerals: Units units = GetUnits(Alliance::Self, IsCarryingMinerals);
//!< \param unit The unit.
//!< \param observation Not needed for this function, only exists for Filter parameters, leave nullptr (its default).
//!< \return Returns true if the unit is carrying minerals, false otherwise.
bool IsCarryingMinerals(const Unit& unit);

//! Helper function used to discover whether a unit is carrying vespene or not. You could use this function in GetUnits
//! to get all units carrying vespene: Units units = GetUnits(Alliance::Self, IsCarryingVespene);
//!< \param unit The unit.
//!< \param observation Not needed for this function, only exists for Filter parameters, leave nullptr (its default).
//!< \return Returns true if the unit is carrying vespene, false otherwise.
bool IsCarryingVespene(const Unit& unit);

struct IsVisible {
    bool operator()(const Unit& unit);
};

}
