#include "common_header.h"
#include "win_OpenGLApp.h"
#include "shaders.h"
#include "texture.h"
#include "vertexBufferObject.h"
#include "flyingCamera.h"
#include "skybox.h"
#include "static_geometry.h"

CVertexBufferObject vboSceneObjects;
UINT uiVAOs[1];

CShader Shaders[4];
CShaderProgram SkyBox, Lighting;

#define NUMTEXTURES 3

CTexture Textures[NUMTEXTURES];

CFlyingCamera cCamera;

CSkybox sbMainSkybox;

int Width, Height, iTorusFaces;

bool initScene()
{
	vboSceneObjects.createVBO();
	glGenVertexArrays(1, uiVAOs);
	glBindVertexArray(uiVAOs[0]);

	vboSceneObjects.bindVBO();

	FOR(i, 36)
	{
		vboSceneObjects.addData(&vCubeVertices[i], sizeof(vec3));
		vboSceneObjects.addData(&vCubeTexCoords[i%6], sizeof(vec2));
		vboSceneObjects.addData(&vCubeNormals[i/6], sizeof(vec3));
	}

	FOR(i, 6)
	{
		vboSceneObjects.addData(&vGround[i], sizeof(vec3));
		vCubeTexCoords[i] *= 10.0f;
		vboSceneObjects.addData(&vCubeTexCoords[i%6], sizeof(vec2));
		vec3 vGroundNormal(0.0f, 1.0f, 0.0f);
		vboSceneObjects.addData(&vGroundNormal, sizeof(vec3));
	}

	iTorusFaces = generateTorus(vboSceneObjects, 7.0f, 2.0f, 20, 20);
	vboSceneObjects.uploadDataToGPU(GL_STATIC_DRAW);

	glEnableVertexAttribArray(0);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 2*sizeof(vec3)+sizeof(vec2), 0);
	glEnableVertexAttribArray(1);
	glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 2*sizeof(vec3)+sizeof(vec2), (void*)sizeof(vec3));
	glEnableVertexAttribArray(2);
	glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 2*sizeof(vec3)+sizeof(vec2), (void*)(sizeof(vec3)+sizeof(vec2)));

	Shaders[0].loadShader("release\\shaders\\skybox.vert", GL_VERTEX_SHADER);
	Shaders[1].loadShader("release\\shaders\\skybox.frag", GL_FRAGMENT_SHADER);

	Shaders[2].loadShader("release\\shaders\\lighting.vert", GL_VERTEX_SHADER);
	Shaders[3].loadShader("release\\shaders\\lighting.frag", GL_FRAGMENT_SHADER);

	SkyBox.createProgram();
	SkyBox.addShaderToProgram(&Shaders[0]);
	SkyBox.addShaderToProgram(&Shaders[1]);
	SkyBox.linkProgram();

	Lighting.createProgram();
	Lighting.addShaderToProgram(&Shaders[2]);
	Lighting.addShaderToProgram(&Shaders[3]);
	Lighting.linkProgram();

	char *sTextureNames[] = {
		"..\\10.) Skybox - V6\\release\\textures\\grass.jpg",
		"..\\10.) Skybox - V6\\release\\textures\\crate.jpg",
		"..\\10.) Skybox - V6\\release\\textures\\metalplate.jpg"
	};

	FOR(i, NUMTEXTURES)
	{
		Textures[i].loadTexture2D(sTextureNames[i]);
	}

	cCamera = CFlyingCamera(vec3(0.0f, 0.0f, 0.0f), vec3(0.0f, 0.0f, -1.0f), vec3(0.0f, 1.0f, 0.0f), 25.0f, 0.1f);
	cCamera.setMovingKeys('W', 'S', 'A', 'D');

	sbMainSkybox.loadSkybox();

	return true;
}

float fGlobalAngle;
mat4 Projection;

void renderScene()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	mat4 mModelView = cCamera.look(), mModelToCamera;

	SkyBox.useProgram();
	SkyBox.setUniform("ModelViewProjection", Projection * translate(mModelView, cCamera.vEye));
	sbMainSkybox.renderSkybox();
	glUseProgram(0);

	glEnable(GL_DEPTH_TEST);

	Lighting.useProgram();

	Lighting.setUniform("Light.Ambient", 0.333333f);
	Lighting.setUniform("Light.Diffuse", 0.666666f);
	Lighting.setUniform("Light.Direction", vec3(0.467757f, 0.424200f, -0.775409f));
	Lighting.setUniform("projectionMatrix", Projection);

	glBindVertexArray(uiVAOs[0]);
	
	Lighting.setUniform("modelViewMatrix", mModelView);
	glBindTexture(GL_TEXTURE_2D, Textures[0]);
	glDrawArrays(GL_TRIANGLES, 36, 6);

	glBindTexture(GL_TEXTURE_2D, Textures[1]);

	SFOR(nb, 1, 9)
	{
		int iCnt = nb > 5 ? 10-nb : nb;
		FOR(i, iCnt)
		{
			vec3 vPos = vec3(-20.0f+nb*8.02f, -6.0f+i*8.02f, -50.0f);
			mModelToCamera = translate(mat4(1.0), vPos);
			mModelToCamera = scale(mModelToCamera, vec3(8.0f, 8.0f, 8.0f));
			Lighting.setUniform("normalMatrix", transpose(inverse(mModelToCamera)));
			Lighting.setUniform("modelViewMatrix", mModelView*mModelToCamera);
			glDrawArrays(GL_TRIANGLES, 0, 36);
		}
	}

	glBindTexture(GL_TEXTURE_2D, Textures[2]);
	
	vec3 vPos = vec3(0.0f, 0.0, 40.0f);
	mModelToCamera = translate(mat4(1.0), vPos);
	mModelToCamera = rotate(mModelToCamera, fGlobalAngle, vec3(0.0f, 1.0f, 0.0f));
	Lighting.setUniform("normalMatrix", transpose(inverse(mModelToCamera)));
	Lighting.setUniform("modelViewMatrix", mModelView*mModelToCamera);
	glDrawArrays(GL_TRIANGLES, 42, iTorusFaces*3);

	mModelToCamera = translate(mat4(1.0), vPos);
	mModelToCamera = rotate(mModelToCamera, fGlobalAngle, vec3(0.0f, 1.0f, 0.0f));
	mModelToCamera = rotate(mModelToCamera, 90.0f, vec3(1.0f, 0.0f, 0.0f));
	Lighting.setUniform("normalMatrix", transpose(inverse(mModelToCamera)));
	Lighting.setUniform("modelViewMatrix", mModelView*mModelToCamera);
	glDrawArrays(GL_TRIANGLES, 42, iTorusFaces*3);

	mModelToCamera = translate(mat4(1.0), vPos);
	mModelToCamera = rotate(mModelToCamera, fGlobalAngle+90.0f, vec3(0.0f, 1.0f, 0.0f));
	Lighting.setUniform("normalMatrix", transpose(inverse(mModelToCamera)));
	Lighting.setUniform("modelViewMatrix", mModelView*mModelToCamera);
	glDrawArrays(GL_TRIANGLES, 42, iTorusFaces*3);

	glBindTexture(GL_TEXTURE_2D, 0);
	glBindVertexArray(0);
	glUseProgram(0);
	glDisable(GL_DEPTH_TEST);

	fGlobalAngle += appMain.sof(100.0f);

	cCamera.update();
}

void resizeScene(int Width, int Height)
{
	::Width = Width;
	::Height = Height;

	glViewport(0, 0, Width, Height);

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

void releaseScene()
{
	FOR(i, NUMTEXTURES)Textures[i].releaseTexture();
	sbMainSkybox.releaseSkybox();

	SkyBox.deleteProgram();
	Lighting.deleteProgram();
	FOR(i, 4)Shaders[i].deleteShader();

	glDeleteVertexArrays(1, uiVAOs);
	vboSceneObjects.releaseVBO();
}