#ifndef _H_DEBUGDRAW_H
#define _H_DEBUGDRAW_H

#include <thirdparty/glad/glad.h>
#include <glm/glm.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <utility/ftfont.h>

#include <vector>
#include <memory>

struct ShaderSource {
	const char* vertexShader;
	const char* fragShader;
};

struct ShaderProgram {
	unsigned programID;
	//uniform locations
	unsigned u_MVP, u_Color;
	void setup(const ShaderSource& source) {
		unsigned vertexID, fragID;
		// compile vertex shader
		vertexID = glCreateShader(GL_VERTEX_SHADER);
		glShaderSource(vertexID, 1, &source.vertexShader, NULL);
		glCompileShader(vertexID);
		// compile fragment shader
		fragID = glCreateShader(GL_FRAGMENT_SHADER);
		glShaderSource(fragID, 1, &source.fragShader, NULL);
		glCompileShader(fragID);
		// link shaders to program
		programID = glCreateProgram();
		glAttachShader(programID, vertexID);
		glAttachShader(programID, fragID);
		glLinkProgram(programID);
		// delete shaders
		glDeleteShader(vertexID);
		glDeleteShader(fragID);
		// get uniform locations
		u_MVP   = glGetUniformLocation(programID, "u_MVP");
		u_Color = glGetUniformLocation(programID, "u_Color");
	}
	void use		()							{ if (programID) glUseProgram(programID); }
	void setMVP		(const float* mvpPointer)	{ use(); glUniformMatrix4fv(u_MVP, 1, GL_FALSE, mvpPointer); }
	void setColor	(const float* colorPointer)	{ use(); glUniform4fv(u_Color, 1, colorPointer); }
	void delShader	()							{ glDeleteProgram(programID); }
};

class Circle
{
private:
	unsigned VAO, VBO;
	glm::mat4 MVP;
	glm::vec4 Color;
	float radius;
public:
	Circle(const glm::vec3& position, float radius);
	void				setMVP	(const glm::mat4& mvp)		{ MVP = mvp; }
	void				setColor(const glm::vec4& color)	{ Color = color; }
	const glm::mat4&	getMVP	() const					{ return MVP; }
	const glm::vec4&	getColor() const					{ return Color; }
	void draw();
	void updatePosition(const glm::vec3& position);
	~Circle();
};

class Line
{
private:
	unsigned VAO, VBO;
	glm::vec4 color;
public:
	Line(glm::vec3 start, glm::vec3 end);
	void				setColor(glm::vec4 color)	{ this->color = color; }
	const glm::vec4&	getColor() const			{ return color; }
	void draw();
	void updateVertices(const glm::vec3& origin, const glm::vec3& target);
	~Line();
};

class DebugDrawer {
public:
	static DebugDrawer& getInstance() {
		static DebugDrawer instance;
		return instance;
	}
	DebugDrawer(DebugDrawer const&) = delete;
	void operator=(DebugDrawer const&) = delete;

	void addLine(const glm::vec3& start, const glm::vec3& end, const glm::vec4& color) {
		auto line = std::make_shared<Line>(start, end);
		line->setColor(color);
		lines.push_back(line);
	}

	void addCircle(const glm::vec3& position, float radius, const glm::vec4& color) {
		auto circle = std::make_shared<Circle>(position, radius);
		circle->setColor(color);
		circles.push_back(circle);
	}

	void draw(const glm::mat4& mvp) {
		lineProgram.use();
		for (auto& line : lines)
		{
			lineProgram.setMVP(glm::value_ptr(mvp));
			lineProgram.setColor(glm::value_ptr(line->getColor()));
			line->draw();
		}
		lines.clear();
		circleProgram.use();
		for (auto& circle : circles)
		{
			circleProgram.setMVP(glm::value_ptr(mvp));
			circleProgram.setColor(glm::value_ptr(circle->getColor()));
			circle->draw();
		}
		circles.clear();
	}
	~DebugDrawer() {
		lineProgram.delShader();
		circleProgram.delShader();
	}

private:
	std::vector<std::shared_ptr<Line>> lines;
	std::vector<std::shared_ptr<Circle>> circles;

	DebugDrawer() {
		circleProgram.setup(circleSource);
		lineProgram.setup(lineSource);
	}

	ShaderSource circleSource {
		// vertex shader program
		"#version 330 core\n"
		"layout (location = 0) in vec3 aPos;\n"
		"layout (location = 1) in float aRadius;\n"
		"uniform mat4 u_MVP;\n"
		"void main()\n"
		"{\n"
		"float angle = 2.0 * 3.14159 * float(gl_InstanceID) / 360.0;\n"
		"float x = aPos.x + cos(angle) * aRadius;\n"
		"float y = aPos.y + sin(angle) * aRadius;\n"
		"gl_Position = u_MVP * vec4(x, y, aPos.z, 1.0);\n"
		"}\0"
		,
		// fragment shader program
		"#version 330 core\n"
		"out vec4 FragColor;\n"
		"uniform vec4 u_Color;\n"
		"void main()\n"
		"{\n"
		"FragColor = u_Color;\n"
		"}\0"
	};
	ShaderSource lineSource {
		// vertex shader
		"#version 330 core\n"
		"layout (location = 0) in vec3 aPos;\n"
		"uniform mat4 mvp;\n"
		"void main()\n"
		"{\n"
		"gl_Position = mvp * vec4(aPos, 1.0);\n"
		"}\0"
		,
		// fragment shader
		"#version 330 core\n"
		"out vec4 FragColor;\n"
		"uniform vec4 Color;\n"
		"void main()\n"
		"{\n"
		"FragColor = Color;\n"
		"}\0"
	};

	ShaderProgram circleProgram;
	ShaderProgram lineProgram;

};

#endif
