#ifndef TWN_DRAW_C_H
#define TWN_DRAW_C_H

/* TODO: structure more categorically */

#include "twn_textures_c.h"
#include "twn_text_c.h"
#include "twn_option.h"
#include "twn_deferred_commands.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)
#define CIRCLE_VERTICES_MAX 2048


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;

    m_option_list(
        Rect, texture_region )

    bool flip_x;
    bool flip_y;
    bool repeat;
} SpritePrimitive;

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

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

typedef struct TextPrimitive {
    Vec2 position;
    char *text;
    const char *font;
    Color color;
    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; /* TODO: separate to structure of arrays for more efficient memory usage? */

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

/* union for in-place recalculation of texture coordinates */
/* needs to be later resolved in texture atlas */
typedef struct UncoloredSpaceTriangle {
    Vec3 v0;
    Vec2 uv0; /* in pixels */
    Vec3 v1;
    Vec2 uv1; /* in pixels */
    Vec3 v2;
    Vec2 uv2; /* in pixels */
} UncoloredSpaceTriangle;

typedef struct SpaceBillboard {
    Vec3 position;
    Vec2 size;
    Color color;
    // TextureKey texture; /* is assumed from other places */
    bool cylindrical;
} SpaceBillboard;

/* batch of primitives with overlapping properties */
typedef struct MeshBatch {
    uint8_t *primitives; /* note: interpretation of it is arbitrary */
} MeshBatch;

/* TODO: use atlas id instead */
typedef struct MeshBatchItem {
    TextureKey key;
    struct MeshBatch value;
} MeshBatchItem;

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


/* TODO: try using the fact we utilize edge coloring and step virtual color attributes to bogus points */
/*       this is only doable is we take out color attribute to separate array or a portion of it */
/* interleaved vertex array data */
typedef struct ElementIndexedQuad {
    /* upper-left */
    Vec2 v0;
    Vec2 uv0;
    Color c0;
    /* bottom-left */
    Vec2 v1;
    Vec2 uv1;
    Color c1;
    /* bottom-right */
    Vec2 v2;
    Vec2 uv2;
    Color c2;
    /* upper-right */
    Vec2 v3;
    Vec2 uv3;
    Color c3;
} ElementIndexedQuad;


typedef struct ElementIndexedQuadWithoutColor {
    /* upper-left */
    Vec2 v0;
    Vec2 uv0;
    /* bottom-left */
    Vec2 v1;
    Vec2 uv1;
    /* bottom-right */
    Vec2 v2;
    Vec2 uv2;
    /* upper-right */
    Vec2 v3;
    Vec2 uv3;
} ElementIndexedQuadWithoutColor;


typedef struct ElementIndexedQuadWithoutTexture {
    /* upper-left */
    Vec2 v0;
    Color c0;
    /* bottom-left */
    Vec2 v1;
    Color c1;
    /* bottom-right */
    Vec2 v2;
    Color c2;
    /* upper-right */
    Vec2 v3;
    Color c3;
} ElementIndexedQuadWithoutTexture;


typedef struct ElementIndexedQuadWithoutColorWithoutTexture {
    /* upper-left */
    Vec2 v0;
    /* bottom-left */
    Vec2 v1;
    /* bottom-right */
    Vec2 v2;
    /* upper-right */
    Vec2 v3;
} ElementIndexedQuadWithoutColorWithoutTexture;

/* TODO: no color variant */
typedef struct ElementIndexedBillboard {
    /* upper-left */
    Vec3 v0;
    Vec2 uv0;
    Color c0;
    /* bottom-left */
    Vec3 v1;
    Vec2 uv1;
    Color c1;
    /* bottom-right */
    Vec3 v2;
    Vec2 uv2;
    Color c2;
    /* upper-right */
    Vec3 v3;
    Vec2 uv3;
    Color c3;
} ElementIndexedBillboard;


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

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

/* fills two existing arrays with the geometry data of a circle */
/* the size of indices must be at least 3 times the number of vertices */
void create_circle_geometry(Vec2 position,
                            float radius,
                            size_t num_vertices,
                            Vec2 vertices[]);

struct QuadBatch {
    size_t size;             /* how many primitives are in current batch */
    TextureKey texture_key;
    TextureMode mode;        /* how color should be applied */
    bool constant_colored;   /* whether colored batch is uniformly colored */
    bool repeat;             /* whether repeat is needed */
    bool textured;
} collect_quad_batch(const Primitive2D primitives[], size_t len);
void render_quad_batch(const Primitive2D primitives[], struct QuadBatch batch);
struct QuadBatch collect_sprite_batch(const Primitive2D primitives[], size_t len);
struct QuadBatch collect_rect_batch(const Primitive2D primitives[], size_t len);
void render_sprite_batch(const Primitive2D primitives[], struct QuadBatch batch);
void render_rect_batch(const Primitive2D primitives[], struct QuadBatch batch);

/* 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);

VertexBuffer get_scratch_vertex_array(void);

void delete_vertex_buffer(VertexBuffer buffer);

void specify_vertex_buffer(VertexBuffer buffer, void const *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 const *bytes,
                                   size_t size);

/* state */

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

void clear_draw_buffer(void);
void finally_clear_draw_buffer(DeferredCommandClear command);

void swap_buffers(void);

void set_depth_range(double low, double high);
void finally_set_depth_range(DeferredCommandDepthRange command);

VertexBuffer get_quad_element_buffer(void);

VertexBuffer get_circle_element_buffer(void);

void render_circle(const CirclePrimitive *circle);

void render_rectangle(const RectPrimitive *rectangle);

void use_space_pipeline(void);
void finally_use_space_pipeline(void);

void use_2d_pipeline(void);
void finally_use_2d_pipeline(void);

void use_texture_mode(TextureMode mode);
void finally_use_texture_mode(TextureMode mode);

void finally_render_quads(Primitive2D const primitives[],
                          struct QuadBatch batch,
                          VertexBuffer buffer);

size_t get_quad_payload_size(struct QuadBatch batch);

bool push_quad_payload_to_vertex_buffer_builder(struct QuadBatch 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);

void finally_draw_billboard_batch(MeshBatch const *batch,
                                  TextureKey texture_key);

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(DeferredCommandDrawSkybox);

void apply_fog(void);
void finally_apply_fog(DeferredCommandApplyFog);
void pop_fog(void);
void finally_pop_fog(void);

void start_render_frame(void);
void end_render_frame(void);

void finally_draw_command(DeferredCommandDraw command);

void issue_deferred_draw_commands(void);

#endif
