#pragma once

#include <psemek/gfx/color.hpp>
#include <psemek/gfx/mesh.hpp>
#include <psemek/gfx/texture.hpp>
#include <psemek/gfx/framebuffer.hpp>
#include <psemek/gfx/render_target.hpp>

#include <psemek/math/camera.hpp>
#include <psemek/math/box.hpp>

#include <psemek/util/pimpl.hpp>
#include <psemek/util/function.hpp>

namespace psemek::gfx
{

#ifndef PSEMEK_GLES
	struct deferred_renderer
	{
		deferred_renderer();
		~deferred_renderer();

		enum class position_mode
		{
			// Default position mode is float32
			// TODO: support fixed16 & fixed32
			float16,
			float32,
		};

		void set_position_mode(position_mode mode);

		struct material
		{
			std::optional<color_4f> color;
			texture_2d const * texture = nullptr;
			texture_2d const * bump_map = nullptr;
			bool transparent = false;
			bool lit = true;
			bool blooming = false;
			bool casts_shadow = true;

			struct
			{
				// specular highlight is calculated as
				//    intensity * dot(reflected, view)^shininess
				float intensity = 0.f;
				float shininess = 1.f;
			} specular;
		};

		struct object
		{
			// Attribute specification:
			// 0 - vec3 position
			// 1 - vec4 color (used if color & texture are not set)
			// 2 - vec2 texcoord (used if texture is set)
			// 3 - vec3 normal (used if lit = true)

			// For instanced mesh:
			// 4 - mat3x4 per-instance transform (used in conjunction with transform)

			gfx::mesh const * mesh = nullptr;
			material const * mat = nullptr;
			math::box<float, 3> bbox;
			std::optional<math::matrix<float, 3, 4>> pre_transform;
			std::optional<math::matrix<float, 3, 4>> post_transform;
		};

		struct directional_light
		{
			color_3f color;
			math::vector<float, 3> direction;
			bool shadowed = true;
			std::size_t shadow_map_size = 1024;
			std::size_t cascades = 4;
			std::vector<math::interval<float>> cascade_ranges;
		};

		struct point_light
		{
			color_3f color;
			// Intensity at distance d is computed as
			//     1.0 / (c0 + d * c1 + d^2 * c2)
			struct {
				float c0, c1, c2;
			} attenuation;
			math::point<float, 3> position;
			bool shadowed = true;
			float min_shadow_distance;
			std::size_t shadow_map_size = 1024;
		};

		struct options
		{
			math::camera const * camera;

			std::optional<color_3f> clear_color;
			util::function<void()> background_generator;

			color_3f ambient;
			std::vector<directional_light> directional_lights;
			std::vector<point_light> point_lights;

			// Used for HDR tone-mapping
			float max_intensity;
			// Equals max_intensity / 256 by default
			std::optional<float> min_intensity;

			struct bloom_data
			{
				int size = 5;
				float sigma = 3.f;
				std::size_t downsample = 2;
			};

			std::optional<bloom_data> bloom;

			struct ssao_data
			{
				float radius;
				std::size_t downsample = 2;
			};

			std::optional<ssao_data> ssao;

			math::vector<std::size_t, 3> grid_size{1, 1, 1};
		};

		void render(std::vector<object> const & objects, render_target const & target, options const & opts);

		gfx::texture_2d const & depth() const;

	private:
		psemek_declare_pimpl
	};
#endif

}
