/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

#pragma once

#include "Math/Math.hpp"
#include <algorithm>

namespace OpenVulkano::Math
{
	class RGB565
	{
		template<typename T>
		static uint16_t Make5(T val)
		{
			if constexpr (!std::is_same_v<T, uint8_t> && !std::is_same_v<T, int8_t>)
			{
				val = std::clamp<int16_t>(val, 0, 255);
			}
			return val * 31.0f / 255.0f;
		}
		template<typename T>
		static uint16_t Make6(T val)
		{
			if constexpr (!std::is_same_v<T, uint8_t> && !std::is_same_v<T, int8_t>)
			{
				val = std::clamp<int16_t>(val, 0, 255);
			}
			return val * 63.0f / 255.0f;
		}
		static uint8_t Unmake5(uint16_t val) { return static_cast<uint8_t>(val * 255.0f / 31.0f); }
		static uint8_t Unmake6(uint16_t val) { return static_cast<uint8_t>(val * 255.0f / 63.0f); }
		static float Unmake5ToFloat(uint16_t val) { return std::clamp(val / 31.0f, 0.0f, 1.0f); }
		static float Unmake6ToFloat(uint16_t val) { return std::clamp(val / 63.0f, 0.0f, 1.0f); }
		static uint16_t Make5FromFloat(float val) { return (uint16_t) std::clamp(val * 31.0f, 0.0f, 31.f); }
		static uint16_t Make6FromFloat(float val) { return (uint16_t) std::clamp(val * 63.0f, 0.0f, 63.f); }

	public:
		union
		{
			uint16_t value;
			struct
			{
				uint16_t b : 5;
				uint16_t g : 6;
				uint16_t r : 5;
			};
		};

		RGB565() : value(0) {}

		template<typename T, std::enable_if_t<std::is_integral_v<T>, bool> = true>
		RGB565(const Math::Vector3<T>& color) : b(Make5(color.b)), g(Make6(color.g)), r(Make5(color.r))
		{
		}
		template<typename T, std::enable_if_t<std::is_integral_v<T>, bool> = true>
		RGB565(const Math::Vector4<T>& color) : b(Make5(color.b)), g(Make6(color.g)), r(Make5(color.r))
		{
		}
		template<typename T, std::enable_if_t<std::is_integral_v<T>, bool> = true>
		RGB565(const Math::Vector3_SIMD<T>& color) : b(Make5(color.b)), g(Make6(color.g)), r(Make5(color.r))
		{
		}
		template<typename T, std::enable_if_t<std::is_floating_point_v<T>, bool> = true>
		RGB565(const Math::Vector3<T>& color)
		    : b(Make5FromFloat(color.b)), g(Make6FromFloat(color.g)), r(Make5FromFloat(color.r))
		{
		}
		template<typename T, std::enable_if_t<std::is_floating_point_v<T>, bool> = true>
		RGB565(const Math::Vector4<T>& color)
		    : b(Make5FromFloat(color.b)), g(Make6FromFloat(color.g)), r(Make5FromFloat(color.r))
		{
		}
		template<typename T, std::enable_if_t<std::is_floating_point_v<T>, bool> = true>
		RGB565(const Math::Vector3_SIMD<T>& color)
		    : b(Make5FromFloat(color.b)), g(Make6FromFloat(color.g)), r(Make5FromFloat(color.r))
		{
		}

		uint8_t GetR() const { return Unmake5(r); }
		uint8_t GetG() const { return Unmake6(g); }
		uint8_t GetB() const { return Unmake5(b); }

		template<typename T, std::enable_if_t<std::is_integral_v<T>, bool> = true>
		Math::Vector3<T> Get3()
		{
			return { GetR(), GetG(), GetB() };
		}
		template<typename T, std::enable_if_t<std::is_integral_v<T>, bool> = true>
		Math::Vector4<T> Get4()
		{
			return { GetR(), GetG(), GetB(), 1 };
		}
		template<typename T, std::enable_if_t<std::is_integral_v<T>, bool> = true>
		Math::Vector3_SIMD<T> GetSIMD()
		{
			return { GetR(), GetG(), GetB() };
		}
		template<typename T, std::enable_if_t<std::is_floating_point_v<T>, bool> = true> 
		Math::Vector3<T> Get3Normalized()
		{
			return { GetR_Normalized(), GetG_Normalized(), GetB_Normalized() };
		}
		template<typename T, std::enable_if_t<std::is_floating_point_v<T>, bool> = true> 
		Math::Vector4<T> Get4Normalized()
		{
			return { GetR_Normalized(), GetG_Normalized(), GetB_Normalized(), 1 };
		}
		template<typename T, std::enable_if_t<std::is_floating_point_v<T>, bool> = true>
		Math::Vector3_SIMD<T> Get3NormalizedSIMD()
		{
			return { GetR_Normalized(), GetG_Normalized(), GetB_Normalized() };
		}

		void SetR(uint8_t red) { r = Make5(red); }
		void SetG(uint8_t green) { g = Make6(green); }
		void SetB(uint8_t blue) { b = Make5(blue); }

		template<typename T, std::enable_if_t<std::is_integral_v<T>, bool> = true>
		void Set(const Math::Vector3<T>& color)
		{
			r = Make5(color.r);
			g = Make6(color.g);
			b = Make5(color.b);
		}

		template<typename T, std::enable_if_t<std::is_integral_v<T>, bool> = true>
		void Set(const Math::Vector4<T>& color)
		{
			r = Make5(color.r);
			g = Make6(color.g);
			b = Make5(color.b);
		}

		template<typename T, std::enable_if_t<std::is_floating_point_v<T>, bool> = true>
		void SetNormalized(const Math::Vector3<T>& color)
		{
			r = Make5FromFloat(color.r);
			g = Make6FromFloat(color.g);
			b = Make5FromFloat(color.b);
		}

		template<typename T, std::enable_if_t<std::is_floating_point_v<T>, bool> = true>
		void SetNormalized(const Math::Vector4<T>& color)
		{
			r = Make5FromFloat(color.r);
			g = Make6FromFloat(color.g);
			b = Make5FromFloat(color.b);
		}

		float GetR_Normalized() { return Unmake5ToFloat(r); }
		float GetG_Normalized() { return Unmake6ToFloat(g); }
		float GetB_Normalized() { return Unmake5ToFloat(b); }

		void SetR_Normalized(float red) { r = Make5FromFloat(red); }
		void SetG_Normalized(float green) { g = Make6FromFloat(green); }
		void SetB_Normalized(float blue) { b = Make5FromFloat(blue); }

		template<typename T, std::enable_if_t<std::is_integral_v<T>, bool> = true>
		RGB565& operator=(const Vector3<T>& color)
		{
			Set(color);
			return *this;
		}

		template<typename T, std::enable_if_t<std::is_integral_v<T>, bool> = true>
		RGB565& operator=(const Vector3_SIMD<T>& color)
		{
			Set(color);
			return *this;
		}

		template<typename T, std::enable_if_t<std::is_integral_v<T>, bool> = true>
		RGB565& operator=(const Vector4_SIMD<T>& color)
		{
			Set(color);
			return *this;
		}

		template<typename T, std::enable_if_t<std::is_floating_point_v<T>, bool> = true>
		RGB565& operator=(const Vector3<T>& color)
		{
			SetNormalized(color);
			return *this;
		}

		template<typename T, std::enable_if_t<std::is_floating_point_v<T>, bool> = true>
		RGB565& operator=(const Vector4<T>& color)
		{
			SetNormalized(color);
			return *this;
		}

		template<typename T, std::enable_if_t<std::is_floating_point_v<T>, bool> = true>
		RGB565& operator=(const Vector3_SIMD<T>& color)
		{
			SetNormalized(color);
			return *this;
		}

		template<typename T, std::enable_if_t<std::is_integral_v<T>, bool> = true>
		RGB565& operator+=(const Vector3<T>& color)
		{
			Set(Get3<T>() + color);
			return *this;
		}

		template<typename T, std::enable_if_t<std::is_integral_v<T>, bool> = true>
		RGB565& operator+=(const Vector4<T>& color)
		{
			Set(Get4<T>() + color);
			return *this;
		}

		template<typename T, std::enable_if_t<std::is_integral_v<T>, bool> = true>
		RGB565& operator+=(const Vector3_SIMD<T>& color)
		{
			Set(GetSIMD<T>() + color);
			return *this;
		}

		template<typename T, std::enable_if_t<std::is_floating_point_v<T>, bool> = true>
		RGB565& operator+=(const Vector3<T>& color)
		{
			SetNormalized(Get3Normalized<T>() + color);
			return *this;
		}

		template<typename T, std::enable_if_t<std::is_floating_point_v<T>, bool> = true>
		RGB565& operator+=(const Vector4<T>& color)
		{
			SetNormalized(Get4Normalized<T>() + color);
			return *this;
		}

		template<typename T, std::enable_if_t<std::is_floating_point_v<T>, bool> = true>
		RGB565& operator+=(const Vector3_SIMD<T>& color)
		{
			SetNormalized(Get3NormalizedSIMD<T>() + color);
			return *this;
		}

		template<typename T, std::enable_if_t<std::is_integral_v<T>, bool> = true>
		RGB565& operator-=(const Vector3<T>& color)
		{
			Set(Get3<T>() - color);
			return *this;
		}

		template<typename T, std::enable_if_t<std::is_integral_v<T>, bool> = true>
		RGB565& operator-=(const Vector4<T>& color)
		{
			Set(Get4<T>() - color);
			return *this;
		}

		template<typename T, std::enable_if_t<std::is_integral_v<T>, bool> = true>
		RGB565& operator-=(const Vector3_SIMD<T>& color)
		{
			Set(GetSIMD<T>() - color);
			return *this;
		}

		template<typename T, std::enable_if_t<std::is_floating_point_v<T>, bool> = true>
		RGB565& operator-=(const Vector3<T>& color)
		{
			SetNormalized(Get3Normalized<T>() - color);
			return *this;
		}

		template<typename T, std::enable_if_t<std::is_floating_point_v<T>, bool> = true>
		RGB565& operator-=(const Vector4<T>& color)
		{
			SetNormalized(Get4Normalized<T>() - color);
			return *this;
		}

		template<typename T, std::enable_if_t<std::is_floating_point_v<T>, bool> = true>
		RGB565& operator-=(const Vector3_SIMD<T>& color)
		{
			SetNormalized(Get3NormalizedSIMD<T>() - color);
			return *this;
		}

		bool operator==(const RGB565& other) const { return value == other.value; }
		bool operator!=(const RGB565& other) const { return value != other.value; }

		template<typename T, std::enable_if_t<std::is_integral_v<T>, bool> = true>
		operator Math::Vector4<T>() { return Get4<T>(); }
		template<typename T, std::enable_if_t<std::is_integral_v<T>, bool> = true>
		operator Math::Vector3<T>() { return Get3<T>(); }
		template<typename T, std::enable_if_t<std::is_integral_v<T>, bool> = true>
		operator Math::Vector3_SIMD<T>() { return GetSIMD<T>(); }
		template<typename T, std::enable_if_t<std::is_floating_point_v<T>, bool> = true>
		operator Math::Vector4<T>() { return Get4Normalized<T>(); }
		template<typename T, std::enable_if_t<std::is_floating_point_v<T>, bool> = true>
		operator Math::Vector3<T>() { return Get3Normalized<T>(); }

	};

	static_assert(sizeof(RGB565) == 2);
}
