// SPDX-FileCopyrightText: 2019 pancake <pancake@nopcode.org>
// SPDX-FileCopyrightText: 2024 pelijah
// SPDX-License-Identifier: MIT

#include <rz_util/rz_set.h>
#include <rz_util/rz_assert.h>

/**
 * \brief Create a new hash set with C-string as elements.
 * \param opt Defines how elements are stored
 */
RZ_API RZ_OWN RzSetS *rz_set_s_new(HtStrOption opt) {
	return ht_sp_new(opt, NULL, NULL);
}

/**
 * \brief Add element \p str to hash set \p set.
 */
RZ_API void rz_set_s_add(RZ_NONNULL RzSetS *set, const char *str) {
	rz_return_if_fail(set);
	ht_sp_insert(set, str, (void *)1);
}

/**
 * \brief Check if hash set \p set contains element \p str.
 */
RZ_API bool rz_set_s_contains(const RZ_NONNULL RzSetS *set, const char *str) {
	rz_return_val_if_fail(set, false);
	return ht_sp_find((RzSetS *)set, str, NULL) != NULL;
}

/**
 * \brief Add element \p str from hash set \p set.
 */
RZ_API void rz_set_s_delete(RZ_NONNULL RzSetS *set, const char *str) {
	rz_return_if_fail(set);
	ht_sp_delete(set, str);
}

/**
 * \brief Returns an iterator over the \p set with immutable elements.
 *
 * \return Iterator yielding immutable elements.
 */
RZ_API RzIterator /* <RzSetS> */ *rz_set_s_as_iter(const RZ_NONNULL RzSetS *set) {
	rz_return_val_if_fail(set, NULL);
	return ht_sp_as_iter_keys((const HtSP *)set);
}

static bool push_to_pvector(void *user, const char *k, RZ_UNUSED const void *v) {
	RzPVector *vec = (RzPVector *)user;
	return !!rz_pvector_push(vec, (void *)k);
}

/**
 * \brief Create a vector from elements of hash set \p set
 *
 * If a hash set owns stored strings the ownership will be transferred.
 */
RZ_API RZ_OWN RzPVector /*<char *>*/ *rz_set_s_to_vector(RZ_NONNULL RzSetS *set) {
	rz_return_val_if_fail(set, NULL);

	RzPVector *vec = rz_pvector_new(set->opt.finiKV ? free : NULL);
	if (!vec || !rz_pvector_reserve(vec, ht_sp_size((HtSP *)set))) {
		rz_pvector_free(vec);
		return NULL;
	}
	ht_sp_foreach(set, push_to_pvector, vec);
	set->opt.finiKV = NULL;
	set->opt.finiKV_user = NULL;
	return vec;
}

RZ_API void rz_set_s_free(RZ_NULLABLE RzSetS *set) {
	ht_sp_free((HtSP *)set);
}

/**
 * \brief Cleans the set.
 */
RZ_API void rz_set_s_clear(RZ_NONNULL RzSetS *set) {
	rz_return_if_fail(set);
	ht_sp_clear(set);
}

/**
 * \brief Return number of elements saved in the set.
 */
RZ_API ut32 rz_set_s_size(const RZ_NONNULL RzSetS *set) {
	rz_return_val_if_fail(set, 0);
	return ht_sp_size((HtSP *)set);
}

/**
 * \brief Create a new hash set with ut64 as elements.
 */
RZ_API RZ_OWN RzSetU *rz_set_u_new(void) {
	return (RzSetU *)ht_up_new(NULL, NULL);
}

/**
 * \brief Add element \p u to hash set \p set.
 */
RZ_API void rz_set_u_add(RZ_NONNULL RzSetU *set, ut64 u) {
	rz_return_if_fail(set);
	ht_up_insert(set, u, (void *)1);
}

static bool take_first(void *user, const ut64 k, const void *v) {
	ut64 *out = user;
	*out = k;
	return false;
}

/**
 * \brief Take an element from \p set.
 * The element is removed from it.
 * There is no indicator what element is taken, because a set is unordered.
 *
 * The set must have a size of 1+ elements.
 * Otherwise a warning is printed and it returns UT64_MAX.
 */
RZ_API ut64 rz_set_u_take(RZ_NONNULL RzSetU *set) {
	rz_return_val_if_fail(set, UT64_MAX);
	if (rz_set_u_size(set) == 0) {
		rz_warn_if_reached();
		return UT64_MAX;
	}
	ut64 out = UT64_MAX;
	ht_up_foreach(set, take_first, &out);
	rz_set_u_delete(set, out);
	return out;
}

/**
 * \brief Check if hash set \p set contains element \p u.
 */
RZ_API bool rz_set_u_contains(const RZ_NONNULL RzSetU *set, ut64 u) {
	rz_return_val_if_fail(set, false);
	return ht_up_find((RzSetU *)set, u, NULL) != NULL;
}

/**
 * \brief Delete element \p u from hash set \p set.
 */
RZ_API void rz_set_u_delete(RZ_NONNULL RzSetU *set, ut64 u) {
	rz_return_if_fail(set);
	ht_up_delete(set, u);
}

/**
 * \brief Cleans the set.
 */
RZ_API void rz_set_u_clear(RZ_NONNULL RzSetU *set) {
	rz_return_if_fail(set);
	ht_up_clear(set);
}

RZ_API void rz_set_u_free(RZ_NULLABLE RzSetU *set) {
	ht_up_free((HtUP *)set);
}

/**
 * \brief Return number of elements saved in the set.
 */
RZ_API ut32 rz_set_u_size(const RZ_NONNULL RzSetU *set) {
	rz_return_val_if_fail(set, 0);
	return ht_up_size((HtUP *)set);
}

/**
 * \brief Returns an iterator over the \p set with immutable elements.
 *
 * \return Iterator yielding immutable elements.
 */
RZ_API RzIterator /* <RzSetU> */ *rz_set_u_as_iter(const RZ_NONNULL RzSetU *set) {
	rz_return_val_if_fail(set, NULL);
	return ht_up_as_iter_keys((const HtUP *)set);
}
