# -*-eselect-*-  vim: ft=eselect
# Copyright 2005-2025 Gentoo Authors
# Distributed under the terms of the GNU GPL version 2 or later

DESCRIPTION="Manage the Swift symlink"
MAINTAINER="itai@itaiferber.net"
VERSION="1.0"

inherit path-manipulation

BIN_DIR="${EROOT%/}/usr/bin"

### Utility Functions ###

# @FUNCTION: get_index
# @USAGE: <element> <elements>...
# @DESCRIPTION:
# Returns the index of an element in an array, or the empty string if the
# element was not found.
get_index() {
	local element="$1"
	shift
	local i elements=( "$@" )
	for i in "${!elements[@]}"; do
		if [[ "${elements[i]}" = "${element}" ]]; then
			echo "${i}"
			return 0
		fi
	done
	return 1
}

# @FUNCTION: sort_swift_targets
# @USAGE: <target identifier>...
# @DESCRIPTION:
# Lexicographically sorts an array of Swift target identifiers.
sort_swift_targets() {
	# `sort` may not support '--version-sort', so fall back on error
	local vsort="sort --version-sort"
	${vsort} </dev/null &>/dev/null || vsort="sort"

	# Stolen from `app-eselect/eselect-luajit`: to handle potential `_beta`
	# version suffixes, we:
	# 1. Turn `swift-x.y.z` into `x.y.z 1 swift-x.y.z`
	# 2. Turn `swift-x.y.z_beta...` into `x.y.z 0 swift-x.y.z_beta...`
	# 3. Sort, which moves `_beta` versions before corresponding non-beta
	#    versions
	# 4. Remove leading trivia
	printf "%s\n" "$@" \
		| sed -e 's/^\(swift-\)\?\([[:digit:].]\+\)[-_]beta/\2 0 &/' \
		      -e 't;s/^\(swift-\)\?\([[:digit:].]\+\)/\2 1 &/' \
		| LC_ALL=C ${vsort} \
		| sed 's/.* //'
}

# @FUNCTION: list_targets
# @DESCRIPTION:
# Returns a lexicographically-sorted list of Swift targets found in `BIN_DIR`.
list_targets() {
	local identifiers
	mapfile -t identifiers < <(find "${BIN_DIR}" -maxdepth 1 -type l -iname 'swift-[[:digit:]]*' -exec basename '{}' ';')
	sort_swift_targets "${identifiers[@]}"
}

# @FUNCTION: list_target_links
# @USAGE: <target identifier>
# @DESCRIPTION:
# Given a Swift target identifier, lists all of the symlinks found in `BIN_DIR`
# that correspond to that Swift target.
list_target_links() {
	local target="$1"
	local version_number="${target#swift-}"
	local swift_dir="$(dirname "$(canonicalise "${BIN_DIR}/${target}")")"

	local files
	mapfile -t files < <(find "${BIN_DIR}" -maxdepth 1 -type l -iname "*-${version_number}")
	for f in "${files[@]}"; do
		local d="$(dirname "$(canonicalise "${f}")")"
		if [[ "${d}" == "${swift_dir}"* ]]; then
			echo "${f}"
		fi
	done
}

# @FUNCTION: current_target_index
# @USAGE: <target identifier>...
# @DESCRIPTION:
# Returns the index of the currently-set target from the given list of available
# Swift targets. If `/usr/bin/swift` does not point to any of the given Swift
# targets, returns an empty string.
current_target_index() {
	local t canonical_targets=()
	for t in "$@"; do
		canonical_targets+=("$(canonicalise "${BIN_DIR}/${t}")")
	done

	local canonical_swift_path="$(canonicalise "${BIN_DIR}/swift")"
	get_index "${canonical_swift_path}" "${canonical_targets[@]}"
}

# @FUNCTION: current_target
# @USAGE: <target identifier>...
# @DESCRIPTION:
# Returns the target identifier of the currently-set target from the given list
# of available Swift targets. If `/usr/bin/swift` does not point to any of the
# given Swift targets, returns an empty string.
current_target() {
	local targets=( "$@" )
	local target="$(current_target_index "${targets[@]}")"
	if is_number "${target}"; then
		echo "${targets[target]}"
	else
		return 1
	fi
}

# @FUNCTION: unset_target
# @USAGE: <target identifier>
# @DESCRIPTION:
# Unsets any unversioned links in `BIN_DIR` if they point to the given Swift
# target identifier.
unset_target() {
	local target="$1"
	local version_number="${target#swift-}"

	local links
	mapfile -t links < <(list_target_links "${target}")

	local link
	for link in "${links[@]}"; do
		local unversioned_link="${link%-"${version_number}"}"
		if [[ "$(canonicalise "${link}")" = "$(canonicalise "${unversioned_link}")" ]]; then
			rm "${unversioned_link}" || die -q "Couldn't remove symlink '${unversioned_link}'"
		fi
	done
}

# @FUNCTION: set_target
# @USAGE: <target identifier> <target identifiers>...
# @DESCRIPTION:
# Unsets the current Swift target and sets unversioned symlinks in `BIN_DIR` for
# the given Swift target identifier. Does nothing if the given target identifier
# is the same as the current target identifier.
set_target() {
	local target="$1"
	shift
	local current_target="$(current_target "$@")"
	if [[ "${target}" = "${current_target}" ]]; then
		return
	fi

	unset_target "${current_target}"

	local version_number="${target#swift-}"
	local links
	mapfile -t links < <(list_target_links "${target}")

	local link
	for link in "${links[@]}"; do
		local unversioned_link="${link%-"${version_number}"}"
		ln -fs "${link}" "${unversioned_link}" || die -q "Couldn't create symlink '${unversioned_link}'"
	done
}

### List Action ###

describe_list() {
	echo "Lists available Swift versions"
}

do_list() {
	local targets
	mapfile -t targets < <(list_targets)
	if [[ -n "${targets}" ]]; then
		local current_target="$(current_target_index "${targets[@]}")"

		if is_number "${current_target}"; then
			targets[current_target]="$(highlight_marker "${targets[current_target]}")"
		fi

		write_list_start "Available Swift versions:"
		write_numbered_list "${targets[@]}"
	else
		write_list_start "No available Swift versions"
	fi
}

### Show Action ###

describe_show() {
	echo "Show the current Swift implementation"
}

describe_show_options() {
	echo "--latest : Show the latest available Swift implementation"
}

do_show() {
	local show_latest=false
	if [[ $# -gt 0 && "$1" = "--latest" ]]; then
		show_latest=true
		shift
	fi

	[[ $# -eq 0 ]] || die "'show' takes only '--latest' as a parameter"

	local targets
	mapfile -t targets < <(list_targets)

	if [[ -z "${targets[@]}" ]]; then 
		write_list_start "No available Swift versions"
	elif [[ "${show_latest}" = 'true' ]]; then
		write_list_start "Latest Swift implementation:"
		write_kv_list_entry "${targets[-1]}"
	else
		write_list_start "Current Swift implementation:"
		local target="$(current_target "${targets[@]}")"
		write_kv_list_entry "${target:-(unset)}"
	fi
}

### Set Action ###

describe_set() {
	echo "Set active Swift version"
}

describe_set_parameters() {
	echo "<target>"
}

describe_set_options() {
	echo "target : Target number or name (from 'list')"
}

do_set() {
	[[ $# -eq 0 || -z "$1" ]] && die -q "No Swift version specified"
	[[ $# -gt 1 ]] && die -q "'set' takes only one parameter"

	local targets
	mapfile -t targets < <(list_targets)

	local target="$1"
	if is_number "${target}"; then
		[[ "$target" -gt 0 && "$1" -le "${#targets[@]}" ]] || die -q "'$1' is not a valid target"
		target="${targets[target-1]}"
	else
		target_index="$(get_index "$1" "${targets[@]}")"
		is_number "${target_index}" || die -q "'$1' is not a valid target"
	fi

	set_target "${target}" "${targets[@]}"
}

### Unset Action ###

describe_unset() {
	echo "Unsets any active Swift version"
}

do_unset() {
	[[ $# -eq 0 ]] || die -q "'unset' does not take any parameters"
	local targets
	mapfile -t targets < <(list_targets)
	local target="$(current_target "${targets[@]}")"
	unset_target "${target}"
}

### Update Action ###

describe_update() {
	echo "Switch to the most recent Swift version"
}

do_update() {
	[[ $# -eq 0 ]] || die -q "'update' does not take any parameters"

	local targets
	mapfile -t targets < <(list_targets)
	[[ "${#targets[@]}" -gt 0 ]] || die -q "No Swift versions found"

	local target="$(current_target "${targets[@]}")"
	if [[ -n "${target}" ]]; then
		unset_target "${target}"
	fi

	local new_target="${targets[-1]}"
	set_target "${new_target}" "${targets[@]}"
}
