extends RefCounted
class_name GedisSets

var _gedis: Gedis

func _init(gedis: Gedis):
	_gedis = gedis

# ----
# Sets
# ----
func sadd(key: String, member) -> int:
	_gedis._core._touch_type(key, _gedis._core._sets)
	var s: Dictionary = _gedis._core._sets.get(key, {})
	var existed := int(s.has(member))
	s[member] = true
	_gedis._core._sets[key] = s
	_gedis.publish("gedis:keyspace:" + key, "set")
	return 1 - existed

func srem(key: String, member) -> int:
	if _gedis._expiry._is_expired(key):
		return 0
	if not _gedis._core._sets.has(key):
		return 0
	var s: Dictionary = _gedis._core._sets[key]
	var existed := int(s.has(member))
	s.erase(member)
	if s.is_empty():
		_gedis._core._sets.erase(key)
		_gedis.publish("gedis:keyspace:" + key, "del")
	else:
		_gedis._core._sets[key] = s
	return existed

func smembers(key: String) -> Array:
	if _gedis._expiry._is_expired(key):
		return []
	var s: Dictionary = _gedis._core._sets.get(key, {})
	return s.keys()

func sismember(key: String, member) -> bool:
	if _gedis._expiry._is_expired(key):
		return false
	var s: Dictionary = _gedis._core._sets.get(key, {})
	return s.has(member)

func scard(key: String) -> int:
	if _gedis._expiry._is_expired(key):
		return 0
	return _gedis._core._sets.get(key, {}).size()

func sexists(key: String) -> bool:
	if _gedis._expiry._is_expired(key):
		return false
	return _gedis._core._sets.has(key)

func spop(key: String):
	if _gedis._expiry._is_expired(key):
		return null
	if not _gedis._core._sets.has(key):
		return null
	var s: Dictionary = _gedis._core._sets[key]
	var keys_arr: Array = s.keys()
	if keys_arr.is_empty():
		return null
	var idx = randi() % keys_arr.size()
	var member = keys_arr[idx]
	s.erase(member)
	if s.is_empty():
		_gedis._core._sets.erase(key)
		_gedis.publish("gedis:keyspace:" + key, "del")
	else:
		_gedis._core._sets[key] = s
	return member

func smove(source: String, destination: String, member) -> bool:
	if _gedis._expiry._is_expired(source):
		return false
	if not sismember(source, member):
		return false
	# remove from source
	srem(source, member)
	# add to destination (creates destination set)
	sadd(destination, member)
	return true

# Computes the union of multiple sets.
# ---
# @param keys: An array of set keys.
# @return: An array containing the members of the resulting union set.
func sunion(keys: Array) -> Array:
	var result_set := {}
	for key in keys:
		var members = smembers(key)
		for member in members:
			result_set[member] = true
	return result_set.keys()

# Computes the intersection of multiple sets.
# ---
# @param keys: An array of set keys.
# @return: An array containing the members of the resulting intersection set.
func sinter(keys: Array) -> Array:
	if keys.is_empty():
		return []
	
	var result_set := {}
	var first_set_members = smembers(keys[0])
	for member in first_set_members:
		result_set[member] = true

	for i in range(1, keys.size()):
		var next_set_members = smembers(keys[i])
		var current_members = result_set.keys()
		for member in current_members:
			if not next_set_members.has(member):
				result_set.erase(member)
	
	return result_set.keys()

# Computes the difference between multiple sets.
# The difference is calculated as the members of the first set minus the members of all subsequent sets.
# ---
# @param keys: An array of set keys. The first key is the set to subtract from.
# @return: An array containing the members of the resulting difference set.
func sdiff(keys: Array) -> Array:
	if keys.is_empty():
		return []

	var result_set := {}
	var first_set_members = smembers(keys[0])
	for member in first_set_members:
		result_set[member] = true

	for i in range(1, keys.size()):
		var next_set_members = smembers(keys[i])
		for member in next_set_members:
			if result_set.has(member):
				result_set.erase(member)

	return result_set.keys()

# Computes the union of multiple sets and stores the result in a new set.
# ---
# @param destination: The key to store the resulting union set in.
# @param keys: An array of set keys.
# @return: The number of members in the resulting union set.
func sunionstore(destination: String, keys: Array) -> int:
	var result_members = sunion(keys)
	_gedis._core._sets.erase(destination)
	for member in result_members:
		sadd(destination, member)
	return result_members.size()

# Computes the intersection of multiple sets and stores the result in a new set.
# ---
# @param destination: The key to store the resulting intersection set in.
# @param keys: An array of set keys.
# @return: The number of members in the resulting intersection set.
func sinterstore(destination: String, keys: Array) -> int:
	var result_members = sinter(keys)
	_gedis._core._sets.erase(destination)
	for member in result_members:
		sadd(destination, member)
	return result_members.size()

# Computes the difference between multiple sets and stores the result in a new set.
# ---
# @param destination: The key to store the resulting difference set in.
# @param keys: An array of set keys. The first key is the set to subtract from.
# @return: The number of members in the resulting difference set.
func sdiffstore(destination: String, keys: Array) -> int:
	var result_members = sdiff(keys)
	_gedis._core._sets.erase(destination)
	for member in result_members:
		sadd(destination, member)
	return result_members.size()

# Gets one or more random members from a set.
# ---
# @param key: The key of the set.
# @param count: The number of random members to return. If positive, returns unique members. If negative, allows for repetitions.
# @return: A single random member if count is 1, or an array of random members. Returns null or an empty array if the set is empty.
func srandmember(key: String, count: int = 1):
	if _gedis._expiry._is_expired(key):
		return null if count == 1 else []

	var s: Dictionary = _gedis._core._sets.get(key, {})
	var members = s.keys()
	if members.is_empty():
		return null if count == 1 else []

	if count == 1:
		return members.pick_random()

	var result = []
	if count > 0:
		# Positive count: return unique elements
		if count >= members.size():
			members.shuffle()
			return members
		
		var used_indices = {}
		while result.size() < count:
			var idx = randi() % members.size()
			if not used_indices.has(idx):
				result.append(members[idx])
				used_indices[idx] = true
	else: # count < 0
		# Negative count: repetitions are allowed
		var abs_count = abs(count)
		for _i in range(abs_count):
			result.append(members.pick_random())
			
	return result