#pragma once

#include "common.h"

inline uint32_t hi32(const uint64_t x)
{
	return (x >> 32);
}

inline uint32_t lo32(const uint64_t x)
{
	return (x & 0xFFFFFFFFu);
}

union uint128_t
{
	inline uint128_t()
		: hi(0)
		, lo(0)
	{
	}

	inline uint128_t(uint64_t x)
		: hi(0)
		, lo(x)
	{
	}

	struct
	{
		uint64_t hi;
		uint64_t lo;
	};
};

uint128_t mul64(const uint64_t a, const uint64_t b);

inline uint32_t float_as_uint(const float x)
{
	union { float f; uint32_t u; } f2u;
	f2u.f = x;
	return f2u.u;
}

inline float uint_as_float(const uint32_t x)
{
	union { uint32_t u; float f; } u2f;
	u2f.u = x;
	return u2f.f;
}

inline uint64_t double_as_uint(const double x)
{
	union { double f; uint64_t u; } f2u;
	f2u.f = x;
	return f2u.u;
}

inline double uint_as_double(const uint64_t x)
{
	union { uint64_t u; double f; } u2f;
	u2f.u = x;
	return u2f.f;
}

template<typename T>
inline typename std::enable_if<std::is_arithmetic<T>::value && std::is_signed<T>::value, T>::type isign(T x)
{
	return (x >> (sizeof(x) * CHAR_BIT - 1));
}

uint32_t bitmask32(const int begin, const int end);

uint32_t rotl(const uint32_t x, int n);
uint32_t rotr(const uint32_t x, int n);

uint32_t rlwinm(const uint32_t rs, const uint32_t sh, const int mb, const int me);
uint32_t rlwimi(const uint32_t ra, const uint32_t rs, const uint32_t sh, const int mb, const int me);
uint32_t clrlslwi(const uint32_t rs, const int b, const int n);
uint32_t slw(const uint32_t ra, const uint32_t rs, const int rb);
int32_t srawi(const int32_t rs, const int b);

template<typename T>
inline typename std::enable_if<std::is_arithmetic<T>::value, T>::type align_up(T address, size_t alignment)
{
	return (address + (alignment - 1)) & ~(T(alignment) - 1);
}

template<typename T>
inline typename std::enable_if<std::is_arithmetic<T>::value, T>::type align_down(T address, size_t alignment)
{
	return address & ~(T(alignment) - 1);
}

inline const void* advance_pointer(const void* ptr, intptr_t offset)
{
	return static_cast<const char*>(ptr) + offset;
}

template<typename T> inline typename std::enable_if<std::is_enum<T>::value, T>::type operator ~(T x) { return static_cast<T>(~static_cast<typename std::underlying_type<T>::type>(x)); }
template<typename T> inline typename std::enable_if<std::is_enum<T>::value, T>::type operator &(T x, T y) { return static_cast<T>(static_cast<typename std::underlying_type<T>::type>(x) & static_cast<typename std::underlying_type<T>::type>(y)); }
template<typename T> inline typename std::enable_if<std::is_enum<T>::value, T>::type operator |(T x, T y) { return static_cast<T>(static_cast<typename std::underlying_type<T>::type>(x) | static_cast<typename std::underlying_type<T>::type>(y)); }
template<typename T> inline typename std::enable_if<std::is_enum<T>::value, T>::type operator ^(T x, T y) { return static_cast<T>(static_cast<typename std::underlying_type<T>::type>(x) ^ static_cast<typename std::underlying_type<T>::type>(y)); }

namespace detail
{
	template<typename T, size_t Size>
	struct swap_bytes
	{
		inline T operator()(const T x)
		{
			throw std::out_of_range("Unsupported type size");
		}
	};

	template<typename T>
	struct swap_bytes<T, 1>
	{
		inline T operator()(const T x)
		{
			return x;
		}
	};

	template<typename T>
	struct swap_bytes<T, 2>
	{
		inline T operator()(const T x)
		{
			return ((x >> 8) & 0xFF) | ((x & 0xFF) << 8);
		}
	};

	template<typename T>
	struct swap_bytes<T, 4>
	{
		inline T operator()(const T x)
		{
			return ((x & 0xFF000000u) >> 24) | ((x & 0x00FF0000u) >> 8) | ((x & 0x0000FF00u) << 8) | ((x & 0x000000FFu) << 24);
		}
	};

	template<>
	struct swap_bytes<float, 4>
	{
		inline float operator()(const float x)
		{
			return uint_as_float(swap_bytes<uint32_t, sizeof(uint32_t)>()(float_as_uint(x)));
		}
	};

	template<typename T>
	struct swap_bytes<T, 8>
	{
		inline T operator()(const T x)
		{
			return ((x & 0xFF00000000000000ull) >> 56) | ((x & 0x00FF000000000000ull) >> 40) | ((x & 0x0000FF0000000000ull) >> 24) | ((x & 0x000000FF00000000ull) >> 8) | ((x & 0x00000000FF000000ull) << 8) | ((x & 0x0000000000FF0000ull) << 24) | ((x & 0x000000000000FF00ull) << 40) | ((x & 0x00000000000000FFull) << 56);
		}
	};

	template<>
	struct swap_bytes<double, 8>
	{
		inline double operator()(const double x)
		{
			return uint_as_double(swap_bytes<uint64_t, sizeof(uint64_t)>()(double_as_uint(x)));
		}
	};

	template<Endianness from, Endianness to, typename T>
	struct do_byte_swap
	{
		inline T operator()(const T x)
		{
			return swap_bytes<T, sizeof(T)>()(x);
		}
	};

	template<typename T> struct do_byte_swap<Endianness::kLITTLE, Endianness::kLITTLE, T>
	{
		inline T operator()(const T x)
		{
			return x;
		}
	};

	template<typename T> struct do_byte_swap<Endianness::kBIG, Endianness::kBIG, T>
	{
		inline T operator()(const T x)
		{
			return x;
		}
	};
}

template<typename T>
inline T swap_bytes(const T x)
{
	static_assert(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8, "Unsupported type size");
	static_assert(std::is_arithmetic<T>::value, "Non arithmetic type");
	return detail::swap_bytes<T, sizeof(T)>()(x);
}

template<Endianness from, Endianness to, typename T>
inline T byte_swap(const T x)
{
	static_assert(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8, "Unsupported type size");
	static_assert(std::is_arithmetic<T>::value, "Non arithmetic type");
	return detail::do_byte_swap<from, to, T>()(x);
}

template<typename T>
inline T little_to_host(const T x)
{
	return byte_swap<Endianness::kLITTLE, Endianness::kHOST>(x);
}

template<typename T> inline T big_to_host(const T x)
{
	return byte_swap<Endianness::kBIG, Endianness::kHOST>(x);
}

template<typename T> inline T host_to_little(const T x)
{
	return byte_swap<Endianness::kHOST, Endianness::kLITTLE>(x);
}

template<typename T> inline T host_to_big(const T x)
{
	return byte_swap<Endianness::kHOST, Endianness::kBIG>(x);
}
