#include <gl\glew.h>
#include <gl\glfw3.h>
#include <Windows.h>
#include "OpenGLController.h"
#include "callback.h"

#include <glm\gtc\matrix_transform.hpp>
#include "shader.hpp"
#include "import.h"
#include "Texture.h"

#define VERTEX_SHADER "Shader/VertexTextureShader.mv2shdr"
#define FRAGMENT_SHADER "Shader/FragmentTextureShader.mv2shdr"
#define TEXTURE_NAME "Textures/texture32R.tga"

/////////////////////////////////////////////////////////////////////////
// public constructor/destructor

OpenGLController* OpenGLController::getInstance(int oglMajor, int oglMinor)
{
	static OpenGLController *instace = new OpenGLController(oglMajor, oglMinor);
	return instace;
}

OpenGLController::~OpenGLController()
{
	glDeleteBuffers(1, &gluiUVBufferID);
	glDeleteBuffers(1, &gluiVertexBufferID);
	glDeleteVertexArrays(1, &gluiVertexArrayID);
	glDeleteProgram(gluiShaderPrgmID);

	glDeleteTextures(1, &gluiSamplerID);
	glfwTerminate();
}


/////////////////////////////////////////////////////////////////////////
// private constructor

OpenGLController::OpenGLController(int oglMajor, int oglMinor)
{
	// init variables
	initDefault();
	iOglMajorVersion = oglMajor;
	iOglMinorVersion = oglMinor;

	// run OGL
	processInit();
}

/////////////////////////////////////////////////////////////////////////
// private functions

void OpenGLController::initDefault()
{
	pWindow = NULL;
	sWindowName = "MeshViewer 2.0 pre-alpha";
	iWidth = 640;
	iHeight = 480;

	iAntiAliasingLevel = 4;

	gluiUVBufferID = 0;
	gluiTextureID = 0;
	gluiShaderPrgmID = 0;
	gluiSamplerID = 0;

	gluiMatrixID = 0;

	fRotationX = 0;
	fRotationY = 0;
	fRotationZ = 0;
	dTranslationX = 0;
	dTranslationY = 0;
	dTranslationZ = 5;

	m4x4Model = glm::mat4(1.0f);
	m4x4View = glm::mat4(1.0f);
	m4x4Projection = glm::mat4(1.f);

	fFOV = 45.0f;
	fMinView = 0.1f;
	fMaxView = 100.0f;
}

void OpenGLController::processInit()
{
	startGLFW();
	createWindow();
	startGLEW();
	setCallbackFunctions();
	
	// set background color
	glClearColor(0.5000f, 0.8000f, 1.0000f, 0.0000f);

	// enable z-order
	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LESS);

	// draw vertics only from one side
	glEnable(GL_CULL_FACE);
}

void OpenGLController::startGLFW()
{
	if (!glfwInit())
	{
		MessageBox(NULL, "Failed to initialize GLFW", "MeshViewer 2.0 Error", MB_OK | MB_ICONERROR);
		exit(0);
	}

	glfwWindowHint(GLFW_SAMPLES, iAntiAliasingLevel);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, iOglMajorVersion);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, iOglMinorVersion);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
}

void OpenGLController::createWindow()
{
	pWindow = glfwCreateWindow(iWidth, iHeight, sWindowName.c_str(), NULL, NULL);

	if (pWindow == NULL)
	{
		std::string message = "Your GPU does not support OpenGL ";
		message += iOglMajorVersion;
		message += ".";
		message += iOglMinorVersion;
		message += "\nTry to use older version";

		MessageBox(NULL, message.c_str(), "MeshViewer 2.0 Error", MB_OK | MB_ICONERROR);

		glfwTerminate();
		exit(0);
	}

	glfwSetWindowUserPointer(pWindow, this);
	glfwMakeContextCurrent(pWindow);
}

void OpenGLController::startGLEW()
{
	glewExperimental = true;

	if (glewInit() != GLEW_OK)
	{
		MessageBox(NULL, "Failed to initialize GLEW", "MeshViewer 2.0 Error", MB_OK | MB_ICONERROR);
		glfwTerminate();
		exit(0);
	}
}

void OpenGLController::setCallbackFunctions()
{
	glfwSetMouseButtonCallback(pWindow, mouseButton);
	glfwSetCursorPosCallback(pWindow, mouseMove);
	glfwSetWindowSizeCallback(pWindow, windowResize);
	glfwSetScrollCallback(pWindow, mouseWheel);
	glfwSetKeyCallback(pWindow, keyPress);
}

glm::mat4 OpenGLController::getMVPMatrix()
{
	m4x4Projection = glm::perspective(fFOV, float(iWidth) / float(iHeight), fMinView, fMaxView);
	m4x4View = glm::lookAt(
		glm::vec3(dTranslationX, dTranslationY, dTranslationZ),
		glm::vec3(dTranslationX, dTranslationY, dTranslationZ - 1),
		glm::vec3(0, 1, 0)
	);
	m4x4Model = glm::mat4(1.0f);
	m4x4Model = glm::rotate(m4x4Model, fRotationX, glm::vec3(1, 0, 0));
	m4x4Model = glm::rotate(m4x4Model, fRotationY, glm::vec3(0, 1, 0));
	m4x4Model = glm::rotate(m4x4Model, fRotationZ, glm::vec3(0, 0, 1));

	return m4x4Projection * m4x4View * m4x4Model;
}


/////////////////////////////////////////////////////////////////////////
// public getter

GLFWwindow * OpenGLController::getWindow() const
{
	return pWindow;
}


/////////////////////////////////////////////////////////////////////////
// public functions

void OpenGLController::resize(int width, int height)
{
	iWidth = width;
	iHeight = height;
}

void OpenGLController::addRotX(float value)
{
	fRotationX += value;
}

void OpenGLController::addRotY(float value)
{
	fRotationY += value;
}

void OpenGLController::addTransX(double value)
{
	dTranslationX += value;
}

void OpenGLController::addTransY(double value)
{
	dTranslationY += value;
}

void OpenGLController::addTransZ(double value)
{
	dTranslationZ += value;
}

void OpenGLController::updateScene()
{
	// get new matrices
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	// use shader prgm
	glUseProgram(gluiShaderPrgmID);

	// tell shader transformation
	glUniformMatrix4fv(gluiMatrixID, 1, GL_FALSE, &getMVPMatrix()[0][0]);

	// bind texture in texture unit 0
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, gluiTextureID);
	// tell sampler to use texture unit 0
	glUniform1i(gluiSamplerID, 0);

	// open attribute position
	glEnableVertexAttribArray(0);
	glBindBuffer(GL_ARRAY_BUFFER, gluiVertexBufferID);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);

	// open attribute uv
	glEnableVertexAttribArray(1);
	glBindBuffer(GL_ARRAY_BUFFER, gluiUVBufferID);
	glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, 0);
	
	//draw objects
	glDrawArrays(GL_TRIANGLES, 0, 12 * 3);

	//close attributes
	glDisableVertexAttribArray(0);
	glDisableVertexAttribArray(1);

	glfwSwapBuffers(pWindow);
	glfwPollEvents();

}

void OpenGLController::loadMsh(const char * path)
{
	// generate stuff
	glGenVertexArrays(1, &gluiVertexArrayID);
	glBindVertexArray(gluiVertexArrayID);

	glGenBuffers(1, &gluiVertexBufferID);
	glGenBuffers(1, &gluiUVBufferID);

	try
	{
		gluiShaderPrgmID = LoadShaders(VERTEX_SHADER, FRAGMENT_SHADER);
	}
	catch (std::invalid_argument e)
	{
		MessageBox(NULL, e.what(), "MeshViewer 2.0 Error", MB_OK | MB_ICONERROR);
		exit(1);
	}

	gluiMatrixID = glGetUniformLocation(gluiShaderPrgmID, "MVP");
	gluiSamplerID = glGetUniformLocation(gluiShaderPrgmID, "textureSampler");

	// get data
	vfVertices = loadData();
	vfUV = loadUV();

	glGenTextures(1, &gluiTextureID);
	glBindTexture(GL_TEXTURE_2D, gluiTextureID);
	TextureTGA tempTex(TEXTURE_NAME);
	glTexImage2D(GL_TEXTURE_2D, 0, tempTex.hasAlpha() ? GL_RGBA : GL_RGB, tempTex.getWidth(), tempTex.getHeight(), 0, tempTex.hasAlpha() ? GL_BGRA : GL_BGR, GL_UNSIGNED_BYTE, tempTex.getData().data());

	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_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
	glGenerateMipmap(GL_TEXTURE_2D);

	glBindBuffer(GL_ARRAY_BUFFER, gluiVertexBufferID);
	glBufferData(
		GL_ARRAY_BUFFER,
		sizeof(vfVertices) * vfVertices.size(),
		vfVertices.data(),
		GL_STATIC_DRAW
	);

	glBindBuffer(GL_ARRAY_BUFFER, gluiUVBufferID);
	glBufferData(
		GL_ARRAY_BUFFER,
		sizeof(vfUV) * vfUV.size(),
		vfUV.data(),
		GL_STATIC_DRAW
	);
}
