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


Ppm::Ppm(GLfloat* bb, GLsizei ww, GLsizei hh, char* nn): buf(bb), w(ww), h(hh), name(nn) {
}


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

    int l = 0;
    unsigned type;
    unsigned ww, hh, max;
    if (sscanf(src, "P%u %u %u %u %n", &type, &ww, &hh, &max, &l) == 4) {
    } else if (sscanf(src, "P%u #%*[^\n] %u %u %u %n", &type, &ww, &hh, &max, &l) == 4) {
    } else {
        LOG("'%s': invalid PPM header", fn);
        free(src);
        return NULL;
    }
    unsigned char* p = (unsigned char*)src + l;

    const unsigned total = ww*hh*3;
    if (!ww || !hh || !max || len != total+l) {
        LOG("'%s': invalid PPM size", fn);
        free(src);
        return NULL;
    }

    GLfloat* buf = (GLfloat*)malloc(ww*hh*3*sizeof(GLfloat));
    if (type == 6) {
        for (unsigned i=0; i<total; ++i) {
            buf[i] = (float)p[i] / (float)max; // TODO: textures start at (0,0), PPMs at (0,h) or so
            assert(buf[i] >= 0.0 && buf[i] <= 1.0);
        }
    } else if (type == 3) {
        for (unsigned i=0; i<ww*hh*3; i+=3) {
            unsigned r, g, b;
            if (sscanf((char*)p, "%u %u %u%n", &r, &g, &b, &l) != 3) {
                LOG("'%s': cannot parse PPM pixel", fn);
                free(src);
                free(buf);
                return NULL;
            }
            buf[i+0] = (float)r / (float)max;
            buf[i+1] = (float)g / (float)max;
            buf[i+2] = (float)b / (float)max;
            assert(buf[i+0] >= 0.0 && buf[i+0] <= 1.0);
            assert(buf[i+1] >= 0.0 && buf[i+1] <= 1.0);
            assert(buf[i+2] >= 0.0 && buf[i+2] <= 1.0);
        }
    } else {
        LOG("'%s': unknown PPM type", fn);
    }
    free(src);

    const char* s = strrchr(fn, '/') ?: fn;
    const char* e = strrchr(fn, '.') ?: fn+strlen(fn)-1;
    char* n = strndup(s+1, e-s-1);

    return new Ppm(buf, ww, hh, n);
}


Ppm::~Ppm() {
    free((void*)buf);
    free((void*)name);
}


GLuint Texture::textures[80] = {};
unsigned Texture::tex_no = 0;
unsigned Texture::refcount = 0;


GLenum Texture::os2tex(unsigned i) {
    return GL_TEXTURE0 + i; // TODO: check if we do need a switch statement for all possible values here
}


Texture::Texture(const char* id): index(tex_no++), name(textures[index]), tex(os2tex(index)), file(strdup(id)), w(0), h(0) {
    assert((unsigned)index < sizeof(textures)/sizeof(textures[0]));
    if (refcount++ == 0) {
        glGenTextures(sizeof(textures)/sizeof(textures[0]), textures);
    }
    glActiveTexture(tex);
    glBindTexture(GL_TEXTURE_2D, name); // TODO: tilemap using glTexSubImage3D, glTexSubImage2D, GL_TEXTURE_2D_ARRAY?
}


bool Texture::set(const GLfloat* buf, GLsizei ww, GLsizei hh, bool mipmap) {
    glActiveTexture(tex);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, ww, hh, 0, GL_RGB, GL_FLOAT, buf);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    if (mipmap) {
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
        glGenerateMipmap(GL_TEXTURE_2D);
    } else {
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    }

    w = ww;
    h = hh;
    return true;
}


bool Texture::set(const unsigned char* buf, GLsizei ww, GLsizei hh) {
    glActiveTexture(tex);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, ww, hh, 0, GL_RED, GL_UNSIGNED_BYTE, buf);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    w = ww;
    h = hh;
    return true;
}


Texture::~Texture() {
    free((void*)file);
    if (--refcount == 0) {
        glDeleteTextures(sizeof(textures)/sizeof(textures[0]), textures);
        tex_no = 0;
    }
}


Texture* Texture::load(const char* fn) {
    Ppm* im = Ppm::getInst(fn);
    if (!im) {
        return NULL;
    }
    Texture* tx = new Texture(im->name);
    if (!tx->set(im->buf, im->w, im->h, false)) { // TODO: can enable mipmaps again due to aliasing?
        delete tx;
        delete im;
        return NULL;
    }
    delete im;

    return tx;
}


bool Texture::load(const char* dir, std::vector<Texture*>& vec) {
    DIR* dirp = opendir(dir);
    if (!dirp) {
        LOG_ERRNO("opendir(%s)", dir);
        return false;
    }

    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;
        strcpy(fnp, dp->d_name);

        Texture* tx = load(fn);
        if (tx) {
            vec.push_back(tx);
        }
    }

    closedir(dirp);
    return true;
}