#include "GEScene.h"

#include "GECube.h"
#include "GEPyramid.h"
#include "GECone.h"
#include "GECylinder.h"
#include "GESphere.h"
#include "GEDisk.h"
#include "GETorus.h"
#include "GEIcosahedron.h"
#include "GEGround.h"
#include "GETransform.h"
#include "GEMaterial.h"
#include "GELight.h"
#include <windows.h>
#include "resource.h"
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

//
// FUNCIÓN: GEScene::GEScene(GEGraphicsContext* gc, GEDrawingContext* dc)
//
// PROPÓSITO: Crea la escena
//
GEScene::GEScene(GEGraphicsContext* gc, GEDrawingContext* dc, GECommandContext* cc)
{
	VkExtent2D extent = dc->getExtent();
	double aspect = (double)extent.width / (double)extent.height;
	aspect_ratio(aspect);

	GEPipelineConfig* config = createPipelineConfig(dc->getExtent());
	rc = new GERenderingContext(gc, dc, config);

	this->camera = new GECamera();
	this->camera->setPosition(glm::vec3(0.0f, 10.0f, 300.0f));
	this->camera->setMoveStep(0.0f);

	GELight light = {};
	light.Ldir = glm::normalize(glm::vec3(1.0f, -0.8f, -0.7f));
	light.La = glm::vec3(0.2f, 0.2f, 0.2f);
	light.Ld = glm::vec3(0.8f, 0.8f, 0.8f);
	light.Ls = glm::vec3(1.0f, 1.0f, 1.0f);

	GEMaterial groundMat = {};
	groundMat.Ka = glm::vec3(0.0f, 0.3f, 0.0f);
	groundMat.Kd = glm::vec3(0.0f, 0.3f, 0.0f);
	groundMat.Ks = glm::vec3(0.8f, 0.8f, 0.8f);
	groundMat.Shininess = 16.0f;


	GEFigure* ground = new GEGround(50.0f, 50.0f);
	ground->initialize(gc, rc);
	ground->setMaterial(groundMat);
	ground->setLight(light);

	GEMaterial fig1Mat = {};
	fig1Mat.Ka = glm::vec3(1.0f, 0.0f, 1.0f);
	fig1Mat.Kd = glm::vec3(1.0f, 0.0f, 1.0f);
	fig1Mat.Ks = glm::vec3(0.8f, 0.8f, 0.8f);
	fig1Mat.Shininess = 16.0f;

	GEFigure* fig1 = new GECone(5, 20, 5.0f, 5.0f);
	fig1->initialize(gc, rc);
	fig1->translate(glm::vec3(25.0f, 5.0f, 25.0f));
	fig1->rotate(-90.0f, glm::vec3(1.0f, 0.0f, 0.0f));
	fig1->setMaterial(fig1Mat);
	fig1->setLight(light);

	GEMaterial fig2Mat = {};
	fig2Mat.Ka = glm::vec3(0.0f, 1.0f, 0.0f);
	fig2Mat.Kd = glm::vec3(0.0f, 1.0f, 0.0f);
	fig2Mat.Ks = glm::vec3(0.8f, 0.8f, 0.8f);
	fig2Mat.Shininess = 16.0f;

	GEFigure* fig2 = new GECube(5.0f);
	fig2->initialize(gc, rc);
	fig2->translate(glm::vec3(-25.0f, 5.0f, 25.0f));
	fig2->rotate(-90.0f, glm::vec3(1.0f, 0.0f, 0.0f));
	fig2->setMaterial(fig2Mat);
	fig2->setLight(light);

	GEMaterial fig3Mat = {};
	fig3Mat.Ka = glm::vec3(0.0f, 0.0f, 1.0f);
	fig3Mat.Kd = glm::vec3(0.0f, 0.0f, 1.0f);
	fig3Mat.Ks = glm::vec3(0.8f, 0.8f, 0.8f);
	fig3Mat.Shininess = 16.0f;

	GEFigure* fig3 = new GECylinder(20, 20, 5.0f, 5.0f);
	fig3->initialize(gc, rc);
	fig3->translate(glm::vec3(25.0f, 5.0f, 0.0f));
	fig3->rotate(90.0f, glm::vec3(1.0f, 0.0f, 0.0f));
	fig3->setMaterial(fig3Mat);
	fig3->setLight(light);

	GEMaterial fig4Mat = {};
	fig4Mat.Ka = glm::vec3(1.0f, 0.0f, 0.0f);
	fig4Mat.Kd = glm::vec3(1.0f, 0.0f, 0.0f);
	fig4Mat.Ks = glm::vec3(0.8f, 0.8f, 0.8f);
	fig4Mat.Shininess = 16.0f;

	GEFigure* fig4 = new GETorus(20, 40, 3.0f, 5.0f);
	fig4->initialize(gc, rc);
	fig4->translate(glm::vec3(-25.0f, 8.0f, 0.0f));
	fig4->setMaterial(fig4Mat);
	fig4->setLight(light);

	GEMaterial fig5Mat = {};
	fig5Mat.Ka = glm::vec3(1.0f, 1.0f, 0.0f);
	fig5Mat.Kd = glm::vec3(1.0f, 1.0f, 0.0f);
	fig5Mat.Ks = glm::vec3(0.8f, 0.8f, 0.8f);
	fig5Mat.Shininess = 16.0f;

	GEFigure* fig5 = new GESphere(20, 40, 8.0f);
	fig5->initialize(gc, rc);
	fig5->translate(glm::vec3(25.0f, 8.0f, -25.0f));
	fig5->setMaterial(fig5Mat);
	fig5->setLight(light);

	GEMaterial fig6Mat = {};
	fig6Mat.Ka = glm::vec3(0.0f, 1.0f, 1.0f);
	fig6Mat.Kd = glm::vec3(0.0f, 1.0f, 1.0f);
	fig6Mat.Ks = glm::vec3(0.8f, 0.8f, 0.8f);
	fig6Mat.Shininess = 16.0f;

	GEFigure* fig6 = new GEIcosahedron(5.0f);
	fig6->initialize(gc, rc);
	fig6->translate(glm::vec3(-25.0f, 8.0f, -25.0f));
	fig6->setMaterial(fig6Mat);
	fig6->setLight(light);

	figures.resize(7);
	figures[0] = ground;
	figures[1] = fig1;
	figures[2] = fig2;
	figures[3] = fig3;
	figures[4] = fig4;
	figures[5] = fig5;
	figures[6] = fig6;

	fillCommandBuffers(cc->commandBuffers);
}

//
// FUNCIÓN: GEScene::destroy(GEGraphicsContext* gc)
//
// PROPÓSITO: Reconstruye los componentes gráficos de la escena
//
void GEScene::destroy(GEGraphicsContext* gc)
{
	rc->destroy(gc);
	delete rc;

	for (int i = 0; i < figures.size(); i++)
	{
		figures[i]->destroy(gc); 
		delete figures[i];
	}

}

//
// FUNCIÓN: GEScene::recreate(GEGraphicsContext* gc, GEDrawingContext* dc)
//
// PROPÓSITO: Reconstruye los componentes gráficos de la escena
//
void GEScene::recreate(GEGraphicsContext* gc, GEDrawingContext* dc, GECommandContext* cc)
{
	rc->destroy(gc);
	GEPipelineConfig* config = createPipelineConfig(dc->getExtent());
	this->rc = new GERenderingContext(gc, dc, config);
	fillCommandBuffers(cc->commandBuffers);
}

//
// FUNCIÓN: GEScene::update(GEGraphicsContext* gc, uint32_t index)
//
// PROPÓSITO: Actualiza la información para generar la imagen 
//
void GEScene::update(GEGraphicsContext* gc, uint32_t index)
{
	camera->update();
	glm::mat4 view = camera->getViewMatrix();

	for (int i = 0; i < figures.size(); i++)
	{
		figures[i]->update(gc, index, view, projection);
	}

}

//
// FUNCIÓN: GEScene::key_action(int key, bool pressed)
//
// PROPÓSITO: Respuesta a acciones de teclado
//
void GEScene::key_action(int key, bool pressed)
{
	switch (key)
	{
	case GLFW_KEY_UP:
		camera->setTurnDown(pressed);
		break;
	case GLFW_KEY_DOWN:
		camera->setTurnUp(pressed);
		break;
	case GLFW_KEY_LEFT:
		camera->setTurnCCW(pressed);
		break;
	case GLFW_KEY_RIGHT:
		camera->setTurnCW(pressed);
		break;
	case GLFW_KEY_S:
		camera->setMoveStep(0.0f);
		break;
	case GLFW_KEY_KP_ADD:
	case GLFW_KEY_1:
		camera->setMoveStep(camera->getMoveStep() + 0.1f);
		break;
	case GLFW_KEY_KP_SUBTRACT:
	case GLFW_KEY_2:
		camera->setMoveStep(camera->getMoveStep() - 0.1f);
		break;
	case GLFW_KEY_Q:
		camera->setMoveUp(pressed);
		break;
	case GLFW_KEY_A:
		camera->setMoveDown(pressed);
		break;
	case GLFW_KEY_O:
		camera->setMoveLeft(pressed);
		break;
	case GLFW_KEY_P:
		camera->setMoveRight(pressed);
		break;
	case GLFW_KEY_K:
		camera->setTurnLeft(pressed);
		break;
	case GLFW_KEY_L:
		camera->setTurnRight(pressed);
		break;
	}
}

//
// FUNCIÓN: GEScene::aspect_ratio(double)
//
// PROPÓSITO: Modifica la relación anchura/altura del modelo
//
void GEScene::aspect_ratio(double aspect)
{
	constexpr double fov = glm::radians(30.0f);
	double sin_fov = sin(fov);
	double cos_fov = cos(fov);
	float wHeight = (float)(sin_fov * 0.2 / cos_fov);
	float wWidth = (float)(wHeight * aspect);

	projection = glm::perspective((float)fov, (float)aspect, 0.2f, 400.0f);
	projection[1][1] *= -1.0f;
}

//
// FUNCIÓN: CAScene::fillCommandBuffers(std::vector<VkCommandBuffer> commandBuffers)
//
// PROPÓSITO: Añade los comandos de renderizado al command buffer
//
void GEScene::fillCommandBuffers(std::vector<VkCommandBuffer> commandBuffers)
{
	rc->startFillingCommandBuffers(commandBuffers);
	for (int i = 0; i < commandBuffers.size(); i++)
	{
		for (int j = 0; j < figures.size(); j++)
		{
			figures[j]->addCommands(commandBuffers[i], rc->pipelineLayout, i);
		}
	}
	rc->endFillingCommandBuffers(commandBuffers);
}

//
// FUNCIÓN: GEScene::createPipelineConfig()
//
// PROPÓSITO: Obtiene la configuración del pipeline de renderizado
//
GEPipelineConfig* GEScene::createPipelineConfig(VkExtent2D extent)
{
	GEPipelineConfig* config = new GEPipelineConfig();
	config->vertex_shader = IDR_HTML1;
	config->fragment_shader = IDR_HTML2;

	config->attrStride = sizeof(GEVertex);
	config->attrOffsets.resize(2);
	config->attrOffsets[0] = offsetof(GEVertex, pos);
	config->attrOffsets[1] = offsetof(GEVertex, norm);
	config->attrFormats.resize(2);
	config->attrFormats[0] = VK_FORMAT_R32G32B32_SFLOAT;
	config->attrFormats[1] = VK_FORMAT_R32G32B32_SFLOAT;

	config->descriptorTypes.resize(3);
	config->descriptorTypes[0] = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
	config->descriptorTypes[1] = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
	config->descriptorTypes[2] = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
	config->descriptorStages.resize(3);
	config->descriptorStages[0] = VK_SHADER_STAGE_ALL_GRAPHICS;
	config->descriptorStages[1] = VK_SHADER_STAGE_FRAGMENT_BIT;
	config->descriptorStages[2] = VK_SHADER_STAGE_FRAGMENT_BIT;

	config->depthTestEnable = VK_TRUE;
	config->cullMode = VK_CULL_MODE_BACK_BIT;
	config->extent = extent;

	return config;
}