/*
 * 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 <cinttypes>
#include <type_traits>

namespace OpenVulkano
{
	class int24 final
	{
		uint8_t m_internal[3];
	public:
		constexpr int24() : m_internal{ 0, 0, 0 }
		{}

		constexpr int24(const int val)
				: m_internal{(uint8_t)(val & 0xff), (uint8_t)((val >> 8) & 0xff), (uint8_t)((val >> 16) & 0xff)}
		{}

		constexpr int24(const int24& val) : m_internal{ val.m_internal[0], val.m_internal[1], val.m_internal[2] }
		{}

		operator int() const
		{
			if (m_internal[2] & 0x80)
			{ // Negative numbers
				return (0xff << 24) | (m_internal[2] << 16) | (m_internal[1] << 8) | (m_internal[0] << 0);
			}
			else
			{
				return (m_internal[2] << 16) | (m_internal[1] << 8) | (m_internal[0] << 0);
			}
		}

		operator float() const
		{
			return (float)this->operator int();
		}

		int24& operator =(const int24& input)
		{
			m_internal[0]   = input.m_internal[0];
			m_internal[1]   = input.m_internal[1];
			m_internal[2]   = input.m_internal[2];
			return *this;
		}

		int24& operator =(const int input)
		{
			m_internal[0]   = ((unsigned char*)&input)[0];
			m_internal[1]   = ((unsigned char*)&input)[1];
			m_internal[2]   = ((unsigned char*)&input)[2];
			return *this;
		}

#define INT24_OPERATORS(op) \
		template<typename T, std::enable_if_t<std::is_convertible_v<T, int>, bool> = true> \
		int24 operator op(const T& val) const { return int24( (int)*this op static_cast<int>(val) ); } \
		template<typename T, std::enable_if_t<std::is_convertible_v<T, int>, bool> = true> \
		int24& operator op##=(const T& val ) { *this = *this op static_cast<int>(val); return *this; }

		INT24_OPERATORS(+)
		INT24_OPERATORS(-)
		INT24_OPERATORS(*)
		INT24_OPERATORS(/)
#undef INT24_OPERATORS

		int24 operator >>(const int val) const
		{
			return int24( (int)*this >> val );
		}

		int24 operator <<(const int val) const
		{
			return int24( (int)*this << val );
		}

		int24& operator >>=(const int val )
		{
			*this = *this >> val;
			return *this;
		}

		int24& operator <<=(const int val )
		{
			*this = *this << val;
			return *this;
		}

		operator bool() const
		{
			return (int)*this != 0;
		}

		bool operator !() const
		{
			return !((int)*this);
		}

		int24 operator -()
		{
			return int24( -(int)*this );
		}

		template<typename T, std::enable_if_t<std::is_convertible_v<T, int>, bool> = true>
		[[nodiscard]] bool operator==(const T& val) const
		{
			return static_cast<int>(*this) == static_cast<int>(val);
		}

		template<typename T, std::enable_if_t<std::is_convertible_v<T, int>, bool> = true>
		[[nodiscard]] constexpr auto operator<=>(const T& other) const
		{
			return operator int() <=> static_cast<int>(other);
		}

		template<typename Key> friend
		struct std::hash;
	};
}

namespace std
{
	template<>
	struct hash<OpenVulkano::int24>
	{
		std::size_t operator()(const OpenVulkano::int24& value) const
		{
			return hash<int>()(static_cast<int>(value));
		}
	};

	template <>
	class numeric_limits<OpenVulkano::int24>
	{
	public:
		static const bool is_specialized = true;
		static constexpr OpenVulkano::int24 min() { return OpenVulkano::int24(0x800000); }
		static constexpr OpenVulkano::int24 max() { return OpenVulkano::int24(0x7FFFFF); }
		static const int radix = 2;
		static const int digits = 23;
		static const int digits10 = 6;
		static const bool is_signed = true;
		static const bool is_integer = true;
		static const bool is_exact = true;
		static const bool traps = false;
		static const bool is_modulo = false;
		static const bool is_bounded = true;

		static constexpr OpenVulkano::int24 epsilon() { return OpenVulkano::int24(1); }
		static constexpr OpenVulkano::int24 round_error() { return OpenVulkano::int24(0); }
		static const int min_exponent10 = 0;
		static const int max_exponent10 = 0;
		static const int min_exponent = 0;
		static const int max_exponent = 0;

		static const bool has_infinity = false;
		static const bool has_quiet_NaN = false;
		static const bool has_signaling_NaN = false;
		static const bool is_iec559 = false;
		static const bool has_denorm = false;
		static const bool tinyness_before = false;
		static const float_round_style round_style = round_toward_zero;

		// Not meaningful for int24...
		static constexpr OpenVulkano::int24 denorm_min() { return OpenVulkano::int24(0); }
		static constexpr OpenVulkano::int24 infinity() { return OpenVulkano::int24(0); }
		static constexpr OpenVulkano::int24 quiet_NaN() { return OpenVulkano::int24(0); }
		static constexpr OpenVulkano::int24 signaling_NaN() { return OpenVulkano::int24(0); }
	};
}