#define GL_GLEXT_PROTOTYPES
#include <GL/gl.h>
#include <stdlib.h>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include "antagonism.h"
#include "shader.h"
#include "renderer.h"
#include "bigcube.h"
#include "mesh3d.h"
#include "matrix.h"
#include "texture.h"
#include "cg_time.h"
#include "cg_x11.h"
#include "pointcloud.h"
#include "hardscene.h"

#define SCREENSHADERNUM 2
#define COLORBUFFER 2
#define BIGCNUM 40
#define ASPECT 1.7777
/* Global variables */

BigCube bigcube;
Renderer renderer;
float pmatrix[16];
Mesh3D *sky;
int beat = -1;
PointCloud pointcloud;
GLuint pc_shaders[2];
GLuint pc_prg;
GLuint bigc_shaders[2];
GLuint bigc_prg;
GLuint def_shaders[2];
GLuint def_prg;
/*Parameters for big cubes */
GLfloat offsets[BIGCNUM * 3];
GLfloat rot[BIGCNUM * 3];
GLfloat col[BIGCNUM * 3];
GLfloat siz[BIGCNUM];
GLint   choosen[BIGCNUM];
GLuint logo;
GLuint cloud;
GLuint lava;
GLuint grass;
GLuint travis;
GLuint shamen;
GLuint endtitle;
/* OpenGL related initialization */
void initOpenGL(){

   glClearColor(0.0, 0.0, 0.0, 0.0);
   glEnable(GL_DEPTH_TEST);
   glEnable(GL_BLEND);
   glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);

   perspectiveMatrix(pmatrix, 35.0, ASPECT, 0.1, 1500.0);
}

/* Init the demo related stuff */
void initAntagonism(int width, int height){
   int i;

   /* Random positions for the big cubes */
   for(i = 0; i < BIGCNUM * 3; i++){
      offsets[i] = ((int)(drand48()*100))/100.0 - 0.5;
      rot[i]     = drand48() - 0.5;
      col[i]     = 0.25 + drand48() / 4.0;
      siz[i/3]   = 0.95 + (drand48() / 10.0);
      choosen[i/3] = drand48() * 2.0;
   }

   initOpenGL();

   bigcube = createBigCube(BC_SEGMENTS);
   /* Set up a renderer */
   renderer = createRenderer(COLORBUFFER, SCREENSHADERNUM, width, height, ASPECT);

   /* These shaders for gbuffer output */
   bigc_shaders[0] = loadShader(GL_VERTEX_SHADER,   "shaders/bigc.vs");
   bigc_shaders[1] = loadShader(GL_FRAGMENT_SHADER, "shaders/bigc.fs");
   bigc_prg        = createProgram(2, bigc_shaders);

   pc_shaders[0]   = loadShader(GL_VERTEX_SHADER,   "shaders/pc.vs");
   pc_shaders[1]   = loadShader(GL_FRAGMENT_SHADER, "shaders/pc.fs");
   pc_prg          = createProgram(2, pc_shaders);

   def_shaders[0]  = loadShader(GL_VERTEX_SHADER,   "shaders/g.vs");
   def_shaders[1]  = loadShader(GL_FRAGMENT_SHADER, "shaders/g.fs");
   def_prg         = createProgram(2, def_shaders);

   /* Post processing shaders */
   renderer.screen_shaders[0]  = loadShader(GL_VERTEX_SHADER,   "shaders/post.vs");
   renderer.screen_shaders[1]  = loadShader(GL_FRAGMENT_SHADER, "shaders/post.fs");
   renderer.screen_prg         = createProgram(SCREENSHADERNUM, renderer.screen_shaders);

   /* Just for testing I can see the scene */
   sky = loadOBJ("data/sky.obj");

   loadHardSceneData();
   logo     = loadPNGTexture("data/logo.png");
   cloud    = loadPNGTexture("data/cloud.png");
   lava     = loadPNGTexture("data/plasma.png");
   grass    = loadPNGTexture("data/grass.png");
   travis   = loadPNGTexture("data/travis.png");
   shamen   = loadPNGTexture("data/shamen.png");
   endtitle = loadPNGTexture("data/endpic.png");

   pointcloud = createPC(PC_NUM, 0.0);

   /* Let's the play begin */
   startTime();
}

/**
  Draw common things, like background
*/
void commonParts(double time, float camera[16]){
   GLint loc;

   glUseProgram(def_prg);

   loc = glGetUniformLocation(def_prg, "pmatrix");
   glUniformMatrix4fv(loc, 1, GL_FALSE, pmatrix);

   loc = glGetUniformLocation(def_prg, "camera");
   glUniformMatrix4fv(loc, 1, GL_FALSE, camera);

   loc = glGetUniformLocation(def_prg, "time");
   glUniform1f(loc, time);

   drawMesh3D(sky);
}

/* Nice music scene */
void niceScene(double time){
   float camera[16];
   float eye[] = {-4.0,-8.0, 1.0};
   float pos[] = { 0.0, 0.0, 0.0};
   float up[]  = { 0.0, 1.0, 0.0};
   float freq;
   GLint loc;
   float ps = 3.0;

   freq = sin(time / 10.0) + 4.0;

   eye[0] = 4.0 * sin(time / freq);
   eye[2] = 1.0 * cos(time / freq);
   eye[1] = 4.0 + sin(time) * 2.0;
   lookAt(camera, eye, pos, up);

   commonParts(time, camera);

   glUseProgram(pc_prg);

   loc = glGetUniformLocation(pc_prg, "pmatrix");
   glUniformMatrix4fv(loc, 1, GL_FALSE, pmatrix);

   loc = glGetUniformLocation(pc_prg, "camera");
   glUniformMatrix4fv(loc, 1, GL_FALSE, camera);

   loc = glGetUniformLocation(pc_prg, "red");
   glActiveTexture(GL_TEXTURE0);

   if(time < 57.249){
      glUniform4f(loc, 0.45, 0.11, 0.09, 1.0);
      glBindTexture(GL_TEXTURE_2D, cloud);
   }
   else{
      glUniform4f(loc, 0.22, 0.29, 0.26, 1.0);
      glBindTexture(GL_TEXTURE_2D, lava);
      ps = 10.0;
   }

   loc = glGetUniformLocation(pc_prg, "tex");
   glUniform1i(loc, 0);

   loc = glGetUniformLocation(pc_prg, "pointsize");
   glUniform1f(loc, ps);

   updatePC(pointcloud, time, PC_REBORN);
   drawPC(pointcloud);
}

/* Hard music scene */
void hardScene(double time){
   unsigned char surface[BC_SEGMENTS*BC_SEGMENTS];
   int i;
   float camera[16], rotm[16];
   float eye[] = {-4.0, -8.0, 6.0};
   float pos[] = {0.0, 0.0, 0.0};
   float up[]  = {0.0, -1.0, 0.0};
   GLint loc;
   int dummy;
   float newtime;

   beat = hardSceneParameters(time, surface, eye);

   lookAt(camera, eye, pos, up);

   commonParts(time, camera);

   glUseProgram(bigc_prg);

   loc = glGetUniformLocation(bigc_prg, "pmatrix");
   glUniformMatrix4fv(loc, 1, GL_FALSE, pmatrix);

   loc = glGetUniformLocation(bigc_prg, "camera");
   glUniformMatrix4fv(loc, 1, GL_FALSE, camera);

   updateBigCube(surface, &bigcube, BCTOPFACE);
   updateBigCube(surface, &bigcube, BCRIGHTFACE);
   updateBigCube(surface, &bigcube, BCBACKFACE);
   updateBigCube(surface, &bigcube, BCBOTTOMFACE);
   updateBigCube(surface, &bigcube, BCLEFTFACE);
   updateBigCube(surface, &bigcube, BCFRONTFACE);

   /* Draw all the big cubes */
   for(i = 0; i < BIGCNUM; i++){
      /*Sorry, this parameter setting should be in hardscene.c */
      if(time < 72.051){
         newtime = (time * 10.0 - 57.642) / 1.739;
         dummy = newtime;
         if(dummy % 10 == 0){
           siz[i] = fabs(sin( (newtime - (float)dummy) / 0.3183) * 1.5);
         }else{
           siz[i] = 1.0;
         }
      }

      loc = glGetUniformLocation(bigc_prg, "offset");
      glUniform3f(loc, offsets[i*3] * 10.0, offsets[i*3+1] * 10.0, offsets[i*3+2] * 10.0);
      rotate(rotm, rot[i*3] * time, rot[i*3+1] * time, rot[i*3+2] * time);
      loc = glGetUniformLocation(bigc_prg, "rotation");
      glUniformMatrix4fv(loc, 1, GL_FALSE, rotm);
      loc = glGetUniformLocation(bigc_prg, "incolor");
      glUniform3f(loc, col[i*3], col[i*3+1], col[i*3+2]);
      loc = glGetUniformLocation(bigc_prg, "size");
      glUniform1f(loc, siz[i]);
      loc = glGetUniformLocation(bigc_prg, "choosen");
      glUniform1i(loc, choosen[i]);
      drawBigCube(bigcube);
   }
}

/* Draw scenes */
void playAntagonism(){
   double time;
   char *cbnames[] = {"color1", "color2", "depth"};
   GLuint loc;
   time = getTimeInterval();

   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

   /***************
     Geometry pass
    ***************/
   renderFirstStage(renderer);

   if(time < 57.249)                    niceScene(time);
   if(time > 57.249  && time < 113.186) hardScene(time);
   if(time > 113.186 && time < 174.00 ) niceScene(time);
   if(time > 174.00)                    beat = 9;

   /**********************
     Post processing pass
    **********************/
   renderSecondStage(renderer, cbnames);

   /*Additional shader settings */
   glActiveTexture(GL_TEXTURE0 + renderer.colorbuffernum + 1);
   /* Yes, it is ugly next time I will plan more carefully */
   switch(beat){
      case -1:
         glBindTexture(GL_TEXTURE_2D, logo);
         break;
      case 1:
         glBindTexture(GL_TEXTURE_2D, travis);
         break;
      case 3:
         glBindTexture(GL_TEXTURE_2D, shamen);
         break;
      case 5:
         glBindTexture(GL_TEXTURE_2D, grass);
         break;
      case 9:
         glBindTexture(GL_TEXTURE_2D, endtitle);
         break;
      default:
         glBindTexture(GL_TEXTURE_2D, 0);
   }
   loc = glGetUniformLocation(renderer.screen_prg, "logo");
   glUniform1i(loc, renderer.colorbuffernum+1);

   loc = glGetUniformLocation(renderer.screen_prg, "time");
   glUniform1f(loc, time);

   drawQuad(renderer);

   /* Time to say good bye */
   if(time > 180.00) cg_isrunning = 0;
}

/* Free the stuff */
void releaseAntagonism(){
   freeBigCube(bigcube);
   freeRenderer(renderer);
   freeMesh3D(sky);
   freePC(pointcloud);
   deleteProgram(pc_prg, 2, pc_shaders);
   deleteProgram(bigc_prg, 2, bigc_shaders);
}
