/* ************************************************************************** */
/*                                                                            */
/*                                                        :::      ::::::::   */
/*   run.c                                              :+:      :+:    :+:   */
/*                                                    +:+ +:+         +:+     */
/*   By: gbrochar <gbrochar@student.42.fr>          +#+  +:+       +#+        */
/*                                                +#+#+#+#+#+   +#+           */
/*   Created: 2020/12/23 19:38:05 by gbrochar          #+#    #+#             */
/*   Updated: 2024/04/25 16:33:52 by gbrochar         ###   ########.fr       */
/*                                                                            */
/* ************************************************************************** */

#include "scop.h"
#include "mat4.h"
#include "vec3.h"

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

GLuint VBO;
//GLuint IBO;
GLuint gScaleLocation;
GLuint gTimeLocation;
GLuint gProjLocation;
GLuint gViewLocation;
GLuint gModelLocation;
GLuint gNormalLocation;
GLuint has_texture_location;
GLuint texture_level_location;
GLuint lighting_location;
//GLuint gCountLocation;
GLuint index_count;

int is_good;

GLuint gSamplerLocation;
GLuint texture;

const char* pVSFileName = "src/shader.vs";
const char*	pFSFileName = "src/shader.fs";

static void RenderSceneCB(t_env *e)
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	glEnable(GL_DEPTH_TEST);
	static float time = 0.0f;

	time += 0.01f;

	t_vec3 eye;
	t_vec3 up;
	t_vec3 target;

	eye.x = 0;
	eye.y = 0;
	eye.z = 2;
	up.x = 0;
	up.y = 1;
	up.z = 0;
	target.x = 0;
	target.y = 0;
	target.z = 0;

	t_mat4 proj = mat4_perspective(45.0 * M_PI / 180.0, 800.0 / 600.0, 0.001, 100);
	t_mat4 view = mat4_inverse(mat4_lookat(eye, up, target));
	// t_mat4 model = mat4_rotatey(mat4_rotatex(mat4_identity(), Scale), Scale / 1.3);
	//t_mat4 model = mat4_rotatey(mat4_identity(), Scale);
	
	if (e->rotating == true) {
		e->rotate.y += 0.01;
		//mat4_rotatey(e->rotate, 0.01);
	}
	if (e->texture == true) {
		e->texture_level += 0.006;
		if (e->texture_level > 1) {
			e->texture_level = 1;
		}
	}
	t_mat4 model = mat4_scale(
		mat4_rotatexyz(
			mat4_translate(mat4_identity(), e->translate), e->rotate), e->scale);
	t_mat4 normal = mat4_transpose(mat4_inverse(mat4_multiply(view, model)));
	glUniform1f(gTimeLocation, time);
	glUniformMatrix4fv(gProjLocation, 1, GL_FALSE, (GLfloat *)proj.data);
	glUniformMatrix4fv(gViewLocation, 1, GL_FALSE, (GLfloat *)view.data);
	glUniformMatrix4fv(gModelLocation, 1, GL_FALSE, (GLfloat *)model.data);
	glUniformMatrix4fv(gNormalLocation, 1, GL_FALSE, (GLfloat *)normal.data);
	//glUniform1i(gCountLocation, index_count);
	glActiveTexture(GL_TEXTURE0);
	glUniform1i(gSamplerLocation, 0);
	glUniform1i(has_texture_location, e->texture);
	glUniform1f(texture_level_location, e->texture_level);
	glUniform1i(lighting_location, e->lighting);

	glEnableVertexAttribArray(0);
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), 0);

	//if (e->texture) {
	glEnableVertexAttribArray(1);
	glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *)(3 * sizeof(float)));

	glEnableVertexAttribArray(2);
	glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *)(5 * sizeof(float)));
	//glBindTexture(GL_TEXTURE_2D, texture);
	//}
	//glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO);

	//glDrawElements(GL_TRIANGLES, index_count * 3, GL_UNSIGNED_INT, 0);
	glDrawArrays(GL_TRIANGLES, 0, index_count);
	glDisableVertexAttribArray(0);
	//glDisableVertexAttribArray(1);
	//glDisableVertexAttribArray(2);
}

static void CreateVertexBuffer(t_env *e)
{
	printf("status of different buffers:\n");
	printf("vertices len %ld\n", e->object.vertices.ptr);
	printf("uv len %ld\n", e->object.uvs.ptr);
	printf("normals len %ld\n", e->object.normals.ptr);
	printf("vertices indices len %ld\n", e->object.vertices_indices.ptr);
	printf("uvs indices len %ld\n", e->object.uvs_indices.ptr);
	printf("normals indices len %ld\n", e->object.normals_indices.ptr);
	is_good = 1;
	e->vbo_data = (float *)malloc(e->object.vertices_indices.ptr * 8 * sizeof(float));
	for (size_t i = 0; i < e->object.vertices_indices.ptr * 8; i += 8) {
		e->vbo_data[i] = e->object.vertices.data[e->object.vertices_indices.data[i / 8] * 3];
		e->vbo_data[i + 1] = e->object.vertices.data[e->object.vertices_indices.data[i / 8] * 3 + 1];
		e->vbo_data[i + 2] = e->object.vertices.data[e->object.vertices_indices.data[i / 8] * 3 + 2];
		if (e->object.uvs_indices.ptr == e->object.vertices_indices.ptr && e->object.uvs_indices.data[i / 8] * 2 + 1 < e->object.uvs.ptr) {
			e->vbo_data[i + 3] = e->object.uvs.data[e->object.uvs_indices.data[i / 8] * 2];
			e->vbo_data[i + 4] = e->object.uvs.data[e->object.uvs_indices.data[i / 8] * 2 + 1];
		} else {
			is_good = 0;
		}
		if (e->object.normals_indices.ptr == e->object.vertices_indices.ptr && e->object.normals_indices.data[i / 8] * 3 + 2 < e->object.normals.ptr) {
			e->vbo_data[i + 5] = e->object.normals.data[e->object.normals_indices.data[i / 8] * 3];
			e->vbo_data[i + 6] = e->object.normals.data[e->object.normals_indices.data[i / 8] * 3 + 1];
			e->vbo_data[i + 7] = e->object.normals.data[e->object.normals_indices.data[i / 8] * 3 + 2];
		} else {
			is_good = 0;
		}
	}

	glGenBuffers(1, &VBO);
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glBufferData(GL_ARRAY_BUFFER, e->object.vertices_indices.ptr * 8 * sizeof(float), e->vbo_data, GL_STATIC_DRAW);
	printf("Buffer created with success !\n");
/*	glGenBuffers(1, &VBO);
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glBufferData(GL_ARRAY_BUFFER, e->object.vertices.ptr * sizeof(double), e->object.vertices.data, GL_STATIC_DRAW);
	glGenBuffers(1, &IBO);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, e->object.vertices_indices.ptr * sizeof(int), e->object.vertices_indices.data, GL_STATIC_DRAW);
	*/
}

static void AddShader(GLuint ShaderProgram, const char* pShaderText, GLenum ShaderType, GLuint *shader)
{
	*shader = glCreateShader(ShaderType);

	if (*shader == 0)
	{
		fprintf(stderr, "Error creating shader type %d\n", ShaderType);
		exit(0);
	}

	const GLchar* p[1];
	p[0] = pShaderText;
	GLint Lengths[1];
	Lengths[0] = strlen(pShaderText);
	glShaderSource(*shader, 1, p, Lengths);
	glCompileShader(*shader);
	GLint success;
	glGetShaderiv(*shader, GL_COMPILE_STATUS, &success);
	if (!success)
	{
		GLchar InfoLog[1024];
		glGetShaderInfoLog(*shader, 1024, NULL, InfoLog);
		fprintf(stderr, "Error compiling shader type %d: '%s'\n", ShaderType, InfoLog);
		exit(1);
	}

	glAttachShader(ShaderProgram, *shader);
}

static void CompileShaders(t_env *e)
{
	e->shader_program = glCreateProgram();

	if (e->shader_program == 0)
	{
		fprintf(stderr, "Error creating shader program\n");
		exit(1);
	}

	char		*vs = (char *)malloc(BUFFER_SIZE * sizeof(char));
	char		*fs = (char *)malloc(BUFFER_SIZE * sizeof(char));
	char		*buffer = (char *)malloc(BUFFER_SIZE * sizeof(char));
	FILE		*fp;

	vs[0] = '\0';
	fs[0] = '\0';
	fp = fopen(pVSFileName, "r");
	while (fgets(buffer, 4096, fp))
	{
		buffer[strcspn(buffer, "\n") + 1] = '\0';
		strcat(vs, buffer);
	}
	fclose(fp);
	fp = fopen(pFSFileName, "r");
	while (fgets(buffer, 4096, fp))
	{
		buffer[strcspn(buffer, "\n") + 1] = '\0';
		strcat(fs, buffer);
	}
	fclose(fp);
	AddShader(e->shader_program, vs, GL_VERTEX_SHADER, &(e->vertex_shader));
	AddShader(e->shader_program, fs, GL_FRAGMENT_SHADER, &(e->fragment_shader));

	GLint Success = 0;
	GLchar ErrorLog[1024] = { 0 };

	glLinkProgram(e->shader_program);
	glGetProgramiv(e->shader_program, GL_LINK_STATUS, &Success);
	if (Success == 0)
	{
		glGetProgramInfoLog(e->shader_program, sizeof(ErrorLog), NULL, ErrorLog);
		fprintf(stderr, "Error linking shader program : '%s'\n", ErrorLog);
		exit(1);
	}

	glValidateProgram(e->shader_program);
	glGetProgramiv(e->shader_program, GL_VALIDATE_STATUS, &Success);
	if (!Success)
	{
		glGetProgramInfoLog(e->shader_program, sizeof(ErrorLog), NULL, ErrorLog);
		fprintf(stderr, "Invalid shader program '%s'\n", ErrorLog);
		exit(1);
	}

	glUseProgram(e->shader_program);

	// gScaleLocation = glGetUniformLocation(e->shader_program, "gScale");
	gTimeLocation = glGetUniformLocation(e->shader_program, "time");
	gProjLocation = glGetUniformLocation(e->shader_program, "proj");
	gViewLocation = glGetUniformLocation(e->shader_program, "view");
	gModelLocation = glGetUniformLocation(e->shader_program, "model");
	gNormalLocation = glGetUniformLocation(e->shader_program, "normalMat");
	gSamplerLocation = glGetUniformLocation(e->shader_program, "ourTexture");
	has_texture_location = glGetUniformLocation(e->shader_program, "has_texture");
	texture_level_location = glGetUniformLocation(e->shader_program, "texture_level");
	lighting_location = glGetUniformLocation(e->shader_program, "lighting");
	// assert(gScaleLocation != 0xFFFFFFFF);
	assert(gTimeLocation != 0xFFFFFFFF);
	assert(gProjLocation != 0xFFFFFFFF);
	assert(gViewLocation != 0xFFFFFFFF);
	assert(gModelLocation != 0xFFFFFFFF);
	free(buffer);
	free(vs);
	free(fs);
}

void			free_shaders(t_env *e)
{
	glDetachShader(e->shader_program, e->vertex_shader);
	glDetachShader(e->shader_program, e->fragment_shader);
	glDeleteShader(e->vertex_shader);
	glDeleteShader(e->fragment_shader);
	glUseProgram(0);
	glDeleteProgram(e->shader_program);
}

void			key_callback(GLFWwindow *window, int key, int scancode, int action, int mods)
{
	t_env		*e = (t_env *)glfwGetWindowUserPointer(window);
	if (key == GLFW_KEY_N && (action == GLFW_PRESS))
		e->rotating = !e->rotating;
	if (key == GLFW_KEY_B && (action == GLFW_PRESS)) {
		e->texture = !e->texture;
		if (e->texture == true) {
			e->texture_level = 0;
		}
	}
	if (key == GLFW_KEY_V && (action == GLFW_PRESS)) {
		e->lighting = !e->lighting;
	}
	if (key == GLFW_KEY_Q && (action == GLFW_PRESS || action == GLFW_REPEAT))
		e->translate.x += 0.03;
	if (key == GLFW_KEY_A && (action == GLFW_PRESS || action == GLFW_REPEAT))
		e->translate.x -= 0.03;
	if (key == GLFW_KEY_W && (action == GLFW_PRESS || action == GLFW_REPEAT))
		e->translate.y += 0.03;
	if (key == GLFW_KEY_S && (action == GLFW_PRESS || action == GLFW_REPEAT))
		e->translate.y -= 0.03;
	if (key == GLFW_KEY_E && (action == GLFW_PRESS || action == GLFW_REPEAT))
		e->translate.z += 0.03;
	if (key == GLFW_KEY_D && (action == GLFW_PRESS || action == GLFW_REPEAT))
		e->translate.z -= 0.03;
	if (key == GLFW_KEY_R && (action == GLFW_PRESS || action == GLFW_REPEAT))
		e->rotate.x += 0.03;
	if (key == GLFW_KEY_F && (action == GLFW_PRESS || action == GLFW_REPEAT))
		e->rotate.x -= 0.03;
	if (key == GLFW_KEY_T && (action == GLFW_PRESS || action == GLFW_REPEAT))
		e->rotate.y += 0.03;
	if (key == GLFW_KEY_G && (action == GLFW_PRESS || action == GLFW_REPEAT))
		e->rotate.y -= 0.03;
	if (key == GLFW_KEY_Y && (action == GLFW_PRESS || action == GLFW_REPEAT))
		e->rotate.z += 0.03;
	if (key == GLFW_KEY_H && (action == GLFW_PRESS || action == GLFW_REPEAT))
		e->rotate.z -= 0.03;
	if (key == GLFW_KEY_U && (action == GLFW_PRESS || action == GLFW_REPEAT))
		e->scale.x *= 1.03;
	if (key == GLFW_KEY_J && (action == GLFW_PRESS || action == GLFW_REPEAT))
		e->scale.x *= 0.97;
	if (key == GLFW_KEY_I && (action == GLFW_PRESS || action == GLFW_REPEAT))
		e->scale.y *= 1.03;
	if (key == GLFW_KEY_K && (action == GLFW_PRESS || action == GLFW_REPEAT))
		e->scale.y *= 0.97;
	if (key == GLFW_KEY_O && (action == GLFW_PRESS || action == GLFW_REPEAT))
		e->scale.z *= 1.03;
	if (key == GLFW_KEY_L && (action == GLFW_PRESS || action == GLFW_REPEAT))
		e->scale.z *= 0.97;
	(void)scancode;
	(void)mods;
}

int				run(t_env *e)
{
	GLFWwindow* window;

    /* Initialize the library */
    if (!glfwInit())
        return (FAILURE);

    /* Create a windowed mode window and its OpenGL context */
    window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL);
	glfwSetWindowUserPointer(window, e);
	glfwSetKeyCallback(window, key_callback);
    if (!window)
    {
        glfwTerminate();
        return (FAILURE);
    }

	glfwMakeContextCurrent(window);

	index_count = e->object.vertices_indices.ptr;
	//printf("%ld\n", e->object.vertices.ptr);
	//printf("%ld\n", e->object.vertices_indices.ptr);
	GLenum res = glewInit();
	if (res != GLEW_OK)
	{
		fprintf(stderr, "Error : '%s'\n", glewGetErrorString(res));
		exit (1);
	}
	printf("GL version: %s\n", glGetString(GL_VERSION));
	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
	CreateVertexBuffer(e);

	glGenTextures(1, &texture);
	glBindTexture(GL_TEXTURE_2D, texture);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

	int tex_width, tex_height, nrChannels;

	unsigned char *tex_data = stbi_load("resources/42.png", &tex_width, &tex_height, &nrChannels, 0);
	printf("number of channels : %d\n", nrChannels);
	if (nrChannels == 4) {
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex_width, tex_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, tex_data);
	} else if (nrChannels == 3) {
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, tex_width, tex_height, 0, GL_RGB, GL_UNSIGNED_BYTE, tex_data);
	} else {
		printf("error: sus text file detected, aborting NOW\n");
		exit(0);
	}
	glGenerateMipmap(GL_TEXTURE_2D);
	stbi_image_free(tex_data);

	CompileShaders(e);
	 /* Loop until the user closes the window */
    while (!glfwWindowShouldClose(window))
    {
        /* Render here */
        glClear(GL_COLOR_BUFFER_BIT);

		RenderSceneCB(e);
        /* Swap front and back buffers */
        glfwSwapBuffers(window);

        /* Poll for and process events */
        glfwPollEvents();
    }
	free_shaders(e);
    glfwTerminate();
	return (SUCCESS);
}

