/*!\file window.c
 * \brief GL4Dummies, exemple simple 3D avec GL4Dummies
 * \author Farès BELHADJ, amsi@up8.edu
 * \date jannuary 20, 2025
 */

/* inclusion des entêtes de fonctions de création et de gestion de
 * fenêtres système ouvrant un contexte favorable à GL4dummies. Cette
 * partie est dépendante de la bibliothèque SDL2 */
#include <GL4D/gl4duw_SDL2.h>
#include <GL4D/gl4du.h>
#include <GL4D/gl4dm.h>
#include <GL4D/gl4dg.h>
#include <GL4D/gl4dp.h>
#include <SDL_image.h>
#include <stdlib.h> // Pour rand
#include <vector>
#include "assimp.h"
#include "animations.h"
#include "audioHelper.h"
#include "bezier.hpp"
#include "point3d.hpp"
#include "waves.hpp"


#define T_SECT_AM 20
#define WAVE_AMOUNT 10
#define MAX_POS_WAVES 10
// Constantes des vagues
#define SPEED 2.5f

static void init(void);
static void draw(void);
static void quit(void);
static bool checkWaterIntersect(const Point3d& , const Point3d& , double, float, bool);
static void draw_tentacle(const BAnim&, const BAnim&, double , bool, const Point3d&);
static void draw_monster(Point3d& , double);
static void draw_waves(double);

// Programmes GPU
static GLuint _tentId = 0;
static GLuint _wavesId = 0;
static GLuint _skyId = 0;
static GLuint _assimpId = 0;

// Geom
static GLuint _tentacle = 0;
static GLuint _water = 0;
static GLuint _sky = 0;
static GLuint _angler = 0;
static GLuint _boat = 0;

/* taille de la fenêtre */
static int _ww = 1920, _wh = 1080;

static GLuint _noiseId = 0;
static GLuint _skyCubeMap = 0;

// Emission distance of Positional light
#define LP_EMI 30.0f
static Point3d _directional = {0.0f,-1.0f, 1.0f};

// Bézier à interpoler pour le mouvement de la tentacule

static BAnim _tentBezier1SW = { {
  {  {
		  Point3d{0.0, 0.0 , 0.0},
		  Point3d{0.0, 0.33, 0.0},
		  Point3d{0.0, 0.66, 0.0},
		  Point3d{0.0, 1.0 , 0.0}
	  },30.0}, // Idle
  {  {
		  Point3d{0.0,0.0,0.0},
		  Point3d{0.0,7.0,0.0},
		  Point3d{-2.5,14.0,0.0},
		  Point3d{-2.5,19.0,0.0}
	  }, 15.0},
  {  {
		  Point3d{0.0,0.0,0.0},
		  Point3d{-5.0,20.0,-5.0},
		  Point3d{-9.0, -2.0,-9.0},
		  Point3d{-15.0,13.0,-15.0}
	  }, 55.0},
  {{
		Point3d{  0.0f,  0.0f, 0.0f},
		Point3d{  0.0f, 5.0f, 0.0f},
		Point3d{  0.0f, 5.0f, 0.0f},
		Point3d{  0.0f, 10.0f, 0.0f}
	  }, 5.0},
    {{
		Point3d{  0.0f,  0.0f, 0.0f},
		Point3d{-15.0f, 20.0f, 0.0f},
		Point3d{  0.0f, 10.0f,12.0f},
		Point3d{  0.0f, 10.0f, 0.0f}
		}, 8.0}
  	}};
static BAnim _tentBezier1NW = { {
  {  {
		  Point3d{0.0, 0.0 , 0.0},
		  Point3d{0.0, 0.33, 0.0},
		  Point3d{0.0, 0.66, 0.0},
		  Point3d{0.0, 1.0 , 0.0}
	  },30.0}, // Idle
  {  {
		  Point3d{0.0,0.0,0.0},
		  Point3d{0.0,7.0,0.0},
		  Point3d{-2.5,14.0,0.0},
		  Point3d{-2.5,19.0,0.0}
	  }, 15.0},
  {  {
		  Point3d{ 0.0, 0.0, 0.0},
		  Point3d{ 0.0,25.0, 0.0},
		  Point3d{5.0,25.0,-5.0},
		  Point3d{5.0, 0.0,-5.0}
	  }, 55.0},
  {{
		Point3d{  0.0f,  0.0f, 0.0f},
		Point3d{  0.0f, 5.0f, 0.0f},
		Point3d{  0.0f, 5.0f, 0.0f},
		Point3d{  0.0f, 10.0f, 0.0f}
	  }, 5.0},
    {{
		Point3d{  0.0f,  0.0f, 0.0f},
		Point3d{  0.0f, 20.0f, 0.0f},
		Point3d{  0.0f,  0.0f, 0.0f},
		Point3d{  0.0f, 10.0f, 0.0f}
		}, 8.0}
  	}};
static BAnim _tentBezier1NE = { {
  {  {
		  Point3d{0.0, 0.0 , 0.0},
		  Point3d{0.0, 0.33, 0.0},
		  Point3d{0.0, 0.66, 0.0},
		  Point3d{0.0, 1.0 , 0.0}
	  },30.0}, // Idle
  {  {
		  Point3d{0.0,0.0,0.0},
		  Point3d{0.0,7.0,0.0},
		  Point3d{-2.5,14.0,0.0},
		  Point3d{-2.5,19.0,0.0}
	  }, 15.0},
  {  {
		  Point3d{0.0,0.0,0.0},
		  Point3d{15.0,20.0,0.0},
		  Point3d{0.0,10.0,12.0},
		  Point3d{0.0,10.0,0.0}
	  }, 55.0},
  {{
		Point3d{  0.0f,  0.0f, 0.0f},
		Point3d{  2.0f,  5.0f,3.0f},
		Point3d{  0.0f,  5.0f,8.0f},
		Point3d{  3.0f, 10.0f,10.0f}
	  }, 16.0},
  	}};
static BAnim _tentBezier1SE = { {
  {  {
		  Point3d{0.0, 0.0 , 0.0},
		  Point3d{0.0, 0.33, 0.0},
		  Point3d{0.0, 0.66, 0.0},
		  Point3d{0.0, 1.0 , 0.0}
	  },30.0}, // Idle
  {  {
		  Point3d{0.0,0.0,0.0},
		  Point3d{0.0,7.0,0.0},
		  Point3d{-2.5,14.0,0.0},
		  Point3d{-2.5,19.0,0.0}
	  }, 15.0},
  {  {
		  Point3d{ 0.0, 0.0, 0.0},
		  Point3d{ 0.0,25.0, 0.0},
		  Point3d{-5.0,25.0,5.0},
		  Point3d{-5.0, 0.0,5.0}
	  }, 55.0},
  {{
		Point3d{  0.0f,  0.0f, 0.0f},
		Point3d{  0.0f,  5.0f,3.0f},
		Point3d{ -1.0f, 10.0f,8.0f},
		Point3d{ -1.0f, 15.0f,10.0f}
	  }, 16.0},
  	}};

static BAnim _tentBezier2 = {{
  {  {
		  Point3d{0.0, 0.0 , 0.0},
		  Point3d{0.0, 0.33, 0.0},
		  Point3d{0.0, 0.66, 0.0},
		  Point3d{0.0, 1.0 , 0.0}
	  },30.0}, // Idle
  {  {
		  Point3d{0.0,0.0,0.0},
		  Point3d{0.0,7.0,0.0},
		  Point3d{2.5,14.0,0.0},
		  Point3d{2.5,19.0,0.0}
	  }, 15.0},
  {  {
		  Point3d{0.0, 0.0,0.0},
		  Point3d{0.0,10.0,0.0},
		  Point3d{0.0, 8.0,-10.0},
		  Point3d{0.0,15.0,-15.0}
	  }, 55.0},
    {{
		Point3d{  0.0f, 0.0f, 0.0f},
		Point3d{  0.0f,20.0f,12.0f},
		Point3d{  0.0f,10.0f,4.0f},
		Point3d{  0.0f,13.0f,15.0f}
		}, 15.0}
	}};

static BAnim _tentBezier3 = {{
  {  {
		  Point3d{0.0, 0.0 , 0.0},
		  Point3d{0.0, 0.33, 0.0},
		  Point3d{0.0, 0.66, 0.0},
		  Point3d{0.0, 1.0 , 0.0}
	  },30.0}, // Idle
  {  {
		  Point3d{0.0,0.0,0.0},
		  Point3d{0.0,7.0,0.0},
		  Point3d{2.5,14.0,0.0},
		  Point3d{2.5,19.0,0.0}
	  }, 15.0},
  {  {
		  Point3d{ 0.0, 0.0, 0.0},
		  Point3d{ 0.0,25.0, 0.0},
		  Point3d{-5.0,25.0,-5.0},
		  Point3d{-5.0, 0.0,-5.0}
	  }, 55.0},
    {{
		Point3d{  0.0f,  0.0f, 0.0f},
		Point3d{-15.0f, 20.0f, 0.0f},
		Point3d{  0.0f, 10.0f,12.0f},
		Point3d{  0.0f, 10.0f, 0.0f}
		}, 5.0},
    {{
		Point3d{  0.0f,  0.0f, 0.0f},
		Point3d{-15.0f, -20.0f, 0.0f},
		Point3d{  0.0f, -10.0f,12.0f},
		Point3d{  0.0f, -10.0f, 0.0f}
		}, 13.0}
	}};

static BAnim _tentBezier4 = {{
  {  {
		  Point3d{0.0, 0.0 , 0.0},
		  Point3d{0.0, 0.33, 0.0},
		  Point3d{0.0, 0.66, 0.0},
		  Point3d{0.0, 1.0 , 0.0}
	  },30.0}, // Idle
  {  {
		  Point3d{0.0,0.0,0.0},
		  Point3d{0.0,7.0,0.0},
		  Point3d{2.5,14.0,0.0},
		  Point3d{2.5,19.0,0.0}
	  }, 15.0},
  {  {
		  Point3d{0.0,0.0,0.0},
		  Point3d{15.0,20.0,0.0},
		  Point3d{0.0,10.0,12.0},
		  Point3d{0.0,10.0,0.0}
	  }, 55.0},
    {{
		Point3d{  0.0f,  0.0f, 0.0f},
		Point3d{-15.0f, 20.0f, 0.0f},
		Point3d{  0.0f, 10.0f,12.0f},
		Point3d{  0.0f, 10.0f, 0.0f}
		}, 5.0},
    {{
		Point3d{  0.0f,  0.0f, 0.0f},
		Point3d{-15.0f, -20.0f, 0.0f},
		Point3d{  0.0f, -10.0f,12.0f},
		Point3d{  0.0f, -10.0f, 0.0f}
		}, 5.0}
	}};
static BAnim _tentBezier5 = {{
  {  {
		  Point3d{0.0, 0.0 , 0.0},
		  Point3d{0.0, 0.33, 0.0},
		  Point3d{0.0, 0.66, 0.0},
		  Point3d{0.0, 1.0 , 0.0}
	  },30.0}, // Idle
  {  {
		  Point3d{0.0,0.0,0.0},
		  Point3d{0.0,7.0,0.0},
		  Point3d{2.5,14.0,0.0},
		  Point3d{2.5,19.0,0.0}
	  }, 15.0},
  {  {
		  Point3d{ 0.0, 0.0, 0.0},
		  Point3d{ 0.0,25.0, 0.0},
		  Point3d{5.0,25.0,5.0},
		  Point3d{5.0, 0.0,5.0}
	  }, 55.0},
    {{
		Point3d{  0.0f,  0.0f, 0.0f},
		Point3d{-15.0f, 20.0f, 0.0f},
		Point3d{  0.0f, 10.0f,12.0f},
		Point3d{  0.0f, 10.0f, 0.0f}
		}, 5.0},
    {{
		Point3d{  0.0f,  0.0f, 0.0f},
		Point3d{-15.0f, -20.0f, 0.0f},
		Point3d{  0.0f, -10.0f,12.0f},
		Point3d{  0.0f, -10.0f, 0.0f}
		}, 5.0}
	}};


static BAnim _anglerAnim = { {
		{{Point3d{0.0f, -50.0f, 300.0f}},10.0}, // Making the fish wait at the start
    {  {
		  Point3d{0.0f, -50.0f, 400.0f},
		  Point3d{0.0f, -20.0f, 300.0f},
	   }, 20.0},
	{  {
		  Point3d{0.0f, -20.0f, 300.0f},
		  Point3d{0.0f,  10.0f, 300.0f},
		  Point3d{0.0f,  10.0f, 250.0f},
		  Point3d{0.0f, -50.0f, 250.0f},
	   }, 15.0},
	{  {
		  Point3d{50.0f, -40.0f, 250.0f},
		  Point3d{50.0f,  10.0f, 250.0f},
		  Point3d{50.0f,  10.0f, 200.0f},
		  Point3d{50.0f, -40.0f, 200.0f},
	   }, 10.0},
	{  {
		  Point3d{-40.0f, -40.0f, 200.0f},
		  Point3d{-40.0f,  12.0f, 200.0f},
		  Point3d{-40.0f,  12.0f, 175.0f},
		  Point3d{-40.0f, -40.0f, 175.0f}
	   }, 10.0},
	{  {
		  Point3d{20.0f,-40.0f, 175.0f},
		  Point3d{20.0f, 13.0f, 175.0f},
		  Point3d{20.0f, 13.0f, 135.0f},
		  Point3d{20.0f, -40.0f, 135.0f}
		},10.0},
	{  {
		  Point3d{0.0f, -50.0f, 100.0f},
		  Point3d{0.0f, 15.0f, 100.0f},
		  Point3d{0.0f, 15.0f, 75.0f},
		  Point3d{0.0f, -20.0f, 75.0f}
		}, 15.0},
	{  {
		  Point3d{0.0f, -20.0f, 75.0f},
		  Point3d{0.0f, -15.0f, 65.0f},
		  Point3d{0.0f, -20.0f, 55.0f},
		  Point3d{0.0f, -25.0f, 45.0f},
		  Point3d{0.0f, -20.0f, 35.0f}
		}, 10.0},
	{  {
		  Point3d{0.0f, -20.0f, 35.0f},
		  Point3d{0.0f, 6.0f, 35.0f}
		}, 5.0},
	{ {
		  Point3d{0.0f, 6.0f, 35.0f}}, 100.0f}
	} };

static Bezier _boatAnim ={
	{
		Point3d{-250.0f, 20.0f, 1000.0f},
		Point3d{1250.0f, 20.0f, 1000.0f},
	},100.0f
};

#define DEB_CAM_ANIM 105.0f
static BAnim _camAnim = {{
		{{Point3d{0.0f,17.0f,00.0f}},DEB_CAM_ANIM}, // Waiting for the fish to come
		{{
				Point3d{0.0f, 17.0f, 0.0f},
				Point3d{0.0f, 20.0f, 0.0f},
				Point3d{0.0f, 20.0f, 5.0f},
				Point3d{0.0f, 25.0f, 5.0f}
			}, 3.0f},
		{{
				Point3d{0.0f, 25.0f, 5.0f},
				Point3d{0.0f, 13.0f, 5.0f},
				Point3d{2.0f, 5.0f, 3.0f},
				Point3d{-1.0f, -2.0f, -2.0f}
			}, 4.0f}
	} };

static Point3d _camPos = Point3d{0.0f,17.0f,00.0f};
static Point3d _camLookAt = Point3d{0.0f,17.0f,1.0f};

static GLfloat _waves[WAVE_AMOUNT * 5];
static std::vector<PosWaves> _posWaves;

/*
int main(int argc, char ** argv) {
  if(!gl4duwCreateWindow(argc, argv,
			 "API8 64Mo Scene 2", 
			 10, 10, _ww, _wh, 
						 GL4DW_SHOWN | GL4DW_FULLSCREEN)){
    return 1;
  }

  
  init();
  
  atexit(quit);
  
  gl4duwDisplayFunc(draw);
  
  gl4duwMainLoop();
  return 0;
}
*/

void scene2(int state){
	switch(state){
	case GL4DH_INIT:
		init();
		return;
	case GL4DH_FREE:
		quit();
		return;
	case GL4DH_UPDATE_WITH_AUDIO:
		return;
	default:
		draw();
		return;
	}
}


/* initialise des paramètres GL et GL4D */
void init(void) {
  srand(1312);
  /* activer la synchronisation verticale */
  SDL_GL_SetSwapInterval(1);
  /* activer l'utilisation du depth test */
  glEnable(GL_DEPTH_TEST);
  /* set la couleur d'effacement OpenGL */
  glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
  /* on active le rendu en mode remplissage */
  glPolygonMode(GL_FRONT_AND_BACK, /*GL_LINE*/GL_FILL);
  // Génération géométrie
  _tentacle = gl4dgGenGrid2df(T_SECT_AM, T_SECT_AM);
  _water = gl4dgGenGrid2df(1000,1000);
  _sky = gl4dgGenCubef();
  _angler = assimpGenScene("models/angler/scene.gltf");
  _boat = assimpGenScene("models/bato/scene.gltf");
  
  /* créer un programme GPU pour OpenGL (en GL4D) */
  _tentId = gl4duCreateProgram("<vs>shaders/tentacle.vs",
							   "<fs>shaders/grayscale_phong.fs", NULL);
  _wavesId = gl4duCreateProgram("<vs>shaders/waves.vs",
								"<fs>shaders/grayscale_phong.fs", NULL);
  _skyId = gl4duCreateProgram("<vs>shaders/sky.vs",
							  "<fs>shaders/grayscale_sky.fs", NULL);
  _assimpId = gl4duCreateProgram("<vs>shaders/assimp.vs",
								 "<gs>shaders/assimp.gs",
								 "<fs>shaders/grayscale_assimp.fs", NULL);
  
  // Gen Matrix
  gl4duGenMatrix(GL_FLOAT, "model");
  gl4duGenMatrix(GL_FLOAT, "view");
  gl4duGenMatrix(GL_FLOAT, "proj");

  // Frustum
  gl4duBindMatrix("proj");
  gl4duLoadIdentityf();
  gl4duFrustumf(-0.05, 0.05, -0.05 * _wh/(GLfloat) _ww, 0.05 * _wh/(GLfloat)_ww,
				0.05, 1000);

  // Génération du bruit de perlin
  GLfloat* map = gl4dmTriangleEdge(_ww, _wh, 0.45f);
  glGenTextures(1, &_noiseId);
  glBindTexture(GL_TEXTURE_2D, _noiseId);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  // Setting the repeat correctly
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_R32F, _ww, _wh, 0, GL_RED, GL_FLOAT, map);
  glBindTexture(GL_TEXTURE_2D, 0);
  free(map);

  // Loading de la skybox
  glGenTextures(1, &_skyCubeMap);
  glBindTexture(GL_TEXTURE_CUBE_MAP, _skyCubeMap);
  const std::string _cm[6] = {"textures/skybox/px.png", "textures/skybox/nx.png",
	  "textures/skybox/py.png", "textures/skybox/ny.png",
	  "textures/skybox/pz.png", "textures/skybox/nz.png"};
  SDL_Surface *s, *d;
  for(int i = 0; i < 6 ; ++i){
	  s = IMG_Load(_cm[i].c_str());
	  assert(s);
	  d = SDL_CreateRGBSurface(0, s->w, s->h, 32, R_MASK, G_MASK, B_MASK, A_MASK);
	  SDL_BlitSurface(s, NULL, d, NULL);
	  glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i,
				   0, GL_RGBA, d->w, d->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, d->pixels);
	  SDL_FreeSurface(s);
	  SDL_FreeSurface(d);
  }
  glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
  glBindTexture(GL_TEXTURE_CUBE_MAP, 0);

  // Init waves
  const float rand_max = RAND_MAX;
  int i;
  float norm;
  float wl = 20.0f, amp = 0.6f;
  const float wlMod = 1.12f, aMod = 0.82f;
  /* waves[0] = wavelength
   * waves[1] = amplitude
   * waves[2] = speed
   * waves[3] = direction.x
   * waves[4] = direction.y
   */
  for(i = 0; i < WAVE_AMOUNT * 5 ; i+=5){
	  _waves[i] = 2.0/wl;
	  _waves[i+1] = amp ;
	  _waves[i+2] = (rand()/rand_max) * SPEED;
	  _waves[i+3] = ((rand()/rand_max) * 2.0 - 1.0);
	  _waves[i+4] = ((rand()/rand_max) * 2.0 - 1.0);
      // Normalising the direction vector
	  norm = sqrt(_waves[i+3]*_waves[i+3]+_waves[i+4]*_waves[i+4]);
	  if(norm > 0.001f){// Vérif div par 0.0
		  _waves[i+3] /= norm;
		  _waves[i+4] /= norm;
	  }
	  printf("{%f,%f,%f,{%f,%f}}\n",_waves[i],_waves[i+1],_waves[i+2],
			 _waves[i+3],_waves[i+4]);
	  wl *= wlMod; amp *= aMod;
  }

  // Envoyer les uniforms qui ne changent pas
  // Waves prog
  glUseProgram(_wavesId);
  glUniform1fv(glGetUniformLocation(_wavesId, "waves"), WAVE_AMOUNT*5, _waves);
  glUniform2f(glGetUniformLocation(_wavesId, "windowSize"), _ww, _wh);	
  glUniform3f(glGetUniformLocation(_wavesId, "directional"),
			  _directional.x, _directional.y, _directional.z);

  // Tent prog
  glUseProgram(_tentId);
  glUniform2f(glGetUniformLocation(_tentId, "windowSize"), _ww, _wh);
  glUniform3f(glGetUniformLocation(_tentId, "directional"),
			  _directional.x, _directional.y, _directional.z);

  // Sky prog
  glUseProgram(_skyId);
  glUniform2f(glGetUniformLocation(_skyId, "windowSize"), _ww, _wh);
  glUniform3f(glGetUniformLocation(_skyId, "directional"),
			  _directional.x, _directional.y, _directional.z);

  // Assimp prog
  glUseProgram(_assimpId);
  glUniform1i(glGetUniformLocation(_assimpId, "objectId"), 2);
  glUniform2f(glGetUniformLocation(_assimpId, "windowSize"), _ww, _wh);
  glUniform3f(glGetUniformLocation(_assimpId, "directional"),
			  _directional.x, _directional.y, _directional.z);

  
  glUseProgram(0);
}


bool checkWaterIntersect(const Point3d& a, const Point3d& b,
						 double elapsed, float wl = 1.0, bool isM = false)
{
	bool inter = false;
	Point3d intersectPoint, top, bot, vec;
	int res;
	if((a.y < 0.0f) && (b.y > 0.0f)){
		inter = true;
		top = b;
		bot = a;
	} else if((a.y > 0.0f) && (b.y < 0.0f)){
		inter = true;
		top = a;
		bot = b;
	}
	// Il y a intersection, on calcule le point d'intersection
	if(inter){
		vec = bot - top;
		intersectPoint = top + (vec * (top.y/abs(vec.y)));
		float scaleFactor = 1.0;
		// Pour avoir des vagues plus petites quand seulement le haut
		// du poisson sort de l'eau
		if(isM)
			scaleFactor = std::min<float>(top.y/2.0, 1.0);
		auto it = _posWaves.begin();
		while(it != _posWaves.end()){
			res = it->update(intersectPoint, elapsed, wl * scaleFactor);
			if(res == 2)
				return inter;
			else if (res != 0){
				// On enlève la vague terminée
				it = _posWaves.erase(it);
			} else {
				++it;
			}
		}
		// Vague inexistante, on l'insère
		_posWaves.push_back(PosWaves{(GLfloat)intersectPoint.x,
				(GLfloat)intersectPoint.z, (GLfloat)elapsed,
				(GLfloat) wl * scaleFactor});
		
	}
	return inter;
}

// Trans est le translate potentiel de la tentacule
// Nécessaire maintenant pour calculer les intersections avec l'eau
void draw_tentacle(const BAnim& bez1, const BAnim& bez2,
				   double elapsed, bool useSinEl,
				   const Point3d& trans = Point3d{0.0f,0.0f,0.0f}){
	glUniform4f(glGetUniformLocation(_tentId,"couleur"), 0.388f,0.408f,0.357f,1.0f);
	glUniform1i(glGetUniformLocation(_tentId, "objectId"), 1);
	Bezier lerpBezier;
	if(useSinEl){//elapsed < DEB_CAM_ANIM - 5.0){
		lerpBezier = lerp(bez1.getBezier(elapsed),
						  bez2.getBezier(elapsed),
						  (float)((sinf(elapsed * 1.5) + 1.0)/2.0));
	} else {
		lerpBezier = lerp(bez1.getBezier(elapsed),
						  bez2.getBezier(elapsed),
						  bez1.reduceToTime(elapsed) /
						  bez1.getBezier(elapsed).getTime());
	}

	GLfloat bezUni[12];
	for(int i = 0; i < 4; ++i){
		lerpBezier[i] = lerpBezier[i] + trans;
		bezUni[i*3] = lerpBezier[i].x;
		bezUni[i*3+1] = lerpBezier[i].y;
		bezUni[i*3+2] = lerpBezier[i].z;
	}

	glUniform3fv(glGetUniformLocation(_tentId, "bezier"), 5, bezUni);
	gl4duLoadIdentityf();
	gl4duSendMatrices();
	gl4dgDraw(_tentacle);
	
    // Intersections avec l'eau
	Point3d nPos, pos = lerpBezier[0];
	double t;
	for(float i = 1 ; i < T_SECT_AM; ++i){
		t = i/(float)T_SECT_AM;
		nPos = lerpBezier.eval(t);
		checkWaterIntersect(pos, nPos, elapsed, 1.5f);
		pos = nPos;
	}

}

void draw_monster(Point3d& monsterPos, double elapsed){
	const Point3d tentMod[4] = {
		{-10.0f,-10.0f,-10.0f}, {10.0f,-10.0f,-10.0f},
		{-10.0f,-10.0f,10.0f}, {10.0f,-10.0f,10.0f}
	};
	// Tentacles
	glUseProgram(_tentId);
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, _noiseId);
	glUniform1i(glGetUniformLocation(_tentId,"noise"), 0);
	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_CUBE_MAP, _skyCubeMap);
	glUniform1i(glGetUniformLocation(_tentId, "skybox"), 1);
	if(elapsed < (DEB_CAM_ANIM - 5.0f)){
		glUniform2f(glGetUniformLocation(_tentId, "scale"), 1.75,1.75);
		draw_tentacle(_tentBezier1SW, _tentBezier3,
					  elapsed, true, monsterPos + tentMod[0]);
		draw_tentacle(_tentBezier1NW, _tentBezier4,
					  elapsed, true, monsterPos + tentMod[1]);
		draw_tentacle(_tentBezier1SE, _tentBezier2,
					  elapsed, true, monsterPos + tentMod[2]);
		draw_tentacle(_tentBezier1NE, _tentBezier5,
					  elapsed, true, monsterPos + tentMod[3]);
	}
	else {
		glUniform2f(glGetUniformLocation(_tentId, "scale"), 2.0, 2.0);
		draw_tentacle(_tentBezier1SW, _tentBezier3,
					  elapsed, false, monsterPos - Point3d{0.0, 0.0, monsterPos.z});
		// On draw deux tentacules derrière le monstre
		glUniform2f(glGetUniformLocation(_tentId, "scale"), 1.75,1.75);
		draw_tentacle(_tentBezier1SE, _tentBezier2,
					  elapsed, true, monsterPos + tentMod[2]);
		draw_tentacle(_tentBezier1NE, _tentBezier2,
					  elapsed, true, monsterPos + tentMod[3]);
	}
	// Angler
	glUseProgram(_assimpId);
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, _noiseId);
	glUniform1i(glGetUniformLocation(_assimpId,"noise"), 0);
	gl4duLoadIdentityf();
	gl4duTranslatef(monsterPos.x, monsterPos.y, monsterPos.z);
	gl4duScalef(30.0f,30.0f,30.0f);
	gl4duRotatef(180, 0.0,1.0,0.0);
	assimpDrawScene(_angler);

	// On rajoute des vagues pour le monstre
	checkWaterIntersect(monsterPos + Point3d{0.0,8.0, 0.0},
						monsterPos - Point3d{0.0,8.0, 0.0},
						elapsed, 4.0f, true);
}

void draw_waves(double elapsed){
	glUniform4f(glGetUniformLocation(_wavesId, "couleur"), 0.0f,0.5f,0.5f,1.0f);
	glUniform1i(glGetUniformLocation(_wavesId, "objectId"), 0);

	// Positional Waves
	auto it = _posWaves.begin();
	while(it != _posWaves.end()){
		if(it->checkErase(elapsed)){
			it = _posWaves.erase(it);
		} else {
			++it;
		}
	}

	
	GLfloat * uniPosW =(GLfloat*) malloc(_posWaves.size() * 4 * sizeof *uniPosW);
	assert(uniPosW);
	for(int i =0 ; i < (int) _posWaves.size(); ++i){
		uniPosW[i*4]     = _posWaves[i].x;
		uniPosW[(i*4)+1] = _posWaves[i].y;
		uniPosW[(i*4)+2] = _posWaves[i].tStart;
		uniPosW[(i*4)+3] = _posWaves[i].wl;
	}

	gl4duLoadIdentityf();
	glUniform4fv(glGetUniformLocation(_wavesId, "posWaves"),
				 (int) _posWaves.size(), uniPosW);
	glUniform1i(glGetUniformLocation(_wavesId, "posWavesAm"),(int) _posWaves.size());

	glUniform2f(glGetUniformLocation(_wavesId, "scale"), 100.0, 100.0);
	glUniform2f(glGetUniformLocation(_wavesId, "translate"), 0.0, 0.0);
	gl4duSendMatrices();
	gl4dgDraw(_water);

	glUniform2f(glGetUniformLocation(_wavesId, "scale"), 450.0, 450.0);
	glUniform2f(glGetUniformLocation(_wavesId, "translate"), 0.0, 550.0);
	gl4duSendMatrices();
	gl4dgDraw(_water);

	free(uniPosW);
}

void draw(void) {
	/* BLOC de récupération du temps */
	static double pt = -1.0;
	double tmpt, dt;
	if(pt < 0.0)
		pt = gl4dGetElapsedTime();
	dt = ((tmpt = gl4dGetElapsedTime()) - pt) / 1000.0;
	pt = tmpt;
	/* fin de BLOC temps */
	static double prevFrameTime = 0.0f;
	prevFrameTime += dt;
	bool newFrame = prevFrameTime > 1.0/24.0;
	static double elapsed = 0.0;
	static GLfloat noiseMov[3] = {rand()/(float) RAND_MAX,
		rand()/(float) RAND_MAX,
		rand()/(float)RAND_MAX};
	elapsed += dt;
	/* effacer le buffer de couleur (image) et le buffer de profondeur d'OpenGL */
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	gl4duBindMatrix("view");
	gl4duLoadIdentityf();

	// Calcul de la position de la cam
	_camPos = _camAnim.evalAnim(elapsed);
	Point3d camUp = Point3d{0.0,1.0,0.0f};
	if(_camAnim.getBezier(elapsed).getTime() == DEB_CAM_ANIM){
		_camLookAt = _camPos + Point3d{0.0,0.0,1.0f};
	} else {
		camUp = normalize(_camAnim.getAnimTan(elapsed));
		// Have cam up always pointing up
		camUp = Point3d{camUp.x, abs(camUp.y), camUp.z};
		_camLookAt = _camPos +  normalize(Point3d{-camUp.x, -camUp.z, camUp.y});
	}

	gl4duLookAtf(_camPos.x,_camPos.y,_camPos.z,
				 _camLookAt.x,_camLookAt.y,_camLookAt.z,
				 camUp.x,camUp.y,camUp.z);


	// Model
	gl4duBindMatrix("model");
	if(newFrame){
		noiseMov[0] += rand() / (float) RAND_MAX;
		noiseMov[1] += rand() / (float) RAND_MAX;
		noiseMov[2] = rand() / (float) RAND_MAX / 50.0f;
		prevFrameTime = 0.0f;
	}

    // Send global uniforms
	// On calcule la position du monstre maintenant pour avoir la position de la light
	Point3d monsterPos = _anglerAnim.evalAnim(elapsed);
	GLuint progs[4] = { _tentId, _assimpId, _skyId, _wavesId};
	for(int i = 0 ; i < 4; ++i){
		glUseProgram(progs[i]);
		glUniform3f(glGetUniformLocation(progs[i],"noiseMov"),
					noiseMov[0], noiseMov[1], noiseMov[2]);
		glUniform1f(glGetUniformLocation(progs[i],"elapsed"),elapsed);
		glUniform3f(glGetUniformLocation(progs[i], "camPos"),
					_camPos.x, _camPos.y, _camPos.z);
		glUniform4f(glGetUniformLocation(progs[i],"Lp"),
					monsterPos.x, monsterPos.y + 3.0f, monsterPos.z - 15.0f, LP_EMI);
	}

	// Draw sky
	glUseProgram(_skyId);
	glDepthMask(GL_FALSE);
	glCullFace(GL_FRONT); // On inverse le cullface pour la skybox
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, _noiseId);
	glUniform1i(glGetUniformLocation(_skyId, "noise"), 0);
	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_CUBE_MAP, _skyCubeMap);
	glUniform1i(glGetUniformLocation(_skyId, "skybox"), 1);
	gl4duLoadIdentityf();
	gl4duSendMatrices();
	gl4dgDraw(_sky);
	glDepthMask(GL_TRUE);
	glCullFace(GL_BACK);

	// Monster
	draw_monster(monsterPos, elapsed);
	
	// Boat
	if(elapsed < _boatAnim.getTime()){
		gl4duLoadIdentityf();
		Point3d boatPos = _boatAnim.eval(elapsed/_boatAnim.getTime());
		gl4duTranslatef(boatPos.x, boatPos.y, boatPos.z);
		gl4duScalef(100.0f,100.0f,100.0f);
		gl4duRotatef(90, 0.0f, 1.0f, 0.0f);
		assimpDrawScene(_boat);
	}

    // Waves
	glUseProgram(_wavesId);
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, _noiseId);
	glUniform1i(glGetUniformLocation(_wavesId, "noise"), 0);
	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_CUBE_MAP, _skyCubeMap);
	glUniform1i(glGetUniformLocation(_wavesId, "skybox"), 1);
	draw_waves(elapsed);
	/* n'utiliser aucun programme GPU (pas nécessaire) */
	glUseProgram(0);
}

/* appelée lors du exit */
void quit(void) {
	if(_noiseId){
		glDeleteTextures(1, &_noiseId);
		glDeleteTextures(1, &_skyCubeMap);
		_noiseId = _skyCubeMap = 0;
	}
}

