/**
 * Copyright (c) 2021 Dominic Masters
 * 
 * This software is released under the MIT License.
 * https://opensource.org/licenses/MIT
 */

#include "primitive.h"

void primitiveInit(primitive_t *primitive, int32_t verticeCount, int32_t indiceCount) {
  primitive->verticeCount = verticeCount;
  primitive->indiceCount = indiceCount;

  // size_t sizeIndices = sizeof(uint32_t) * verticeCount;
  size_t sizePositions = sizeof(float) * verticeCount * PRIMITIVE_POSITIONS_PER_VERTICE;
  size_t sizeCoordinates = sizeof(float) * verticeCount * PRIMITIVE_COORDINATES_PER_VERTICE;
  size_t sizeIndices = sizeof(indice_t) * indiceCount;

  // Create some buffers, one for the vertex data, one for the indices
  GLuint *buffer = malloc(sizeof(GLuint) * 2);
  glGenBuffers(2, buffer);

  primitive->vertexBuffer = buffer[0];
  primitive->indexBuffer = buffer[1];
  free(buffer);
  
  //Buffer an empty set of data then buffer each component
  glBindBuffer(GL_ARRAY_BUFFER, primitive->vertexBuffer);
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, primitive->indexBuffer);
  glBufferData(GL_ARRAY_BUFFER, sizePositions+sizeCoordinates, 0, GL_DYNAMIC_DRAW);
  glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeIndices, 0, GL_DYNAMIC_DRAW);

  size_t offset = 0;

  // Setup the attrib pointers
  glVertexAttribPointer(0, PRIMITIVE_POSITIONS_PER_VERTICE, GL_FLOAT,
    GL_FALSE, 0, (void *)offset
  );
  glEnableVertexAttribArray(0);
  offset += sizePositions;
  glVertexAttribPointer(1, PRIMITIVE_COORDINATES_PER_VERTICE, GL_FLOAT,
    GL_FALSE, 0, (void *)offset
  );
  glEnableVertexAttribArray(1);
}

void primitiveBufferVertices(primitive_t *primitive,
  int32_t position, int32_t count, vertice_t *vertices
) {
  // Memory
  size_t lengthPositions, lengthCoordinates, offsetPositions, offsetCoordinates;
  float *positions, *coordinates;
  int32_t i;

  // Setup the size of the memory that the positions and coordinates will use
  lengthPositions = sizeof(float) * PRIMITIVE_POSITIONS_PER_VERTICE * count;
  offsetPositions = sizeof(float) * PRIMITIVE_POSITIONS_PER_VERTICE * position;

  lengthCoordinates  = sizeof(float) * PRIMITIVE_COORDINATES_PER_VERTICE * count;
  offsetCoordinates = (
    (sizeof(float) * PRIMITIVE_POSITIONS_PER_VERTICE * primitive->verticeCount)+
    (sizeof(float) * PRIMITIVE_COORDINATES_PER_VERTICE * position)
  );

  // Create some memory
  positions = malloc(lengthPositions);
  coordinates = malloc(lengthCoordinates);

  // Now copy the positions and coordinates from the vertices into the buffer
  for(i = 0; i < count; i++) {
    positions[i * PRIMITIVE_POSITIONS_PER_VERTICE] = vertices[i].x;
    positions[i * PRIMITIVE_POSITIONS_PER_VERTICE + 1] = vertices[i].y;
    positions[i * PRIMITIVE_POSITIONS_PER_VERTICE + 2] = vertices[i].z;

    coordinates[i * PRIMITIVE_COORDINATES_PER_VERTICE] = vertices[i].u;
    coordinates[i * PRIMITIVE_COORDINATES_PER_VERTICE + 1] = vertices[i].v;
  }

  // Buffer the data into the GPU
  glBindBuffer(GL_ARRAY_BUFFER, primitive->vertexBuffer);
  glBufferSubData(GL_ARRAY_BUFFER, offsetPositions, lengthPositions, positions);
  glBufferSubData(GL_ARRAY_BUFFER, offsetCoordinates, lengthCoordinates, coordinates);

  // Free the vertices.
  free(positions);
  free(coordinates);
}

void primitiveBufferIndices(primitive_t *primitive, 
  int32_t position, int32_t count, indice_t *indices
) {
  size_t length, offset;
  offset = position * sizeof(indice_t);
  length = count * sizeof(indice_t);

  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, primitive->indexBuffer);
  glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, length, indices);
}

void primitiveDraw(primitive_t *primitive, int32_t start, int32_t count) {
  if(count == -1) count = primitive->indiceCount;

  // Re-Bind the buffers
  glBindBuffer(GL_ARRAY_BUFFER, primitive->vertexBuffer);
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, primitive->indexBuffer);

  // Re-Calculate the attrib pointers.
  size_t offset = 0;
  glVertexAttribPointer(0, PRIMITIVE_POSITIONS_PER_VERTICE, GL_FLOAT,
    GL_FALSE, 0, (void *)offset
  );
  glEnableVertexAttribArray(0);
  offset += sizeof(float) * primitive->verticeCount * PRIMITIVE_POSITIONS_PER_VERTICE;
  glVertexAttribPointer(1, PRIMITIVE_COORDINATES_PER_VERTICE, GL_FLOAT,
    GL_FALSE, 0, (void *)offset
  );
  glEnableVertexAttribArray(1);

  // Render the elements.
  glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_INT, (void *)(
    sizeof(indice_t)*start
  ));
}

void primitiveDispose(primitive_t *primitive) {
  glBindBuffer(GL_ARRAY_BUFFER, 0);
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
	glDeleteBuffers(1, &primitive->vertexBuffer);
	glDeleteBuffers(1, &primitive->indexBuffer);
}