#include "client/render/MainRenderer.h"

#include "blitz/misc/Easing.h"
#include "blitz/misc/Format.h"
#include "blitz/misc/Log.h"
#include "blitz/misc/Maths.h"
#include "blitz/misc/Test.h"
#include "blitz/misc/Time.h"
#include "client/Client.h"
#include "client/game/ClientGame.h"
#include "client/render/loader/GLLoader.h"
#include "client/render/loader/ModelLoader.h"
#include "client/render/loader/TextureLoader.h"
#include "client/render/shader/EntityShader.h"
#include "client/render/shader/GunShader.h"
#include "client/render/shader/WorldShader.h"
#include "imgui.h"
#include <glbinding/gl/gl.h>

using namespace gl;

namespace blitz {
namespace render {

static const Vec4f SkyColor = {0.6, 0.8, 1, 1};
static const Vec4f MenuColor = {0, 0, 0, 0};

MainRenderer::MainRenderer(Client* client) : m_Client(client), m_ShootTime(0) {

	LoadModels();

	client->BindListener(this);
	m_PlayerController.BindListener(this);
	m_PlayerController.BindListener(client);

	m_EntityShader = std::make_unique<shader::EntityShader>();
	blitz_debug_assert(m_EntityShader->LoadShader());

	m_WorldShader = std::make_unique<shader::WorldShader>();
	blitz_debug_assert(m_WorldShader->LoadShader());

	m_GunShader = std::make_unique<shader::GunShader>();
	blitz_debug_assert(m_GunShader->LoadShader());

	glEnable(GL_DEPTH_TEST);
	glEnable(GL_TEXTURE_2D);

	// TODO: fix that
	glEnable(GL_CULL_FACE);
	glCullFace(GL_FRONT);
	glFrontFace(GL_CW);
}

MainRenderer::~MainRenderer() {}

void MainRenderer::LoadModels() {
	m_WorldModel = ModelLoader::LoadModel("base_deambu.glb");
	m_PlayerModel = ModelLoader::LoadModel("human.obj");
	m_GunModel = ModelLoader::LoadModel("fingergun.obj");

	m_Texture = TextureLoader::LoadGLTexture("carrelage.png");
	glBindTexture(GL_TEXTURE_2D, m_Texture);
}

void MainRenderer::RenderEntity(const GL::VertexArray& vao, const Vec3f& position, float yaw) {
	m_EntityShader->Start();
	m_EntityShader->SetModelTransform(maths::Dot(maths::RotateY(yaw - maths::PI / 2.0f), maths::Translate(position)));
	vao.Bind();
	glDrawElements(GL_TRIANGLES, vao.GetVertexCount(), GL_UNSIGNED_INT, nullptr);
	vao.Unbind();
}

void MainRenderer::RenderWorld(const GL::VertexArray& vao) {
	m_WorldShader->Start();
	vao.Bind();
	glDrawElements(GL_TRIANGLES, vao.GetVertexCount(), GL_UNSIGNED_INT, nullptr);
	vao.Unbind();
}

void MainRenderer::Render() {
	if (!m_Client->IsConnected()) {
		glClearColor(MenuColor.r, MenuColor.g, MenuColor.b, MenuColor.a);
		return;
	}

	glClear(GL_DEPTH_BUFFER_BIT);
	glClearColor(SkyColor.r, SkyColor.g, SkyColor.b, SkyColor.a);

	RenderWorld();
	RenderPlayers();
	RenderGun();
}

void MainRenderer::RenderPlayers() {
	for (const auto& [playerId, player] : m_Client->GetGame()->GetPlayers()) {
		if (playerId != m_Client->GetPlayerID()) {
			for (auto& Vao : m_PlayerModel.mVaos) {
				RenderEntity(*Vao.get(), player.GetPosition(), player.GetYaw());
			}
		}
	}
}

void MainRenderer::OnPlayerShoot(const Vec3f& position, float yaw, float pitch) {
	m_ShootTime = 1.0f;
}

void MainRenderer::RenderGun() {
	if (!m_Camera.GetAttachedPlayer())
		return;

	m_GunShader->Start();

	float progression = 1.0f - m_ShootTime;

	float angle = progression * progression * progression * 7 - progression * progression * 11 + progression * 4;

	Mat4f transform = maths::RotateX(-angle);
	m_GunShader->SetModelTransform(transform);

	for (auto& Vao : m_GunModel.mVaos) {
		Vao->Bind();
		glDrawElements(GL_TRIANGLES, Vao->GetVertexCount(), GL_UNSIGNED_INT, nullptr);
		Vao->Unbind();
	}
}

void MainRenderer::RenderWorld() {
	// temporary code
	if (m_Camera.GetAttachedPlayer()) {
		m_WorldShader->Start();
		m_WorldShader->SetLightPosition(m_Camera.GetAttachedPlayer()->GetPosition());
	}

	for (auto& Vao : m_WorldModel.mVaos) {
		RenderWorld(*Vao.get());
	}
}

void MainRenderer::Update() {
	if (!m_Client->IsConnected() || !m_Camera.GetAttachedPlayer())
		return;

	float delta = ImGui::GetIO().DeltaTime;

	m_PlayerController.Update(delta);
	m_Camera.Update(delta);

	m_EntityShader->Start();
	m_EntityShader->SetProjectionMatrix(m_Camera.GetPerspectiveMatrix());
	m_EntityShader->SetViewMatrix(m_Camera.GetViewMatrix());

	m_WorldShader->Start();
	m_WorldShader->SetProjectionMatrix(m_Camera.GetPerspectiveMatrix());
	m_WorldShader->SetViewMatrix(m_Camera.GetViewMatrix());

	m_GunShader->Start();
	m_GunShader->SetProjectionMatrix(m_Camera.GetPerspectiveMatrix());

	m_ShootTime = std::max(0.0f, m_ShootTime - delta);
}

void MainRenderer::OnSpectatorChange(const game::PlayerID player) {
	game::Player* playerPtr = m_Client->GetGame()->GetPlayerById(player);

	m_Camera.SetAttachedPlayer(playerPtr);
	m_PlayerController.SetAttachedPlayer(playerPtr);
}

}  // namespace render
}  // namespace blitz
