#include "debugrender.h"
#include "render_shared.h"
#include "render.h"
#include "renderdevice.h"
#include "gpu_buffer.h"
#include "shadersystem.h"

#include "glad/glad.h"

struct DebugVertex
{
	glm::vec3 position;
	glm::vec3 color;
};

static InputLayoutDesc_t g_debugRenderLayout[] =
{
	{ VERTEXATTR_VEC3, SHADERSEMANTIC_POSITION },
	{ VERTEXATTR_VEC3, SHADERSEMANTIC_COLOR }
};

bool g_drawDebug = true;

DebugRender* g_debugRender;

DebugRender::DebugRender()
{
	m_verticesBuffer = nullptr;
	m_shader = nullptr;
}

DebugRender::~DebugRender()
{
	m_verticesBuffer = nullptr;
	m_shader = nullptr;
}

void DebugRender::Initialize()
{
	//float points[12];
	//m_verticesBuffer = g_renderDevice->CreateVertexBuffer(points, sizeof(points), true);

	m_verticesBuffer = g_renderDevice->CreateVertexBuffer(NULL, kMaxDebugVBSize, true);

	m_shader = g_shaderSystem->CreateShader("debug_draw",
		"data/shaders/debug_draw.vs",
		"data/shaders/debug_draw.ps",
		g_debugRenderLayout,
		sizeof(g_debugRenderLayout) / sizeof(g_debugRenderLayout[0]));
}

void DebugRender::Shutdown()
{
	if (m_verticesBuffer) {
		delete m_verticesBuffer;
		m_verticesBuffer = nullptr;
	}
}

void DebugRender::DrawLine(const glm::vec3& from, const glm::vec3& to, const glm::vec3& color)
{
	if (!g_drawDebug)
		return;

	Line line;
	line.from = from;
	line.color0 = color;
	line.to = to;
	line.color1 = color;
	m_lines.push_back(line);
}

void DebugRender::DrawBoundingBox(const BoundingBox& box, const glm::vec3& color)
{
	glm::vec3 p0 = glm::vec3(box.m_min.x, box.m_min.y, box.m_min.z);
	glm::vec3 p1 = glm::vec3(box.m_max.x, box.m_max.y, box.m_max.z);
	glm::vec3 p2 = glm::vec3(box.m_min.x, box.m_min.y, box.m_max.z);
	glm::vec3 p3 = glm::vec3(box.m_min.x, box.m_max.y, box.m_min.z);
	glm::vec3 p4 = glm::vec3(box.m_min.x, box.m_max.y, box.m_max.z);
	glm::vec3 p5 = glm::vec3(box.m_max.x, box.m_min.y, box.m_min.z);
	glm::vec3 p6 = glm::vec3(box.m_max.x, box.m_min.y, box.m_max.z);
	glm::vec3 p7 = glm::vec3(box.m_max.x, box.m_max.y, box.m_min.z);

	DrawLine(p0, p2, color);
	DrawLine(p0, p3, color);
	DrawLine(p0, p5, color);
	DrawLine(p1, p4, color);
	DrawLine(p1, p6, color);
	DrawLine(p1, p7, color);
	DrawLine(p2, p4, color);
	DrawLine(p2, p6, color);
	DrawLine(p3, p4, color);
	DrawLine(p3, p7, color);
	DrawLine(p5, p6, color);
	DrawLine(p5, p7, color);
}

void DebugRender::DrawSphere(const glm::vec3& origin, float radius, const glm::vec3& color)
{
#ifndef M_PI
#define M_PI       3.14159265358979323846   // pi
#endif // !M_PI

	const int kSegments = 16;
	const float kStep = 2.0f * M_PI / (float)kSegments;

#undef M_PI

	// XY
	for (int i = 0; i < kSegments; i++)
	{
		float theta = i * kStep;
		float theta2 = (i + 1) * kStep;

		glm::vec3 p1 = origin + glm::vec3(cos(theta) * radius, sin(theta) * radius, 0.0f);
		glm::vec3 p2 = origin + glm::vec3(cos(theta2) * radius, sin(theta2) * radius, 0.0f);
		DrawLine(p1, p2, color);
	}

	// XZ
	for (int i = 0; i < kSegments; i++)
	{
		float theta = i * kStep;
		float theta2 = (i + 1) * kStep;

		glm::vec3 p1 = origin + glm::vec3(cos(theta) * radius, 0.0f, sin(theta) * radius);
		glm::vec3 p2 = origin + glm::vec3(cos(theta2) * radius, 0.0f, sin(theta2) * radius);
		DrawLine(p1, p2, color);
	}

	// YZ
	for (int i = 0; i < kSegments; i++)
	{
		float theta = i * kStep;
		float theta2 = (i + 1) * kStep;

		glm::vec3 p1 = origin + glm::vec3(0.0f, cos(theta) * radius, sin(theta) * radius);
		glm::vec3 p2 = origin + glm::vec3(0.0f, cos(theta2) * radius, sin(theta2) * radius);
		DrawLine(p1, p2, color);
	}
}

void DebugRender::RenderFrame()
{
	if (!g_drawDebug)
		return;

	// draw lines
	DrawLinesInternal();

	// and clear them
	m_lines.clear();
}

void DebugRender::DrawAxis(const glm::vec3& vec)
{
	const float length = 0.2f;
	DrawLine(vec, glm::vec3(vec.x + length, vec.y, vec.z), glm::vec3(1.0f, 0.0, 0.0f));
	DrawLine(vec, glm::vec3(vec.x, vec.y + length, vec.z), glm::vec3(0.0f, 1.0f, 0.0f));
	DrawLine(vec, glm::vec3(vec.x, vec.y, vec.z + length), glm::vec3(0.0f, 0.0f, 1.0f));
}

void DebugRender::DrawLinesInternal()
{
	if (m_lines.empty())
		return;

	g_renderDevice->SetDepthTest(true);
	g_renderDevice->SetDepthWrite(true);

	g_renderDevice->SetVerticesBuffer(m_verticesBuffer);

	m_verticesBuffer->UpdateBuffer(m_lines.data(), m_lines.size() * sizeof(Line));

	// Bind our shader
	g_shaderSystem->SetShader(m_shader);

	// #TODO: Fix stupid bug, when we get very far from wireframe and lines can start cliping
	glm::mat4 proj = g_render->GetProjectionMatrix();
	glm::mat4 view = g_render->GetViewMatrix();
	proj[2][3] -= 0.0001f;

	glm::mat4 mv = glm::mat4(1.0f);
	mv = proj * view;

	g_shaderSystem->SetUniformMatrix(m_shader, UNIFORM_MVP_MATRIX, &mv[0]);

	// draw stuff
	g_renderDevice->DrawArrays(PT_LINES, 0, m_lines.size() * 2);
}
