#include "GL.h"
#include "gl3w.h"
#include "ruby.h"
#include "GLBuffer.h"
#include "GLProgram.h"
#include "Font.h"

enum
{
    PROGRAM_TEXT,
    PROGRAM_BASIC,
    PROGRAM_RECT,
    PROGRAM_COUNT
};

static const char * program_names[PROGRAM_COUNT] = {
    "text",
    "basic",
    "rect",
};

static VALUE ruby_module;
static GLBufferRef rect_buffer;
static GLProgram * programs[PROGRAM_COUNT];

static VALUE GL_draw_rect(VALUE klass,
                          VALUE x, VALUE y, VALUE width, VALUE height,
                          VALUE r, VALUE g, VALUE b, VALUE a)
{
    glUseProgram(programs[PROGRAM_RECT]->id);
    glUniform2i(programs[PROGRAM_RECT]->uniforms[UNIFORM_POSITION], FIX2INT(x), FIX2INT(y));
    glUniform2i(programs[PROGRAM_RECT]->uniforms[UNIFORM_SIZE], FIX2INT(width), FIX2INT(height));
    glUniform4f(programs[PROGRAM_RECT]->uniforms[UNIFORM_COLOR],
                NUM2DBL(r),
                NUM2DBL(g),
                NUM2DBL(b),
                NUM2DBL(a));
    rect_buffer->bind();
    glEnableVertexAttribArray(0);
    glVertexAttribIPointer(0, 2, GL_INT, 0, 0);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    return Qnil;
}

static VALUE GL_draw_text(VALUE klass,
                          VALUE font, VALUE x, VALUE y, VALUE string,
                          VALUE r, VALUE g, VALUE b, VALUE a)
{
    int x_int = FIX2INT(x);
    glUseProgram(programs[PROGRAM_TEXT]->id);
    glUniform1i(programs[PROGRAM_TEXT]->uniforms[UNIFORM_TEXTURE], 0);
    glUniform4f(programs[PROGRAM_TEXT]->uniforms[UNIFORM_COLOR],
                NUM2DBL(r),
                NUM2DBL(g),
                NUM2DBL(b),
                NUM2DBL(a));

    int length = RSTRING_LEN(string);
    const char * string_ptr = RSTRING_PTR(string);
    int advance = FIX2INT(rb_iv_get(font, "@advance"));
    for (int i = 0; i < length; i++)
    {
        glUniform2i(programs[PROGRAM_TEXT]->uniforms[UNIFORM_POSITION], x_int, FIX2INT(y));
        Font_RenderGlyph(font, string_ptr[i]);
        x_int += advance;
    }

    return Qnil;
}

void GL_Init()
{
    ruby_module = rb_define_module("GL");
    rb_define_singleton_method(ruby_module, "draw_rect",
                               (VALUE(*)(...))GL_draw_rect, 8);
    rb_define_singleton_method(ruby_module, "draw_text",
                               (VALUE(*)(...))GL_draw_text, 8);
}

static void load_gl_buffers()
{
    static const GLint rect_coords[4][2] = {
        {0, 0},
        {1, 0},
        {0, 1},
        {1, 1},
    };

    rect_buffer = new GLBuffer();
    if (!rect_buffer->create(GL_ARRAY_BUFFER, GL_STATIC_DRAW,
                             &rect_coords, sizeof(rect_coords)))
    {
        rb_raise(rb_eRuntimeError, "Failed to create rectangle VBO");
    }
}

void GL_Load()
{
    static bool loaded = false;

    if (loaded)
        return;

    if (gl3wInit() != 0)
    {
        rb_raise(rb_eRuntimeError, "Failed to gl3wInit()");
    }

    if (!gl3wIsSupported(3, 0))
    {
        rb_raise(rb_eRuntimeError, "OpenGL 3.0 is not supported");
    }

    load_gl_buffers();

    rb_funcall(ruby_module, rb_intern("load_shaders"), 0);

    VALUE programs_hash = rb_iv_get(ruby_module, "@programs");
    for (int i = 0; i < PROGRAM_COUNT; i++)
    {
        VALUE program = rb_funcall(programs_hash,
                                   rb_intern("[]"),
                                   1,
                                   ID2SYM(rb_intern(program_names[i])));
        if (RTEST(program))
        {
            programs[i] = GLProgram_FromRuby(program);
        }
        else
        {
            rb_raise(rb_eLoadError,
                     "Error resolving %s program",
                     program_names[i]);
        }
    }

    glEnable(GL_SCISSOR_TEST);
    glActiveTexture(GL_TEXTURE0);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    loaded = true;
}

void GL_Resize(int width, int height)
{
    GLint viewport_size[] = {width, height};
    glViewport(0, 0, width, height);
    for (int i = 0; i < PROGRAM_COUNT; i++)
    {
        glUseProgram(programs[i]->id);
        glUniform2iv(programs[i]->uniforms[UNIFORM_VIEWPORT_SIZE], 1, &viewport_size[0]);
    }
}
