#define MATH_3D_IMPLEMENTATION 1
#include "demo.h"

extern const GLchar *vertexshader_1, *fragmentshader_1;
extern const GLchar *vertexshader_2, *fragmentshader_2;
extern GLint vertexshader_1_len, fragmentshader_1_len;
extern GLint vertexshader_2_len, fragmentshader_2_len;

float cubeVertices[] = {
	// X      Y      Z     R     G     B     U     V	T
	  -0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f,	1.0,
	   0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f,	1.0,
	   0.5f,  0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,	1.0,
	   0.5f,  0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,	1.0,
	  -0.5f,  0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f,	1.0,
	  -0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f,	1.0,

	  -0.5f, -0.5f,  0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f,	0.0,
	   0.5f, -0.5f,  0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f,	0.0,
	   0.5f,  0.5f,  0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,	0.0,
	   0.5f,  0.5f,  0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,	0.0,
	  -0.5f,  0.5f,  0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f,	0.0,
	  -0.5f, -0.5f,  0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f,	0.0,

	  -0.5f,  0.5f,  0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f,	0.0,
	  -0.5f,  0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,	0.0,
	  -0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f,	0.0,
	  -0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f,	0.0,
	  -0.5f, -0.5f,  0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f,	0.0,
	  -0.5f,  0.5f,  0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f,	0.0,

	   0.5f,  0.5f,  0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f,	0.0,
	   0.5f,  0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,	0.0,
	   0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f,	0.0,
	   0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f,	0.0,
	   0.5f, -0.5f,  0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f,	0.0,
	   0.5f,  0.5f,  0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f,	0.0,

	  -0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f,	0.0,
	   0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,	0.0,
	   0.5f, -0.5f,  0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f,	0.0,
	   0.5f, -0.5f,  0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f,	0.0,
	  -0.5f, -0.5f,  0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f,	0.0,
	  -0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f,	0.0,

	  -0.5f,  0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f,	0.0,
	   0.5f,  0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,	0.0,
	   0.5f,  0.5f,  0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f,	0.0,
	   0.5f,  0.5f,  0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f,	0.0,
	  -0.5f,  0.5f,  0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f,	0.0,
	  -0.5f,  0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f,	0.0,

	  -1.0f, -1.0f, -0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,	0.0,
	   1.0f, -1.0f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f,	0.0,
	   1.0f,  1.0f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f,	0.0,
	   1.0f,  1.0f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f,	0.0,
	  -1.0f,  1.0f, -0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f,	0.0,
	  -1.0f, -1.0f, -0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,	0.0,
};

float rectVertices[] = {
	-1.0f, 1.0f, 0.0f, 1.0f,
	 1.0f, 1.0f, 1.0f, 1.0f,
	 1.0f, -1.0f, 1.0f, 0.0f, 

	 1.0f, -1.0f, 1.0f, 0.0f,
	 -1.0f, -1.0f, 0.0f, 0.0f,
	 -1.0, 1.0f, 0.0f, 1.0f,
};

struct shaderProgram sceneShader = {
	.vertexShadersrc = &vertexshader_1,
	.fragmentShadersrc = &fragmentshader_1,
	.vertexShadersize = &vertexshader_1_len,
	.fragmentShadersize = &fragmentshader_1_len,
	.name = "scene",
};

struct shaderProgram screenShader = {
	.vertexShadersrc = &vertexshader_2,
	.fragmentShadersrc = &fragmentshader_2,
	.vertexShadersize = &vertexshader_2_len,
	.fragmentShadersize = &fragmentshader_2_len,
	.name = "screen",
};

struct attribute cubeAttributes[] = {
	{ .name = "position", .size = 3, .offset = 0 },
	{ .name = "color", .size = 3, .offset = 3 },
	{ .name = "texcoord", .size = 2, .offset = 6 },
	{ .name = "texid", .size = 1, .offset = 7 },
};

struct attribute rectAttributes[] = {
	{ .name = "position", .size = 2, .offset = 0},
	{ .name = "texcoord", .size = 2, .offset = 2},
};

GLuint sceneVbo, sceneVao, screenVbo, screenVao, sceneVao2, sceneVbo2;
GLuint sceneTextures[4], screenTextures[1];

GLuint frameBuffer[2];
GLuint texColorBuffer[2];

static double pos = 0;
static int bpm = 147;
static int win_height, win_width;
static double win_aspect;

void drawCube(double ts, vec3_t eye, int texindex)
{
	GLint time = glGetUniformLocation(sceneShader.shaderProgram, "time");
	GLint uniModel = glGetUniformLocation(sceneShader.shaderProgram, "model");
	GLint uniView = glGetUniformLocation(sceneShader.shaderProgram, "view");
	GLint uniProj = glGetUniformLocation(sceneShader.shaderProgram, "proj");
	GLint uniColor = glGetUniformLocation(sceneShader.shaderProgram, "overrideColor");
	mat4_t view, proj;
	vec3_t center, up;
	mat4_t model;
	int i;

	center = vec3(0.0, 0.0, 0.0);
	up = vec3(0.0, 0.0, 1.0);
	proj = m4_perspective(45, 800.0/600.0, 1.0, 10.0);
	view = m4_look_at(eye, center, up);

	glEnable(GL_DEPTH_TEST);

	glUseProgram(sceneShader.shaderProgram);

	glBindVertexArray(sceneVao);
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, sceneTextures[texindex]);

	glUniformMatrix4fv(uniProj, 1, GL_FALSE, &proj.m00);
	glUniformMatrix4fv(uniView, 1, GL_FALSE, &view.m00);

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	// Setup rotation
	ts /= 1000;
	ts *= bpm / (4*60.0);

	model = m4_rotation_z( ts * M_PI);
	glUniformMatrix4fv(uniModel, 1, GL_FALSE, &model.m00);

	// Draw top cube
	glUniform3f(uniColor, 1.0f, 1.0f, 1.0f);	
	glDrawArrays(GL_TRIANGLES, 0, 36);

	glEnable(GL_STENCIL_TEST);	

	// Setup stencil buffer writing	
	glStencilFunc(GL_ALWAYS, 1, 0xFF); // Set any stencil to 1
	glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
	glStencilMask(0xFF); // Write to stencil buffer
	glClear(GL_STENCIL_BUFFER_BIT);

	// Draw floor
	glDepthMask(GL_FALSE);
	glDrawArrays(GL_TRIANGLES, 36, 6);
	glDepthMask(GL_TRUE);

	// Use stencil buffer when drawing reflected cube
	glStencilFunc(GL_EQUAL, 1, 0xFF); // Pass test if stencil value is 1
	glStencilMask(0x00); // Don't write anything to stencil buffer

	// Setup reflection transform
	model = m4_mul(model, m4_translation(vec3(0, 0, -1)));
	model = m4_mul(model, m4_scaling(vec3(1, 1, -1)));
	glUniformMatrix4fv(uniModel, 1, GL_FALSE, &model.m00);
	glUniform3f(uniColor, 0.3f, 0.3f, 0.3f); // darken reflected cube
	glDrawArrays(GL_TRIANGLES, 0, 36);

	glDisable(GL_STENCIL_TEST);

	glDisable(GL_DEPTH_TEST);

}

void drawRectangle(double ts, GLuint texture, float boundary)
{
	GLint uniModel = glGetUniformLocation(screenShader.shaderProgram, "model");
	GLint uniView = glGetUniformLocation(screenShader.shaderProgram, "view");
	GLint uniProj = glGetUniformLocation(screenShader.shaderProgram, "proj");
	GLint uniBoundary = glGetUniformLocation(screenShader.shaderProgram, "boundary");
	mat4_t proj, view, model;
	vec3_t center, up;

	glUseProgram(screenShader.shaderProgram);

	model = view = proj = m4_identity();
	glBindVertexArray(screenVao);

	glUniformMatrix4fv(uniProj, 1, GL_FALSE, &proj.m00);
	glUniformMatrix4fv(uniView, 1, GL_FALSE, &view.m00);
	glUniformMatrix4fv(uniModel, 1, GL_FALSE, &model.m00);
	glUniform1f(uniBoundary, boundary);
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, texture);
	glDrawArrays(GL_TRIANGLES, 0, 6);
}

void do_main_loop_images(double ts, double base_ts)
{
	SDL_Event windowEvent;
	GLint uniTexOffset;
	static beat_base = -1;
	static int index;
	struct timespec now;
	static float prev_boundary = 0;
	static int texindex = 0;
	float elapsed;

	uniTexOffset = glGetUniformLocation(screenShader.shaderProgram, "texOffset");

	glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer[0]);
	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);

#if 0
		clock_gettime(CLOCK_MONOTONIC, &now);
		if (now.tv_nsec - begin.tv_nsec < 0) {
			ts = (now.tv_sec - begin.tv_sec - 1) * 1e3;
			ts += (now.tv_nsec - begin.tv_nsec + 1000000000) * 1e-6;
		} else {
			ts = (now.tv_sec - begin.tv_sec) * 1e3;
			ts += (now.tv_nsec - begin.tv_nsec) * 1e-6;
		}
#endif

	drawCube(ts, vec3(-2.5, 0.5, 0.0),  texindex);

#if 0
		glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer[1]);
		glClearColor(0.0, 0.0, 0.0, 0.0);
		glUseProgram(screenShader.shaderProgram);
		glUniform2f(uniTexOffset, 1.0f / 300.0f, 0.0f);
		drawRectangle(texColorBuffer[0]);
#endif
	glBindFramebuffer(GL_FRAMEBUFFER, 0);
	glClearColor(fabs(sin(ts/5000)), fabs(sin(ts/5000)), fabs(sin(ts/5000)), 1.0f);
//		drawCube(ts, vec3(-2.5, 0.5, 0.0));
//		glEnablei(GL_BLEND, 0);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
//		drawRectangle(ts, texColorBuffer[0], vec3(-2.5, -0.5, 0.0));

	if (beat_base < 0)
		beat_base = base_ts;

	if ((ts - beat_base) / 1000 > (60 / 1470)) {
		index++;
		beat_base = ts;
	}

	texindex = (index  / 160) % ARRAY_SIZE(sceneTextures);
	drawRectangle(ts, texColorBuffer[0], (float)(index % 160) / 159.0f);
//		glDisablei(GL_BLEND, 0);

}

int setup_cube_textures(struct shaderProgram *p, GLuint *textures)
{

	glUseProgram(p->shaderProgram);

	textures[0] = create_texture_from_file("summer1.jpg", GL_CLAMP_TO_EDGE);
	textures[1] = create_texture_from_file("summer2.jpg", GL_CLAMP_TO_EDGE);
	textures[2] = create_texture_from_file("summer3.jpg", GL_CLAMP_TO_EDGE);
	textures[3] = create_texture_from_file("sauna.jpg", GL_CLAMP_TO_EDGE);

	return 0;
}

void setup_framebuffer(GLuint *framebuffer, GLuint *texcolorbuffer)
{
	GLenum frameBufferState;
	GLuint rbodepthstencil;

	glGenFramebuffers(1, framebuffer);
	glBindFramebuffer(GL_FRAMEBUFFER, *framebuffer);

	glGenTextures(1, texcolorbuffer);
	glBindTexture(GL_TEXTURE_2D, *texcolorbuffer);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, win_height, win_width, 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);
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, *texcolorbuffer, 0);

	glGenRenderbuffers(1, &rbodepthstencil);
	glBindRenderbuffer(GL_RENDERBUFFER, rbodepthstencil);
	glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, win_height ,win_width );
	glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbodepthstencil);

	frameBufferState = glCheckFramebufferStatus(GL_FRAMEBUFFER);
	if (frameBufferState != GL_FRAMEBUFFER_COMPLETE)
		fprintf(stderr, "framebuffer status: %d\n", frameBufferState);
}

void audio_processing(void *udata, Uint8 *stream, int len)
{
	static unsigned long total_len;
	double cpos;

	total_len += len;
	cpos = (total_len * 1000) / (44100 * 2 * 2);

	pos = cpos;
}

void do_audio(void)
{
	Mix_Music *music;

	if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 1024) == -1) {
		fprintf(stderr, "Mix_OpenAudio: %s\n", Mix_GetError());
		return;
	}
	Mix_AllocateChannels(16);
	music=Mix_LoadMUS("music.ogg");
	if (!music) {
		fprintf(stderr,"Error opening music file: %s\n",Mix_GetError());
		return;
	}
	
	Mix_SetPostMix(audio_processing, NULL);

	if (Mix_PlayMusic(music, 1))
		fprintf(stderr,"Music playback failed: %s\n", Mix_GetError());
		
}

static void check_esc(SDL_Window *window)
{
	SDL_Event windowEvent;
	while (SDL_PollEvent(&windowEvent)) {
		if (windowEvent.type == SDL_QUIT)
			exit(2);
		if (windowEvent.type == SDL_KEYUP &&
		windowEvent.key.keysym.sym == SDLK_ESCAPE)
			exit(2);
	}
}
static loop_until(double pos_until, SDL_Window *window, void(*loop_fn)(double ts, double base_ts))
{
	double base_pos = pos;

	while ((pos - base_pos) < pos_until) {
		check_esc(window);
		loop_fn(pos, base_pos);
		SDL_GL_SwapWindow(window);
	}
}

int main(int argc, char *argv[])
{
	SDL_GLContext context;
	SDL_Window *window;
	int t_width, t_height;

	if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_AUDIO) == -1) {
		fprintf(stderr, "SDL_Init: %s\n", SDL_GetError());
		exit(1);
	}

	SDL_StartTextInput();

	SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
	SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
	SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
	SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);

	window = SDL_CreateWindow("opengl", 0, 0, 1920, 1080, SDL_WINDOW_OPENGL  | SDL_WINDOW_FULLSCREEN_DESKTOP ); 
	context = SDL_GL_CreateContext(window);
	if (!context) {
		fprintf(stderr, "gl context create failed\n");
		return 0;
	}

//	SDL_SetWindowDisplayMode(window, NULL);
	SDL_GL_GetDrawableSize(window, &win_height, &win_width);
	fprintf(stderr,"%d %d\n", win_height, win_width);
	win_aspect = (float)win_width / (float)win_height;

	glewExperimental = GL_TRUE;
	glewInit();

	SDL_GL_SetSwapInterval(1);

	setup_framebuffer(&frameBuffer[0], &texColorBuffer[0]);
	setup_framebuffer(&frameBuffer[1], &texColorBuffer[1]);

	setup_vertices(&sceneVao, &sceneVbo, cubeVertices, sizeof(cubeVertices));
	if (setup_shaders(&sceneShader) < 0)
       		goto out;

	setup_cube_textures(&sceneShader, sceneTextures);

	setup_attributes(&sceneShader, cubeAttributes, ARRAY_SIZE(cubeAttributes), 9);

	setup_vertices(&screenVao, &screenVbo, rectVertices, sizeof(rectVertices));
	if (setup_shaders(&screenShader) < 0)
       		goto out;
	glUseProgram(screenShader.shaderProgram);
	glUniform1i(glGetUniformLocation(screenShader.shaderProgram, "texFramebuffer"), 0);

	setup_attributes(&screenShader, rectAttributes, ARRAY_SIZE(rectAttributes), 4);

	setup_intro(win_width, win_height);
	setup_raymarcher(win_width, win_height);
	do_audio();
	
	loop_until(26100, window, do_loop_intro);
	loop_until(4 * 8 * 60 * 1000 / 147 , window, do_main_loop_images);
	loop_until(117551, window, do_raymarcher);
	loop_until(6531, window, do_loop_outro);

	Mix_FadeOutMusic((60.0 / 147.0) * 1000 * 8);
	while(Mix_PlayingMusic()) 
		check_esc(window);	
out:
	delete_shaders(&sceneShader);
	
	glDeleteBuffers(1, &sceneVbo);
	glDeleteVertexArrays(1, &sceneVao);
	glDeleteFramebuffers(1, &frameBuffer[0]);
	glDeleteFramebuffers(1, &frameBuffer[1]);

	SDL_GL_DeleteContext(context);

	SDL_Quit();

	return 0;
}
