#include "mesh.hpp"


unsigned Mesh::iso2tile_x(GLfloat a) const {
    assert(a >= -1.0f && a <= 1.0f);
    a /= len_x;
    return (float)(w/2) + round(a);
}


unsigned Mesh::iso2tile_y(GLfloat a) const {
    assert(a >= -1.0f && a <= 1.0f);
    a /= len_y;
    return (float)(h/2) + round(a);
}


GLfloat Mesh::tile2iso_x(unsigned a, bool mid) const {
    assert(a < w);
    return -1.0 + off_x + ((float)a*len_x);
}


GLfloat Mesh::tile2iso_y(unsigned a, bool mid) const {
    assert(a < h);
    return -1.0 + off_y + ((float)a*len_y);
}


static unsigned greatest_odd(unsigned a) { return (a%2)? a: a-1; }


Mesh::Mesh(const Window* win, unsigned hh): h(greatest_odd(hh)), w(greatest_odd(floor(float(h)*(float)win->w)/(float)win->h)) {
    assert(w && h);
    assert(h % 2 == 1); // odd, s.t. the player can be at 0.0/0.0
    assert(w % 2 == 1);
    vertices_num = w * h * 4 * 2 * 2;
    vertices_len = vertices_num * sizeof(GLfloat);
    vertices = (GLfloat*)malloc(vertices_len); // can't we overlap and thus re-use vertices?

    // make sure 0.0 is at the center and the aspect is preserved (square)
    float dim_x = (float)win->w/(float)w;
    float dim_y = (float)win->h/(float)h;
    if (dim_x < dim_y) { // y tiles would be too wide and need os
        off_x = 0.0f;
        off_y = ((2.0f*dim_y/dim_x)-2.0f) / 2.0f;
    } else {
        off_x = ((2.0f*dim_x/dim_y)-2.0f) / 2.0f;
        off_y = 0.0f;
    }
    len_x = (2.0f - (2.0f*off_x)) / (float)w;
    len_y = (2.0f - (2.0f*off_y)) / (float)h;


    // we dont use stripes so we can hardcode texture positions
    GLfloat* p = vertices;
    for (unsigned y=0; y<h; ++y) {
        GLfloat y_start = tile2iso_y(y);
        GLfloat y_end   = (y==h-1)? 1.0f-off_y: tile2iso_y(y+1);
        for (unsigned x=0; x<w; ++x) {
            GLfloat x_start = tile2iso_x(x);
            GLfloat x_end   = (x==w-1)? 1.0f-off_x: tile2iso_x(x+1);
            // top left
            *(p++) = x_start;
            *(p++) = y_end;
            *(p++) = 0.0f;
            *(p++) = 0.0f;
            // bottom left
            *(p++) = x_start;
            *(p++) = y_start;
            *(p++) = 0.0f;
            *(p++) = 1.0f;
            // top right
            *(p++) = x_end;
            *(p++) = y_end;
            *(p++) = 1.0f;
            *(p++) = 0.0f;
            // bottom right
            *(p++) = x_end;
            *(p++) = y_start;
            *(p++) = 1.0f;
            *(p++) = 1.0f;
        }
    }

    // 0,1,2, 1,2,3, 4,5,6, 5,6,7
    size_t elements_num = w * h * 6;
    elements_len = elements_num * sizeof(GLuint);
    elements = (GLuint*)malloc(elements_len);
    GLuint* q = elements;
    for (GLuint i=0; i<w*h; ++i) {
        GLuint ii = i*4;
        *(q++) = ii+0;
        *(q++) = ii+1;
        *(q++) = ii+2;
        *(q++) = ii+1;
        *(q++) = ii+2;
        *(q++) = ii+3;
    }
}


void Mesh::draw(unsigned line, unsigned pos, unsigned len) const {
    assert(line < h);
    assert(pos+len-1 < w);
    glDrawElements(GL_TRIANGLES, 6*len, GL_UNSIGNED_INT, (void*)((intptr_t)((line*w)+pos)*6*sizeof(GLuint)));
}


Mesh::~Mesh() {
    free(vertices);
    free(elements);
}