#pragma once

#include <psemek/util/hash.hpp>
#include <psemek/util/md5_inl.hpp>

#include <cstdint>
#include <string_view>
#include <iostream>

namespace psemek::util
{

	struct uuid
	{
		std::uint64_t values[2];

		constexpr std::uint64_t & operator[](std::size_t i)
		{
			return values[i];
		}

		constexpr std::uint64_t const & operator[](std::size_t i) const
		{
			return values[i];
		}

		constexpr friend bool operator == (uuid const &, uuid const &) = default;
		constexpr friend std::strong_ordering operator <=> (uuid const &, uuid const &) = default;
	};

	static_assert(sizeof(uuid) == 16);
	static_assert(alignof(uuid) <= 8);

	constexpr bool is_rfc_4122(uuid const & uuid)
	{
		return ((uuid[0] & 0x00f0000000000000ull) == 0x0040000000000000ull)
			&& ((uuid[1] & 0x00000000000000c0ull) == 0x0000000000000080ull);
	}

	constexpr uuid make_rfc_4122(uuid uuid)
	{
		uuid[0] = (uuid[0] & 0xff0fffffffffffffull) | 0x0040000000000000ull;
		uuid[1] = (uuid[1] & 0xffffffffffffff3full) | 0x0000000000000080ull;
		return uuid;
	}

	// TODO: use SHA-1 or something like that?
	constexpr uuid make_uuid(std::string_view str)
	{
		auto md5_result = md5_inl::hash(str);

		uuid result;
		result.values[0] = static_cast<std::uint64_t>(md5_result[0]) | (static_cast<std::uint64_t>(md5_result[1]) << 32);
		result.values[1] = static_cast<std::uint64_t>(md5_result[2]) | (static_cast<std::uint64_t>(md5_result[3]) << 32);

		make_rfc_4122(result);

		return result;
	}

	std::ostream & operator << (std::ostream & os, uuid const & uuid);

}

namespace std
{

	template <>
	struct hash<::psemek::util::uuid>
	{
		std::uint64_t operator()(::psemek::util::uuid const & uuid) const noexcept
		{
			std::uint64_t result = uuid[0];
			::psemek::util::hash_combine(result, uuid[1]);
			return result;
		}
	};

}
