/*
 * Renderer.cpp
 *
 *  Created on: 4 nov. 2020
 *      Author: simon
 */

#include <stdio.h>
#include <algorithm>

#include "client/render/Renderer.h"
#include "client/render/GL.h"

#include "td/misc/Time.h"
#include "td/misc/Easing.h"
#include "td/misc/Maths.h"
#include "td/misc/Log.h"
#include "td/misc/Format.h"


namespace td {
namespace render {

Renderer::Renderer() : m_BackgroundColor(0, 0, 0) {

}

Renderer::~Renderer() {

}

void Renderer::InitShaders() {
	m_WorldShader = std::make_unique<shader::WorldShader>();
	m_WorldShader->LoadShader();
	m_EntityShader = std::make_unique<shader::EntityShader>();
	m_EntityShader->LoadShader();
}

// TODO : change loader check

bool Renderer::Init() {
#if __has_include(<glbinding/glbinding.h>)
	glbinding::initialize();
#elif __has_include(<GL/glew.h>)
	glewInit();
#elif __has_include(<glad/glad.h>)
	gladLoadGL();
#elif __has_include(<GL/gl3w.h>)
	gl3wInit();
#elif __has_include(<glbinding/Binding.h>)
	glbinding::Binding::initialize();
#endif
	glEnable(GL_TEXTURE_2D);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	InitShaders();
	return true;
}

void Renderer::RenderVAO(const GL::VertexArray& vao) {
	m_WorldShader->Start();
	vao.Bind();
	glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(vao.GetVertexCount()));
	vao.Unbind();
}

void Renderer::RenderModel(const Model& model) {
	m_EntityShader->Start();
	m_EntityShader->SetModelPos(model.positon);
	m_EntityShader->SetColorEffect(model.color);
	model.vao->Bind();
	glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(model.vao->GetVertexCount()));
	model.vao->Unbind();
}

void Renderer::Prepare() {
	glClear(GL_COLOR_BUFFER_BIT);
	glClearColor(m_BackgroundColor.r, m_BackgroundColor.g, m_BackgroundColor.b, 0);
}

void Renderer::Resize(int width, int height) {
	m_Camera.projectionMatrix = maths::Perspective(80.0f / 180.0f * PI, static_cast<float>(width) / height, 0.1f, 160.0f);
	m_Camera.InvProjectionMatrix = maths::Inverse(m_Camera.projectionMatrix);
	m_WorldShader->Start();
	m_WorldShader->SetProjectionMatrix(m_Camera.projectionMatrix);
	m_EntityShader->Start();
	m_WorldShader->SetProjectionMatrix(m_Camera.projectionMatrix);
	m_WindowSize = { width, height };
	glViewport(0, 0, width, height);
}

void Renderer::AddZoom(float zoom) {
	m_Camera.CamPos.y = std::max(1.0f, m_Camera.CamPos.y - zoom);
	m_Camera.CamDistance = std::max(1.0f, m_Camera.CamDistance - zoom);
	SetCamLook(m_Camera.CamLook);
}

void Renderer::SetCamAngularMovement(const Vec2f& mov) {
	m_Camera.m_Pitch = std::clamp(m_Camera.m_Pitch - mov.y / m_MouseSensitivity, -PI / 2.0f + 0.0000001f, -PI / 12.0f);
	m_Camera.m_Yaw += mov.x / m_MouseSensitivity;
	SetCamLook(m_Camera.CamLook);
}

void Renderer::SetCamMovement(const Vec2f& lastCursorPos, const Vec2f& currentCursorPos) {
	Vec2f worldLastCursorPos = GetCursorWorldPos(lastCursorPos, m_WindowSize.x, m_WindowSize.y);
	Vec2f worldCurrentCursorPos = GetCursorWorldPos(currentCursorPos, m_WindowSize.x, m_WindowSize.y);
	Vec2f movement = worldCurrentCursorPos - worldLastCursorPos;
	SetCamLook(m_Camera.CamLook - movement);
}

void Renderer::ResetCamLook() {
	m_Camera = Camera();
}

void Renderer::SetCamPos(const Vec3f& newPos) {

	Vec3f front = {
		std::cos(m_Camera.m_Yaw) * std::cos(m_Camera.m_Pitch),
		std::sin(m_Camera.m_Pitch),
		std::sin(m_Camera.m_Yaw) * std::cos(m_Camera.m_Pitch)
	};

	m_Camera.CamPos = newPos;
	m_Camera.viewMatrix = maths::Look(m_Camera.CamPos, front, { 0, 1, 0 });
	m_Camera.InvViewMatrix = maths::Transpose(maths::Inverse(m_Camera.viewMatrix));  // why transpose ? I don't know

	m_WorldShader->Start();
	m_WorldShader->SetViewMatrix(m_Camera.viewMatrix);
	m_EntityShader->Start();
	m_EntityShader->SetViewMatrix(m_Camera.viewMatrix);
}

void Renderer::SetCamLook(const Vec2f& worldPos) {
	static const float WORLD_HEIGHT = 0;

	m_Camera.CamLook = worldPos;

	Vec3f front = {
		std::cos(m_Camera.m_Yaw) * std::cos(m_Camera.m_Pitch),
		std::sin(m_Camera.m_Pitch),
		std::sin(m_Camera.m_Yaw) * std::cos(m_Camera.m_Pitch)
	};

	SetCamPos({
		-m_Camera.CamDistance * front.x + m_Camera.CamLook.x,
		-m_Camera.CamDistance * front.y + WORLD_HEIGHT,
		-m_Camera.CamDistance * front.z + m_Camera.CamLook.y
	});
}

Vec2f Renderer::GetCursorWorldPos(const Vec2f& cursorPos, float windowWidth, float windowHeight) {

	float relativeX = (cursorPos.x / windowWidth * 2) - 1.0f;
	float relativeY = 1.0f - (cursorPos.y / windowHeight * 2);

	Vec4f rayClip{ relativeX, relativeY, -1.0f, 1.0f };

	Vec4f rayEye = maths::Dot(m_Camera.InvProjectionMatrix, rayClip);

	rayEye = { rayEye.x, rayEye.y, -1.0f, 0.0f };

	Vec4f rayWorld = maths::Dot(m_Camera.InvViewMatrix, rayEye);

	float lambda = -m_Camera.CamPos.y / rayWorld.y;

	return { lambda * rayWorld.x + m_Camera.CamPos.x, lambda * rayWorld.z + m_Camera.CamPos.z };
}


} // namespace render
} // namespace td
