#ifndef MINORI_CORE_ENDIAN_H_
#define MINORI_CORE_ENDIAN_H_

/* definition of endian-related stuff. primarily used for*/

#include <cstdint>
#include <type_traits>

class Endian {
private:
	static constexpr uint32_t uint32_ = 0x01020304;
	static constexpr uint8_t magic_ = static_cast<const uint8_t&>(uint32_);

	/* check for compiler builtins for byteswapping */
#ifdef __has_builtin
#	if __has_builtin(__builtin_bswap16)
#		define COMPILER_BUILTIN_BSWAP16(x) __builtin_bswap16(x)
#	endif
#	if __has_builtin(__builtin_bswap32)
#		define COMPILER_BUILTIN_BSWAP32(x) __builtin_bswap32(x)
#	endif
#	if __has_builtin(__builtin_bswap64)
#		define COMPILER_BUILTIN_BSWAP64(x) __builtin_bswap64(x)
#	endif
#endif

	static constexpr uint16_t byteswap_16(uint16_t x) {
#ifdef COMPILER_BUILTIN_BSWAP16
		return COMPILER_BUILTIN_BSWAP16(x);
#else
		return (
			  ((x & UINT16_C(0x00FF)) << 8)
			| ((x & UINT16_C(0xFF00)) >> 8)
		);
#endif
	}

	static constexpr uint32_t byteswap_32(uint32_t x) {
#ifdef COMPILER_BUILTIN_BSWAP32
		return COMPILER_BUILTIN_BSWAP32(x);
#else
		return (
			  ((x & UINT32_C(0x000000FF)) << 24)
			| ((x & UINT32_C(0x0000FF00)) << 8)
			| ((x & UINT32_C(0x00FF0000)) >> 8)
			| ((x & UINT32_C(0xFF000000)) >> 24)
		);
#endif
	}

	static constexpr uint64_t byteswap_64(uint64_t x) {
#ifdef COMPILER_BUILTIN_BSWAP64
		return COMPILER_BUILTIN_BSWAP64(x);
#else
		return (
			  ((x & UINT64_C(0x00000000000000FF)) << 56)
			| ((x & UINT64_C(0x000000000000FF00)) << 40)
			| ((x & UINT64_C(0x0000000000FF0000)) << 24)
			| ((x & UINT64_C(0x00000000FF000000)) << 8)
			| ((x & UINT64_C(0x000000FF00000000)) >> 8)
			| ((x & UINT64_C(0x0000FF0000000000)) >> 24)
			| ((x & UINT64_C(0x00FF000000000000)) >> 40)
			| ((x & UINT64_C(0xFF00000000000000)) >> 56)
		);
#endif
	}

#ifdef COMPILER_BUILTIN_BSWAP16
#	undef COMPILER_BUILTIN_BSWAP16
#endif
#ifdef COMPILER_BUILTIN_BSWAP32
#	undef COMPILER_BUILTIN_BSWAP32
#endif
#ifdef COMPILER_BUILTIN_BSWAP64
#	undef COMPILER_BUILTIN_BSWAP64
#endif
public:
	static constexpr bool little = magic_ == 0x04;
	static constexpr bool big = magic_ == 0x01;
	static_assert(little || big, "unsupported endianness");

	template<typename T>
	static constexpr T byteswap(T x) {
		static_assert(std::is_integral<T>::value);
		static_assert(std::is_unsigned<T>::value);

		if constexpr (std::is_same<T, uint8_t>::value) {
			return x;
		} else if constexpr (std::is_same<T, uint16_t>::value) {
			return byteswap_16(x);
		} else if constexpr (std::is_same<T, uint32_t>::value) {
			return byteswap_32(x);
		} else if constexpr (std::is_same<T, uint64_t>::value) {
			return byteswap_64(x);
		} else {
			static_assert(!sizeof(T), "invalid integer type given to byteswap");
		}
	}

	template<typename T>
	static constexpr T byteswap_little_to_host(T x) {
		if constexpr (little) {
			return x;
		} else if constexpr (big) {
			return byteswap(x);
		}
	}

	template<typename T>
	static constexpr T byteswap_big_to_host(T x) {
		if constexpr (big) {
			return x;
		} else if constexpr (little) {
			return byteswap(x);
		}
	}
private:
	Endian() = delete;
};

#endif /* MINORI_CORE_ENDIAN_H_ */
