#ifndef MINORI_CORE_ENDIAN_H_
#define MINORI_CORE_ENDIAN_H_

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

#include "core/bit_cast.h"
#include <cstdint>
#include <type_traits>

class Endian {
private:
	/* 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:
#if defined(BYTE_ORDER_BIG)
	static constexpr bool big = true;
	static constexpr bool little = false;
#elif defined(BYTE_ORDER_LITTLE)
	static constexpr bool big = false;
	static constexpr bool little = true;
#else
# error "unsupported endianness"
#endif

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

		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");
		}
	}

	/* this can't be constexpr */
	template<typename T>
	static T signed_byteswap(T x)
	{
		static_assert(std::is_integral<T>::value);
		static_assert(std::is_signed<T>::value, "use regular byteswap");

		using uT = typename std::make_unsigned<T>::type;
		return minori::BitCast<T, uT>(byteswap<uT>(minori::BitCast<uT, T>(x)));
	}

	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);
		}
	}

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

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

private:
	Endian() = delete;
};

#endif /* MINORI_CORE_ENDIAN_H_ */
