#include "GLShader.h"

typedef struct
{
    GLuint id;
} GLShader;

static VALUE ruby_class;

static const char * get_shader_type_name(GLenum shader_type)
{
    switch (shader_type)
    {
    case GL_VERTEX_SHADER:
        return "vertex";
    case GL_FRAGMENT_SHADER:
        return "fragment";
    default:
        return "unknown";
    }
}

static GLuint create_shader(GLenum shader_type, const char *source, size_t size)
{
    GLuint id = glCreateShader(shader_type);
    if (id > 0)
    {
        GLint status;

        GLint length = size;
        glShaderSource(id, 1, &source, &length);

        glCompileShader(id);

        glGetShaderiv(id, GL_COMPILE_STATUS, &status);
        if (status == GL_TRUE)
        {
            return true;
        }

        GLint log_length;
        glGetShaderiv(id, GL_INFO_LOG_LENGTH, &log_length);
        if (log_length > 0)
        {
            char log[log_length + 1];
            glGetShaderInfoLog(id, log_length, &log_length, log);
            log[log_length] = '\0';
            glDeleteShader(id);
            rb_raise(rb_eRuntimeError,
                     "Error compiling %s shader:\n%s\n%s\n%s\n",
                     get_shader_type_name(shader_type),
                     "--------------------",
                     log,
                     "--------------------");
        }
        else
        {
            glDeleteShader(id);
            rb_raise(rb_eRuntimeError,
                     "Error compiling %s shader",
                     get_shader_type_name(shader_type));
        }
    }

    return id;
}

static void GLShader_free(void * ptr)
{
    GLShader * glshader = (GLShader *)ptr;

    if (glshader->id > 0)
    {
        glDeleteShader(glshader->id);
    }

    delete glshader;
}

static VALUE GLShader_new(VALUE klass, VALUE type, VALUE source)
{
    GLenum shader_type;

    if (type == ID2SYM(rb_intern("vertex")))
    {
        shader_type = GL_VERTEX_SHADER;
    }
    else if (type == ID2SYM(rb_intern("fragment")))
    {
        shader_type = GL_FRAGMENT_SHADER;
    }
    else
    {
        type = rb_funcall(type, rb_intern("inspect"), 0);
        const char * type_cstr = StringValueCStr(type);
        rb_raise(rb_eRuntimeError,
                 "Unknown shader type: %s",
                 type_cstr);
    }

    GLuint id = create_shader(shader_type,
                              RSTRING_PTR(source),
                              RSTRING_LEN(source));

    GLShader * glshader = new GLShader();
    glshader->id = id;

    return Data_Wrap_Struct(klass, NULL, GLShader_free, glshader);
}

void GLShader_Init()
{
    ruby_class = rb_define_class("GLShader", rb_cObject);
    rb_define_singleton_method(ruby_class, "new", (VALUE(*)(...))GLShader_new, 2);
}

GLuint GLShader_id(VALUE shader)
{
    GLShader * glshader;
    Data_Get_Struct(shader, GLShader, glshader);

    return glshader->id;
}
