#ifndef AMT_PIXEL_HPP
#define AMT_PIXEL_HPP

#include "matrix.hpp"
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <string>
#include <stdexcept>
#include <type_traits>

namespace amt {

	enum class PixelFormat {
		rgba,
		abgr,
		rgb ,
		bgr ,
		ga  , // gray scale and alpha
		ag  , // alpha and gray scale
		g     // gray scale
	};

	inline static constexpr auto get_pixel_format_from_channel(std::size_t c, bool little_endian = false) -> PixelFormat {
		switch (c) {
			case 1:	return PixelFormat::g;
			case 2:	return little_endian ? PixelFormat::ag : PixelFormat::ga;
			case 3:	return little_endian ? PixelFormat::bgr : PixelFormat::rgb;
			case 4:	return little_endian ? PixelFormat::abgr : PixelFormat::abgr;
		}
		throw std::runtime_error(std::string("get_pixel_format_from_channel: unknown channel ") + std::to_string(c));
	}

	namespace detail {
		static constexpr auto compare_float(float l, float r) noexcept -> bool {
			return std::abs(l - r) < std::numeric_limits<float>::epsilon();
		}
	} // namespace detail
	
	enum class BlendMode {
		normal,
		multiply,
		screen,
		overlay,
		darken,
		lighten,
		colorDodge,
		colorBurn,
		hardLight,
		softLight,
		difference,
		exclusion
	};
	
	struct RGBA {
		using pixel_t = std::uint8_t;
		using pixels_t = std::uint32_t;

		constexpr RGBA() noexcept = default;
		constexpr RGBA(RGBA const&) noexcept = default;
		constexpr RGBA(RGBA &&) noexcept = default;
		constexpr RGBA& operator=(RGBA const&) noexcept = default; 
		constexpr RGBA& operator=(RGBA &&) noexcept = default;
		constexpr ~RGBA() noexcept = default;

		
		// NOTE: Accepts RRGGBBAA
		explicit constexpr RGBA(pixels_t color) noexcept
			: RGBA((color >> (8 * 3)) & 0xff, (color >> (8 * 2)) & 0xff, (color >> (8 * 1)) & 0xff, (color >> (8 * 0)) & 0xff)
		{}

		constexpr RGBA(pixel_t r, pixel_t g, pixel_t b, pixel_t a = 0xff) noexcept
			: m_data {r, g, b, a}
		{}

		constexpr RGBA(pixel_t color, pixel_t a = 0xff) noexcept
			: RGBA(color, color, color, a)
		{}

		// NOTE: Returns RRGGBBAA
		constexpr auto to_hex() const noexcept -> pixels_t {
			auto r = static_cast<pixels_t>(this->r());
			auto b = static_cast<pixels_t>(this->b());
			auto g = static_cast<pixels_t>(this->g());
			auto a = static_cast<pixels_t>(this->a());
			return (r << (8 * 3)) | (g << (8 * 2)) | (b << (8 * 1)) | (a << (8 * 0));
		}

		constexpr auto r() const noexcept -> pixel_t { return m_data[0]; }
		constexpr auto g() const noexcept -> pixel_t { return m_data[1]; }
		constexpr auto b() const noexcept -> pixel_t { return m_data[2]; }
		constexpr auto a() const noexcept -> pixel_t { return m_data[3]; }

		constexpr auto r() noexcept -> pixel_t& { return m_data[0]; }
		constexpr auto g() noexcept -> pixel_t& { return m_data[1]; }
		constexpr auto b() noexcept -> pixel_t& { return m_data[2]; }
		constexpr auto a() noexcept -> pixel_t& { return m_data[3]; }

		/**
		 * @returns the value is between 0 and 1
		 */
		constexpr auto brightness() const noexcept -> float {
			// 0.299*R + 0.587*G + 0.114*B
			auto tr = normalize(r());
			auto tg = normalize(g());
			auto tb = normalize(b());
			return (0.299 * tr + 0.587 * tg + 0.114 * tb); 
		}

		template <typename T>
			requires std::is_arithmetic_v<T>
		constexpr auto operator/(T val) const noexcept {
			auto d = static_cast<float>(val);
			return RGBA(
				static_cast<pixel_t>(r() / d),
				static_cast<pixel_t>(g() / d),
				static_cast<pixel_t>(b() / d),
				a()
			);
		}

		template <typename T>
			requires std::is_arithmetic_v<T>
		constexpr auto operator*(T val) const noexcept {
			auto d = static_cast<float>(val);
			return RGBA(
				static_cast<pixel_t>(r() * d),
				static_cast<pixel_t>(g() * d),
				static_cast<pixel_t>(b() * d),
				a()
			);
		}
	private:
		static constexpr auto normalize(pixel_t p) noexcept -> float {
			return float(p) / 255;
		}

		static constexpr auto to_pixel(float p) noexcept -> pixel_t {
			return static_cast<pixel_t>(p * 255);
		}

		template <BlendMode M>
		static constexpr auto blend_helper() noexcept {
			constexpr auto mix_helper = [](float s, float b, float a) -> float {
				// (1 - αb) x Cs + αb x B(Cb, Cs)
				return (1 - a) * s + a * b;
			};

			if constexpr (M == BlendMode::normal) {
				return [mix_helper](float bg, float fg, float alpha) { return mix_helper(bg, fg, alpha); };
			} else if constexpr (M == BlendMode::multiply) {
				return [mix_helper](float bg, float fg, float alpha) { return mix_helper(bg, bg * fg, alpha); };
			} else if constexpr (M == BlendMode::screen) {
				return [mix_helper](float bg, float fg, float alpha) {
					// Cb + Cs -(Cb x Cs)
					auto bf = bg + fg - (bg * fg);
					return mix_helper(bg, bf, alpha);
				};
			} else if constexpr (M == BlendMode::overlay) {
				return [mix_helper](float bg, float fg, float alpha, auto&& hard_light_fn, auto&& multiply_fn, auto&& screen_fn) {
					// HardLight(Cs, Cb)
					auto hl = hard_light_fn(bg, fg, alpha, multiply_fn, screen_fn);
					return mix_helper(bg, hl, alpha);
				};
			} else if constexpr (M == BlendMode::darken) {
				return [mix_helper](float bg, float fg, float alpha) {
					return mix_helper(bg, std::min(bg, fg), alpha);
				};
			} else if constexpr (M == BlendMode::lighten) {
				return [mix_helper](float bg, float fg, float alpha) {
					return mix_helper(bg, std::max(bg, fg), alpha);
				};
			} else if constexpr (M == BlendMode::colorDodge) {
				return [mix_helper](float bg, float fg, float alpha) {
					constexpr auto fn = [](float b, float s) -> float {
						if (b == 0) return 0;
						if (s == 255) return 255;
						return std::min(1.f, b / (1.f - s));
					};
					auto bf = fn(bg, fg);
					return mix_helper(bg, bf, alpha);
				};
			} else if constexpr (M == BlendMode::colorBurn) {
				return [mix_helper](float bg, float fg, float alpha) {
					constexpr auto fn = [](float b, float s) -> float {
						if (b == 255) return 255;
						if (s == 0) return 0;
						return 1.f - std::min(1.f, (1.f - b) / s);
					};
					auto bf = fn(bg, fg);
					return mix_helper(bg, bf, alpha);
				};
			} else if constexpr (M == BlendMode::hardLight) {
				return [mix_helper](float bg, float fg, float alpha, auto&& multiply_fn, auto&& screen_fn) {
					auto fn = [&multiply_fn, &screen_fn](float b, float s, float a) -> float {
						if (s <= 0.5f) {
							return multiply_fn(b, 2.f * s, a);
						} else {
							return screen_fn(b, 2.f * s - 1.f, a);
						}
					};
					
					auto bf = fn(bg, fg, alpha);
					return mix_helper(bg, bf, alpha);
				};
			} else if constexpr (M == BlendMode::softLight) {
				return [mix_helper](float bg, float fg, float alpha) {
					constexpr auto fn = [](float b, float s) -> float {
						if (s <= 0.5f) {
							// B(Cb, Cs) = Cb - (1 - 2 x Cs) x Cb x (1 - Cb)
							return b - (1.f - 2.f * s) * b * (1 - b);
						} else {
							float d{};

							if (b <= 0.5f) {
								// D(Cb) = ((16 * Cb - 12) x Cb + 4) x Cb
								d = ((16 * b - 12) * b + 4) * b;
							} else {
								// D(Cb) = sqrt(Cb)
								d = std::sqrt(b);
							}

							// B(Cb, Cs) = Cb + (2 x Cs - 1) x (D(Cb) - Cb)
							return b + (2 * s - 1) * (d - b);
						}
					};
					
					auto bf = fn(bg, fg);
					return mix_helper(bg, bf, alpha);
				};
			} else if constexpr (M == BlendMode::difference) {
				return [mix_helper](float bg, float fg, float alpha) {
					// B(Cb, Cs) = | Cb - Cs |
					return mix_helper(bg, (bg > fg ? (bg - fg) : (fg - bg)), alpha);
				};
			} else if constexpr (M == BlendMode::exclusion) {
				return [mix_helper](float bg, float fg, float alpha) {
					constexpr auto fn = [](float b, float s) -> float {
						// B(Cb, Cs) = Cb + Cs - 2 x Cb x Cs
						return b + s - 2 * b * s;
					};
					
					auto bf = fn(bg, fg);
					return mix_helper(bg, bf, alpha);
				};
			}
		};

	public:
		template <BlendMode M>
		constexpr auto blend(RGBA color) const noexcept -> RGBA {
			auto ab = normalize(a());
			auto as = normalize(color.a());
			// αs x 1 + αb x (1 – αs)
			auto alpha = to_pixel(as + ab * (1 - as));
			auto lr = normalize(r());
			auto lg = normalize(g());
			auto lb = normalize(b());
			auto rr = normalize(color.r());
			auto rg = normalize(color.g());
			auto rb = normalize(color.b());

			auto nr = 0.f;
			auto ng = 0.f;
			auto nb = 0.f;

			if constexpr (M == BlendMode::normal) {
				auto fn = blend_helper<BlendMode::normal>();
				nr = fn(lr, rr, ab);
				ng = fn(lg, rg, ab);
				nb = fn(lb, rb, ab);
			} else if constexpr (M == BlendMode::multiply) {
				auto fn = blend_helper<BlendMode::multiply>();
				nr = fn(lr, rr, ab);
				ng = fn(lg, rg, ab);
				nb = fn(lb, rb, ab);
			} else if constexpr (M == BlendMode::screen) {
				auto fn = blend_helper<BlendMode::screen>();
				nr = fn(lr, rr, ab);
				ng = fn(lg, rg, ab);
				nb = fn(lb, rb, ab);
			} else if constexpr (M == BlendMode::overlay) {
				auto fn = blend_helper<BlendMode::overlay>();
				auto hard_light_fn = blend_helper<BlendMode::hardLight>();
				auto multiply_fn = blend_helper<BlendMode::multiply>();
				auto screen_fn = blend_helper<BlendMode::screen>();
				nr = fn(lr, rr, ab, hard_light_fn, multiply_fn, screen_fn);
				ng = fn(lg, rg, ab, hard_light_fn, multiply_fn, screen_fn);
				nb = fn(lb, rb, ab, hard_light_fn, multiply_fn, screen_fn);
			} else if constexpr (M == BlendMode::darken) {
				auto fn = blend_helper<BlendMode::darken>();
				nr = fn(lr, rr, ab);
				ng = fn(lg, rg, ab);
				nb = fn(lb, rb, ab);
			} else if constexpr (M == BlendMode::lighten) {
				auto fn = blend_helper<BlendMode::lighten>();
				nr = fn(lr, rr, ab);
				ng = fn(lg, rg, ab);
				nb = fn(lb, rb, ab);
			} else if constexpr (M == BlendMode::colorDodge) {
				auto fn = blend_helper<BlendMode::colorDodge>();
				nr = fn(lr, rr, ab);
				ng = fn(lg, rg, ab);
				nb = fn(lb, rb, ab);
			} else if constexpr (M == BlendMode::colorBurn) {
				auto fn = blend_helper<BlendMode::colorDodge>();
				nr = fn(lr, rr, ab);
				ng = fn(lg, rg, ab);
				nb = fn(lb, rb, ab);
			} else if constexpr (M == BlendMode::hardLight) {
				auto fn = blend_helper<BlendMode::hardLight>();
				auto multiply_fn = blend_helper<BlendMode::multiply>();
				auto screen_fn = blend_helper<BlendMode::screen>();
				nr = fn(lr, rr, ab, multiply_fn, screen_fn);
				ng = fn(lg, rg, ab, multiply_fn, screen_fn);
				nb = fn(lb, rb, ab, multiply_fn, screen_fn);
			} else if constexpr (M == BlendMode::softLight) {
				auto fn = blend_helper<BlendMode::softLight>();
				nr = fn(lr, rr, ab);
				ng = fn(lg, rg, ab);
				nb = fn(lb, rb, ab);
			} else if constexpr (M == BlendMode::difference) {
				auto fn = blend_helper<BlendMode::difference>();
				nr = fn(lr, rr, ab);
				ng = fn(lg, rg, ab);
				nb = fn(lb, rb, ab);
			} else if constexpr (M == BlendMode::exclusion) {
				auto fn = blend_helper<BlendMode::exclusion>();
				nr = fn(lr, rr, ab);
				ng = fn(lg, rg, ab);
				nb = fn(lb, rb, ab);
			}

			return RGBA(
				to_pixel(nr),
				to_pixel(ng),
				to_pixel(nb),
				alpha
			);
		}

		constexpr auto blend(RGBA color, BlendMode mode) const noexcept -> RGBA {
			switch (mode) {
			case BlendMode::normal:		return blend<BlendMode::normal>(color);
			case BlendMode::multiply:	return blend<BlendMode::multiply>(color);
			case BlendMode::screen:		return blend<BlendMode::screen>(color);
			case BlendMode::overlay:	return blend<BlendMode::overlay>(color);
			case BlendMode::darken:		return blend<BlendMode::darken>(color);
			case BlendMode::lighten:	return blend<BlendMode::lighten>(color);
			case BlendMode::colorDodge: return blend<BlendMode::colorDodge>(color);
			case BlendMode::colorBurn:	return blend<BlendMode::colorBurn>(color);
			case BlendMode::hardLight:	return blend<BlendMode::hardLight>(color);
			case BlendMode::softLight:	return blend<BlendMode::softLight>(color);
			case BlendMode::difference: return blend<BlendMode::difference>(color);
			case BlendMode::exclusion:	return blend<BlendMode::exclusion>(color);
			}
		}
	private:
		pixel_t m_data[4]{};
	};

	struct HSLA {
		using pixel_t = float;
		using pixels_t = float[4];

		// ensures pixel to be in range
		template <unsigned min, unsigned max>
		struct PixelWrapper {
			pixel_t& p;
			
			constexpr PixelWrapper& operator=(float val) noexcept {
				p = std::clamp(val, float(min), float(max));	
			}

			constexpr operator pixel_t() const noexcept { return p; }
		};

		constexpr HSLA() noexcept = default;
		constexpr HSLA(HSLA const&) noexcept = default;
		constexpr HSLA(HSLA &&) noexcept = default;
		constexpr HSLA& operator=(HSLA const&) noexcept = default;
		constexpr HSLA& operator=(HSLA &&) noexcept = default;
		constexpr ~HSLA() noexcept = default;

		constexpr HSLA(pixel_t h, pixel_t s, pixel_t l, pixel_t a = 100) noexcept
			: m_data({ .hsla = { .h = h, .s = s, .l = l, .a = a } })
		{}

		constexpr HSLA(RGBA color) noexcept {
			auto min = std::min({color.r(), color.g(), color.b()});
			auto max = std::max({color.r(), color.g(), color.b()});
			auto c = (max - min) / 255.f;

			auto tr = float(color.r()) / 255;
			auto tg = float(color.g()) / 255;
			auto tb = float(color.b()) / 255;
			auto ta = float(color.a()) / 255;

			float hue = 0;
			float s = 0;
			auto l = ((max + min) / 2.f) / 255.f;

			if (min == max) {
				if (max == color.r()) {
					auto seg = (tg - tb) / c;
					auto shift = (seg < 0 ? 360.f : 0.f) / 60.f;
					hue = seg + shift;
				} else if (max == color.g()) {
					auto seg = (tb - tr) / c;
					auto shift = 120.f / 60.f;
					hue = seg + shift;	
				} else {
					auto seg = (tr - tg) / c;
					auto shift = 240.f / 60.f;
					hue = seg + shift;	
				}
				s = c / (1 - std::abs(2 * l - 1)); 
			}

			hue = hue * 60.f + 360.f;
			auto q = static_cast<float>(static_cast<int>(hue / 360.f));
			hue -= q * 360.f;

			m_data.hsla.h = hue;
			m_data.hsla.s = s * 100.f;
			m_data.hsla.l = l * 100.f;
			m_data.hsla.a = ta * 100.f;
		}
		
		constexpr operator RGBA() const noexcept {
			auto ts = s() / 100.f;
			auto tl = l() / 100.f;
			auto ta = a() / 100.f;
			if (s() == 0) return RGBA(to_pixel(tl), to_pixel(ta));

			auto th = h() / 360.f;
			
			float const q = tl < 0.5 ? tl * (1 + ts) : tl + ts - tl * ts;
			float const p = 2 * tl - q;
				
			return RGBA(
				to_pixel(convert_hue(p, q, th + 1.f / 3)),
				to_pixel(convert_hue(p, q, th)),
				to_pixel(convert_hue(p, q, th - 1.f / 3)),
				to_pixel(ta)
			);
		}

		constexpr auto blend(HSLA color, BlendMode mode) const noexcept -> HSLA {
			auto lhs = RGBA(*this);
			auto rhs = RGBA(color);
			return HSLA(lhs.blend(rhs, mode));
		}

		constexpr auto h() const noexcept -> pixel_t { return m_data.hsla.h; }
		constexpr auto s() const noexcept -> pixel_t { return m_data.hsla.s; }
		constexpr auto l() const noexcept -> pixel_t { return m_data.hsla.l; }
		constexpr auto a() const noexcept -> pixel_t { return m_data.hsla.a; }

		constexpr auto h() noexcept -> PixelWrapper<0, 360> { return { m_data.hsla.h }; }
		constexpr auto s() noexcept -> PixelWrapper<0, 100> { return { m_data.hsla.s }; }
		constexpr auto l() noexcept -> PixelWrapper<0, 100> { return { m_data.hsla.l }; }
		constexpr auto a() noexcept -> PixelWrapper<0, 100> { return { m_data.hsla.a }; }
	private:
		
		static constexpr auto to_pixel(float a) noexcept -> RGBA::pixel_t {
			return static_cast<RGBA::pixel_t>(a * 255);
		}

		static constexpr auto convert_hue(float p, float q, float t) noexcept -> float {
			t = t - (t > 1) + (t < 0);
			if (t * 6 < 1) return p + (q - p) * 6 * t;
			if (t * 2 < 1) return q;
			if (t * 3 < 2) return p + (q - p) * (2.f / 3 - t) * 6;
			return p;
		}
	private:
		union {
			struct {
				pixel_t h{}; // hue: 0-360
				pixel_t s{}; // saturation: 0-100%
				pixel_t l{}; // lightness: 0-100%
				pixel_t a{}; // alpha: 0-100%
			} hsla;
			pixels_t color;
		} m_data{};
	};

	namespace detail {
		template <PixelFormat F>
		inline static constexpr auto parse_pixel_helper(RGBA color, std::uint8_t* out_ptr) noexcept {
			if constexpr (F == PixelFormat::rgba) {
				out_ptr[0] = color.r();
				out_ptr[1] = color.g();
				out_ptr[2] = color.b();
				out_ptr[3] = color.a();
			} else if constexpr (F == PixelFormat::abgr) {
				out_ptr[0] = color.a();
				out_ptr[1] = color.b();
				out_ptr[2] = color.g();
				out_ptr[3] = color.r();
			} else if constexpr (F == PixelFormat::rgb) {
				out_ptr[0] = color.r();
				out_ptr[1] = color.g();
				out_ptr[2] = color.b();
			} else if constexpr (F == PixelFormat::bgr) {
				out_ptr[0] = color.b();
				out_ptr[1] = color.g();
				out_ptr[2] = color.r();
			} else if constexpr (F == PixelFormat::ga) {
				out_ptr[0] = color.r();
				out_ptr[1] = color.a();
			} else if constexpr (F == PixelFormat::ag) {
				out_ptr[0] = color.a();
				out_ptr[1] = color.r();
			} else {
				out_ptr[0] = color.r();
			}
		}

		template <PixelFormat F>
		inline static constexpr auto parse_pixel_helper(std::uint8_t const* in_ptr) noexcept -> RGBA {
			if constexpr (F == PixelFormat::rgba) {
				return {
					in_ptr[0],
					in_ptr[1],
					in_ptr[2],
					in_ptr[3]
				};
			} else if constexpr (F == PixelFormat::abgr) {
				return {
					in_ptr[3],
					in_ptr[2],
					in_ptr[1],
					in_ptr[0]
				};
			} else if constexpr (F == PixelFormat::rgb) {
				return {
					in_ptr[0],
					in_ptr[1],
					in_ptr[2],
					0xff
				};
			} else if constexpr (F == PixelFormat::bgr) {
				return {
					in_ptr[2],
					in_ptr[1],
					in_ptr[0],
					0xff
				};
			} else if constexpr (F == PixelFormat::ga) {
				return {
					in_ptr[0],
					in_ptr[1],
				};
			} else if constexpr (F == PixelFormat::ag) {
				return {
					in_ptr[1],
					in_ptr[0],
				};
			} else {
				return { in_ptr[0], 0xff };
			}
		}
	} // namespace detail

	inline static constexpr auto get_pixel_format_channel(PixelFormat format) noexcept -> std::size_t {
		switch (format) {
			case PixelFormat::rgba: case PixelFormat::abgr: return 4u;
			case PixelFormat::rgb: case PixelFormat::bgr: return 3u;
			case PixelFormat::ga: case PixelFormat::ag: return 2u;
			case PixelFormat::g: return 1u;
		}
		assert(false && "unreachable");
	}

	struct PixelBuf {
	private:
	public:
		using value_type = RGBA;
		using base_type = Matrix<value_type>;
		using pointer = typename base_type::pointer;
		using const_pointer = typename base_type::const_pointer;
		using reference = typename base_type::reference;
		using const_reference = typename base_type::const_reference;
		using iterator = typename base_type::iterator;
		using const_iterator = typename base_type::const_iterator;
		using reverse_iterator = typename base_type::reverse_iterator;
		using const_reverse_iterator = typename base_type::const_reverse_iterator;
		using difference_type = typename base_type::difference_type;
		using size_type = typename base_type::size_type;

		PixelBuf(size_type r, size_type c)
			: m_data(r, c)
		{}
		PixelBuf(size_type r, size_type c, RGBA color)
			: m_data(r, c, color)
		{}

		PixelBuf(std::uint8_t const* in, size_type r, size_type c, PixelFormat format = PixelFormat::rgba)
			: PixelBuf(r, c)
		{
			assert(in != nullptr);

			switch (format) {
				case PixelFormat::rgba: from_helper<PixelFormat::rgba>(in, data(), size()); break;
				case PixelFormat::abgr: from_helper<PixelFormat::abgr>(in, data(), size()); break;
				case PixelFormat::rgb:  from_helper<PixelFormat::rgb >(in, data(), size()); break;
				case PixelFormat::bgr:  from_helper<PixelFormat::bgr >(in, data(), size()); break;
				case PixelFormat::ga:   from_helper<PixelFormat::ga  >(in, data(), size()); break;
				case PixelFormat::ag:   from_helper<PixelFormat::ag  >(in, data(), size()); break;
				case PixelFormat::g:    from_helper<PixelFormat::g   >(in, data(), size()); break;
			}
		}

		PixelBuf() noexcept = default;
		PixelBuf(PixelBuf const&) = default;
		PixelBuf(PixelBuf &&) noexcept = default;
		PixelBuf& operator=(PixelBuf const&) = default;
		PixelBuf& operator=(PixelBuf &&) noexcept = default;
		~PixelBuf() = default;

		constexpr auto size() const noexcept -> size_type { return m_data.size(); }
		constexpr auto rows() const noexcept -> size_type { return m_data.rows(); }
		constexpr auto cols() const noexcept -> size_type { return m_data.cols(); }
		constexpr auto data() noexcept -> pointer { return m_data.data(); }
		constexpr auto data() const noexcept -> const_pointer { return m_data.data(); }
		auto to_raw_buf() noexcept -> std::uint8_t* { return reinterpret_cast<std::uint8_t*>(data()); }
		auto to_raw_buf() const noexcept -> std::uint8_t const* { return reinterpret_cast<std::uint8_t const*>(data()); }
		constexpr auto raw_buf_size() const noexcept { return size() * sizeof(RGBA); }

		constexpr auto begin() noexcept -> iterator { return m_data.begin(); }
		constexpr auto end() noexcept -> iterator { return m_data.end(); }
		constexpr auto begin() const noexcept -> const_iterator { return m_data.begin(); }
		constexpr auto end() const noexcept -> const_iterator { return m_data.end(); }
		constexpr auto rbegin() noexcept -> reverse_iterator { return m_data.rbegin(); }
		constexpr auto rend() noexcept -> reverse_iterator { return m_data.rend(); }
		constexpr auto rbegin() const noexcept -> const_reverse_iterator { return m_data.rbegin(); }
		constexpr auto rend() const noexcept -> const_reverse_iterator { return m_data.rend(); }

		constexpr decltype(auto) operator[](size_type r) noexcept { return m_data[r]; }
		constexpr decltype(auto) operator[](size_type r) const noexcept { return m_data[r]; }
		constexpr auto operator()(size_type r, size_type c) noexcept -> reference { return m_data(r, c); }
		constexpr auto operator()(size_type r, size_type c) const noexcept -> const_reference { return m_data(r, c); }

		constexpr auto fill(RGBA color) noexcept -> void {
			std::fill(begin(), end(), color);
		}

		constexpr auto copy_to(std::uint8_t* out, PixelFormat format = PixelFormat::rgba) const noexcept {
			assert(out != nullptr);

			switch (format) {
				case PixelFormat::rgba: copy_to_helper<PixelFormat::rgba>(data(), out, size());return;
				case PixelFormat::abgr: copy_to_helper<PixelFormat::abgr>(data(), out, size());return;
				case PixelFormat::rgb:  copy_to_helper<PixelFormat::rgb >(data(), out, size());return;
				case PixelFormat::bgr:  copy_to_helper<PixelFormat::bgr >(data(), out, size());return;
				case PixelFormat::ga:   copy_to_helper<PixelFormat::ga  >(data(), out, size());return;
				case PixelFormat::ag:   copy_to_helper<PixelFormat::ag  >(data(), out, size());return;
				case PixelFormat::g:    copy_to_helper<PixelFormat::g   >(data(), out, size());return;
			}
			assert(false && "unreachable");
		}


	private:
		template <PixelFormat F>
		constexpr auto copy_to_helper(const_pointer in, std::uint8_t* out, size_type size) const noexcept -> void {
			constexpr auto channels = get_pixel_format_channel(F);
			for (auto i = size_type{}; i < size; ++i) {
				detail::parse_pixel_helper<F>(in[i], out + i * channels);
			}
		} 

		template <PixelFormat F>
		constexpr auto from_helper(std::uint8_t const* in, pointer out, size_type size) const noexcept -> void {
			constexpr auto channels = get_pixel_format_channel(F);
			for (auto i = size_type{}; i < size; ++i) {
				out[i] = detail::parse_pixel_helper<F>(in + i * channels);
			}
		} 

	private:
		base_type m_data;
	};



} // namespace amt

#include <format>
namespace std {
	template <>
	struct formatter<amt::RGBA> {
		constexpr auto parse(format_parse_context& ctx) {
			return ctx.begin();
		}
		
		auto format(amt::RGBA const& color, auto& ctx) const {
			return format_to(ctx.out(), "rgba({}, {}, {}, {})", color.r(), color.g(), color.b(), color.a());
		}
	};

	template <>
	struct formatter<amt::HSLA> {
		constexpr auto parse(format_parse_context& ctx) {
			return ctx.begin();
		}
		
		auto format(amt::HSLA const& color, auto& ctx) const {
			return format_to(ctx.out(), "hsla({:.1f}deg, {:.1f}%, {:.1f}%, {:.1f}%)", color.h(), color.s(), color.l(), color.a());
		}
	};

	template <>
	struct formatter<amt::PixelBuf> {
		bool hsla = false;

		constexpr auto parse(format_parse_context& ctx) {
			auto it = ctx.begin();
			while (it != ctx.end() && *it != '}') {
				if (*it == 'h') hsla = true;
				++it;
			}
			return it;
		}
		
		auto format(amt::PixelBuf const& buf, auto& ctx) const {
			std::string s = "[\n";
			for (auto r = std::size_t{}; r < buf.rows(); ++r) {
				for (auto c = std::size_t{}; c < buf.cols(); ++c) {
					auto color = buf(r, c);
					if (hsla) s += std::format("{}, ", amt::HSLA(color));
					else s += std::format("{}, ", color);
				}
				s += '\n';
			}	
			s += "]";
			return format_to(ctx.out(), "{}", s);
		}
	};
} // namespace std

#endif // AMT_PIXEL_HPP
