/*  GameEngine.h - Abstract class representing an SDL Game Engine.

    flatzebra - Generic 2D Game Engine library
    Copyright (C) 1999-2024 Pierre Sarrazin <http://sarrazip.com/>

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
    02110-1301, USA.
*/

#ifndef _H_GameEngine
#define _H_GameEngine

#include <flatzebra/Couple.h>
#include <flatzebra/RCouple.h>
#include <flatzebra/Sprite.h>
#include <flatzebra/RSprite.h>

#include <flatzebra/PixmapArray.h>
#include <flatzebra/PixmapLoadError.h>
/*  Include directive instead of forward declaration, because of the
    exception specifications that mention PixmapLoadError.
    This is necessary for g++ 2.96, but it was not for g++ 2.95.2.
    --2001-03-24
*/

#include <SDL2_gfxPrimitives.h>

#include <string>
#include <stdint.h>


namespace flatzebra {


// Abstract class representing a Generic Game Engine.
//
// A game should derive a class from this one, instantiate it,
// then call the run() method.
//
// These operations are liable to throw exceptions of type
// std::string and int.
//
class GameEngine
{
public:

    // Initializes SDL and an adequate video mode.
    //
    // The x and y fields of 'screenSizeInPixels' specify the size of
    // the screen in pixels.
    //
    // 'wmCaption' must be the caption to be used by the window manager
    // as the window and icon titles.
    //
    // If 'fullScreen' is true, an attempt is made to use the
    // full-screen mode.
    //
    // Upon failure, an error message of type std::string is thrown
    // as an exception.
    //
    // If 'processActiveEvent' is true, SDL's window focus events are
    // processed, and the virtual method processActivation() is called
    // when the app loses and regains focus.
    //
    // The run() method should be called after successfully constructing
    // this object.
    //
    GameEngine(Couple screenSizeInPixels,
               const std::string &wmCaption,
               bool fullScreen,
               bool processActiveEvent,
               bool useAcceleratedRendering);

    // Calls SDL_Quit().
    //
    virtual ~GameEngine();

    // Returns an empty string upon success, or an error message upon failure.
    //
    std::string setFullScreenMode(bool fullScreenMode);

    // Indicates if the currently selected video mode is in full screen
    // instead of a window.
    // Reflects the state selected by the most recent successful call
    // to setVideoMode(), or the state selected by the constructor if
    // setVideoMode() has never been called.
    //
    bool inFullScreenMode() const;

    // Called upon a key event.
    // 'keysym' is an SDL key symbol, e.g., SDLK_ESCAPE.
    // 'pressed' indicates the new state of the key.
    //
    virtual void processKey(SDL_Keycode keysym, bool pressed) = 0;

    // Called when the app is activated or deactivated (iconified).
    // 'appActive' is true if the app has just become active,
    // or false if it has just become inactive.
    // This method does nothing; an override of this method in a derived
    // class does not have to call this method.
    // After this method has been called, the tick() method will not be
    // called until the app has been reactivated. However, the SDL
    // screen is flipped after this call, so the effect of drawing
    // commands made by this method will appear.
    // Never called if processActiveEvent argument of constructor is false.
    //
    virtual void processActivation(bool appActive);

    // Called at the beginning of every animation period.
    // Must return true to ask to be called again at the next period.
    // Must return false to ask not to be called anymore.
    //
    virtual bool tick() = 0;

    // Main loop.
    // To be called after the object has been successfully constructed.
    // Runs until the user asks to quit the program.
    //
    // 'millisecondsPerFrame' must be the number of milliseconds
    // between the start of each frame of animation.  50 ms means
    // 20 frames per second, which is about the minimum speed needed
    // to fool the human eye into seeing smooth motion.
    //
    void run(int millisecondsPerFrame = 50);

    int getScreenWidthInPixels() const;
    int getScreenHeightInPixels() const;

    static SDL_Color mapRGBA(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255);

    // Writes a NUL-terminated string representation of the given color.
    // dest: Must hold at least 9 characters.
    static void sdlColorToString(char dest[], size_t destSize, SDL_Color color); 

    // Returns the value of the environment variable named by 'envVarName'
    // if defined and not empty.
    // Otherwise, returns 'defaultValue' if not null (an empty string is used if null).
    // If the resulting path is not empty, a directory separator is appended if one
    // is not already present at the end.
    // If neither defaultValue nor envVarName yield a non empty string, then
    // an empty string is returned.
    //
    static std::string getDirPathFromEnv(const char *defaultValue, const char *envVarName);

    // Loads a pixmap from the specified file and stores the results
    // in the PixmapArray at the specified index.
    // Upon failure, throws a std::string that contains an error message.
    //
    void loadPixmap(const char *filePath,
                    PixmapArray &pa,
                    size_t index);

    void loadPixmap(const std::string &filePath,
                    PixmapArray &pa,
                    size_t index);

    // Upon error, throws a std::string that contains an error message.
    //
    SDL_Texture *createTextureFromFile(const char *filePath);

    SDL_Texture *createTextureFromFile(const std::string &filePath);

    // Throws a string exception upon error.
    //
    static Couple getTextureSize(SDL_Texture *texture);

    // Sets the alpha value of the designated texture.
    // This can be used to make the texture totally or partly transparent.
    // Throws a string exception upon error.
    //
    static void setTextureAlphaMod(SDL_Texture *texture, uint8_t alpha);

    // Copies 'src' at the specified destination in the drawing pixmap.
    // Throw a string exception upon failure.
    //
    void copyPixmap(SDL_Texture *src, Couple dest) const;

    // Copies sprite s's pixmap number 'pixmapNo' into the surface
    // at the specified (integer) position.
    // Uses the sprite's size but not its position.
    //
    void copySpritePixmap(const Sprite &s, size_t pixmapNo, Couple dest);

    // Copies sprite s's pixmap number 'pixmapNo' into the surface
    // at the specified position (rounded to integers).
    // Uses the sprite's size but not its position.
    //
    void copySpritePixmap(const RSprite &s, size_t pixmapNo, RCouple dest);

    // Writes the given zero-terminated Latin-1 (ISO-8859-1) string
    // at the given position.
    // Uses a fixed width white-on-black non-transparent font whose dimensions
    // are given by getFontDimensions().
    //
    void writeString(const char *s, Couple pos);
    void writeString(const std::string &s, Couple pos);
    void writeStringCentered(const char *s, Couple pos);
    void writeStringCentered(const std::string &s, Couple pos);
    void writeStringXCentered(const char *s, int y);
    void writeStringXCentered(const std::string &s, int y);
    void writeStringXCentered(const char *s, Couple pos);
    void writeStringXCentered(const std::string &s, Couple pos);
    void writeStringRightJustified(const char *s, Couple pos);
    void writeStringRightJustified(const std::string &s, Couple pos);

    // Returns the width and height (in the x and y fields) of the fixed
    // font used by writeString().
    //
    Couple getFontDimensions() const;

    // Writes a string using SDL2_gfx instead of the fixed font used by
    // writeString() et al. above.
    // Uses the font set by SDL2_gfx's gfxPrimitivesSetFont().
    //
    void gfxWriteString(const char *s, Couple pos, SDL_Color color);

    // Sets the color of the pixel at the given position.
    // The upper-left corner of the game screen is at (0, 0).
    //
    void drawPixel(int x, int y, SDL_Color color);

    // Draws a line with the given color that goes from point (x1, y1)
    // to point (x2, y2).
    //
    void drawLine(int x1, int y1, int x2, int y2, SDL_Color color);

    // Fills a rectangle with the given color at an upper-left corner
    // given by (x, y) and with dimensions given by width and height.
    //
    void fillRect(int x, int y, int width, int height, SDL_Color color);

    // Fills a circle with the given color at a center given by (x, y)
    // and with the given radius in pixels.
    //
    void fillCircle(int x, int y, int radius, SDL_Color color);

    // Sets the blend mode and the rendering color.
    // Must be called before every drawing operation.
    //
    virtual void prepareDrawingOp(const SDL_Color &color);

    // Get the blend mode that will be used for the next drawing operations.
    //
    SDL_BlendMode getBlendMode() const;

protected:

    // After initialization, this variable should not be modified by derived class methods.
    //
    Couple theScreenSizeInPixels;

    SDL_Window *window;

    SDL_Renderer *renderer;

    // Color that fills the sides of the screen in full screen mode.
    //
    SDL_Color fullScreenLetterboxColor;

    SDL_Texture *fixedWidthFontPixmap;

    bool usingFullScreen;

    bool processActiveEvent;  // if true, SDL_ACTIVEEVENT is processed by run()

    bool useAcceleratedRendering;

protected:

    // Called before starting to render a frame.
    //
    virtual void startDrawingFrame();

    // Called when finished drawing a frame.
    // This makes the frame visible.
    //
    virtual void flip();

    // Sleeps while waiting for SDL events until a reactivation event
    // or a quit event is received.
    // Returns true to request the continuation of the program.
    // Returns false to request quitting the program.
    // Returns false if a wait error occurs.
    // Never called if the processActiveEvent argument passed to the
    // constructor is false.
    //
    virtual bool waitForReactivation();

private:

    /*  Forbidden operations:
    */
    GameEngine(const GameEngine &x);
    GameEngine &operator = (const GameEngine &x);
};


inline
bool
GameEngine::inFullScreenMode() const
{
    return usingFullScreen;
}


inline
int
GameEngine::getScreenWidthInPixels() const
{
    return theScreenSizeInPixels.x;
}


inline
int
GameEngine::getScreenHeightInPixels() const
{
    return theScreenSizeInPixels.y;
}


inline
void
GameEngine::copySpritePixmap(const Sprite &s, size_t pixmapNo, Couple dest)
{
    copyPixmap(s.getPixmap(pixmapNo), dest);
}


inline
void
GameEngine::copySpritePixmap(const RSprite &s, size_t pixmapNo, RCouple dest)
{
    copyPixmap(s.getPixmap(pixmapNo), dest.round());
}


inline
void
GameEngine::writeString(const std::string &s, Couple pos)
{
    writeString(s.c_str(), pos);
}


inline
void
GameEngine::writeStringCentered(const std::string &s, Couple pos)
{
    writeStringCentered(s.c_str(), pos);
}


inline
void
GameEngine::writeStringXCentered(const std::string &s, Couple pos)
{
    writeStringXCentered(s.c_str(), pos);
}


inline
void
GameEngine::writeStringXCentered(const char *s, int y)
{
    writeStringXCentered(s, Couple(getScreenWidthInPixels() / 2, y));
}


inline
void
GameEngine::writeStringXCentered(const std::string &s, int y)
{
    writeStringXCentered(s.c_str(), y);
}


inline
void
GameEngine::writeStringRightJustified(const std::string &s, Couple pos)
{
    writeStringRightJustified(s.c_str(), pos);
}


inline
Couple
GameEngine::getFontDimensions() const
{
    return Couple(7, 13);
}


inline SDL_Color
GameEngine::mapRGBA(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
    SDL_Color c = { r, g, b, a };
    return c;
}


}  // namespace flatzebra


#endif  /* _H_GameEngine */
