/*
 * 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 "def.hpp"
#include "hash.hpp"
#include "ignore.hpp"
#include "stringview.hpp"
#include "strops.hpp"
#include "vector.hpp"

namespace ox {

template<typename K, typename T, size_t SmallSz = 0>
class SmallMap {

	public:
		using key_t = K;
		using value_t = T;
		struct Pair {
			K key = {};
			T value{};
		};

	protected:
		using PairVector = Vector<Pair, SmallSz>;
		PairVector m_pairs;

	public:
		constexpr SmallMap() = default;

		constexpr SmallMap(SmallMap const&other);

		constexpr SmallMap(SmallMap &&other) noexcept;

		constexpr bool operator==(SmallMap const&other) const;

		constexpr SmallMap &operator=(SmallMap const&other);

		constexpr SmallMap &operator=(SmallMap &&other) noexcept;

		constexpr T &operator[](MaybeView_t<K> const&key);

		constexpr Result<T*> at(MaybeView_t<K> const&key) noexcept;

		constexpr Result<const T*> at(MaybeView_t<K> const&key) const noexcept;

		constexpr void erase(MaybeView_t<K> const&key);

		[[nodiscard]]
		constexpr bool contains(MaybeView_t<K> const&key) const noexcept;

		[[nodiscard]]
		constexpr std::size_t size() const noexcept;

		[[nodiscard]]
		constexpr Vector<K> keys() const noexcept;

		[[nodiscard]]
		constexpr Vector<T> values() const noexcept;

		[[nodiscard]]
		constexpr K const&key(size_t i) const noexcept;

		[[nodiscard]]
		constexpr T const&value(size_t i) const noexcept;

		[[nodiscard]]
		constexpr T &value(size_t i) noexcept;

		[[nodiscard]]
		constexpr Pair const&get(size_t i) const noexcept;

		[[nodiscard]]
		constexpr Pair &get(size_t i) noexcept;

		[[nodiscard]]
		constexpr ox::SpanView<Pair> pairs() const noexcept {
			return m_pairs;
		}

		[[nodiscard]]
		constexpr ox::Span<Pair> pairs() noexcept {
			return m_pairs;
		}

		constexpr void clear();

	private:
		template<typename KK>
		constexpr Pair const*access(PairVector const&pairs, KK const&key, bool &isNew) const;

		template<typename KK>
		constexpr Pair *access(PairVector &pairs, KK const&key, bool &isNew);

		template<typename KK>
		constexpr Pair *accessNoCreate(PairVector &pairs, KK const&key);

};

template<typename K, typename T, size_t SmallSz>
constexpr SmallMap<K, T, SmallSz>::SmallMap(SmallMap<K, T, SmallSz> const&other) {
	m_pairs = other.m_pairs;
}

template<typename K, typename T, size_t SmallSz>
constexpr SmallMap<K, T, SmallSz>::SmallMap(SmallMap<K, T, SmallSz> &&other) noexcept {
	m_pairs = std::move(other.m_pairs);
}

template<typename K, typename T, size_t SmallSz>
constexpr bool SmallMap<K, T, SmallSz>::operator==(SmallMap const&other) const {
	return m_pairs == other.m_pairs;
}

template<typename K, typename T, size_t SmallSz>
constexpr SmallMap<K, T, SmallSz> &SmallMap<K, T, SmallSz>::operator=(SmallMap<K, T, SmallSz> const&other) {
	if (this != &other) {
		clear();
		m_pairs = other.m_pairs;
	}
	return *this;
}

template<typename K, typename T, size_t SmallSz>
constexpr SmallMap<K, T, SmallSz> &SmallMap<K, T, SmallSz>::operator=(SmallMap<K, T, SmallSz> &&other) noexcept {
	if (this != &other) {
		clear();
		m_pairs = std::move(other.m_pairs);
	}
	return *this;
}

template<typename K, typename T, size_t SmallSz>
constexpr T &SmallMap<K, T, SmallSz>::operator[](MaybeView_t<K> const&k) {
	bool isNew{};
	auto p = access(m_pairs, k, isNew);
	if (isNew) {
		p->key = k;
	}
	return p->value;
}

template<typename K, typename T, size_t SmallSz>
constexpr Result<T*> SmallMap<K, T, SmallSz>::at(MaybeView_t<K> const&k) noexcept {
	auto const p = accessNoCreate(m_pairs, k);
	if (!p) {
		return {nullptr, ox::Error(1, "value not found for given key")};
	}
	return &p->value;
}

template<typename K, typename T, size_t SmallSz>
constexpr Result<const T*> SmallMap<K, T, SmallSz>::at(MaybeView_t<K> const&k) const noexcept {
	bool isNew{};
	auto p = access(m_pairs, k, isNew);
	if (!p) {
		return {nullptr, ox::Error(1, "value not found for given key")};
	}
	return &p->value;
}

template<typename K, typename T, size_t SmallSz>
constexpr void SmallMap<K, T, SmallSz>::erase(MaybeView_t<K> const&k) {
	size_t i{};
	for (auto const&p : m_pairs) {
		if (k == p.key) {
			break;
		}
		++i;
	}
	std::ignore = m_pairs.erase(i);
}

template<typename K, typename T, size_t SmallSz>
constexpr bool SmallMap<K, T, SmallSz>::contains(MaybeView_t<K> const&k) const noexcept {
	bool isNew{};
	return access(m_pairs, k, isNew) != nullptr;
}

template<typename K, typename T, size_t SmallSz>
constexpr std::size_t SmallMap<K, T, SmallSz>::size() const noexcept {
	return m_pairs.size();
}

template<typename K, typename T, size_t SmallSz>
constexpr Vector<K> SmallMap<K, T, SmallSz>::keys() const noexcept {
	ox::Vector<K> keys;
	keys.reserve(m_pairs.size());
	for (auto const&p : m_pairs) {
		keys.emplace_back(p.key);
	}
	return keys;
}

template<typename K, typename T, size_t SmallSz>
constexpr Vector<T> SmallMap<K, T, SmallSz>::values() const noexcept {
	ox::Vector<T> keys;
	keys.reserve(m_pairs.size());
	for (auto const&p : m_pairs) {
		keys.emplace_back(p.key);
	}
	return keys;
}

template<typename K, typename T, size_t SmallSz>
constexpr K const&SmallMap<K, T, SmallSz>::key(size_t i) const noexcept {
	return m_pairs[i].key;
}

template<typename K, typename T, size_t SmallSz>
constexpr T const&SmallMap<K, T, SmallSz>::value(size_t i) const noexcept {
	return m_pairs[i].value;
}

template<typename K, typename T, size_t SmallSz>
constexpr T &SmallMap<K, T, SmallSz>::value(size_t i) noexcept {
	return m_pairs[i].value;
}

template<typename K, typename T, size_t SmallSz>
constexpr typename SmallMap<K, T, SmallSz>::Pair const&SmallMap<K, T, SmallSz>::get(size_t i) const noexcept {
	return m_pairs[i];
}

template<typename K, typename T, size_t SmallSz>
constexpr typename SmallMap<K, T, SmallSz>::Pair &SmallMap<K, T, SmallSz>::get(size_t i) noexcept {
	return m_pairs[i];
}

template<typename K, typename T, size_t SmallSz>
constexpr void SmallMap<K, T, SmallSz>::clear() {
	m_pairs.clear();
}

template<typename K, typename T, size_t SmallSz>
template<typename KK>
constexpr typename SmallMap<K, T, SmallSz>::Pair const*SmallMap<K, T, SmallSz>::access(
		PairVector const&pairs, KK const&k, bool &isNew) const {
	for (auto const&p : pairs) {
		if (p.key == k) {
			isNew = false;
			return &p;
		}
	}
	isNew = true;
	return nullptr;
}

template<typename K, typename T, size_t SmallSz>
template<typename KK>
constexpr typename SmallMap<K, T, SmallSz>::Pair *SmallMap<K, T, SmallSz>::access(
		PairVector &pairs, KK const&k, bool &isNew) {
	for (auto &p : pairs) {
		if (p.key == k) {
			isNew = false;
			return &p;
		}
	}
	isNew = true;
	return &pairs.emplace_back();
}

template<typename K, typename T, size_t SmallSz>
template<typename KK>
constexpr typename SmallMap<K, T, SmallSz>::Pair *SmallMap<K, T, SmallSz>::accessNoCreate(
		PairVector &pairs, KK const&k) {
	for (auto &p : pairs) {
		if (p.key == k) {
			return &p;
		}
	}
	return nullptr;
}

template<typename T, typename K, typename V, size_t SmallSz>
constexpr Error model(T *io, ox::CommonPtrWith<SmallMap<K, V, SmallSz>> auto *obj) noexcept {
	using Map = SmallMap<K, V, SmallSz>;
	OX_RETURN_ERROR(io->template setTypeInfo<Map>());
	OX_RETURN_ERROR(io->field("pairs", &obj->m_pairs));
	return {};
}

}
