#include "tilingandblending_func.h"
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

#include <iostream>
#include <sstream>
#include <fstream>
#include <vector>
#include <string>
#include <filesystem> //If having any issue, replace with #include <experimental/filesystem>
#include <GL/glew.h>
#include <GLFW/glfw3.h>


using namespace std;

// macro for easy access to data
#define macro_str(s) #s
#define macro_xstr(s) macro_str(s)
#define DATA_PATH std::string(macro_xstr(DEF_DATA_PATH))

/* ======================= Forward Declarations ============================ */
void RenderFrame();
void RenderFrameClassic(bool splitScreenMode);
void RenderFrameByExample(bool splitScreenMode);

void ResizeEventHandling(GLFWwindow* window, int width, int height);
void KeyboardEventHandling(GLFWwindow* window,int k, int c, int action, int mods);
void MouseBoutonEventHandling(GLFWwindow* window, int button, int action, int mods);
void MouseWheelEventHandling(GLFWwindow* wi, double xoffset, double yoffset);
void MouseDragHandling(GLFWwindow* wi, double x, double y);

void glfw_error_callback(int error, const char* description)
{
	cerr <<"Glfw Error "<<error << ": " << description << endl;
}

GLuint CreateGLTextureFromTextureDataStruct(const TextureDataFloat& im, GLenum wrapMode, bool generateMips, bool compress);
GLuint CreateGLMapFromMapDataStruct(const std::vector<vec2> dataVector, GLenum wrapMode);

int ReadPNGtoTextureData(string path, TextureDataFloat& out);
//int ReadPPMtoTextureData(string path, TextureDataFloat& out);
int ReadMaptoVector(string path, std::vector<vec2>& out);
GLuint CompileShader(const string& vertexFile, const string& fragmentFile);
GLuint vao_empty;

unsigned long GetFileLength(ifstream& file);
int LoadShader(const char* filename, GLchar** shaderSource, GLint* len);
void RenderFramePrimalAndDual(bool splitScreenMode);
void RenderFrameByExampleClassic(bool splitScreenMode);
void RenderFramePrimalAndDualClassic(bool splitScreenMode);

/* ========================== Global Variables ============================= */
GLFWwindow* window;
int WINDOW_WIDTH = 1000;
int WINDOW_HEIGHT = 1000;
int FB_W = 0;
int FB_H = 0;	

vec2 textureScale = vec2(1.0f, 1.0f);
vec2 textureTranslate = vec2(0.0f, 0.0f);
int renderingMode = 2;
bool dragging = false;
vec2 previousMousePosition = vec2(0.0f, 0.0f);
int frame = 0;
double timebase = 0;
int vbsync = 0;

GLuint TilingRepeatShader;
GLuint TilingAndBlendingShader;
GLuint PrimalAndDualShader;
GLuint TilingAndBlendingClassicShader;
GLuint PrimalAndDualClassicShader;
GLuint input_textureID = 0;
GLuint Tinput_textureID = 0;
GLuint invT_textureID = 0;

GLuint map_textureID = 0;
GLuint mean_textureID = 0;
GLuint inputMeanRemoved_textureID = 0;
GLuint TinputMeanRemoved_textureID = 0;
GLuint invTMeanRemoved_textureID = 0;


// Uniform variables to pass to the fragment shader
vec3 colorSpaceVector1, colorSpaceVector2, colorSpaceVector3;
vec3 colorSpaceVector1MeanRemoved, colorSpaceVector2MeanRemoved, colorSpaceVector3MeanRemoved;
vec3 colorSpaceOrigin, colorSpaceOriginMeanRemoved;
vec3 DXTScalers; // COMPRESSION FIX

vector<string> tex_list;
vector<string>::iterator tex_list_it;

void load_sample(const std::string& inputName)
{
	string inputSampleFile = DATA_PATH + "/Textures/" + inputName + ".png";
	string inputMapFile = DATA_PATH + "/Maps/" + inputName + ".map";
	string meanSampleFile = DATA_PATH + "/PreComputedMeans/" + inputName + ".png";

	// Read data from input texture sample
	TextureDataFloat input;
	if (ReadPNGtoTextureData(inputSampleFile, input) == -1)
		exit(1);
	TextureDataFloat mean;
	if (ReadPNGtoTextureData(meanSampleFile, mean) == -1)
		exit(1);
	std::vector<vec2> map;
	if (ReadMaptoVector(inputMapFile, map) == -1)
		exit(1);

	// preprocess
	TextureDataFloat Tinput;
	TextureDataFloat lut;
	TextureDataFloat inputMeanRemoved;
	TextureDataFloat TinputMeanRemoved;
	TextureDataFloat lutMeanRemoved;
	inputMeanRemoved = TextureDataFloat(input.width, input.height, 3);
	// 2022 added precomputation: removing the spatially-varying mean
	for(int channel = 0 ; channel < 3 ; channel++)
	{
		PreRemoveMean(input, inputMeanRemoved, mean, channel);
	}
	Precomputations(inputMeanRemoved, TinputMeanRemoved, lutMeanRemoved, colorSpaceVector1MeanRemoved, colorSpaceVector2MeanRemoved, colorSpaceVector3MeanRemoved, colorSpaceOriginMeanRemoved);
	Precomputations(input, Tinput, lut, colorSpaceVector1, colorSpaceVector2, colorSpaceVector3, colorSpaceOrigin);

	// If we use DXT compression
	// we need to rescale the Gaussian channels (see Section 1.6)

	DXTScalers.x = -1.0f;
	DXTScalers.y = -1.0f;
	DXTScalers.z = -1.0f;

	if (glIsTexture(input_textureID))
		glDeleteTextures(1,&input_textureID);
	if (glIsTexture(mean_textureID))
		glDeleteTextures(1,&mean_textureID);
	if (glIsTexture(map_textureID))
		glDeleteTextures(1,&map_textureID);

	if (glIsTexture(Tinput_textureID))
		glDeleteTextures(1,&Tinput_textureID);
	if (glIsTexture(invT_textureID))
		glDeleteTextures(1,&invT_textureID);
	if (glIsTexture(TinputMeanRemoved_textureID))
		glDeleteTextures(1,&TinputMeanRemoved_textureID);
	if (glIsTexture(invTMeanRemoved_textureID))
		glDeleteTextures(1,&invTMeanRemoved_textureID);


	// Initialize textures
	input_textureID = CreateGLTextureFromTextureDataStruct(input, GL_REPEAT, true, USE_DXT_COMPRESSION);
	mean_textureID = CreateGLTextureFromTextureDataStruct(mean, GL_REPEAT, true, USE_DXT_COMPRESSION);
	map_textureID = CreateGLMapFromMapDataStruct(map, GL_REPEAT);

	Tinput_textureID = CreateGLTextureFromTextureDataStruct(Tinput, GL_REPEAT, true, USE_DXT_COMPRESSION);
	invT_textureID = CreateGLTextureFromTextureDataStruct(lut, GL_CLAMP_TO_EDGE, false, USE_DXT_COMPRESSION);
	TinputMeanRemoved_textureID = CreateGLTextureFromTextureDataStruct(TinputMeanRemoved, GL_REPEAT, true, USE_DXT_COMPRESSION);
	invTMeanRemoved_textureID = CreateGLTextureFromTextureDataStruct(lutMeanRemoved, GL_CLAMP_TO_EDGE, false, USE_DXT_COMPRESSION);	

}

/* =============================== MAIN ==================================== */

int main(int argc, char** argv)
{
	glfwSetErrorCallback(glfw_error_callback);
	if (!glfwInit())
	{
		cerr << "Failed to initialize GFLW!" << endl;
	}

#ifdef __APPLE__
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
	glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, true);
#else
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
#endif

	window = glfwCreateWindow(1280, 1024, "PDTnBDemo", nullptr, nullptr);
	if (window == nullptr)
	{
		cerr << "Failed to create Window!" << endl;
	}
	glfwMakeContextCurrent(window);
	glfwGetFramebufferSize (window, &FB_W, &FB_H);
	
	glfwSetFramebufferSizeCallback(window, ResizeEventHandling);
	glfwSetKeyCallback(window, KeyboardEventHandling);
	glfwSetMouseButtonCallback(window, MouseBoutonEventHandling);
	glfwSetCursorPosCallback(window, MouseDragHandling);
	glfwSetScrollCallback(window, MouseWheelEventHandling);
	glfwSwapInterval(vbsync);
	cout << glGetString(GL_VENDOR)<< endl;
	cout << glGetString(GL_RENDERER)<< endl;
	cout << glGetString(GL_VERSION)<< endl;


	GLenum err = glewInit();
	if (GLEW_OK != err)
	{
		// Problem: glewInit failed, something is seriously wrong. 
		cerr << " Error " << glewGetErrorString(err) << endl;
	}

	cout << "Scanning "<< DATA_PATH+"/Textures/ :" << endl;

	//If having any issue, replace std::filesystem with std::experimental::filesystem and uncomment lib stdc++fs in CmakeLists.txt
	const std::filesystem::path textures_dir{DATA_PATH + "/Textures/"};
    for (auto const& dir_entry : std::filesystem::directory_iterator{textures_dir})
	{
		auto p = dir_entry.path();
		auto ext = p.extension();
		if ((ext == ".png") || (ext == ".PNG"))
			tex_list.push_back(dir_entry.path().stem().string());
	}
	
	for (auto t : tex_list)
		cout << t << " ";
	cout <<endl;

	tex_list_it = tex_list.begin();

	if (argc > 1)
	{
		auto it = find(tex_list.begin(),tex_list.end(), string(argv[1]));
		if (it != tex_list.end())
			tex_list_it = it;
	}
	
	cout << endl;
	cout << "Controls:" << endl;
	cout << "F1: display repeated input"  <<endl;
	cout << "F2: display ACf-preserving tiling and blending" << endl;
	cout << "F3: display ACf-preserving primal and dual blending" << endl;
	cout << "F4: display classically-sampled tiling and blending" << endl;
	cout << "F5: display classically-sampled primal and dual blending" << endl;
	cout << "F10: reset pos and scale (use mouse)" << endl;
	cout << "BACKSPACE: swap VBlankSync/FPS Mode" << endl;
	cout << "Enter: load next texture" << endl;
	cout << "Use mouse click and drag to move and wheel mouse to zoom" << endl << endl;

	glfwMakeContextCurrent(window);

	load_sample(*tex_list_it);


	// Initialize shaders
	TilingRepeatShader = CompileShader(DATA_PATH+"/Shaders/tiling_repeat.vert",
		DATA_PATH + "/Shaders/tiling_repeat.frag");
	TilingAndBlendingShader = CompileShader(DATA_PATH + "/Shaders/tiling_and_blending.vert",
		DATA_PATH + "/Shaders/tiling_and_blending.frag");
	PrimalAndDualShader = CompileShader(DATA_PATH + "/Shaders/primal_and_dual.vert",
		DATA_PATH + "/Shaders/primal_and_dual.frag");
	TilingAndBlendingClassicShader = CompileShader(DATA_PATH + "/Shaders/tiling_and_blending.vert",
		DATA_PATH + "/Shaders/tiling_and_blending_classic.frag");
	PrimalAndDualClassicShader = CompileShader(DATA_PATH + "/Shaders/primal_and_dual.vert",
		DATA_PATH + "/Shaders/primal_and_dual_classic.frag");

	glGenVertexArrays(1, &vao_empty);
	glBindVertexArray(vao_empty);

	timebase =  glfwGetTime();
	while (!glfwWindowShouldClose(window))
	{
		if (vbsync==0)
		{
			double now = glfwGetTime();
			++frame;
			double interv = now - timebase;
			if (interv > 1.0) 
			{
				cout << "FPS: "<< frame/interv<<endl;
				cout << "Frame time: "<< interv/frame <<endl;
				timebase = now;
				frame = 0;
			}
		}
		

		glfwPollEvents();
		glfwMakeContextCurrent(window);
		RenderFrame();
		glfwSwapBuffers(window);
	}
	glfwDestroyWindow(window);

	return 0;
}

/* ============================ GLUT Events ================================ */
void RenderFrame()
{
	glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
	glDrawBuffer(GL_BACK);

	glViewport(0, 0, FB_W, FB_H);

	glClearColor(0,0,0.5f,0);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	switch(renderingMode)
	{
		case 0:
			RenderFrameClassic(false);
			break;
		case 1:
			RenderFrameByExample(false);
			break;
		case 2:
			RenderFramePrimalAndDual(false);
			break;
		case 3:
			RenderFrameByExampleClassic(false);
			break;
		case 4:
			RenderFramePrimalAndDualClassic(false);
			break;
		default:
		break;
	}
			// Draw
	glBindVertexArray(vao_empty);
	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}

void RenderFrameClassic(bool splitScreenMode)
{
	// Bind shader
	glUseProgram(TilingRepeatShader);

	// Bind uniforms
	glUniform1f(glGetUniformLocation(TilingRepeatShader, "_SplitScreenMode"),
		splitScreenMode ? 1.0f : 0.0f);
	glUniform1f(glGetUniformLocation(TilingRepeatShader, "_AspectRatio"), float(FB_W) / float(FB_H));
	glUniform4f(glGetUniformLocation(TilingRepeatShader, "_ScaleTranslate"),
		textureScale.x, textureScale.y, textureTranslate.x, textureTranslate.y);
	int id = glGetUniformLocation(TilingRepeatShader, "input");
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, input_textureID);
	glUniform1i(id, 0);

}

void RenderFrameByExample(bool splitScreenMode)
{
	// Bind shader
	glUseProgram(TilingAndBlendingShader);

	// Bind uniforms
	glUniform1f(glGetUniformLocation(TilingAndBlendingShader, "_HalfScreenMode"),
		splitScreenMode ? 1.0f : 0.0f);
	glUniform1f(glGetUniformLocation(TilingAndBlendingShader, "_AspectRatio"),float(FB_W) / float(FB_H));

	glUniform4f(glGetUniformLocation(TilingAndBlendingShader, "_ScaleTranslate"),
		textureScale.x, textureScale.y, textureTranslate.x, textureTranslate.y);

	int id = glGetUniformLocation(TilingAndBlendingShader, "Tinput");
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, TinputMeanRemoved_textureID);
	glUniform1i(id, 0);
	id = glGetUniformLocation(TilingAndBlendingShader, "invT");
	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_2D, invTMeanRemoved_textureID);
	glUniform1i(id, 1);
	id = glGetUniformLocation(TilingAndBlendingShader, "map");
	glActiveTexture(GL_TEXTURE2);
	glBindTexture(GL_TEXTURE_1D, map_textureID);
	glUniform1i(id, 2);
	id = glGetUniformLocation(TilingAndBlendingShader, "mean");
	glActiveTexture(GL_TEXTURE3);
	glBindTexture(GL_TEXTURE_2D, mean_textureID);
	glUniform1i(id, 3);


	glUniform3f(glGetUniformLocation(TilingAndBlendingShader, "_colorSpaceVector1"),
		colorSpaceVector1MeanRemoved.x, colorSpaceVector1MeanRemoved.y, colorSpaceVector1MeanRemoved.z);
	glUniform3f(glGetUniformLocation(TilingAndBlendingShader, "_colorSpaceVector2"),
		colorSpaceVector2MeanRemoved.x, colorSpaceVector2MeanRemoved.y, colorSpaceVector2MeanRemoved.z);
	glUniform3f(glGetUniformLocation(TilingAndBlendingShader, "_colorSpaceVector3"),
		colorSpaceVector3MeanRemoved.x, colorSpaceVector3MeanRemoved.y, colorSpaceVector3MeanRemoved.z);
	glUniform3f(glGetUniformLocation(TilingAndBlendingShader, "_colorSpaceOrigin"),
		colorSpaceOriginMeanRemoved.x, colorSpaceOriginMeanRemoved.y, colorSpaceOriginMeanRemoved.z);
	glUniform3f(glGetUniformLocation(TilingAndBlendingShader, "_DXTScalers"),
		DXTScalers.x, DXTScalers.y, DXTScalers.z);

}

void RenderFramePrimalAndDual(bool splitScreenMode)
{
	// Bind shader
	glUseProgram(PrimalAndDualShader);

	// Bind uniforms
	glUniform1f(glGetUniformLocation(PrimalAndDualShader, "_HalfScreenMode"),
		splitScreenMode ? 1.0f : 0.0f);
	glUniform1f(glGetUniformLocation(PrimalAndDualShader, "_AspectRatio"),float(FB_W) / float(FB_H));

	glUniform4f(glGetUniformLocation(PrimalAndDualShader, "_ScaleTranslate"),
		textureScale.x, textureScale.y, textureTranslate.x, textureTranslate.y);

	int id = glGetUniformLocation(PrimalAndDualShader, "Tinput");
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, TinputMeanRemoved_textureID);
	glUniform1i(id, 0);
	id = glGetUniformLocation(PrimalAndDualShader, "invT");
	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_2D, invTMeanRemoved_textureID);
	glUniform1i(id, 1);
	id = glGetUniformLocation(PrimalAndDualShader, "map");
	glActiveTexture(GL_TEXTURE2);
	glBindTexture(GL_TEXTURE_1D, map_textureID);
	glUniform1i(id, 2);
	id = glGetUniformLocation(PrimalAndDualShader, "mean");
	glActiveTexture(GL_TEXTURE3);
	glBindTexture(GL_TEXTURE_2D, mean_textureID);
	glUniform1i(id, 3);


	glUniform3f(glGetUniformLocation(PrimalAndDualShader, "_colorSpaceVector1"),
		colorSpaceVector1MeanRemoved.x, colorSpaceVector1MeanRemoved.y, colorSpaceVector1MeanRemoved.z);
	glUniform3f(glGetUniformLocation(PrimalAndDualShader, "_colorSpaceVector2"),
		colorSpaceVector2MeanRemoved.x, colorSpaceVector2MeanRemoved.y, colorSpaceVector2MeanRemoved.z);
	glUniform3f(glGetUniformLocation(PrimalAndDualShader, "_colorSpaceVector3"),
		colorSpaceVector3MeanRemoved.x, colorSpaceVector3MeanRemoved.y, colorSpaceVector3MeanRemoved.z);
	glUniform3f(glGetUniformLocation(PrimalAndDualShader, "_colorSpaceOrigin"),
		colorSpaceOriginMeanRemoved.x, colorSpaceOriginMeanRemoved.y, colorSpaceOriginMeanRemoved.z);
	glUniform3f(glGetUniformLocation(PrimalAndDualShader, "_DXTScalers"),
		DXTScalers.x, DXTScalers.y, DXTScalers.z);

}

void RenderFrameByExampleClassic(bool splitScreenMode)
{
	// Bind shader
	glUseProgram(TilingAndBlendingClassicShader);

	// Bind uniforms
	glUniform1f(glGetUniformLocation(TilingAndBlendingClassicShader, "_HalfScreenMode"),
		splitScreenMode ? 1.0f : 0.0f);
	glUniform1f(glGetUniformLocation(TilingAndBlendingClassicShader, "_AspectRatio"),float(FB_W) / float(FB_H));

	glUniform4f(glGetUniformLocation(TilingAndBlendingClassicShader, "_ScaleTranslate"),
		textureScale.x, textureScale.y, textureTranslate.x, textureTranslate.y);

	int id = glGetUniformLocation(TilingAndBlendingClassicShader, "Tinput");
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, Tinput_textureID);
	glUniform1i(id, 0);
	id = glGetUniformLocation(TilingAndBlendingClassicShader, "invT");
	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_2D, invT_textureID);
	glUniform1i(id, 1);

	glUniform3f(glGetUniformLocation(TilingAndBlendingClassicShader, "_colorSpaceVector1"),
		colorSpaceVector1.x, colorSpaceVector1.y, colorSpaceVector1.z);
	glUniform3f(glGetUniformLocation(TilingAndBlendingClassicShader, "_colorSpaceVector2"),
		colorSpaceVector2.x, colorSpaceVector2.y, colorSpaceVector2.z);
	glUniform3f(glGetUniformLocation(TilingAndBlendingClassicShader, "_colorSpaceVector3"),
		colorSpaceVector3.x, colorSpaceVector3.y, colorSpaceVector3.z);
	glUniform3f(glGetUniformLocation(TilingAndBlendingClassicShader, "_colorSpaceOrigin"),
		colorSpaceOrigin.x, colorSpaceOrigin.y, colorSpaceOrigin.z);
	glUniform3f(glGetUniformLocation(TilingAndBlendingClassicShader, "_DXTScalers"),
		DXTScalers.x, DXTScalers.y, DXTScalers.z);
}

void RenderFramePrimalAndDualClassic(bool splitScreenMode)
{
	// Bind shader
	glUseProgram(PrimalAndDualClassicShader);

	// Bind uniforms
	glUniform1f(glGetUniformLocation(PrimalAndDualClassicShader, "_HalfScreenMode"),
		splitScreenMode ? 1.0f : 0.0f);
	glUniform1f(glGetUniformLocation(PrimalAndDualClassicShader, "_AspectRatio"),float(FB_W) / float(FB_H));

	glUniform4f(glGetUniformLocation(PrimalAndDualClassicShader, "_ScaleTranslate"),
		textureScale.x, textureScale.y, textureTranslate.x, textureTranslate.y);

	int id = glGetUniformLocation(PrimalAndDualClassicShader, "Tinput");
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, Tinput_textureID);
	glUniform1i(id, 0);
	id = glGetUniformLocation(PrimalAndDualClassicShader, "invT");
	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_2D, invT_textureID);
	glUniform1i(id, 1);

	glUniform3f(glGetUniformLocation(PrimalAndDualClassicShader, "_colorSpaceVector1"),
		colorSpaceVector1.x, colorSpaceVector1.y, colorSpaceVector1.z);
	glUniform3f(glGetUniformLocation(PrimalAndDualClassicShader, "_colorSpaceVector2"),
		colorSpaceVector2.x, colorSpaceVector2.y, colorSpaceVector2.z);
	glUniform3f(glGetUniformLocation(PrimalAndDualClassicShader, "_colorSpaceVector3"),
		colorSpaceVector3.x, colorSpaceVector3.y, colorSpaceVector3.z);
	glUniform3f(glGetUniformLocation(PrimalAndDualClassicShader, "_colorSpaceOrigin"),
		colorSpaceOrigin.x, colorSpaceOrigin.y, colorSpaceOrigin.z);
	glUniform3f(glGetUniformLocation(PrimalAndDualClassicShader, "_DXTScalers"),
		DXTScalers.x, DXTScalers.y, DXTScalers.z);
}

void ResizeEventHandling(GLFWwindow* window, int width, int height)
{
	WINDOW_WIDTH = width;
	WINDOW_HEIGHT = height;
	glfwGetFramebufferSize (window, &FB_W, &FB_H);
}

void KeyboardEventHandling(GLFWwindow* window, int k, int c, int action, int mods)
{
	// Switch rendering mode
	if (action == GLFW_PRESS)
	{
		switch(k)
		{
			case GLFW_KEY_ESCAPE:
				exit(0);
				break;
			case GLFW_KEY_F1:
				renderingMode =  0;
				break;
			case GLFW_KEY_F2:
				renderingMode = 1;
				break;
			case GLFW_KEY_F3:
				renderingMode = 2;
				break;
			case GLFW_KEY_F4:
				renderingMode = 3;
				break;
			case GLFW_KEY_F5:
				renderingMode = 4;
				break;
			case GLFW_KEY_F10:
				textureScale = vec2(1.0f, 1.0f);
				textureTranslate = vec2(0.0f, 0.0f);
				break;
			case GLFW_KEY_BACKSPACE:
				vbsync = 1 - vbsync;
				glfwSwapInterval(vbsync);
				timebase = glfwGetTime();
				frame = 0;
				break;
			case GLFW_KEY_ENTER:
				++tex_list_it;
				if (tex_list_it==tex_list.end())
					tex_list_it = tex_list.begin();
				cout << "loading " << *tex_list_it <<endl;
				load_sample(*tex_list_it);
				break;
		}
//		RenderFrame();
	}
}


void MouseBoutonEventHandling(GLFWwindow* window, int button, int action, int mods)
{
	// Drag start
	if (button == GLFW_MOUSE_BUTTON_1
		|| button == GLFW_MOUSE_BUTTON_2
		|| button == GLFW_MOUSE_BUTTON_3)
	{
		double cx,cy;
		glfwGetCursorPos(window, &cx,&(cy));
		previousMousePosition = vec2(float(cx), float(cy));
		dragging = (action == GLFW_PRESS);
	}
	

}

void MouseWheelEventHandling(GLFWwindow* wi, double xoffset, double yoffset)
{
    textureScale *= (yoffset>0)?1.1f:0.9f;
}

void MouseDragHandling(GLFWwindow* wi, double x, double y)
{
	if (dragging)
	{
		vec2 currentMousePos = vec2(float(x), float(y));
		textureTranslate -= (currentMousePos - previousMousePosition)
			* (textureScale.x * 0.002f);
		previousMousePosition = currentMousePos;
	}
}


/* ========================================================================= */


/* ========================== Texture Handling ============================= */
GLuint CreateGLTextureFromTextureDataStruct(const TextureDataFloat& im, GLenum wrapMode, bool generateMips, bool DXTcompress=false)
{
	if (im.data.empty())
		return 0;

	GLuint texture;
	glGenTextures(1, &texture);
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, texture);

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
		generateMips ? GL_LINEAR_MIPMAP_LINEAR : GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);

	glTexImage2D(GL_TEXTURE_2D, 0, DXTcompress ? GL_COMPRESSED_RGB_S3TC_DXT1_EXT : GL_RGB8,
	im.width, im.height, 0,	GL_RGB, GL_FLOAT, im.data.data());

	if (generateMips)
	 	glGenerateMipmap(GL_TEXTURE_2D);
	return texture;
}

GLuint CreateGLMapFromMapDataStruct(const std::vector<vec2> dataVector, GLenum wrapMode)
{
	if (dataVector.empty())
		return 0;

	GLuint texture;
	glGenTextures(1, &texture);
	glActiveTexture(GL_TEXTURE2);
	glBindTexture(GL_TEXTURE_1D, texture);

	glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, wrapMode);
	glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_T, wrapMode);

	glTexImage1D(GL_TEXTURE_1D, 0, GL_RG32F, int32_t(dataVector.size()), 0, GL_RG, GL_FLOAT, dataVector.data());
	return texture;
}



int ReadPNGtoTextureData(string path, TextureDataFloat& out)
{
	uint8_t *dat = stbi_load(path.c_str(), &(out.width), &(out.height), &(out.channels),0);

	if (dat == nullptr)
	{
		cerr << "Couldn't open " << path << endl;
		return -1;
	}

	if ((out.channels != 3) && (out.channels != 1))
	{
		cerr << "Error " << path << " is not a rgb image nor a gray image" << endl;
		return -1;
	}
	
	std::size_t nbf = std::size_t(out.width) * std::size_t(out.height) * 3;
	out.data.reserve(nbf);
	uint8_t* ptr = dat;
	if (out.channels == 1)
	{
		nbf /= 3;
		for (std::size_t i = 0; i < nbf; ++i)
		{
			float p = float(double(*ptr++) / 255.0);
			out.data.push_back(p);
			out.data.push_back(p);
			out.data.push_back(p);
		}
	}
	else if (out.channels == 3)
		for (std::size_t i = 0; i < nbf; ++i)
			out.data.push_back(float(double(*ptr++) / 255.0));

	stbi_image_free(dat);
	return 0;
}

//
//int ReadPPMtoTextureData(string path, TextureDataFloat& out)
//{
//	filebuf fb;
//	if (!fb.open(path, ios::in | ios::binary))
//	{
//		cerr << "Couldn't open ppm file !" << endl;
//		return -1;
//	}
//	istream in(&fb);
//
//	// Only RGB format supported
//	out.channels = 3;
//
//	// Header parsing while ignoring comment lines
//	// Read magic number
//	string s;
//	in >> s;
//	while (s.front() == '#')
//	{
//		in.ignore(numeric_limits<streamsize>::max(), '\n');
//		in >> s;
//	}
//	string magicNumber = s;
//
//	// Read width and height
//	in >> s;
//	while (s.front() == '#')
//	{
//		in.ignore(numeric_limits<streamsize>::max(), '\n');
//		in >> s;
//	}
//	out.width = stoi(s);
//	in >> s;
//	out.height = stoi(s);
//
//	// Read max value
//	in >> s;
//	while (s.front() == '#')
//	{
//		in.ignore(numeric_limits<streamsize>::max(), '\n');
//		in >> s;
//	}
//	string maxValue = s;
//
//	// handle error cases
//	if (maxValue != string("255"))
//	{
//		cerr << "PPM file's max value needs to be 255" << endl;
//		return -1;
//	}
//	if (magicNumber != string("P6"))
//	{
//		cerr << "PPM file's magic number needs to be P6" << endl;
//		return -1;
//	}
//
//	// Pixel data reading
//	in.ignore(numeric_limits<streamsize>::max(), '\n');
//	vector<unsigned char> temp;
//	temp.resize((out.width) * (out.height) * 3);
//	in.read(reinterpret_cast<char*>(temp.data()), temp.size());
//
//	// Store in floating point structure
//	out.data.resize((out.width) * (out.height) * 3);
//	for (unsigned int i = 0; i < temp.size(); i++)
//		out.data[i] = temp[i] / 255.0f;
//
//	if (!in)
//	{
//		cerr << "Error reading PPM file" << endl;
//		return -1;
//	}
//
//	return 0;
//}

int ReadMaptoVector(string path, std::vector<vec2>& out)
{
	filebuf fb;
	if (!fb.open(path, ios::in | ios::binary))
	{
		cerr << "Couldn't open ppm file !" << endl;
		return -1;
	}
	istream in(&fb);
	int size;
	in >> size;
	out.reserve(size);
	for(int i=0; i<size; ++i)
	{
		float x, y;
		in >> x >> y;
		out.push_back(vec2(x, y));
	}
	fb.close();
	return 0;
}

/* ========================================================================= */


/* =========================== Shader Compiler ============================= */
GLuint CompileShader(const string& vertexFile, const string& fragmentFile)
{
	// Read vertex and fragment shaders source
	GLint vertexLen = 0;
	GLchar* vertexSource;
	LoadShader(vertexFile.c_str(), &vertexSource, &vertexLen);
	GLint fragmentLen = 0;
	GLchar* fragmentSource;
	LoadShader(fragmentFile.c_str(), &fragmentSource, &fragmentLen);

	// Create and compile vertex and fragment shaders
	GLuint vertexObject, fragmentObject;
	vertexObject = glCreateShader(GL_VERTEX_SHADER);
	fragmentObject = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(vertexObject, 1, &vertexSource, &vertexLen);
	glShaderSource(fragmentObject, 1, &fragmentSource, &fragmentLen);
	glCompileShader(vertexObject);
	glCompileShader(fragmentObject);

	// Check compilation status and log errors
	GLint compiled;
	glGetShaderiv(vertexObject, GL_COMPILE_STATUS, &compiled);
	if (!compiled)
	{
		GLint blen = 0;
		GLsizei slen = 0;
		glGetShaderiv(vertexObject, GL_INFO_LOG_LENGTH, &blen);
		if (blen > 1)
		{
			GLchar* compiler_log = (GLchar*)malloc(blen);
			glGetShaderInfoLog(vertexObject, blen, &slen, compiler_log);
			cout << "Vertex compiler Log: " << compiler_log;
			free(compiler_log);
		}
	}
	glGetShaderiv(fragmentObject, GL_COMPILE_STATUS, &compiled);
	if (!compiled)
	{
		GLint blen = 0;
		GLsizei slen = 0;
		glGetShaderiv(fragmentObject, GL_INFO_LOG_LENGTH, &blen);
		if (blen > 1)
		{
			GLchar* compiler_log = (GLchar*)malloc(blen);
			glGetShaderInfoLog(fragmentObject, blen, &slen, compiler_log);
			cout << "Fragment compiler Log: " << compiler_log;
			free(compiler_log);
		}
	}

	// Link vertex and fragment shaders
	GLuint res = glCreateProgram();
	glAttachShader(res, vertexObject);
	glAttachShader(res, fragmentObject);
	glLinkProgram(res);
	GLint linked;
	glGetProgramiv(res, GL_LINK_STATUS, &linked);
	if (!linked)
	{
		GLint blen = 0;
		GLsizei slen = 0;
		glGetShaderiv(fragmentObject, GL_INFO_LOG_LENGTH, &blen);
		if (blen > 1)
		{
			GLchar* compiler_log = (GLchar*)malloc(blen);
			glGetShaderInfoLog(fragmentObject, blen, &slen, compiler_log);
			cout << "Linking Log: " << compiler_log;
			free(compiler_log);
		}
	}

	// Free sources
	delete[] vertexSource;
	delete[] fragmentSource;

	return res;
}

unsigned long GetFileLength(ifstream& file)
{
	if (!file.good()) return 0;

	unsigned long pos = (unsigned long)file.tellg();
	file.seekg(0, ios::end);
	unsigned long len = (unsigned long)file.tellg();
	file.seekg(ios::beg);

	return len;
}

int LoadShader(const char* filename, GLchar** shaderSource, GLint* len)
{
	ifstream file;
	file.open(filename, ios::in);
	if (!file)
		return -1;

	*len = GetFileLength(file);
	if (len == 0)
		return -2;

	*shaderSource = (GLchar*)new char[*len + 1];
	if (*shaderSource == 0)
		return -3;

	(*shaderSource)[*len] = 0;
	unsigned int i = 0;
	while (file.good())
	{
		(*shaderSource)[i] = file.get();
		if (!file.eof())
			i++;
	}

	(*shaderSource)[i] = 0;
	file.close();
	return 0;
}
/* ========================================================================= */
