/*!\file window.c
 *
 * \brief using GL4Dummies and Assimp Library to load 3D models or scenes.
 *
 * \author Farès Belhadj amsi@up8.edu
 * \date February 14 2017, modified on March 24, 2024
 */

#include <GL4D/gl4duw_SDL2.h>
#include <GL4D/gl4du.h>
#include <GL4D/gl4dm.h>
#include <GL4D/gl4dg.h>
#include <GL4D/gl4dp.h> // Pour Color MASK
#include <SDL_image.h>
#include <SDL_ttf.h>
#include <stdlib.h>
#include <vector>
#include "animations.h"
#include "audioHelper.h"
#include "initText.h"
#include "assimp.h"
#include "point3d.hpp"
#include "bezier.hpp"
#include "waves.hpp"

#define WAVE_AMOUNT 10

/*!\brief opened window width */
static int _ww = 1920;
/*!\brief opened window height */
static int _wh = 1080;
/*!\brief GLSL program Id */
static GLint _wavesId = 0;
static GLint _skyId = 0;
static GLint _textpId = 0;
static BAnim _camBezier = {
  {
	{	{
			{0.0f,  -5.0f, 0.0}, 
			{0.0f,-200.0f, 0.0},
			{0.0f,-350.0f, 0.0},
			{0.0f,-500.0f, 0.0}
		},
		600.0f
	}
  }
};

static Point3d directional = {0.0f, -1.0f, -1.0f};

static void initGL(void);
static void quit(void);
static void resize(int w, int h);
static void draw(void);

static GLfloat _waves[WAVE_AMOUNT * 5];
static GLuint _wavesPlan = 0;
static GLuint _sky = 0;
static GLuint _textPlan = 0;

static GLuint _skyCubeMap = 0;
static GLuint _noiseId = 0;
static GLuint _textId = 0;
const char cred[] =
	"                                              BEHOLDER\n"
	"\n    -------------------------------------------------------------------------------\n\n"
	"A demo by .............................................................Oscar MAZUIR\n"
	"\n    -------------------------------------------------------------------------------\n\n"
	"                                               Modèles 3D\n"
	"Cliff.......................................................................Kenney.nl - CC0\n"
	"Vintage Camera........................vvariaa - CC-BY-4.0 - Sketchfab\n"
	"Anglerfish........................Petr Janečka - CC-BY-4.0 - Sketchfab\n"
	"Final Projet Boat................heaven wolf - CC-BY-4.0 - Sketchfab\n"
	"\n    -------------------------------------------------------------------------------\n\n"
	"                                                 Textures\n"
	"Space skybox................................kurtk84 - CC0 - OpenGameArt\n"
	"Moon..................................Saroman - CC-BY-3.0 - OpenGameArt\n"
	"\n    -------------------------------------------------------------------------------\n\n"
	"                                                   Music\n"
	"Danse Macabre op.40...............................................Saint-Saëns\n"
	"..................Interprétation Gregor Quendel - CC-BY-NC-ND-4.0\n"
	"\n    -------------------------------------------------------------------------------\n\n"
	"                                                    Font\n"
	"Basteleur................................................Keussel - OFL - Velvetyne\n"
	"\n    -------------------------------------------------------------------------------\n\n"
	"                                           Special Thanks\n"
	"Farès Belhadj..................................................................Gaëlle Klos\n"
	"Acerola........................................................................Freya Holmer\n"
	"GPU Gems....................................................................................API8\n"
	"You................................................................................................... <3\n"
	;

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

/*!\brief function that initialize OpenGL / GL4D params and objects.
 */
static void initGL(void) {
	srand(1312);
  glEnable(GL_DEPTH_TEST);
  glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
  SDL_GL_SetSwapInterval(1);
  glClearColor(0.0f,0.0f,0.0f,1.0f);
  _wavesId = gl4duCreateProgram("<vs>shaders/waves.vs",
								"<fs>shaders/grayscale_credits.fs",NULL);
  _skyId = gl4duCreateProgram("<vs>shaders/sky.vs",
							  "<fs>shaders/grayscale_sky.fs", NULL);
  _textpId = gl4duCreateProgram("<vs>shaders/credits.vs",
							   "<fs>shaders/credits.fs", NULL);
  // Create matrix
  gl4duGenMatrix(GL_FLOAT, "view");
  gl4duGenMatrix(GL_FLOAT, "model");
  gl4duGenMatrix(GL_FLOAT, "proj");
  glEnable(GL_CULL_FACE);
  glCullFace(GL_BACK);
  resize(_ww, _wh);

  // Génération texture de bruit pour dégats
  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);

  initText(&_textId, cred);
  
  // Load texture cubemap
  // Inspiré de : https://learnopengl.com/Advanced-OpenGL/Cubemaps
  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;
  SDL_Surface *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);
  

  _textPlan = gl4dgGenQuadf();
  // Ciel
  _sky = gl4dgGenCubef();

  
  
  // Initialisation des vagues
  _wavesPlan = gl4dgGenGrid2df(250,250);
  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;
  for(i=0; i < WAVE_AMOUNT * 5 ; i+=5){
	  _waves[i] = 2.0/wl;
	  _waves[i+1] = amp;
	  _waves[i+2] = (rand()/rand_max) * 1.5f;
	  _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;
	  }
	  wl *= wlMod; amp *= aMod;
  }
  
  /* Envoi des uniform statics */
  glUseProgram(_wavesId);
  glUniform1fv(glGetUniformLocation(_wavesId, "waves"), WAVE_AMOUNT*5, _waves);
  glUniform1i(glGetUniformLocation(_wavesId, "posWavesAm"), 0);
  glUniform3f(glGetUniformLocation(_wavesId, "directional"),
			  directional.x, directional.y, directional.z);
  glUniform2f(glGetUniformLocation(_wavesId, "scale"), 10000.0, 10000.0);
  glUniform2f(glGetUniformLocation(_wavesId, "translate"), 0.0, 0.0);
  glUniform2f(glGetUniformLocation(_wavesId, "windowSize"), _ww,_wh);
  glUniform1i(glGetUniformLocation(_wavesId, "objectId"), 0);
  glUniform4f(glGetUniformLocation(_wavesId, "couleur"), 0.0f, 0.25f, 0.4f, 1.0f);
  
  glUseProgram(_skyId);
  glUniform2f(glGetUniformLocation(_skyId, "windowSize"), _ww,_wh);
  
  glUseProgram(0);
}

/*!\brief function called by GL4Dummies' loop at resize. Sets the
 *  projection matrix and the viewport according to the given width
 *  and height.
 * \param w window width
 * \param h window height
 */
static void resize(int w, int h) {
  _ww = w; 
  _wh = h;
  glViewport(0.0f, 0.0f, _ww, _wh);
  gl4duBindMatrix("proj");
  gl4duLoadIdentityf();
  gl4duFrustumf(-0.05f, 0.05f,
				-0.05 * _wh / (GLfloat) _ww,
				0.05 *_wh / (GLfloat) _ww,
				0.05f, 10000.0f);
  gl4duBindMatrix("model");
}

/*!\brief function called on each GL4Dummies' display event. It draws
 * the scene with its given params.
 */
static void draw(void) {
	/* Bloc de récup du temps */
	static double elapsed = 0.0, pt = -1.0;
	double tmpt, dt;
	if(pt < 0.0)
		pt = gl4dGetElapsedTime();
	dt = ((tmpt = gl4dGetElapsedTime()) - pt) / 1000.0;
	pt = tmpt;
	static double prevFrameTime = 0.0f;
	prevFrameTime += dt;;
	bool newFrame = prevFrameTime > 1.0/24.0;
	static GLfloat noiseMov[3] = {rand()/(float) RAND_MAX,
		rand()/(float) RAND_MAX,
		rand()/(float) RAND_MAX};
	elapsed += dt;
	/* fin bloc temps */
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glDisable(GL_BLEND);
	glEnable(GL_DEPTH_TEST);
	// View matrix
	gl4duBindMatrix("view");
	gl4duLoadIdentityf();
	Point3d pos = _camBezier.evalAnim(elapsed);
	Point3d lookat = pos + Point3d{0.0, 0.55, 1.0f};
	gl4duLookAtf(pos.x, pos.y, pos.z,
				 lookat.x, lookat.y, lookat.z,
				 0.0f, 1.0f, 0.0f);
	
	gl4duBindMatrix("model");

	gl4duLoadIdentityf();
	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;
	}
	
  
  // Vagues
  glUseProgram(_wavesId);
  glUniform1f(glGetUniformLocation(_wavesId, "elapsed"), elapsed);
  lookat = normalize(lookat);
  glUniform3f(glGetUniformLocation(_wavesId, "camPos"), pos.x, pos.y, pos.z);
  glCullFace(GL_FRONT);
  glActiveTexture(GL_TEXTURE0);
  glBindTexture(GL_TEXTURE_2D, _noiseId);
  glUniform1i(glGetUniformLocation(_wavesId, "noise"), 0);
  glUniform3f(glGetUniformLocation(_wavesId, "noiseMov"),
			  noiseMov[0],noiseMov[1],noiseMov[2]);
  glActiveTexture(GL_TEXTURE1);
  glBindTexture(GL_TEXTURE_CUBE_MAP, _skyCubeMap);
  glUniform1i(glGetUniformLocation(_wavesId, "skybox"), 1);
  gl4duLoadIdentityf();
  //gl4duRotatef(90, 1.0, 0.0, 0.0);
  gl4duSendMatrices();
  gl4dgDraw(_wavesPlan);

  glActiveTexture(GL_TEXTURE1);
  glBindTexture(GL_TEXTURE_CUBE_MAP, 0);


  static float a = 0.0f;
  a+=100.0f * dt;
	  
  glUseProgram(_textpId);
  glCullFace(GL_BACK);
  glDisable(GL_DEPTH_TEST);
  glDisable(GL_CULL_FACE);
  glEnable(GL_BLEND);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  gl4duBindMatrix("view");
  gl4duLoadIdentityf();
  gl4duLookAtf(pos.x, pos.y, pos.z,
			   pos.x, pos.y, pos.z + 1,
			   0,1,0);
  gl4duBindMatrix("model");
  gl4duLoadIdentityf();
  glActiveTexture(GL_TEXTURE0);
  glBindTexture(GL_TEXTURE_2D, _textId);
  glUniform1i(glGetUniformLocation(_textpId, "tex"), 0);
  glUniform1i(glGetUniformLocation(_textpId, "inv"), 1);
  glUniform1f(glGetUniformLocation(_textpId, "transp"), 1.0);
  gl4duScalef(3,5,1);
  gl4duTranslatef(0,-3.6f, 5.5f);
  gl4duRotatef(-180.0, 0, 1, 0);
  gl4duSendMatrices();
  gl4dgDraw(_textPlan);
  glEnable(GL_CULL_FACE);
  glDisable(GL_BLEND);
  glEnable(GL_DEPTH_TEST);
  glUseProgram(0);  
}

/*!\brief function called at exit, it cleans all created GL4D objects.
 */
static void quit(void) {
	if(_skyCubeMap){
		glDeleteTextures(1, &_skyCubeMap);
	}
	if(_noiseId){
		glDeleteTextures(1, &_noiseId);
	}
	if(_textId){
		glDeleteTextures(1, &_textId);
	}
}
