#pragma once

#include <psemek/gfx/color.hpp>
#include <psemek/gfx/gl.hpp>

namespace psemek::gfx
{

	struct depth24_pixel
	{
		std::uint8_t depth[3];
	};

	struct depth24_stencil8_pixel
	{
		std::uint8_t depth[3];
		std::uint8_t stencil;
	};

	struct depth32f_pixel
	{
		float depth;
	};

	struct float16
	{
		std::uint16_t value;
	};

	struct uint10;
	struct uint12;

	struct uint10_10_10_2
	{
		std::uint32_t value;
	};

	template <typename T>
	struct integer;

	template <typename T>
	struct srgb;

	template <typename Pixel>
	struct pixel_traits;

	// Normalized 8-bit

	template <>
	struct pixel_traits<std::uint8_t>
	{
		static constexpr GLenum internal_format = gl::R8;
		static constexpr GLenum format = gl::RED;
		static constexpr GLenum type = gl::UNSIGNED_BYTE;
	};

	template <>
	struct pixel_traits<math::vector<std::uint8_t, 2>>
	{
		static constexpr GLenum internal_format = gl::RG8;
		static constexpr GLenum format = gl::RG;
		static constexpr GLenum type = gl::UNSIGNED_BYTE;
	};

	template <>
	struct pixel_traits<math::vector<std::uint8_t, 3>>
	{
		static constexpr GLenum internal_format = gl::RGB8;
		static constexpr GLenum format = gl::RGB;
		static constexpr GLenum type = gl::UNSIGNED_BYTE;
	};

	template <>
	struct pixel_traits<math::vector<std::uint8_t, 4>>
	{
		static constexpr GLenum internal_format = gl::RGBA8;
		static constexpr GLenum format = gl::RGBA;
		static constexpr GLenum type = gl::UNSIGNED_BYTE;
	};

	// Normalized 8-bit sRGB

	template <>
	struct pixel_traits<srgb<math::vector<std::uint8_t, 3>>>
	{
		static constexpr GLenum internal_format = gl::SRGB8;
		static constexpr GLenum format = gl::RGB;
		static constexpr GLenum type = gl::UNSIGNED_BYTE;
	};

	template <>
	struct pixel_traits<srgb<math::vector<std::uint8_t, 4>>>
	{
		static constexpr GLenum internal_format = gl::SRGB8_ALPHA8;
		static constexpr GLenum format = gl::RGBA;
		static constexpr GLenum type = gl::UNSIGNED_BYTE;
	};

	// Normalized 10-bit

#ifndef PSEMEK_GLES
	template <>
	struct pixel_traits<math::vector<uint10, 3>>
	{
		static constexpr GLenum internal_format = gl::RGB10;
		static constexpr GLenum format = gl::RGB;
		static constexpr GLenum type = gl::UNSIGNED_BYTE;
	};

	// Normalized 12-bit

	template <>
	struct pixel_traits<math::vector<uint12, 3>>
	{
		static constexpr GLenum internal_format = gl::RGB12;
		static constexpr GLenum format = gl::RGB;
		static constexpr GLenum type = gl::UNSIGNED_BYTE;
	};

	template <>
	struct pixel_traits<math::vector<uint12, 4>>
	{
		static constexpr GLenum internal_format = gl::RGBA12;
		static constexpr GLenum format = gl::RGBA;
		static constexpr GLenum type = gl::UNSIGNED_BYTE;
	};

	// Normalized 16-bit

	template <>
	struct pixel_traits<std::uint16_t>
	{
		static constexpr GLenum internal_format = gl::R16;
		static constexpr GLenum format = gl::RED;
		static constexpr GLenum type = gl::UNSIGNED_SHORT;
	};

	template <>
	struct pixel_traits<math::vector<std::uint16_t, 2>>
	{
		static constexpr GLenum internal_format = gl::RG16;
		static constexpr GLenum format = gl::RG;
		static constexpr GLenum type = gl::UNSIGNED_SHORT;
	};

	template <>
	struct pixel_traits<math::vector<std::uint16_t, 3>>
	{
		static constexpr GLenum internal_format = gl::RGB16;
		static constexpr GLenum format = gl::RGB;
		static constexpr GLenum type = gl::UNSIGNED_SHORT;
	};

	template <>
	struct pixel_traits<math::vector<std::uint16_t, 4>>
	{
		static constexpr GLenum internal_format = gl::RGBA16;
		static constexpr GLenum format = gl::RGBA;
		static constexpr GLenum type = gl::UNSIGNED_SHORT;
	};

#endif

	// Normalized 10-10-10-2

	template <>
	struct pixel_traits<uint10_10_10_2>
	{
		static constexpr GLenum internal_format = gl::RGB10_A2;
		static constexpr GLenum format = gl::RGBA;
		static constexpr GLenum type = gl::UNSIGNED_BYTE;
	};

	// Unsigned 8-bit

	template <>
	struct pixel_traits<integer<std::uint8_t>>
	{
		static constexpr GLenum internal_format = gl::R8UI;
		static constexpr GLenum format = gl::RED_INTEGER;
		static constexpr GLenum type = gl::UNSIGNED_BYTE;
	};

	template <>
	struct pixel_traits<math::vector<integer<std::uint8_t>, 2>>
	{
		static constexpr GLenum internal_format = gl::RG8UI;
		static constexpr GLenum format = gl::RG_INTEGER;
		static constexpr GLenum type = gl::UNSIGNED_BYTE;
	};

	template <>
	struct pixel_traits<math::vector<integer<std::uint8_t>, 3>>
	{
		static constexpr GLenum internal_format = gl::RGB8UI;
		static constexpr GLenum format = gl::RGB_INTEGER;
		static constexpr GLenum type = gl::UNSIGNED_BYTE;
	};

	template <>
	struct pixel_traits<math::vector<integer<std::uint8_t>, 4>>
	{
		static constexpr GLenum internal_format = gl::RGBA8UI;
		static constexpr GLenum format = gl::RGBA_INTEGER;
		static constexpr GLenum type = gl::UNSIGNED_BYTE;
	};

	// Unsigned 16-bit

	template <>
	struct pixel_traits<integer<std::uint16_t>>
	{
		static constexpr GLenum internal_format = gl::R16UI;
		static constexpr GLenum format = gl::RED_INTEGER;
		static constexpr GLenum type = gl::UNSIGNED_SHORT;
	};

	template <>
	struct pixel_traits<math::vector<integer<std::uint16_t>, 2>>
	{
		static constexpr GLenum internal_format = gl::RG16UI;
		static constexpr GLenum format = gl::RG_INTEGER;
		static constexpr GLenum type = gl::UNSIGNED_SHORT;
	};

	template <>
	struct pixel_traits<math::vector<integer<std::uint16_t>, 3>>
	{
		static constexpr GLenum internal_format = gl::RGB16UI;
		static constexpr GLenum format = gl::RGB_INTEGER;
		static constexpr GLenum type = gl::UNSIGNED_SHORT;
	};

	template <>
	struct pixel_traits<math::vector<integer<std::uint16_t>, 4>>
	{
		static constexpr GLenum internal_format = gl::RGBA16UI;
		static constexpr GLenum format = gl::RGBA_INTEGER;
		static constexpr GLenum type = gl::UNSIGNED_SHORT;
	};

	// Unsigned 32-bit

	template <>
	struct pixel_traits<integer<std::uint32_t>>
	{
		static constexpr GLenum internal_format = gl::R32UI;
		static constexpr GLenum format = gl::RED_INTEGER;
		static constexpr GLenum type = gl::UNSIGNED_INT;
	};

	template <>
	struct pixel_traits<math::vector<integer<std::uint32_t>, 2>>
	{
		static constexpr GLenum internal_format = gl::RG32UI;
		static constexpr GLenum format = gl::RG_INTEGER;
		static constexpr GLenum type = gl::UNSIGNED_INT;
	};

	template <>
	struct pixel_traits<math::vector<integer<std::uint32_t>, 3>>
	{
		static constexpr GLenum internal_format = gl::RGB32UI;
		static constexpr GLenum format = gl::RGB_INTEGER;
		static constexpr GLenum type = gl::UNSIGNED_INT;
	};

	template <>
	struct pixel_traits<math::vector<integer<std::uint32_t>, 4>>
	{
		static constexpr GLenum internal_format = gl::RGBA32UI;
		static constexpr GLenum format = gl::RGBA_INTEGER;
		static constexpr GLenum type = gl::UNSIGNED_INT;
	};

	// Signed 8-bit

	template <>
	struct pixel_traits<integer<std::int8_t>>
	{
		static constexpr GLenum internal_format = gl::R8I;
		static constexpr GLenum format = gl::RED_INTEGER;
		static constexpr GLenum type = gl::BYTE;
	};

	template <>
	struct pixel_traits<math::vector<integer<std::int8_t>, 2>>
	{
		static constexpr GLenum internal_format = gl::RG8I;
		static constexpr GLenum format = gl::RG_INTEGER;
		static constexpr GLenum type = gl::BYTE;
	};

	template <>
	struct pixel_traits<math::vector<integer<std::int8_t>, 3>>
	{
		static constexpr GLenum internal_format = gl::RGB8I;
		static constexpr GLenum format = gl::RGB_INTEGER;
		static constexpr GLenum type = gl::BYTE;
	};

	template <>
	struct pixel_traits<math::vector<integer<std::int8_t>, 4>>
	{
		static constexpr GLenum internal_format = gl::RGBA8I;
		static constexpr GLenum format = gl::RGBA_INTEGER;
		static constexpr GLenum type = gl::BYTE;
	};

	// Signed 16-bit

	template <>
	struct pixel_traits<integer<std::int16_t>>
	{
		static constexpr GLenum internal_format = gl::R16I;
		static constexpr GLenum format = gl::RED_INTEGER;
		static constexpr GLenum type = gl::SHORT;
	};

	template <>
	struct pixel_traits<math::vector<integer<std::int16_t>, 2>>
	{
		static constexpr GLenum internal_format = gl::RG16I;
		static constexpr GLenum format = gl::RG_INTEGER;
		static constexpr GLenum type = gl::SHORT;
	};

	template <>
	struct pixel_traits<math::vector<integer<std::int16_t>, 3>>
	{
		static constexpr GLenum internal_format = gl::RGB16I;
		static constexpr GLenum format = gl::RGB_INTEGER;
		static constexpr GLenum type = gl::SHORT;
	};

	template <>
	struct pixel_traits<math::vector<integer<std::int16_t>, 4>>
	{
		static constexpr GLenum internal_format = gl::RGBA16I;
		static constexpr GLenum format = gl::RGBA_INTEGER;
		static constexpr GLenum type = gl::SHORT;
	};

	// Signed 32-bit

	template <>
	struct pixel_traits<integer<std::int32_t>>
	{
		static constexpr GLenum internal_format = gl::R32I;
		static constexpr GLenum format = gl::RED_INTEGER;
		static constexpr GLenum type = gl::INT;
	};

	template <>
	struct pixel_traits<math::vector<integer<std::int32_t>, 2>>
	{
		static constexpr GLenum internal_format = gl::RG32I;
		static constexpr GLenum format = gl::RG_INTEGER;
		static constexpr GLenum type = gl::INT;
	};

	template <>
	struct pixel_traits<math::vector<integer<std::int32_t>, 3>>
	{
		static constexpr GLenum internal_format = gl::RGB32I;
		static constexpr GLenum format = gl::RGB_INTEGER;
		static constexpr GLenum type = gl::INT;
	};

	template <>
	struct pixel_traits<math::vector<integer<std::int32_t>, 4>>
	{
		static constexpr GLenum internal_format = gl::RGBA32I;
		static constexpr GLenum format = gl::RGBA_INTEGER;
		static constexpr GLenum type = gl::INT;
	};

	// Float 16-bit

	template <>
	struct pixel_traits<float16>
	{
		static constexpr GLenum internal_format = gl::R16F;
		static constexpr GLenum format = gl::RED;
		static constexpr GLenum type = gl::FLOAT;
	};

	template <>
	struct pixel_traits<math::vector<float16, 2>>
	{
		static constexpr GLenum internal_format = gl::RG16F;
		static constexpr GLenum format = gl::RG;
		static constexpr GLenum type = gl::FLOAT;
	};

	template <>
	struct pixel_traits<math::vector<float16, 3>>
	{
		static constexpr GLenum internal_format = gl::RGB16F;
		static constexpr GLenum format = gl::RGB;
		static constexpr GLenum type = gl::FLOAT;
	};

	template <>
	struct pixel_traits<math::vector<float16, 4>>
	{
		static constexpr GLenum internal_format = gl::RGBA16F;
		static constexpr GLenum format = gl::RGBA;
		static constexpr GLenum type = gl::FLOAT;
	};

	// Float 32-bit

	template <>
	struct pixel_traits<float>
	{
		static constexpr GLenum internal_format = gl::R32F;
		static constexpr GLenum format = gl::RED;
		static constexpr GLenum type = gl::FLOAT;
	};

	template <>
	struct pixel_traits<math::vector<float, 2>>
	{
		static constexpr GLenum internal_format = gl::RG32F;
		static constexpr GLenum format = gl::RG;
		static constexpr GLenum type = gl::FLOAT;
	};

	template <>
	struct pixel_traits<math::vector<float, 3>>
	{
		static constexpr GLenum internal_format = gl::RGB32F;
		static constexpr GLenum format = gl::RGB;
		static constexpr GLenum type = gl::FLOAT;
	};

	template <>
	struct pixel_traits<math::vector<float, 4>>
	{
		static constexpr GLenum internal_format = gl::RGBA32F;
		static constexpr GLenum format = gl::RGBA;
		static constexpr GLenum type = gl::FLOAT;
	};

	// Depth-stencil

	template <>
	struct pixel_traits<depth24_pixel>
	{
		static constexpr GLenum internal_format = gl::DEPTH_COMPONENT24;
		static constexpr GLenum format = gl::DEPTH_COMPONENT;
		static constexpr GLenum type = gl::UNSIGNED_INT;
	};

	template <>
	struct pixel_traits<depth24_stencil8_pixel>
	{
		static constexpr GLenum internal_format = gl::DEPTH24_STENCIL8;
		static constexpr GLenum format = gl::DEPTH_STENCIL;
		static constexpr GLenum type = gl::UNSIGNED_INT_24_8;
	};

	template <>
	struct pixel_traits<depth32f_pixel>
	{
		static constexpr GLenum internal_format = gl::DEPTH_COMPONENT32F;
		static constexpr GLenum format = gl::DEPTH_COMPONENT;
		static constexpr GLenum type = gl::FLOAT;
	};

	std::size_t pixel_size(GLint internal_format);

}
