#include <psemek/gfx/renderer/simple.hpp>

#include <psemek/gfx/program.hpp>

namespace psemek::gfx
{

	static char const attrib_color_vertex_source[] =
R"(#version 330

uniform mat4 u_transform;

layout (location = 0) in vec4 in_position;
layout (location = 1) in vec4 in_color;

out vec4 color;

void main()
{
	gl_Position = u_transform * in_position;
	color = in_color;
}
)";

	static char const uniform_color_vertex_source[] =
R"(#version 330

uniform mat4 u_transform;
uniform vec4 u_color;

layout (location = 0) in vec4 in_position;

out vec4 color;

void main()
{
	gl_Position = u_transform * in_position;
	color = u_color;
}
)";

	static char const color_fragment_source[] =
R"(#version 330

in vec4 color;
out vec4 out_color;

void main()
{
	out_color = color;
}
)";

	static char const textured_vertex_source[] =
R"(#version 330

uniform mat4 u_transform;

layout (location = 0) in vec4 in_position;
layout (location = 2) in vec2 in_texcoord;

out vec2 texcoord;

void main()
{
	gl_Position = u_transform * in_position;
	texcoord = in_texcoord;
}
)";

	static char const textured_fragment_source[] =
R"(#version 330

uniform sampler2D u_texture;

in vec2 texcoord;
out vec4 out_color;

void main()
{
	out_color = texture(u_texture, texcoord);
}
)";

	struct simple_renderer::impl
	{
		program attrib_color_program{attrib_color_vertex_source, color_fragment_source};
		program uniform_color_program{uniform_color_vertex_source, color_fragment_source};
		program textured_program{textured_vertex_source, textured_fragment_source};

		std::vector<render_object *> objects;
		std::vector<render_state> attrib_color_states;
		std::vector<render_state> uniform_color_states;
		std::vector<render_state> textured_states;
	};

	simple_renderer::simple_renderer()
		: pimpl_{make_impl()}
	{}

	simple_renderer::~simple_renderer() = default;

	void simple_renderer::add(render_object * o)
	{
		impl().objects.push_back(o);
	}

	void simple_renderer::remove(render_object * o)
	{
		auto it = std::find(impl().objects.begin(), impl().objects.end(), o);
		if (it != impl().objects.end())
			impl().objects.erase(it);
	}

	void simple_renderer::push(render_state s)
	{
		if (s.mesh)
		{
			if (s.texture)
				impl().textured_states.push_back(s);
			else if (s.color)
				impl().uniform_color_states.push_back(s);
			else
				impl().attrib_color_states.push_back(s);
		}
	}

	void simple_renderer::render(render_options const & opts)
	{
		for (auto o : impl().objects)
		{
			for (auto const & s : o->get_render_states())
				push(s);
		}

		if (!impl().attrib_color_states.empty())
		{
			impl().attrib_color_program.bind();
			impl().attrib_color_program["u_transform"] = opts.transform;

			for (auto const & s : impl().attrib_color_states)
			{
				s.mesh->draw();
			}

			impl().attrib_color_states.clear();
		}

		if (!impl().uniform_color_states.empty())
		{
			impl().uniform_color_program.bind();
			impl().uniform_color_program["u_transform"] = opts.transform;

			for (auto const & s : impl().uniform_color_states)
			{
				impl().uniform_color_program["u_color"] = to_colorf(*s.color);
				s.mesh->draw();
			}

			impl().uniform_color_states.clear();
		}

		if (!impl().textured_states.empty())
		{
			impl().textured_program.bind();
			impl().textured_program["u_transform"] = opts.transform;
			impl().textured_program["u_texture"] = 0;
			gl::ActiveTexture(gl::TEXTURE0);

			for (auto const & s : impl().textured_states)
			{
				s.texture->bind();
				s.mesh->draw();
			}

			impl().textured_states.clear();
		}
	}

}
