#ifndef TWN_DRAW_C_H
#define TWN_DRAW_C_H

#include "twn_textures_c.h"
#include "twn_text_c.h"
#include "twn_util.h"
#include "twn_option.h"

#include <SDL2/SDL.h>
#include <stb_truetype.h>

#ifdef EMSCRIPTEN
#include <GLES2/gl2.h>
#else
#include <glad/glad.h>
#endif

#include <stdbool.h>

extern Matrix4 camera_projection_matrix;
extern Matrix4 camera_look_at_matrix;

#define QUAD_ELEMENT_BUFFER_LENGTH (65536 / 6)


typedef GLuint VertexBuffer;

typedef struct VertexBufferBuilder {
    size_t bytes_left;
    void *mapping;
} VertexBufferBuilder;


typedef struct SpritePrimitive {
    Rect rect;
    Color color;
    float rotation;
    TextureKey texture_key;
    bool flip_x;
    bool flip_y;
    bool repeat;

    m_option_list(
        Rect, texture_region )
} SpritePrimitive;

typedef struct RectPrimitive {
    Rect rect;
    Color color;
} RectPrimitive;

typedef struct CirclePrimitive {
    float radius;
    Color color;
    Vec2 position;
} CirclePrimitive;

typedef struct TextPrimitive {
    Color color;
    Vec2 position;
    char *text;
    const char *font;
    int height_px;
} TextPrimitive;

typedef enum Primitive2DType {
    PRIMITIVE_2D_SPRITE,
    PRIMITIVE_2D_RECT,
    PRIMITIVE_2D_CIRCLE,
    PRIMITIVE_2D_TEXT,
} Primitive2DType;

typedef struct Primitive2D {
    Primitive2DType type;

    union {
        SpritePrimitive sprite;
        RectPrimitive rect;
        CirclePrimitive circle;
        TextPrimitive text;
    };
} Primitive2D;

/* union for in-place recalculation of texture coordinates */
union UncoloredSpaceTriangle {
    /* pending for sending, uvs are not final as texture atlases could update */
    struct UncoloredSpaceTrianglePrimitive {
        Vec3 v0;
        Vec2 uv0; /* in pixels */
        Vec3 v1;
        Vec2 uv1; /* in pixels */
        Vec3 v2;
        Vec2 uv2; /* in pixels */
    } primitive;

    /* TODO: have it packed? */
    /* structure that is passed in opengl vertex array */
    struct UncoloredSpaceTrianglePayload {
        Vec3 v0;
        Vec2 uv0;
        Vec3 v1;
        Vec2 uv1;
        Vec3 v2;
        Vec2 uv2;
    } payload;
};

/* batch of primitives with overlapping properties */
typedef struct MeshBatch {
    uint8_t *primitives;
} MeshBatch;

typedef struct MeshBatchItem {
    TextureKey key;
    struct MeshBatch value;
} MeshBatchItem;

typedef struct TextCache {
    struct FontData **data;
} TextCache;


/* renders the background, then the primitives in all render queues */
void render(void);

/* clears all render queues */
void render_queue_clear(void);

void create_circle_geometry(Vec2 position,
                            Color color,
                            float radius,
                            size_t num_vertices,
                            SDL_Vertex **vertices_out,
                            int **indices_out);

struct SpriteBatch {
    size_t size;             /* how many primitives are in current batch */
    TextureMode mode;
    bool constant_colored;   /* whether colored batch is uniformly colored */
    bool repeat;             /* whether repeat is needed */
} collect_sprite_batch(const Primitive2D primitives[], size_t len);

void render_sprites(const Primitive2D primitives[],
                    const struct SpriteBatch batch);

void draw_uncolored_space_traingle_batch(MeshBatch *batch,
                                         TextureKey texture_key);

/* text */

void render_text(const TextPrimitive *text);

void text_cache_init(TextCache *cache);

void text_cache_deinit(TextCache *cache);

void text_cache_reset_arena(TextCache *cache);

/* vertex buffer */

VertexBuffer create_vertex_buffer(void);

void delete_vertex_buffer(VertexBuffer buffer);

void specify_vertex_buffer(VertexBuffer buffer, void *data, size_t bytes);

/* uses present in 1.5 buffer mapping feature */
VertexBufferBuilder build_vertex_buffer(VertexBuffer buffer, size_t bytes);

/* collects bytes for sending to the gpu until all is pushed, which is when false is returned */
bool push_to_vertex_buffer_builder(VertexBufferBuilder *builder,
                                   void *bytes,
                                   size_t size);

/* state */

void setup_viewport(int x, int y, int width, int height);

void clear_draw_buffer(void);

void swap_buffers(void);

void set_depth_range(double low, double high);

void bind_quad_element_buffer(void);

void render_circle(const CirclePrimitive *circle);

void render_rectangle(const RectPrimitive *rectangle);

void use_space_pipeline(void);

void use_2d_pipeline(void);

void use_texture_mode(TextureMode mode);

void upload_quad_vertices(Rect rect);

void finally_render_sprites(Primitive2D const primitives[],
                            struct SpriteBatch batch,
                            VertexBuffer buffer);

size_t get_sprite_payload_size(struct SpriteBatch batch);

bool push_sprite_payload_to_vertex_buffer_builder(struct SpriteBatch batch,
                                                  VertexBufferBuilder *builder,
                                                  Vec2 v0, Vec2 v1, Vec2 v2, Vec2 v3,
                                                  Vec2 uv0, Vec2 uv1, Vec2 uv2, Vec2 uv3,
                                                  Color color);

void finally_draw_uncolored_space_traingle_batch(MeshBatch const *batch,
                                                 TextureKey texture_key,
                                                 VertexBuffer buffer);

size_t get_text_payload_size(void);

bool push_text_payload_to_vertex_buffer_builder(FontData const *font_data,
                                                VertexBufferBuilder *builder,
                                                stbtt_aligned_quad quad);

void finally_draw_text(FontData const *font_data,
                       size_t len,
                       Color color,
                       VertexBuffer buffer);

void render_skybox(void);

void finally_render_skybox(char *paths_in_use);

void apply_fog(void);

void finally_apply_fog(float start, float end, float density, Color color);

void pop_fog(void);

void finally_pop_fog(void);

#endif
