#include "client/render/MainRenderer.h"

#include "blitz/misc/Format.h"
#include "blitz/misc/Log.h"
#include "blitz/misc/Maths.h"
#include "blitz/misc/Time.h"
#include "client/Client.h"
#include "client/game/ClientGame.h"
#include "client/render/GL.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/WorldShader.h"
#include "imgui.h"

namespace blitz {
namespace render {

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

MainRenderer::MainRenderer(Client* client) : m_Client(client) {

	LoadModels();

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

	m_WorldShader = std::make_unique<shader::WorldShader>();
	m_WorldShader->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_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())
		return;

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

	RenderWorld();
	RenderPlayers();
}

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::RenderWorld() {
	// temporary code
	if (!m_Client->GetGame()->GetPlayers().empty()) {
		m_WorldShader->Start();
		m_WorldShader->SetLightPosition(m_Camera.GetPosition());
	}

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

void MainRenderer::UpdateCamera() {
	m_Camera.Update(ImGui::GetIO().DeltaTime);

	// send position every tick (50 ms)

	static const std::uint64_t TickTime = 50;

	static std::uint64_t lastTime = utils::GetTime();
	std::uint64_t now = utils::GetTime();

	if (now - lastTime >= TickTime) {
		m_Client->SendPlayerPosAndLook(m_Camera.GetPosition() - Vec3f{0, eyeHeight, 0}, m_Camera.GetYaw(), m_Camera.GetPitch());
		lastTime = now;
	}
}

void MainRenderer::Update() {
	if (!m_Client->IsConnected())
		return;

	UpdateCamera();

	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());
}

}  // namespace render
}  // namespace blitz
