//
//  Game.h
//  Meteor
//
//  Created by Cesar Parent on 17/07/2015.
//  Copyright (c) 2015 Cesar Parent. All rights reserved.
//

#ifndef __Meteor__Game__
#define __Meteor__Game__


#include <cstdio>
#include <memory>
#include <functional>
#include "../defines.h"
#include "../Geometry.h"
#include "../Graphics.h"
#include "../Graphics/SDLRenderer.h"

NS_BEGIN

class Transition;

/*!
 * @ingroup         Notifications
 * @brief           Notification posted when the game is about to present the first scene.
 */
extern const char* kGameStartedNotification;

/*!
 * @ingroup         Notifications
 * @brief           Notification posted when the game is about to quit.
 */
extern const char* kGameWillQuitNotification;

/*!
 * @ingroup         Notifications
 * @brief           Notification posted when the game will switch to full screen mode.
 */
extern const char* kGameWillEnterFullScreenNotification;

/*!
 * @ingroup         Notifications
 * @brief           Notification posted when the game will switch to windowed mode
 */
extern const char* kGameWillExitFullScreenNotification;

class Scene;

/*!
 * @brief           A class that manages a Meteor game.
 * @ingroup         Management
 * @details         Game is a singleton: there can only be one instance of it in a programme, and
 *                  it stays alive until the programme quits.
 *
 *                  <h2>Initializing The Game</h2>
 *
 *                  The game is instantiated the first time the shared instance is requested with
 *                  Game::instance(). The requires a file called `settings.plist` to be present
 *                  at the root of the Resources folder. This file should contain the following
 *                  keys:
 *                  @table_start
 *                  @table_row{width, The width of the game renderer in pixels.}
 *                  @table_row{height, The height of the game renderer in pixels.}
 *                  @table_row{scale, The scaling factor between the renderer and window size.}
 *                  @table_row{title, The title of the game's window}
 *                  @table_end
 *                  
 *                  If the configuration file cannot be found, an error will be thrown.
 *                  
 *                  <h2>Running The Game</h2>
 *
 *                  The game can be started by calling startWithScene(ARC<Scene>). This will start
 *                  running the given scene and present it to the player. Changing the currently
 *                  presented game is done by calling presentScene(ARC<Scene>). Once the previous
 *                  scene has finished presenting the current frame, the game will swap it for the
 *                  new one.
 *
 *                  If presentScene(ARC<Scene>, Transition) is called, the game will swap scenes
 *                  over the duration of the transition instead, and the previous scene will be
 *                  released only once the transition has finished running.
 *
 *                  By default, a Game instance is allowed to run in full screen mode when
 *                  toggleFullScreen() is called. This can be changed by calling
 *                  setAllowFullScreen(bool).
 */
class Game {
public:
    
    // MARK: Singleton Management
    
    /**
     * @name        Getting The Shared Game Instance
     * @{
     */
    
    /*!
     * @brief       Returns the shared Game instance.
     * @details     There can only be one insance of Game in memory at any one point during
     *              execution. Game does not expose a way to create instance, you should always
     *              access the shared instance instead.
     * @brief       Reference to the shared Game instance.
     */
    static Game&
    instance();
    
    /**
     * @}
     * @name        Getting The Game's Renderer
     * @{
     */
    
    /*!
     * @brief       Returns the renderer used by the game.
     * @return      An object that implements <code>RendererProtocol</code>
     */
    RendererProtocol&
    renderer() { return *_renderer.raw(); }
    
    /**
     * @}
     */
    // MARK: -
    // MARK: Scene management
    /**
     * @name        Changing The Game's State
     * @{
     */
    
    void
    quit() { _quit = true; }
    
    /*!
     * @brief       Starts the game and presents a scene to the player.
     * @param       scene       The first scene to present.
     */
    void
    startWithScene(ARC<Scene> scene);
    
    /*!
     * @brief       Presents a scene.
     * @details     The new scene immediately replaces the current scene, if one exists.
     * @param       scene       The scene to present.
     */
    void
    presentScene(ARC<Scene> scene);
    
    /*!
     * @brief       Transitions from the current scene to a new scene.
     * @details     The new scene immediately replaces the current scene, if one exists.
     * @param       scene       The scene to present.
     * @param       transition  A transition used to animate between the two scenes.
     */
    void
    presentScene(ARC<Scene> scene, ARC<Transition> transition);
    
    /**
     * @}
     */
    
    // MARK: -
    // MARK: Getters
    
    /**
     * @name        Inspecting The Game's Properties
     * @{
     */
    
    /*!
     * @brief       Returns the game's view dimensions.
     * @note        The returned dimensions can be different from the actual window's dimensions
     *              if the Game's was set to run at a scale different than `1`, or if the game
     *              is running in full screen mode.
     * @return      The game's view dimensions
     */
    size2
    viewSize() { return _size; }
    
    /*!
     * @brief       Returns the game's application identifier (a short string set in the
     *              `settings.plist` file used to identify the game's preferences directory).
     * @return      The game's identifier.
     */
    std::string
    applicationIdentifier() const { return _application; }
    
    /*!
     * @brief       Returns the game's organisation identifier (a short string set in the
     *              `settings.plist` file used to identify the game publisher's preferences directory).
     * @return      The game's identifier.
     */
    std::string
    organisationIdentifier() const { return _organisation; }
    
    /*!
     * @brief       Returns wether the game is allowed to run in full screen mode.
     * @return      `true` if the game is allowed to run in full screen mode, `false` otherwise.
     */
    bool
    allowFullScreen() { return _allowFullScreen; }
    
    /*!
     * @brief       Returns wether the game is currently running in full screen mode.
     * @return      `true` if the game is running in full screen mode, `false` otherwise.
     */
    bool
    fullScreen() { return _fullScreen; }
    
    /**
     * @}
     */
    
    // MARK: -
    // MARK: Setters
    
    /**
     * @name        Changing The Game's Properties
     * @{
     */
    
    /*!
     * @brief       Sets wether the game is allowed to run in full screen mode.
     * @param       allow   Wether the game is allowed to run in full screen mode.
     * @see         allowFullScreen()
     */
    void
    setAllowFullScreen(bool allow) { _allowFullScreen = allow; }
    
    /*!
     * @brief       Changes the game's display mode bewteen windowed and full screen.
     * @note        If the game is not allowed to run in full screen mode, this will have
     *              no effect.
     * @see         setFullScreen(bool)
     * @see         setAllowFullScreen(bool)
     */
    void
    toggleFullScreen() { setFullScreen(!_fullScreen); }
    
    /*!
     * @brief       Sets the game's display mode.
     * @param       fullScreen  Wether the game runs in full screen or windowed mode.
     */
    void
    setFullScreen(bool fullScreen);
    
    /**
     * @}
     */
    
    /*!
     * @internal
     * @brief       Sets the game's background color
     */
    void
    setBackgroundColor(const Color& color) { _backgroundColor = color; }
    
    void
    takeScreenshot(std::function<void(bool, const std::string&)> callback,
                   const std::string& filename);
    
    Game(const Game&)           = delete;
    void operator=(const Game&) = delete;
    
private:
    
    /*!
     * @brief       Private constructor.
     */
    Game();
    
    /*!
     * @brief       Destructor. Destroys any implementation-specific object needed by the game.
     */
    ~Game();
    
    /*!
     * @brief       Draws the current scene, or both the current and next scene if the game
     *              is transitionning.
     */
    void
    _draw();
    
    void
    _updateTransition(double delta);
    
    /*!
     * @brief       Gets the time elapsed since last frame
     * @return      Time in seconds since the last frame was rendered.
     */
    double
    timeSinceLastFrame();
    
    void
    _takeScreenshot();
    
    int             _lastFrame;
    
    /*!
     * @brief       The game's renderer. This is used to draw every object to the screen.
     */
    ARC<RendererProtocol> _renderer;
    
    /*!
     * @brief       Wether the game is transitioning between two scenes.
     */
    bool             _inTransition;
    
    /*!
     * @brief       Game's current display mode.
     */
    bool             _fullScreen;
    
    /*!
     * @brief       Wether the game can run in full screen mode.
     */
    bool             _allowFullScreen;
    
    /*!
     * @brief       The game's window.
     */
    SDL_Window*      _window;
        
    /*!
     * @brief       Game's logical view size.
     */
    size2           _size;
    
    /*!
     * @brief       The publisher's name, used to save game data.
     */
    std::string
    _organisation;
    
    /*!
     * @brief       The game's name, used to save game data.
     */
    std::string
    _application;
    
    /*!
     * @brief       Wether a screenshot should be taken once the renderer has finished its
     *              frame.
     */
    bool            _screenshot;
    
    std::string     _screenshotFilename;
    
    std::function<void(bool, const std::string&)> _screenshotCallback;
    
    /*!
     * @brief       Game's current scene
     */
    ARC<Scene>      _currentScene;
    
    /*!
     * @brief       Game's next scene
     */
    ARC<Scene>      _nextScene;
    
    /*!
     * @brief       Game's current transition;
     */
    ARC<Transition> _transition;
    
    /*!
     * @brief       Game's background color.
     */
    Color           _backgroundColor;
    
    bool            _quit;
};

NS_END

#endif /* defined(__Meteor__Game__) */
