#include "txt.hpp"


#define GLSL(src) "#version 150 core\n" #src


const char* TextRenderer::vert_glsl =
    "#version 150\n"
    "///shaderType:vertex\n"
    "\n"
    "in vec4 position;\n"
    "out vec2 texPosition;\n"
    "\n"
    "void main(void) {\n"
    "	gl_Position = vec4(position.xy, 0.0, 1.0);\n"
    "	texPosition = position.zw;\n"
    "}\n";


const char* TextRenderer::frag_glsl =
    "#version 150\n"
    "///shaderType:fragment\n"
    "\n"
    "in vec2 texPosition;\n"
    "uniform sampler2D tex;\n"
    "const vec4 color = vec4(1, 1, 0, 1);\n"
    "out vec4 outColor;\n"
    "\n"
    "void main(void) {\n"
    "	outColor = vec4(1, 1, 1, texture2D(tex, texPosition).r) * color;\n"
    "}\n";


Program* TextRenderer::get_prog() {
    Shader* vert_sh = Shader::getInst(vert_glsl, strlen(vert_glsl));
    if (!vert_sh) {
        return NULL;
    }
    Shader* frag_sh = Shader::getInst(frag_glsl, strlen(frag_glsl));
    if (!frag_sh) {
        delete vert_sh;
        return NULL;
    }

    Program* program = Program::getInst();
    if (!program) {
        delete vert_sh;
        delete frag_sh;
        return NULL;
    }
    if (!program->attach(vert_sh)) {
        delete vert_sh;
        delete frag_sh;
        delete program;
        return NULL;
    }
    if (!program->attach(frag_sh)) {
        delete frag_sh;
        delete program;
        return NULL;
    }

    glLinkProgram(*program);
    GLint rv;
    glGetProgramiv(*program, GL_LINK_STATUS, &rv);
    if (rv != GL_TRUE) {
        delete program;
        return NULL;
    }

    return program;
}


unsigned TextRenderer::max_height() const {
    unsigned w, h;
    font.get_glyph_max(w, h);
    return h;
}


TextRenderer::TextRenderer(): vbo(4), ebo(GL_ELEMENT_ARRAY_BUFFER), program(NULL), texture(NULL) {
    assert(!glerr() || !glerr());
    program = get_prog();
    if (!program) return;
    glUseProgram(*program); // GL_INVALID_OPERATION for glUniform1i

    vbo.setAttrib(program, "position", 0, 4);

    GLint texloc = glGetUniformLocation(*program, "tex");
    if (texloc == -1) {
        delete program;
        program = NULL;
        return;
    }
    texture = new Texture("TextRenderer");
    glUniform1i(texloc, texture->index);

    GLuint elements[] = {
        0, 1, 2,
        2, 3, 0
    };
    ebo.bind(elements, sizeof(elements), GL_STATIC_DRAW);

    assert(!glerr());
}


TextRenderer::~TextRenderer() {
    if (program) delete program;
    if (texture) delete texture;
}


bool TextRenderer::draw(const char* str, unsigned x, unsigned y, const Window* win, unsigned scale) { // resets current program, array buffer, and element array buffer
    if (!program) {
        return false;
    }
    glUseProgram(*program);

    ebo.bind();

    for (const char* p=str; *p; p++) {
        // TODO: color, transparency
        // TODO: use a single texture with all glyphs?
        const unsigned char* g;
        unsigned w, h;
        font.get_glyph(*p, g, w, h);
        if (!texture->set(g, w, h)) {
            return false;
        }
        w *= scale;
        h *= scale;

        GLfloat x_start = win->px2iso_x(x);
        GLfloat y_start = win->px2iso_y(y);
        GLfloat x_end = win->px2iso_x(x+w);
        GLfloat y_end = win->px2iso_y(y+h);
        GLfloat vertices[] = {
            x_start, y_end,    0.0f, 0.0f,
            x_end,   y_end,    1.0f, 0.0f,
            x_end,   y_start,  1.0f, 1.0f,
            x_start, y_start,  0.0f, 1.0f
        };
        vbo.bind(vertices, 4*4, GL_DYNAMIC_DRAW);

        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

        x += w+1;
    }

    return true;
}


Logger* Logger::inst = NULL;
const size_t Logger::max_lines = 5;


Logger::~Logger() {
    while (!lines.empty()) {
        LOG("%s", lines.front());
        free(lines.front());
        lines.erase(lines.begin());
    }
    inst = NULL;
}


void Logger::log(const char *fmt, ...) {
    char* s;
    va_list vl;
    va_start(vl, fmt);
    if (vasprintf(&s, fmt, vl) <= 0) {
        va_end(vl);
        return;
    }
    va_end(vl);

    if (!inst || inst->blocked) {
        LOG("%s", s);
        free(s);
    } else {
        while (inst->lines.size() >= max_lines) {
            free(inst->lines.front());
            inst->lines.erase(inst->lines.begin());
        }
        inst->lines.push_back(s);
        inst->dirty = true;
    }
}


bool Logger::logall(const Window* win, bool redraw) {
    if (blocked || lines.empty()) {
        return false;
    }
    if (!dirty && !redraw) {
        return false;
    }

    static const unsigned scale = 2;
    int num = 0;
    for (std::vector<char*>::const_iterator it=lines.begin(); it!=lines.end(); ++it) {
        int y = win->h - 10 - ((renderer.max_height()*scale + 1) * ++num);
        (void)renderer.draw(*it, 10, y, win, scale);
    }
    dirty = false;

    return true; // might have changed some state
}