#include "shader.hpp"
#include "io.hpp"
#include <dirent.h>


Shader::Shader(GLuint s, GLenum t): shader(s), type(t) {
}


Shader* Shader::getInst(const char* buf, size_t len) {
    const char* pos = strstr(buf, "///shaderType:");
    if (!pos) {
        LOG("no shaderType comment found");
        return NULL;
    }
    pos += sizeof("///shaderType:")-1;
    GLenum type;
    if (!strncmp(pos, "vertex", sizeof("vertex")-1)) {
        type = GL_VERTEX_SHADER;
    } else if (!strncmp(pos, "fragment", sizeof("fragment")-1)) {
        type = GL_FRAGMENT_SHADER;
    } else if (!strncmp(pos, "geometry", sizeof("geometry")-1)) {
        type = GL_GEOMETRY_SHADER;
    } else {
        return NULL;
    }

    if (type == GL_FRAGMENT_SHADER) {
        unsigned outnum = 0;
        pos = buf;
        while ((pos = strstr(pos, "\nout ")) != NULL) {
            outnum++;
            pos++;
        }
        if (outnum > 1) {
            LOG("glBindFragDataLocation would be needed for %u fragment outputs", outnum);
            return NULL;
        }
    }

    // TODO: better error detection
    GLuint s = glCreateShader(type);
    glShaderSource(s, 1, &buf, (int*)&len);
    glCompileShader(s);

    GLint status;
    glGetShaderiv(s, GL_COMPILE_STATUS, &status);
    if (status != GL_TRUE) {
        static char buffer[512];
        glGetShaderInfoLog(s, sizeof(buffer), NULL, buffer);
        LOG("%s", buffer);
        glDeleteShader(s);
        return NULL;
    }

    return s? new Shader(s, type): NULL;
}


Shader* Shader::getInst(const char* fn) {
    char* src;
    size_t len;
    if (!file_read(fn, src, len)) {
        return NULL;
    }

    Shader* rv = getInst(src, len);
    free(src);
    return rv;
}


Shader::~Shader() {
    glDeleteShader(shader);
}


Program* Program::getInst() {
    GLuint p = glCreateProgram();
    return new Program(p);
}


Program* Program::getInst(const char* dir) {
    DIR* dirp = opendir(dir);
    if (!dirp) {
        LOG_ERRNO("opendir(%s)", dir);
        return NULL;
    }

    Program* rv = getInst();
    if (!rv) {
        closedir(dirp);
        return NULL;
    }

    char fn[PATH_MAX+1];
    strcpy(fn, dir);
    char* fnp = fn+strlen(fn);
    *fnp = '/';
    fnp++;

    struct dirent* dp;
    while ((dp = readdir(dirp)) != NULL) {
        if (dp->d_type != DT_REG) continue;
        if (!strstr(dp->d_name, ".glsl")) continue;
        strcpy(fnp, dp->d_name);

        Shader* shader = Shader::getInst(fn);
        if (!shader) {
            delete rv;
            rv = NULL;
            break;
        }

        if (!rv->attach(shader)) {
            delete shader;
            delete rv;
            rv = NULL;
            break;
        }
    }

    closedir(dirp);
    return rv;
}


Program::~Program() {
    glDeleteProgram(program);
    for (std::vector<Shader*>::iterator it=shaders.begin(); it != shaders.end(); ++it) {
        delete *it;
    }
}


bool Program::attach(Shader* shader) {
    glAttachShader(program, *shader);
    shaders.push_back(shader);
    return true;
}