#define GL_GLEXT_PROTOTYPES
#include <GL/gl.h>
#include <stdlib.h>

#include "bigcube.h"

/* Allocate memory for BigCube and create OpenGL bindings */
BigCube createBigCube(int segments){
   BigCube new;
   int vertnum, indnum, face, x, y, icount = 0;

   vertnum = segments * segments * 6 * 3;
   indnum =  segments * segments * 6 * 6;

   new.vertices = malloc(sizeof(GLfloat) * vertnum);
   new.indices  = malloc(sizeof(GLuint) * indnum);
   new.indnum   = indnum;
   new.vernum   = vertnum;
   new.segments = segments;

      for(x = 0; x < segments; x++){
         for(y = 0; y < segments; y++){
            new.vertices[ (BCFRONTFACE  * segments * segments * 3) + ((segments * x * 3) + (y * 3)) + 0 ] = (float)x/(float)(segments-1);
            new.vertices[ (BCFRONTFACE  * segments * segments * 3) + ((segments * x * 3) + (y * 3)) + 1 ] = (float)y/(float)(segments-1);
            new.vertices[ (BCFRONTFACE  * segments * segments * 3) + ((segments * x * 3) + (y * 3)) + 2 ] = 0.0;

            new.vertices[ (BCBACKFACE   * segments * segments * 3) + ((segments * x * 3) + (y * 3)) + 0 ] = (float)x/(float)(segments-1);
            new.vertices[ (BCBACKFACE   * segments * segments * 3) + ((segments * x * 3) + (y * 3)) + 1 ] = (float)y/(float)(segments-1);
            new.vertices[ (BCBACKFACE   * segments * segments * 3) + ((segments * x * 3) + (y * 3)) + 2 ] = 1.0;

            new.vertices[ (BCTOPFACE    * segments * segments * 3) + ((segments * x * 3) + (y * 3)) + 0 ] = (float)x/(float)(segments-1);
            new.vertices[ (BCTOPFACE    * segments * segments * 3) + ((segments * x * 3) + (y * 3)) + 1 ] = 1.0;
            new.vertices[ (BCTOPFACE    * segments * segments * 3) + ((segments * x * 3) + (y * 3)) + 2 ] = (float)y/(float)(segments-1);

            new.vertices[ (BCBOTTOMFACE * segments * segments * 3) + ((segments * x * 3) + (y * 3)) + 0 ] = (float)x/(float)(segments-1);
            new.vertices[ (BCBOTTOMFACE * segments * segments * 3) + ((segments * x * 3) + (y * 3)) + 1 ] = 0.0;
            new.vertices[ (BCBOTTOMFACE * segments * segments * 3) + ((segments * x * 3) + (y * 3)) + 2 ] = (float)y/(float)(segments-1);

            new.vertices[ (BCLEFTFACE   * segments * segments * 3) + ((segments * x * 3) + (y * 3)) + 0 ] = 0.0;
            new.vertices[ (BCLEFTFACE   * segments * segments * 3) + ((segments * x * 3) + (y * 3)) + 1 ] = (float)y/(float)(segments-1);
            new.vertices[ (BCLEFTFACE   * segments * segments * 3) + ((segments * x * 3) + (y * 3)) + 2 ] = (float)x/(float)(segments-1);

            new.vertices[ (BCRIGHTFACE  * segments * segments * 3) + ((segments * x * 3) + (y * 3)) + 0 ] = 1.0;
            new.vertices[ (BCRIGHTFACE  * segments * segments * 3) + ((segments * x * 3) + (y * 3)) + 1 ] = (float)y/(float)(segments-1);
            new.vertices[ (BCRIGHTFACE  * segments * segments * 3) + ((segments * x * 3) + (y * 3)) + 2 ] = (float)x/(float)(segments-1);
         }
      }

   for(face = 0; face < 6; face++){
      for( x = 0; x < segments - 1; x++){
         for(y = 0; y < segments - 1; y++){
            new.indices[icount] = face * segments * segments + segments * x + y;
            icount++;
            new.indices[icount] = face * segments * segments + segments * (x+1) + y;
            icount++;
            new.indices[icount] = face * segments * segments + segments * (x+1) + y + 1;
            icount++;

            new.indices[icount] = face * segments * segments + segments * x + y;
            icount++;
            new.indices[icount] = face * segments * segments + segments * (x+1) + y + 1;
            icount++;
            new.indices[icount] = face * segments * segments + segments * x + y + 1;
            icount++;
         }
      }
   }

   glGenVertexArrays(1, &new.vao);
   glBindVertexArray(new.vao);

   glGenBuffers(2, new.vbo);
   glBindBuffer(GL_ARRAY_BUFFER, new.vbo[0]);
   glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * vertnum, new.vertices, GL_DYNAMIC_DRAW);
   glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
   glEnableVertexAttribArray(0);

   glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, new.vbo[1]);
   glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint) * indnum, new.indices, GL_STATIC_DRAW);

   return(new);
}

/* Release resources of BigCube */
void freeBigCube(BigCube bc){
   free(bc.indices);
   free(bc.vertices);
   glDeleteBuffers(2, bc.vbo);
   glDeleteVertexArrays(1, &bc.vao);
}

void drawBigCube(BigCube bc){
   glBindVertexArray(bc.vao);
   glDrawElements(GL_TRIANGLES, bc.indnum, GL_UNSIGNED_INT, 0);
}

void updateBigCube(unsigned char *buffer, BigCube *bc, int face){
   GLfloat *verts;
   int i,x,y;
   int coord;
   float dir, base;

   glBindBuffer(GL_ARRAY_BUFFER, bc->vbo[0]);
   verts = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
   for(i = 0; i < bc->segments*bc->segments; i++){

      x = i % bc->segments;
      y = i / bc->segments;

      /* Which coordinate should we change? Which direction? */
      switch(face){
         case BCFRONTFACE:
            coord = 2;
            dir   = -1.0;
            base  = 1.0;
            break;
         case BCBACKFACE:
            coord = 2;
            dir   = 1.0;
            base  = 0.0;
            break;
         case BCTOPFACE:
            coord = 1;
            dir   = 1.0;
            base  = 0.0;
            break;
         case BCBOTTOMFACE:
            coord = 1;
            dir   = -1.0;
            base  = 1.0;
            break;
         case BCLEFTFACE:
            coord = 0;
            dir   = -1.0;
            base  = 1.0;
            break;
         case BCRIGHTFACE:
            coord = 0;
            dir   = 1.0;
            base  = 0.0;
            break;
      }

      verts[ (face  * bc->segments * bc->segments * 3) + ((bc->segments * x * 3) + (y * 3)) + coord ] = base + (float)buffer[i] / 127.0 * dir;
   }
   glUnmapBuffer(GL_ARRAY_BUFFER);
   glBindBuffer(GL_ARRAY_BUFFER, 0);
}
