#ifndef KRIMI_DINNER_ENGINE_RENDERER_H
#define KRIMI_DINNER_ENGINE_RENDERER_H

/// @file Renderer.h
/// @brief GLES3 renderer

#ifdef _WIN32
    #include "glad.h"
#else
    #include <GLES3/gl3.h>
#endif
#include <unordered_map>
#include <vector>

#include "Texture.h"
#include "StringRepository.h"

/// @brief Renderer singleton
class Renderer
{
public:
    /// The singleton instance
    static Renderer* ptr;

    /// @brief Creates the singleton and sets @ref Renderer::ptr
    static void create();

    /// @brief Destroys the singleton, only call this if you know what you are doing.
    static void destroy();

    /// \brief Adds a white rectangle to the screen
    /// \param x X-coordinate of the top-left corner
    /// \param y Y-coordinate of the top-left corner
    /// \param w width in pixels
    /// \param h height in pixels
    void addRect(float x, float y, float w, float h);

    /// @brief Adds a textured rectangle to the screen
    /// \param x X-coordinate of the top-left corner
    /// \param y Y-coordinate of the top-left corner
    /// \param w width in pixels
    /// \param h height in pixels
    /// \param texture String handle in the global string repository of the texture file name
    void addRect(float x, float y, float w, float h, StringHandle texture);

    /// @brief Adds a colored rectangle to the screen
    /// \param x X-coordinate of the top-left corner
    /// \param y Y-coordinate of the top-left corner
    /// \param w width in pixels
    /// \param h height in pixels
    /// \param r red [0,1]
    /// \param g green [0,1]
    /// \param b blue [0,1]
    /// \param a alpha [0,1]
    void addRect(float x, float y, float w, float h, float r, float g, float b, float a);

    /// @brief Adds a tinted textured rectangle to the screen
    /// \param x X-coordinate of the top-left corner
    /// \param y Y-coordinate of the top-left corner
    /// \param w width in pixels
    /// \param h height in pixels
    /// \param r red [0,1]
    /// \param g green [0,1]
    /// \param b blue [0,1]
    /// \param a alpha [0,1]
    /// \param texture String handle in the global string repository of the texture file name
    void addRect(float x, float y, float w, float h, float r, float g, float b, float a, StringHandle texture);

    /// @brief Adds a textured rectangle to the screen
    /// \param x X-coordinate of the top-left corner
    /// \param y Y-coordinate of the top-left corner
    /// \param w width in pixels
    /// \param h height in pixels
    /// \param texture String handle in the global string repository of the texture file name
    /// \param src_x top left corner of the source rectangle inside the texture [0, 1]
    /// \param src_y top left corner of the source rectangle inside the texture [0, 1]
    /// \param src_w width of the source rectangle inside the texture [0, 1]
    /// \param src_h height of the source rectangle inside the texture [0, 1]
    void addRect(float x,
                 float y,
                 float w,
                 float h,
                 StringHandle texture,
                 float src_x,
                 float src_y,
                 float src_w,
                 float src_h);

    /// @brief Adds a tinted textured rectangle to the screen
    /// \param x X-coordinate of the top-left corner
    /// \param y Y-coordinate of the top-left corner
    /// \param w width in pixels
    /// \param h height in pixels
    /// \param r red [0,1]
    /// \param g green [0,1]
    /// \param b blue [0,1]
    /// \param a alpha [0,1]
    /// \param texture String handle in the global string repository of the texture file name
    /// \param src_x top left corner of the source rectangle inside the texture [0, 1]
    /// \param src_y top left corner of the source rectangle inside the texture [0, 1]
    /// \param src_w width of the source rectangle inside the texture [0, 1]
    /// \param src_h height of the source rectangle inside the texture [0, 1]
    void addRect(float x,
                 float y,
                 float w,
                 float h,
                 float r,
                 float g,
                 float b,
                 float a,
                 StringHandle texture,
                 float src_x,
                 float src_y,
                 float src_w,
                 float src_h);

    /// @brief Adds a font rectangle to the screen with black text
    /// \param x X-coordinate of the top-left corner
    /// \param y Y-coordinate of the top-left corner
    /// \param w width in pixels
    /// \param h height in pixels
    /// \param char_height Height of characters in pixels
    /// \param font String handle in the global string repository of the ttf file name
    /// \param src_x0 bottom left corner of the source rectangle inside the texture [0, 1]
    /// \param src_y0 bottom left corner of the source rectangle inside the texture [0, 1]
    /// \param src_x1 top right of the source rectangle inside the texture [0, 1]
    /// \param src_y1 top right of the source rectangle inside the texture [0, 1]
    void addFontRect(float x,
                     float y,
                     float w,
                     float h,
                     float char_height,
                     StringHandle font,
                     StringHandle id,
                     float src_x0,
                     float src_y0,
                     float src_x1,
                     float src_y1);

    /// @brief Adds a tinted font rectangle to the screen
    /// \param x X-coordinate of the top-left corner
    /// \param y Y-coordinate of the top-left corner
    /// \param w width in pixels
    /// \param h height in pixels
    /// \param r red [0,1]
    /// \param g green [0,1]
    /// \param b blue [0,1]
    /// \param a alpha [0,1]
    /// \param char_height Height of characters in pixels
    /// \param font String handle in the global string repository of the ttf file name
    /// \param src_x0 bottom left corner of the source rectangle inside the texture [0, 1]
    /// \param src_y0 bottom left corner of the source rectangle inside the texture [0, 1]
    /// \param src_x1 top right of the source rectangle inside the texture [0, 1]
    /// \param src_y1 top right of the source rectangle inside the texture [0, 1]
    void addFontRect(float x,
                 float y,
                 float w,
                 float h,
                 float r,
                 float g,
                 float b,
                 float a,
                 float char_height,
                 StringHandle font,
                 StringHandle id,
                 float src_x0,
                 float src_y0,
                 float src_x1,
                 float src_y1);

    /// @brief Renders the frame
    ///
    /// You don't need to call this.
    /// @param width width of the screen
    /// @param height height of the screen
    void renderFrame(float width, float height);

    /// @brief loads texture (if not loaded) to get texture size
    /// \param w output width in pixels
    /// \param h output height in pixels
    void getTextureSize(
            StringHandle texture,
            unsigned int* w,
            unsigned int* h);

private:
    Renderer();
    ~Renderer();

    Renderer(const Renderer&) = delete;
    Renderer& operator=(const Renderer&) = delete;

    struct Rect
    {
        float dst_p0_x, dst_p0_y;
        float dst_p1_x, dst_p1_y;
        float src_p0_x, src_p0_y;
        float src_p1_x, src_p1_y;
        float r, g, b, a;
        int expand_r;
    };

    GLuint m_shader;
    GLuint m_vao;
    GLuint m_vbo;

    StringHandle m_dummy_texture;

    std::vector<Rect> m_rects;
    std::vector<StringHandle> m_draw_textures;

    std::unordered_map<StringHandle, Texture> m_textures;
};

#endif