/// The game manages all game systems and runs the game loop
///
/// The systems managed by the game are the modules, resource handlers and the event system
/// The game loop is started by calling startGame() and ended by calling endGame()
/// When you start the game it will first initialize the modules, then load the first scene and then start updating all the modules.
/// Modules can get access to other systems when they are initialized.
/// The game can also be paused and the current scene can be both set and retrieved
class Game
{
public:
    /// Constructor
    Game();

    /// Destructor
    ///
    /// Frees all modules, resource handlers and the event system
    ~Game();

    /// Adds a module to the game
    ///
    /// Adds a module of the given type to the game
    /// @tparam ModuleType The type of module to add
    /// @tparam ConstructionArgs [deducible] The arguments necessary to construct the module
    /// @param a_arguments The arguments used to construct the module, can be left empty
    /// @returns A reference to the added module
    /// @note You do not need to specify the types of construction arguments in the template list, you can simple write addModule<ExampleModule>(arg1, arg2, arg3)
    template <typename ModuleType, typename... ConstructionArgs>
    ModuleType& addModule(ConstructionArgs&&... a_arguments);

    /// Returns the module of the given type
    ///
    /// @tparam ModuleType The type of module to get
    /// @returns A reference to the requested module
    /// @warning The game must contain the module of the given type
    /// @see hasModule()
    template <typename ModuleType>
    ModuleType& getModule();

    /// Returns the module of the given type
    ///
    /// @tparam ModuleType The type of module to get
    /// @returns A const reference to the requested module
    /// @warning The game must contain the module of the given type
    /// @see hasModule()
    template <typename ModuleType>
    const ModuleType& getModule()const;

    /// Returns true if the game has the module of the given type
    /// @tparam ModuleType The type of module to check
    /// @returns True if the game has the requested module
    /// @see addModule()
    template <typename ModuleType>
    bool hasModule()const;

    /// Adds a ResourceManager of the given type for the given type of resource using the given construction arguments
    /// 
    /// @tparam ResourceType The type of resource the handler will handle
    /// @tparam ResourceHandlerType The type of resource handler to add
    /// @tparam ConstructionArgs [deducible] The arguments used to call the constructor of the resource handler
    /// @param a_arguments The arguments of the constructor of the resource handler
    /// @returns A reference to the added resource handler
    /// @see getHandlerFor() hasHandlerFor()
    /// @warning Any given resource type can only have one handler at once
    template <typename ResourceType, typename ResourceHandlerType, typename... ConstructionArgs>
    ResourceHandlerType& addResourceHandler(ConstructionArgs&&... a_arguments);

    /// Gets a reference to the current resource handler for the given type of resource
    /// 
    /// @tparam ResourceType The type of resource the requested handler is for
    /// @returns A reference to the current resource handler for the given type of resource
    /// @warning The game MUST contain a handler for the given type of resource
    /// @see hasHandlerFor() addResourceHandler()
    template <typename ResourceType>
    IResourceHandler<ResourceType>& getHandlerFor();

    /// Checks if the game contains a resource handler for the given type of resource
    /// 
    /// @tparam ResourceType The type of resource the requested handler is for
    /// @returns True if the game contains a resource handler for the given type of resource
    /// @see addResourceHandler()
    template <typename ResourceType>
    bool hasHandlerFor()const;

    /// Registers the given module to the game loop
    ///
    /// Will make the game call the module's update method every update
    /// @param a_module The module that will be registered to the game loop
    /// @param a_priority The priority of the module within the game loop - this determines the order of systems to update
    /// @attention The given module must have a public method with the following signature: void update(float); the float being the elapsed time since the last update
    /// @warning A module can only be registered to the game loop once
    /// @see isInGameLoop()
    void registerToGameLoop(IModule& a_module, const GameLoopPriority& a_priority, bool a_updateWhilePaused);

    /// Checks if the given module is currently registered to the game loop
    ///
    /// @param a_module The module to test
    /// @returns True if the given module is currently registered to the game loop
    /// @see registerToGameLoop()
    /// @note This is a slow function and is meant mainly for debugging purposes
    bool isInGameLoop(const IModule& a_module)const;

    /// Starts the game loop
    ///
    /// Will initialize all modules, load the first scene and start updating until the game ends
    void startGameLoop();

    /// Ends the game loop
    ///
    /// Will make the game loop end as soon as the current update finishes
    void endgameLoop();

    /// Gets a reference to the event system
    /// 
    /// @returns A reference to the event system
    inline EventSystem& getEventSystem();

    /// Gets a const-reference to the event system
    /// 
    /// @returns A const-reference to the event system
    inline const EventSystem& getEventSystem()const;

    /// @brief Checks if the game has a valid current scene
    /// @return True if the game has a valid current scene
    bool hasScene()const;

    /// Gets a reference to the current scene
    ///  
    /// @returns A reference to the current scene
    Scene& getCurrentScene();

    /// Gets a const-reference to the current scene
    ///  
    /// @returns A const-reference to the current scene
    const Scene& getCurrentScene()const;

    /// Switches to the given scene
    ///
    /// Will switch to the given scene before the next update.
    /// A SceneSwitchEvent will be emitted through the EventSystem when the switching occurs.
    /// @param a_newScene A reference to the scene to switch to
    /// @attention General scene ownership and loading is not managed by the game class, it only holds a reference to the current scene
    /// @see getCurrentScene()
    void switchToScene(const UniquePtrReference<Scene>& a_newScene);

    /// Checks if the game is currently paused
    /// 
    /// @returns True if the game is currently paused
    /// @see pauseGame() unpauseGame()
    inline bool isPaused()const;

    /// Pauses the game
    ///
    /// Emits a GamePauseEvent through the EventSystem owned by this game
    /// @note This does nothing if the game is already paused
    /// @see isPaused()
    void pauseGame();

    /// Unpauses the game
    /// 
    /// Emits an GameUnpauseEvent through the EventSystem owned by this game
    /// @note This does nothing if the game isn't paused
    /// @see isPaused()
    void unpauseGame();

}