#include "openGLControl.h"

// ----------------------------------------------------------------------------------------------------------------------------

COpenGLControl::COpenGLControl()
{
}

COpenGLControl::~COpenGLControl()
{
}

bool COpenGLControl::Init()
{
	bool Error = false;

	// load textures and shaders first and return false if an error occurs

	Error |= !Skybox.Init();

	char *FileNames[] = {"grass.jpg", "crate.jpg", "metalplate.jpg"};

	for(int i = 0; i < 3; i++)
	{
		Error |= !Textures[i].LoadTexture2D(FileNames[i]);
	}

	Error |= !Lighting.Load("lighting.vert", "lighting.frag");

	if(Error)
	{
		return false;
	}

	// get uniform locations
	
	Lighting.UniformLocations = new GLuint[2];
	Lighting.UniformLocations[0] = glGetUniformLocation(Lighting, "ModelMatrix");
	Lighting.UniformLocations[1] = glGetUniformLocation(Lighting, "ViewProjectionMatrix");

	// get attribute locations

	Lighting.AttribLocations = new GLuint[3];
	Lighting.AttribLocations[0] = glGetAttribLocation(Lighting, "vert_TexCoord");
	Lighting.AttribLocations[1] = glGetAttribLocation(Lighting, "vert_Normal");
	Lighting.AttribLocations[2] = glGetAttribLocation(Lighting, "vert_Position");

	// set uniforms

	glUseProgram(Lighting);
	glUniform1f(glGetUniformLocation(Lighting, "Light.Ambient"), 0.333333f);
	glUniform1f(glGetUniformLocation(Lighting, "Light.Diffuse"), 0.666666f);
	glUniform3fv(glGetUniformLocation(Lighting, "Light.Direction"), 1, (float*)&vec3(0.467757f, 0.424200f, -0.775409f));

	// init data

	CBuffer Buffer;

	VerticesCount[0] = 0;

	for(int i = 0; i < 6; i++)
	{
		Buffer.AddData(&(vCubeTexCoords[i % 6] * 10.0f), sizeof(vec2));
		Buffer.AddData(&vec3(0.0f, 1.0f, 0.0f), sizeof(vec3));
		Buffer.AddData(&(vGround[i] * 20.0f), sizeof(vec3));

		VerticesCount[0]++;
	}

	VerticesCount[1] = 0;

	for(int y = 0; y < 8; y++)
	{
		for(int x = 0; x < 8 - y; x++)
		{
			vec3 offset = vec3(-5.5f + x * 1.5f + 0.75f * y, 0.5f + y, -5.0f);

			for(int i = 0; i < 36; i++)
			{
				Buffer.AddData(&vCubeTexCoords[i % 6], sizeof(vec2));
				Buffer.AddData(&vCubeNormals[i / 6], sizeof(vec3));
				Buffer.AddData(&(vCubeVertices[i] + offset), sizeof(vec3));

				VerticesCount[1]++;
			}
		}
	}

	VerticesCount[2] = 0;

	VerticesCount[2] += generateTorus(Buffer, 1.0f, 0.25f, 32, 16, mat4x4());
	VerticesCount[2] += generateTorus(Buffer, 1.0f, 0.25f, 32, 16, rotate(90.0f, vec3(0.0f, 1.0f, 0.0f)));
	VerticesCount[2] += generateTorus(Buffer, 1.0f, 0.25f, 32, 16, rotate(90.0f, vec3(1.0f, 0.0f, 0.0f)));

	// init VBO

	glGenBuffers(1, &VBO);

	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glBufferData(GL_ARRAY_BUFFER, Buffer.GetDataSize(), Buffer.GetData(), GL_STATIC_DRAW);

	glBindBuffer(GL_ARRAY_BUFFER, 0); // don't forget to unbind current VBO

	// free alocated memory

	Buffer.Destroy();

	// init VAO

	glGenVertexArrays(1, &VAO);

	glBindVertexArray(VAO);
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glVertexAttribPointer(Lighting.AttribLocations[0], 2, GL_FLOAT, GL_FALSE, 32, (void*)0);
	glEnableVertexAttribArray(Lighting.AttribLocations[0]);
	glVertexAttribPointer(Lighting.AttribLocations[1], 3, GL_FLOAT, GL_FALSE, 32, (void*)8);
	glEnableVertexAttribArray(Lighting.AttribLocations[1]);
	glVertexAttribPointer(Lighting.AttribLocations[2], 3, GL_FLOAT, GL_FALSE, 32, (void*)20);
	glEnableVertexAttribArray(Lighting.AttribLocations[2]);
	
	glBindVertexArray(0); // don't forget to unbind current VAO

	// set view matrix pointer - do not calculate view matrix every frame

	Camera.SetViewMatrixPointer(&ViewMatrix);

	return true;
}

void COpenGLControl::Render(float FrameTime)
{
	static float Angle = 0.0f;

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	ViewProjectionMatrix = ProjectionMatrix * ViewMatrix;

	// render skybox - depth test must be disabled

	Skybox.Render(ViewProjectionMatrix, Camera.Position);

	// render scene
	
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_CULL_FACE);

	glUseProgram(Lighting);
	glUniformMatrix4fv(Lighting.UniformLocations[0], 1, GL_FALSE, (float*)&mat4x4());
	glUniformMatrix4fv(Lighting.UniformLocations[1], 1, GL_FALSE, (float*)&ViewProjectionMatrix);

	glBindVertexArray(VAO);
	
	// render grass

	glBindTexture(GL_TEXTURE_2D, Textures[0]);
	glDrawArrays(GL_TRIANGLES, 0, VerticesCount[0]);

	// render cubes

	glBindTexture(GL_TEXTURE_2D, Textures[1]);
	glDrawArrays(GL_TRIANGLES, VerticesCount[0], VerticesCount[1]);

	// render tori

	glBindTexture(GL_TEXTURE_2D, Textures[2]);
	ModelMatrix = translate(vec3(0.0f, 1.5f, 5.0f)) * rotate(Angle, vec3(0.0f, 1.0f, 0.0f));
	glUniformMatrix4fv(Lighting.UniformLocations[0], 1, GL_FALSE, (float*)&ModelMatrix);
	glDrawArrays(GL_TRIANGLES, VerticesCount[0] + VerticesCount[1], VerticesCount[2]);

	glBindTexture(GL_TEXTURE_2D, 0); // don't forget to unbind current texture

	glBindVertexArray(0); // don't forget to unbind current VAO

	glUseProgram(0); // don't forget to unbind current program

	glDisable(GL_CULL_FACE); // don't forget to disable what was enabled
	glDisable(GL_DEPTH_TEST);

	// increase angle - rotating object

	Angle += 22.5f * FrameTime;
}

void COpenGLControl::Resize(int Width, int Height)
{
	this->Width = Width;
	this->Height = Height;

	glViewport(0, 0, Width, Height);

	ProjectionMatrix = perspective(45.0f, (float)Width / (float)Height, 0.125f, 512.0f);
}

void COpenGLControl::Destroy()
{
	Skybox.Destroy();

	for(int i = 0; i < 3; i++)
	{
		Textures[i].Destroy();
	}

	Lighting.Destroy();

	glDeleteBuffers(1, &VBO);

	glDeleteVertexArrays(1, &VAO);
}
