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

/**
   Initialize a quad for post processing
   @param screenvao vertex array object id
   @param screenvbo vertex buffer object id
*/
void initQuad(GLuint *screenvao, GLuint *screenvbo){
   GLfloat v[] = {-1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0};

   glGenVertexArrays(1, screenvao);
   glBindVertexArray(screenvao[0]);
   glGenBuffers(1, screenvbo);
   glBindBuffer(GL_ARRAY_BUFFER, screenvbo[0]);
   glBufferData(GL_ARRAY_BUFFER, 8 * sizeof(GLfloat), v, GL_STATIC_DRAW);
   glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
   glEnableVertexAttribArray(0);

   glBindVertexArray(0);
}

/**
   Draw a quad for post processing
   @param renderer informations
*/
void drawQuad(Renderer renderer){
   glBindVertexArray(renderer.screenvao);
   glBindBuffer(GL_ARRAY_BUFFER, renderer.screenvbo);
   glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
}

/**
 * Calculate the dimension of viewports
 * We need two viewport. One for the first pass
 * and an other one for the second pass
*/
void calcViewDim(Renderer *renderer, int width, int height, float aspect){
   int newheight;

   newheight = (float)width / aspect;

   /* Framebuffer viewport */
   renderer->fviewport[0] = 0;
   renderer->fviewport[1] = 0;
   renderer->fviewport[2] = width;
   renderer->fviewport[3] = newheight;

   /* Screen viewport (this is what you see) */
   renderer->vviewport[0] = 0;
   renderer->vviewport[1] = (height - newheight) / 2;
   renderer->vviewport[2] = width;
   renderer->vviewport[3] = newheight;
}

/**
 * Create a Renderer
 *
 * @param colorbuffernum     Number of the color buffers
 * @param screenshadernum    Number of shaders those draw second pass
 * @param width              Width of buffers
 * @param height             Height of buffers
*/
Renderer createRenderer(unsigned int colorbuffernum, unsigned int screenshadernum, int width, int height, float aspect){
   Renderer renderer;
   unsigned int i;

   /* Non OpenGL based initialization */
   renderer.colorbuffernum   = colorbuffernum;

   renderer.colorbuffers     = malloc(sizeof(GLuint) * colorbuffernum);
   renderer.bufferids        = malloc(sizeof(GLenum) * colorbuffernum);
   renderer.screen_shaders   = malloc(sizeof(GLuint) * screenshadernum);

   /* Quad for the second pass drawing */
   initQuad(&renderer.screenvao, &renderer.screenvbo);

   /* Viewport dim calculations */
   calcViewDim(&renderer, width, height, aspect);

   /* Generate the framebuffer for the first pass */
   glGenFramebuffers(1, &renderer.framebuffer);
   glBindFramebuffer(GL_FRAMEBUFFER, renderer.framebuffer);

   /* Storage for the depth component */
   glGenTextures(1, &renderer.depthbuffer);
   glBindTexture(GL_TEXTURE_2D, renderer.depthbuffer);
   glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, renderer.fviewport[2], renderer.fviewport[3], 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE);
   glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, renderer.depthbuffer, 0);

   /* Textures for the color components (color, normal, etc) */
   glGenTextures(colorbuffernum, renderer.colorbuffers);
   for(i = 0; i < colorbuffernum; i++){
      glBindTexture(GL_TEXTURE_2D, renderer.colorbuffers[i]);
      glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, renderer.fviewport[2], renderer.fviewport[3], 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
      glGenerateMipmap(GL_TEXTURE_2D);
      glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_TEXTURE_2D, renderer.colorbuffers[i], 0);

      renderer.bufferids[i] = GL_COLOR_ATTACHMENT0 + i;
   }

   /* Everything is fine? */
   if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE){
      printf("Not complete\n");
   }

   return(renderer);
}

/**
   Free the renderer resources
   @param renderer informations
*/
void freeRenderer(Renderer renderer){
/*FIXME check everything, maybe some object release is missing */
   glDeleteBuffers(1, &renderer.screenvbo);
   glDeleteTextures(renderer.colorbuffernum, renderer.colorbuffers);
   glDeleteTextures(1, &renderer.depthbuffer);
   glDeleteVertexArrays(1, &renderer.screenvao);
   glDeleteFramebuffers(1, &renderer.framebuffer);

   free(renderer.colorbuffers);
   free(renderer.bufferids);
}

/**
   First renderer for geometry
   @param renderer information
*/
void renderFirstStage(Renderer renderer){
   glBindFramebuffer(GL_FRAMEBUFFER, renderer.framebuffer);
   glViewport(renderer.fviewport[0], renderer.fviewport[1], renderer.fviewport[2], renderer.fviewport[3]);
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   glDrawBuffers(renderer.colorbuffernum, renderer.bufferids);
}

/**
   Renderer for post processing
   @param renderer the renderer informations
   @param uniform variable names for shaders. The last
          name should be the depth texture name
*/
void renderSecondStage(Renderer renderer, char **varname){
   unsigned int i;
   GLint loc;

   glBindFramebuffer(GL_FRAMEBUFFER, 0);
   glViewport(renderer.vviewport[0], renderer.vviewport[1], renderer.vviewport[2], renderer.vviewport[3]);
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   glUseProgram(renderer.screen_prg);
  
   for(i = 0; i < renderer.colorbuffernum + 1; i++){
      glActiveTexture(GL_TEXTURE0 + i);

      if(i == renderer.colorbuffernum){
         /* Last texture for depth component */
         glBindTexture(GL_TEXTURE_2D, renderer.depthbuffer);
      }
      else{
         /* Texture for color information */
         glBindTexture(GL_TEXTURE_2D, renderer.colorbuffers[i]);
      }

      loc = glGetUniformLocation(renderer.screen_prg, varname[i]);
      glUniform1i(loc, i);
   }

}
