#ifndef _H_LINE_H
#define _H_LINE_H

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

#include <iostream>
#include <vector>

class Line
{
private:
	unsigned VAO, VBO;
	glm::vec4 color;
public:
	Line(glm::vec3 start, glm::vec3 end)
	{
		float vertices[] = {
			start.x, start.y, start.z,
			end.x,   end.y,   end.z
		};

		glGenVertexArrays(1, &VAO);
		glGenBuffers(1, &VBO);

		glBindVertexArray(VAO);
		glBindBuffer(GL_ARRAY_BUFFER, VBO);

		glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), &vertices, GL_DYNAMIC_DRAW);
		glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
		glEnableVertexAttribArray(0);

		glBindBuffer(GL_ARRAY_BUFFER, 0);
		glBindVertexArray(0);
		color = glm::vec4(1.0f, 0.0f, 0.0f, 1.0f);
	}

	void updateVertices(const glm::vec3& origin, const glm::vec3& target)
	{
		float vertices[] = {
			origin.x, origin.y, origin.z,
			target.x, target.y, target.z
		};
		glBindBuffer(GL_ARRAY_BUFFER, VBO);
		float* ptr = (float*)glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
		if (ptr)
		{
			for (int i = 0; i < sizeof(vertices) / sizeof(vertices[0]); i++)
			{
				ptr[i] = vertices[i];
			}
			glUnmapBuffer(GL_ARRAY_BUFFER);
		}
		glBindBuffer(GL_ARRAY_BUFFER, 0);
	}

	void setColor(glm::vec4 color) { this->color = color; }

	const glm::vec4& getColor() const { return color; }

	void draw()
	{
		glBindVertexArray(VAO);
		glLineWidth(10.0f);
		glDrawArrays(GL_LINES, 0, 2);
		glBindVertexArray(0);
	}
	~Line()
	{
		glDeleteVertexArrays(1, &VAO);
		glDeleteBuffers(1, &VBO);
	}
};

class LineDrawer {
public:
	static LineDrawer& getInstance() {
		static LineDrawer instance;
		return instance;
	}
	LineDrawer(LineDrawer const&) = delete;
	void operator=(LineDrawer 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 draw(const glm::mat4& mvp) {
		glUseProgram(programID);
		for (auto& line : lines)
		{
			glUniform4fv(glGetUniformLocation(programID, "Color"), 1, glm::value_ptr(line->getColor()));
			glUniformMatrix4fv(glGetUniformLocation(programID, "mvp"), 1, GL_FALSE, glm::value_ptr(mvp));
			line->draw();
		}
		lines.clear();
	}
	~LineDrawer() {
		glDeleteProgram(programID);
	}

private:
	unsigned programID;
	LineDrawer() {
		const char* vertexShader = "#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";
		const char* fragmentShader = "#version 330 core\n"
			"out vec4 FragColor;\n"
			"uniform vec4 Color;\n"
			"void main()\n"
			"{\n"
			"FragColor = Color;\n"
			"}\0";
		unsigned vertexID, fragmentID;

		vertexID = glCreateShader(GL_VERTEX_SHADER);
		glShaderSource(vertexID, 1, &vertexShader, NULL);
		glCompileShader(vertexID);

		fragmentID = glCreateShader(GL_FRAGMENT_SHADER);
		glShaderSource(fragmentID, 1, &fragmentShader, NULL);
		glCompileShader(fragmentID);

		programID = glCreateProgram();
		glAttachShader(programID, vertexID);
		glAttachShader(programID, fragmentID);
		glLinkProgram(programID);

		glDeleteShader(vertexID);
		glDeleteShader(fragmentID);
	}

	std::vector<std::shared_ptr<Line>> lines;
};

#endif