/*
 * Copyright 2015 - 2025 gary@drinkingtea.net
 *
 * 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 "bit.hpp"
#include "def.hpp"
#include "ignore.hpp"
#include "istring.hpp"
#include "buffer.hpp"
#include "hash.hpp"
#include "random.hpp"
#include "ranges.hpp"
#include "stringview.hpp"
#include "strops.hpp"

OX_CLANG_NOWARN_BEGIN(-Wunsafe-buffer-usage)

namespace ox {

using UUIDStr = ox::IString<36>;

namespace detail {

[[nodiscard]]
constexpr auto isHexChar(char c) noexcept {
	return (c >= '0' && c <= '9')
	    || (c >= 'a' && c <= 'f')
	    || (c >= 'A' && c <= 'F');
}

constexpr ox::Result<uint8_t> fromHex(ox::StringViewCR v) noexcept {
	constexpr auto valMap = [] {
		ox::Array<uint8_t, 128> out;
		out['A'] = out['a'] = 10;
		out['B'] = out['b'] = 11;
		out['C'] = out['c'] = 12;
		out['D'] = out['d'] = 13;
		out['E'] = out['e'] = 14;
		out['F'] = out['f'] = 15;
		out['0'] = 0;
		out['1'] = 1;
		out['2'] = 2;
		out['3'] = 3;
		out['4'] = 4;
		out['5'] = 5;
		out['6'] = 6;
		out['7'] = 7;
		out['8'] = 8;
		out['9'] = 9;
		return out;
	}();
	if (!detail::isHexChar(v[0]) || !detail::isHexChar(v[1])) {
		return ox::Error(1, "Invalid UUID");
	}
	if (v.size() != 2) {
		return ox::Error(2);
	}
	uint8_t out = 0;
	out += static_cast<uint8_t>(valMap[static_cast<unsigned char>(v[0])] * 16);
	out += valMap[static_cast<unsigned char>(v[1])];
	return out;
}

constexpr ox::IString<2> toHex(uint8_t v) noexcept {
	constexpr ox::Array<char, 16> valMap {
		'0',
		'1',
		'2',
		'3',
		'4',
		'5',
		'6',
		'7',
		'8',
		'9',
		'a',
		'b',
		'c',
		'd',
		'e',
		'f',
	};
	ox::IString<2> out;
	std::ignore = out.resize(2);
	out[0] = valMap[static_cast<unsigned>((v & 0xf0) / 16)];
	out[1] = valMap[static_cast<unsigned>(v & 0x0f)];
	out[2] = 0;
	return out.data();
}

}

class UUID {
	template<typename T>
	friend constexpr Error model(T *io, ox::CommonPtrWith<UUID> auto *obj) noexcept;
	protected:
		static bool s_seeded;
		static Random s_rand;
		ox::Array<uint8_t, 16> m_value{};

	public:

		static constexpr auto TypeName = "net.drinkingtea.ox.UUID";
		static constexpr auto TypeVersion = 1;

		static void seedGenerator(const RandomSeed &seed) noexcept;

		static ox::Result<UUID> generate() noexcept;

		[[nodiscard]]
		constexpr ox::Array<uint8_t, 16> const&value() const noexcept {
			return m_value;
		}

		[[nodiscard]]
		constexpr bool isNull() const noexcept {
			if (std::is_constant_evaluated()) {
				if (ox::all_of(m_value.begin(), m_value.end(), [](auto v) { return v == 0; })) {
					return true;
				}
				return false;
			} else {
				constexpr uint64_t zero = 0;
				return memcmp(&zero, m_value.data() + 0, 8) == 0
				    && memcmp(&zero, m_value.data() + 8, 8) == 0;
			}
		}

		static constexpr ox::Result<ox::UUID> fromString(ox::StringViewCR s) noexcept {
			if (s.size() < 36) {
				return ox::Error(1, "Insufficient data to contain a complete UUID");
			}
			UUID out;
			auto valueI = 0u;
			for (size_t i = 0; i < s.size();) {
				if (s[i] == '-') {
					++i;
					continue;
				}
				const auto seg = substr(s, i, i + 2);
				if (seg.size() != 2) {
					return ox::Error(1, "Invalid UUID");
				}
				OX_REQUIRE(val, detail::fromHex(seg));
				out.m_value[valueI] = val;
				i += 2;
				++valueI;
			}
			return out;
		}

		constexpr bool operator==(UUID const&o) const noexcept {
			return m_value == o.m_value;
		}

		constexpr bool operator!=(UUID const&o) const noexcept {
			return !operator==(o);
		}

		[[nodiscard]]
		constexpr ox::Error toString(Writer_c auto &writer) const noexcept {
			auto valueI = 0u;
			constexpr auto printChars = [](
					Writer_c auto &writer,
					const Array<uint8_t, 16> &value,
					std::size_t cnt,
					unsigned &valueI) {
				for (auto i = 0u; i < cnt; ++i) {
					const auto v = value[valueI];
					const auto h = detail::toHex(v);
					std::ignore = writer.write(h.c_str(), h.size());
					++valueI;
				}
			};
			printChars(writer, m_value, 4, valueI);
			OX_RETURN_ERROR(writer.put('-'));
			printChars(writer, m_value, 2, valueI);
			OX_RETURN_ERROR(writer.put('-'));
			printChars(writer, m_value, 2, valueI);
			OX_RETURN_ERROR(writer.put('-'));
			printChars(writer, m_value, 2, valueI);
			OX_RETURN_ERROR(writer.put('-'));
			printChars(writer, m_value, 6, valueI);
			return {};
		}

		[[nodiscard]]
		constexpr UUIDStr toString() const noexcept {
			UUIDStr out;
			std::ignore = out.resize(UUIDStr::cap());
			ox::CharBuffWriter bw{{out.data(), UUIDStr::cap()}};
			std::ignore = toString(bw);
			out[UUIDStr::cap()] = 0;
			return out;
		}
};


template<>
struct hash<ox::UUID> {
	[[nodiscard]]
	constexpr size_t operator()(ox::UUID v) const noexcept {
		size_t out{};
		if (std::is_constant_evaluated()) {
			for (auto i = 0u; i < sizeof(out); ++i) {
				out |= static_cast<size_t>(v.value()[i]) << (i * 8);
			}
		} else {
			memcpy(&out, &v, sizeof(out));
		}
		return out;
	}
};


template<typename T>
constexpr Error model(T *io, ox::CommonPtrWith<UUID> auto *obj) noexcept {
	OX_RETURN_ERROR(io->template setTypeInfo<UUID>());
	OX_RETURN_ERROR(io->field("value", &obj->m_value));
	return {};
}

}

OX_CLANG_NOWARN_END
