/*
 * Copyright 2015 - 2022 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 "array.hpp"
#include "bstring.hpp"
#include "buffer.hpp"
#include "random.hpp"
#include "ranges.hpp"
#include "stringview.hpp"

namespace ox {

using UUIDStr = ox::BString<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::CRStringView 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 OxError(1, "Invalid UUID");
	}
	if (v.len() != 2) {
		return OxError(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::BString<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::Array<char, 3> out;
	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 void seedGenerator(const RandomSeed &seed) noexcept;

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

		[[nodiscard]]
		constexpr auto value() const noexcept {
			return m_value;
		}

		[[nodiscard]]
		constexpr auto 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 ox::memcmp(&zero, m_value.data() + 0, 8) == 0
				    && ox::memcmp(&zero, m_value.data() + 8, 8) == 0;
			}
		}

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

		[[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);
					oxIgnoreError(writer.write(h.c_str(), h.len()));
					++valueI;
				}
			};
			printChars(writer, m_value, 4, valueI);
			oxReturnError(writer.put('-'));
			printChars(writer, m_value, 2, valueI);
			oxReturnError(writer.put('-'));
			printChars(writer, m_value, 2, valueI);
			oxReturnError(writer.put('-'));
			printChars(writer, m_value, 2, valueI);
			oxReturnError(writer.put('-'));
			printChars(writer, m_value, 6, valueI);
			return {};
		}

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

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

}
