/*!\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/gl4df.h> // Pour sobel
#include <GL4D/gl4dp.h> // Pour Color MASK
#include <SDL_image.h>
#include <vector>
#include "initText.h"
#include "assimp.h"
#include "animations.h"
#include "audioHelper.h"
#include "point3d.hpp"
#include "bezier.hpp"

#define WAVE_AMOUNT 10

/*!\brief opened window width */
static int _windowWidth = 1920;
/*!\brief opened window height */
static int _windowHeight = 1080;
/*!\brief GLSL program Id */
static GLuint _assimpId = 0;
static GLuint _wavesId = 0;
static GLuint _skyId = 0;
static GLuint _textpId = 0;

#define END_INTRO 5.0
static BAnim _camBezier = {
  {
	  { {
			  {-15.0f, 17.95f, -18.0f}}, END_INTRO},
	  {	{
			{-15.0f, 17.95f,-18.0f},
			{-6.435f, 17.975f,-10.0f},
			{-6.435f, 17.975f,-5.0f},
			{-6.435f, 17.975f,-1.025f}
		},
		20.0f
	}
  }
};
static Point3d directional = {0.0f, -1.0f, -1.0f};

/*!\brief identifiant de la scene générée par assimpGenScene */
static GLuint _camera_scene = 0;
static GLuint _cliff_scene = 0;
static GLuint _cliffB_scene = 0;

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 _textTexId = 0;

/*
int main(int argc, char ** argv) {
  if(!gl4duwCreateWindow(argc, argv, "API8 64Mo Scene 1",
						 GL4DW_POS_UNDEFINED, GL4DW_POS_UNDEFINED,
                         _windowWidth, _windowHeight, GL4DW_SHOWN | GL4DW_FULLSCREEN))
    return 1;

  initGL();
  atexit(quit);
  gl4duwResizeFunc(resize);
  gl4duwDisplayFunc(draw);
  gl4duwMainLoop();
  return 0;
}
*/

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

/*!\brief function that initialize OpenGL / GL4D params and objects.
 */
static void initGL(void) {
  glEnable(GL_DEPTH_TEST);
  glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
  SDL_GL_SetSwapInterval(1);
  // Load models
  _camera_scene = assimpGenScene("models/camera/scene.gltf");
  _cliff_scene = assimpGenScene("models/cliff/cliffBrown_top.gltf");
  _cliffB_scene = assimpGenScene("models/cliff/cliffBrown_block.gltf");
  // Create GPU programs
  _assimpId = gl4duCreateProgram("<vs>shaders/assimp.vs","<gs>shaders/assimp.gs","<fs>shaders/assimp.fs",NULL);
  _wavesId = gl4duCreateProgram("<vs>shaders/waves.vs", "<fs>shaders/gen_phong.fs",NULL);
  _skyId = gl4duCreateProgram("<vs>shaders/sky.vs", "<fs>shaders/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(_windowWidth, _windowHeight);

  // 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);
  
  // Ciel
  _sky = gl4dgGenCubef();

  // Text
  _textPlan = gl4dgGenQuadf();
  initText(&_textTexId, "BEHOLDER");
  
  // 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);
  glUniform2f(glGetUniformLocation(_wavesId, "scale"), 1000,1000);
  glUniform2f(glGetUniformLocation(_wavesId, "translate"), 0, 0);
  glUniform1i(glGetUniformLocation(_wavesId, "posWavesAm"), 0);
  glUniform3f(glGetUniformLocation(_wavesId, "directional"),
			  directional.x, directional.y, directional.z);

  glUseProgram(_assimpId);
  glUniform3f(glGetUniformLocation(_assimpId, "directional"),
			  directional.x, directional.y, directional.z);
  
  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) {
  _windowWidth = w; 
  _windowHeight = h;
  glViewport(0.0f, 0.0f, _windowWidth, _windowHeight);
  gl4duBindMatrix("proj");
  gl4duLoadIdentityf();
  gl4duFrustumf(-0.05f, 0.05f,
				-0.05 * _windowHeight / (GLfloat) _windowWidth,
				0.05 *_windowHeight / (GLfloat) _windowWidth,
				0.05f, 1000.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;
	elapsed += dt;
	/* fin bloc temps */
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	// View matrix
	gl4duBindMatrix("view");
	gl4duLoadIdentityf();
	Point3d pos = _camBezier.evalAnim(elapsed);
	Point3d lookat;
	if(elapsed > END_INTRO)
		lookat = pos + _camBezier.getAnimTan(elapsed);
	else
		lookat = pos + _camBezier.getAnimTan(END_INTRO + dt);
	if(pos == lookat){
		lookat = pos + Point3d{0.01, 0.01, 0.01};
	}
	gl4duLookAtf(pos.x, pos.y, pos.z,
				 lookat.x, lookat.y, lookat.z,
				 0.0f, 1.0f, 0.0f);
	
	gl4duBindMatrix("model");

  // Sky
  glUseProgram(_skyId);
  glDepthMask(GL_FALSE);
  gl4duLoadIdentityf();
  glCullFace(GL_FRONT);
  glActiveTexture(GL_TEXTURE0);
  glBindTexture(GL_TEXTURE_CUBE_MAP, _skyCubeMap);
  glUniform1i(glGetUniformLocation(_skyId, "skybox"), 0);
  gl4duSendMatrices();
  gl4dgDraw(_sky);
  glCullFace(GL_BACK);
  glDepthMask(GL_TRUE);
  glBindTexture(GL_TEXTURE_2D, 0);
  
  // Camera
  glUseProgram(_assimpId);
  gl4duLoadIdentityf();
  gl4duTranslatef(-6.0f,16.5f,0.0f);
  gl4duScalef(5.0f,5.0f,5.0f);
  gl4duRotatef(180,0,1,0);
  glUniform1i(glGetUniformLocation(_assimpId, "objectId"), 1);
  assimpDrawScene(_camera_scene);
  
  // Cliffs
  gl4duLoadIdentityf();
  glUniform1i(glGetUniformLocation(_assimpId, "objectId"), 0);
  gl4duScalef(30.0f,30.0f,30.0f);
  assimpDrawScene(_cliff_scene);

  gl4duLoadIdentityf();
  gl4duTranslatef(30.0f,0.0f,0.0f);
  gl4duScalef(30.0f,30.0f,30.0f);
  assimpDrawScene(_cliff_scene);

  gl4duLoadIdentityf();
  gl4duTranslatef(60.0f,0.0f,0.0f);
  gl4duScalef(30.0f,30.0f,30.0f);
  assimpDrawScene(_cliff_scene);

  gl4duLoadIdentityf();
  gl4duTranslatef(-30.0f,0.0f,0.0f);
  gl4duScalef(30.0f,30.0f,30.0f);
  assimpDrawScene(_cliff_scene);

  
  gl4duLoadIdentityf();
  
  gl4duTranslatef(0.0f, -0.570f, -21.85f);
  gl4duScalef(200.0f, 30.0f, 30.0f);
  assimpDrawScene(_cliffB_scene);
  
  
  // Vagues
  glUseProgram(_wavesId);
  glUniform1f(glGetUniformLocation(_wavesId, "elapsed"), elapsed);
  lookat = normalize(lookat);
  glUniform3f(glGetUniformLocation(_wavesId, "camPos"), pos.x, pos.y, pos.z);
  glUniform4f(glGetUniformLocation(_wavesId, "couleur"), 0.0f, 0.25f, 0.4f, 1.0f);
  glActiveTexture(GL_TEXTURE0);
  glBindTexture(GL_TEXTURE_CUBE_MAP, _skyCubeMap);
  gl4duLoadIdentityf();
  gl4duSendMatrices();
  gl4dgDraw(_wavesPlan);
  glBindTexture(GL_TEXTURE_2D, 0);
  glActiveTexture(0);

  if(elapsed < END_INTRO){
	  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(0,0,0,0,0,1,0,1,0);
	  gl4duBindMatrix("model");
	  gl4duLoadIdentityf();
	  glActiveTexture(GL_TEXTURE0);
	  glBindTexture(GL_TEXTURE_2D, _textTexId);
	  glUniform1i(glGetUniformLocation(_textpId, "tex"), 0);
	  glUniform1i(glGetUniformLocation(_textpId, "inv"), 1);
	  glUniform1f(glGetUniformLocation(_textpId, "transp"),1.0 - elapsed/END_INTRO);
	  gl4duScalef(1, 1 ,1);
	  gl4duTranslatef(0, 0, 3);
	  gl4duRotatef(180, 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(_textTexId){
		glDeleteTextures(1, &_textTexId);
	}
}

