#!/bin/bash
# package:      Part of VPL-Jail-System
# copyright:    Copyright (C) 2026 Juan Carlos Rodriguez-del-Pino
# license:      GNU/GPL, see LICENCE.txt or http://www.gnu.org/licenses/gpl.txt
# Description:  Script to install Language Servers (LS) and their VPL launchers
#
# Launchers are installed at $VPL_LS_BIN_DIR/{languageId}_ls
# The languageId follows the LSP standard (not the ace editor mode):
#   python, java, shellscript, c, cpp, go, rust, r, php, lua, kotlin, csharp, typescript, javascript, yaml, html, css, elixir
# Launchers talk LSP over stdio, with stderr discarded, and use the
# project folder "vplproject" as workspace folder.

set -o pipefail

usage() {
	cat <<END
Usage: $0 [options] [language ...]

Languages: ${DISPLAY_LANGUAGES[*]}
Language names are case-insensitive; C++ and C# are accepted as aliases for cpp and csharp.
Use "all" or give no language to install every supported language.

Examples:
	$0 --list
	$0 python php
	$0 ALL

Options:
  -h, --help             Show this help.
  -l, --list             List supported languages and exit.
  --python-ls=NAME       Python LS to use: pyright | basedpyright (default: $PYTHON_LS).
  --jdtls-version=VER    Version tag used for the jdtls install dir (default: $JDTLS_VERSION).
  --prefix=DIR           Directory for the launchers (default: $VPL_LS_BIN_DIR).
  --no-upgrade           Do not refresh/upgrade the package manager cache.
END
}

ALL_LANGUAGES=(python java shellscript c cpp go rust r php lua kotlin csharp typescript javascript yaml html css)
DISPLAY_LANGUAGES=(Python Java Shellscript C C++ Go Rust R PHP Lua Kotlin C# TypeScript JavaScript YAML HTML CSS)
VPL_LS_BIN_DIR=/usr/local/bin/vpl
VPL_LS_OPT_DIR=/usr/local/bin/vpl
PYTHON_LS=basedpyright
JDTLS_VERSION=latest
JDTLS_URL="https://www.eclipse.org/downloads/download.php?file=/jdtls/snapshots/jdt-language-server-latest.tar.gz&r=1"
KOTLIN_LS_URL="https://github.com/fwcd/kotlin-language-server/releases/latest/download/server.zip"
JUNIT_VERSION=4.13.2
LOGFILE=/var/log/vpl/vpl_ls_installation.log
DO_UPGRADE=yes
SELECTED=()
{
	mkdir -p /var/log/vpl
	chmod 700 /var/log/vpl
	touch "$LOGFILE"
	chmod 600 "$LOGFILE"
} &> /dev/null

# ---------------------------------------------------------------- utilities --
log() { echo "[vpl-ls] $*"; }
warn() { echo "[vpl-ls] WARNING: $*" >&2; }
fail() { echo "[vpl-ls] ERROR: $*" >&2; }

check_root() {
	if [ "$(id -u)" != "0" ] ; then
		fail "This script must be run as the root user."
		exit 1
	fi
}

detect_package_manager() {
	if command -v dnf &> /dev/null ; then
		PMTYPE=dnf
		[ "$DO_UPGRADE" = yes ] && dnf -y -q makecache >> "$LOGFILE" 2>&1
		pm_install() { dnf -y -q install "$@" >> "$LOGFILE" 2>&1; }
	elif command -v apt-get &> /dev/null ; then
		PMTYPE=apt
		export DEBIAN_FRONTEND=noninteractive
		[ "$DO_UPGRADE" = yes ] && apt-get -q update >> "$LOGFILE" 2>&1
		pm_install() { apt-get -q -y --no-install-recommends install "$@" >> "$LOGFILE" 2>&1; }
	elif command -v apk &> /dev/null ; then
		PMTYPE=apk
		[ "$DO_UPGRADE" = yes ] && apk update >> "$LOGFILE" 2>&1
		pm_install() { apk add --no-cache "$@" >> "$LOGFILE" 2>&1; }
	else
		fail "No supported package manager found (dnf, apt-get or apk)."
		exit 1
	fi
	log "Using package manager: $PMTYPE"
}

# On minimal images curl is installed without the CA bundle needed for https.
require_curl() {
	command -v curl &> /dev/null || install_any curl || return 1
	[ -s /etc/ssl/certs/ca-certificates.crt ] || [ -s /etc/pki/tls/certs/ca-bundle.crt ] \
		|| install_any ca-certificates &> /dev/null
	return 0
}

# Installs the first package name that the package manager accepts.
install_any() {
	local pkg
	for pkg in "$@" ; do
		if pm_install "$pkg" ; then
			log "Installed package $pkg"
			return 0
		fi
	done
	fail "Could not install any of: $*"
	return 1
}

# write_launcher <languageId> <<'END' ... END
write_launcher() {
	local name="$1"
	local target="$VPL_LS_BIN_DIR/${name}_ls"
	{
		echo '#!/bin/bash'
		echo "# LSP launcher for ${name^}"
		echo '# Generated by install-vpl-ls-sh. Do not edit.'
		echo 'mkdir -p vplproject'
		cat
	} > "$target"
	chmod 755 "$target"
	log "Launcher created: $target"
}

require_npm() {
	command -v npm &> /dev/null && return 0
	install_any nodejs-npm npm nodejs || return 1
	command -v npm &> /dev/null
}

npm_install_global() {
	require_npm || { fail "npm is required to install $1"; return 1; }
	npm install -g "$1" >> "$LOGFILE" 2>&1
}

require_pipx() {
	command -v pipx &> /dev/null && return 0
	install_any pipx python3-pipx || return 1
	command -v pipx &> /dev/null
}

pipx_install_global() {
	require_pipx || { fail "pipx is required to install $1"; return 1; }
	pipx install --global "$1" >> "$LOGFILE" 2>&1
}

# ------------------------------------------------------------------ python --
install_python() {
	local server="$PYTHON_LS"
	local binary
	case "$server" in
		pyright) binary=pyright-langserver ;;
		basedpyright) binary=basedpyright-langserver ;;
		*) fail "Unknown Python LS '$server'"; return 1 ;;
	esac
	install_any python3 &> /dev/null
	if ! command -v "$binary" &> /dev/null ; then
		pipx_install_global "$server" || return 1
	fi
	local path
	path=$(command -v "$binary" || echo "/usr/local/bin/$binary")
	write_launcher python <<END
cd vplproject || exit 1
if [ ! -f pyrightconfig.json ] ; then
	cat > pyrightconfig.json <<CONFIG
{"include": ["."], "extraPaths": ["."], "typeCheckingMode": "standard"}
CONFIG
fi
exec $path --stdio 2> /dev/null
END
}

# -------------------------------------------------------------------- java --
install_java() {
	local dir="$VPL_LS_OPT_DIR/jdtls-$JDTLS_VERSION"
	install_any java-latest-openjdk-devel default-jdk openjdk21-jdk || return 1
	# src.zip of the JDK, needed for the hover of the standard library.
	install_any java-latest-openjdk-src openjdk-21-source openjdk-17-source openjdk21-src &> /dev/null
	install_any junit junit4 &> /dev/null
	if [ ! -x "$dir/bin/jdtls" ] ; then
		command -v tar &> /dev/null || install_any tar
		require_curl || return 1
		local tmp
		tmp=$(mktemp -d) || return 1
		log "Downloading Eclipse JDT Language Server"
		if ! curl -fsSL "$JDTLS_URL" -o "$tmp/jdtls.tar.gz" ; then
			fail "Download of jdtls failed"
			rm -rf "$tmp"
			return 1
		fi
		mkdir -p "$dir"
		if ! tar -xzf "$tmp/jdtls.tar.gz" -C "$dir" ; then
			fail "Extraction of jdtls failed"
			rm -rf "$tmp"
			return 1
		fi
		rm -rf "$tmp"
	fi
	local junit_jar=/usr/share/java/junit.jar
	[ -f "$junit_jar" ] || junit_jar=/usr/share/java/junit4.jar
	write_launcher java <<END
DATADIR="\$(pwd)/.cache/lsjavadata"
mkdir -p "\$DATADIR"
cd vplproject || exit 1
if [ ! -f .classpath ] ; then
	cat > .classpath <<CONFIG
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
	<classpathentry kind="src" path="."/>
	<classpathentry kind="lib" path="$junit_jar" sourcepath="/usr/share/java/junit-sources.jar">
		<attributes>
			<attribute name="javadoc_location" value="jar:file:/usr/share/java/junit-javadoc.jar!/"/>
		</attributes>
	</classpathentry>
	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
	<classpathentry kind="output" path=".bin"/>
</classpath>
CONFIG
fi
if [ ! -f .project ] ; then
	cat > .project <<CONFIG
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
	<name>vplproject</name>
	<comment></comment>
	<projects></projects>
	<buildSpec>
		<buildCommand>
			<name>org.eclipse.jdt.core.javabuilder</name>
			<arguments></arguments>
		</buildCommand>
	</buildSpec>
	<natures>
		<nature>org.eclipse.jdt.core.javanature</nature>
	</natures>
</projectDescription>
CONFIG
fi
exec $dir/bin/jdtls \\
	--configuration config_linux \\
	--data "\$DATADIR" \\
	--jvm-arg=-Xms100m --jvm-arg=-Xmx250m 2> /dev/null
END
	install_junit_artifacts
}

# ------------------------------------------------------------------ kotlin --
install_kotlin() {
	local dir="$VPL_LS_OPT_DIR/kotlin-language-server"
	install_any java-latest-openjdk default-jre openjdk21-jre java &> /dev/null || return 1
	if [ ! -x "$dir/server/bin/kotlin-language-server" ] ; then
		command -v unzip &> /dev/null || install_any unzip &> /dev/null || return 1
		require_curl || return 1
		local tmp
		tmp=$(mktemp -d) || return 1
		log "Downloading Kotlin Language Server"
		if ! curl -fsSL "$KOTLIN_LS_URL" -o "$tmp/server.zip" ; then
			fail "Download of Kotlin language server failed"
			rm -rf "$tmp"
			return 1
		fi
		mkdir -p "$dir"
		if ! unzip -q "$tmp/server.zip" -d "$dir" ; then
			fail "Extraction of Kotlin language server failed"
			rm -rf "$tmp"
			return 1
		fi
		rm -rf "$tmp"
	fi
	[ -x "$dir/server/bin/kotlin-language-server" ] || {
		fail "Kotlin language server executable is not available"
		return 1
	}
	write_launcher kotlin <<END
cd vplproject || exit 1
exec "$dir/server/bin/kotlin-language-server" 2> /dev/null
END
}

# ------------------------------------------------------------------- csharp --
install_csharp() {
	local tool_dir="$VPL_LS_OPT_DIR/dotnet-tools"
	command -v dotnet &> /dev/null || \
		install_any dotnet8-sdk dotnet-sdk-8.0 dotnet-sdk-9.0 dotnet-sdk &> /dev/null || return 1
	if [ ! -x "$tool_dir/csharp-ls" ] ; then
		mkdir -p "$tool_dir"
		dotnet tool install --tool-path "$tool_dir" csharp-ls >> "$LOGFILE" 2>&1 || \
			dotnet tool update --tool-path "$tool_dir" csharp-ls >> "$LOGFILE" 2>&1
	fi
	[ -x "$tool_dir/csharp-ls" ] || {
		fail "C# language server executable is not available"
		return 1
	}
	write_launcher csharp <<END
cd vplproject || exit 1
exec "$tool_dir/csharp-ls" 2> /dev/null
END
}

install_junit_artifacts() {
	require_curl || return 0
	local base="https://repo1.maven.org/maven2/junit/junit/$JUNIT_VERSION"
	local pair
	mkdir -p /usr/share/java
	for pair in "junit-$JUNIT_VERSION.jar:/usr/share/java/junit.jar" \
				"junit-$JUNIT_VERSION-sources.jar:/usr/share/java/junit-sources.jar" \
				"junit-$JUNIT_VERSION-javadoc.jar:/usr/share/java/junit-javadoc.jar" ; do
		local remote="${pair%%:*}" local_file="${pair#*:}"
		[ -f "$local_file" ] && continue
		curl -fsSL "$base/$remote" -o "$local_file" \
			|| log "Warning: could not download $remote"
	done
}

# -------------------------------------------------------------- shellscript --
install_shellscript() {
	install_any shellcheck ShellCheck &> /dev/null
	install_any shfmt &> /dev/null
	install_manpages
	if ! command -v bash-language-server &> /dev/null ; then
		install_any nodejs-bash-language-server bash-language-server &> /dev/null \
			|| npm_install_global bash-language-server || return 1
	fi
	write_launcher shellscript <<'END'
cd vplproject || exit 1
exec bash-language-server start 2> /dev/null
END
}

# The LS builds the hover of commands from their man pages, which container
# images usually strip.
install_manpages() {
	local common="bash coreutils grep sed gawk findutils diffutils tar util-linux"
	case "$PMTYPE" in
		apt)
			rm -f /etc/dpkg/dpkg.cfg.d/excludes /etc/dpkg/dpkg.cfg.d/docker-nodoc
			install_any man-db &> /dev/null
			install_any manpages &> /dev/null
			apt-get -q -y --reinstall install $common >> "$LOGFILE" 2>&1
			;;
		dnf)
			sed -i '/^tsflags=nodocs/d' /etc/dnf/dnf.conf /etc/yum.conf 2> /dev/null
			install_any man-db &> /dev/null
			install_any man-pages &> /dev/null
			dnf -y -q --setopt=tsflags= reinstall $common >> "$LOGFILE" 2>&1
			;;
		apk)
			install_any mandoc &> /dev/null
			install_any man-pages &> /dev/null
			local pkg
			for pkg in $common ; do
				pm_install "$pkg-doc" &> /dev/null
			done
			;;
	esac
	command -v mandb &> /dev/null && mandb -q >> "$LOGFILE" 2>&1
	command -v man &> /dev/null || log "Warning: 'man' is not available, no hover for commands"
	return 0
}

# ------------------------------------------------------------------- c/c++ --
install_clangd() {
	local language="$1"
	# The hover and the diagnostics come from the system headers.
	install_any gcc &> /dev/null
	install_any glibc-devel libc6-dev musl-dev &> /dev/null
	if [ "$language" = cpp ] ; then
		install_any gcc-c++ g++ &> /dev/null
		install_any libstdc++-devel libstdc++-dev &> /dev/null
	fi
	if ! command -v clangd &> /dev/null ; then
		install_any clangd clang-extra-tools clang-tools-extra clang-tools clangd-tools || return 1
	fi
	local flags="-xc -std=c17"
	[ "$language" = cpp ] && flags="-xc++ -std=c++17"
	write_launcher "$language" <<END
cd vplproject || exit 1
if [ ! -f .clang-format ] ; then
	cat > .clang-format <<CONFIG
BasedOnStyle: LLVM
IndentWidth: 4
TabWidth: 4
UseTab: Never
CONFIG
fi
if [ ! -f compile_flags.txt ] && [ ! -f compile_commands.json ] ; then
	printf '%s\\n' $flags -Wall -I. > compile_flags.txt
fi
exec clangd --background-index --header-insertion=never 2> /dev/null
END
}

install_c() { install_clangd c; }
install_cpp() { install_clangd cpp; }

# --------------------------------------------------------------------- go --
GO_DOWNLOAD_URL="https://go.dev/dl"

install_go() {
	# gopls requires the go command at run time to load the packages.
	command -v go &> /dev/null || install_any golang-go golang go &> /dev/null
	command -v go &> /dev/null || install_go_toolchain || return 1
	if ! command -v gopls &> /dev/null ; then
		install_any golang-x-tools-gopls gopls &> /dev/null
		if ! command -v gopls &> /dev/null ; then
			GOBIN=/usr/local/bin GOFLAGS=-mod=mod go install golang.org/x/tools/gopls@latest \
				>> "$LOGFILE" 2>&1
		fi
	fi
	command -v gopls &> /dev/null || { fail "gopls not available"; return 1; }
	write_launcher go <<'END'
export PATH="$PATH:/usr/local/go/bin"
cd vplproject || exit 1
if [ ! -f go.mod ] ; then
	go mod init vplproject &> /dev/null
fi
exec gopls serve 2> /dev/null
END
}

install_go_toolchain() {
	local arch
	case "$(uname -m)" in
		x86_64) arch=amd64 ;;
		aarch64|arm64) arch=arm64 ;;
		*) fail "No go toolchain for architecture $(uname -m)"; return 1 ;;
	esac
	require_curl || return 1
	command -v tar &> /dev/null || install_any tar || return 1
	local version
	version=$(curl -fsSL "https://go.dev/VERSION?m=text" 2> /dev/null | head -1)
	if [ -z "$version" ] ; then
		fail "Can't get the go version to download"
		return 1
	fi
	log "Downloading go toolchain $version"
	if ! curl -fsSL "$GO_DOWNLOAD_URL/$version.linux-$arch.tar.gz" | tar -xz -C /usr/local ; then
		fail "Download of the go toolchain failed"
		return 1
	fi
	ln -sf /usr/local/go/bin/go /usr/local/bin/go
	ln -sf /usr/local/go/bin/gofmt /usr/local/bin/gofmt
	command -v go &> /dev/null
}

# ------------------------------------------------------------------- rust --
RUST_ANALYZER_URL="https://github.com/rust-lang/rust-analyzer/releases/latest/download"

install_rust() {
	# rustc/cargo provide the sysroot and rust-src the std sources needed for hover.
	command -v cargo &> /dev/null || install_any cargo rust &> /dev/null
	command -v rustc &> /dev/null || install_any rust rustc &> /dev/null
	install_any rust-src rust-std &> /dev/null
	if ! command -v cargo &> /dev/null ; then
		fail "cargo not available, rust-analyzer can not load the project"
		return 1
	fi
	if ! command -v rust-analyzer &> /dev/null ; then
		install_any rust-analyzer &> /dev/null || install_rust_analyzer_binary || return 1
	fi
	write_launcher rust <<'END'
cd vplproject || exit 1
export PATH="$PATH:$HOME/.cargo/bin"
# rust-analyzer only analyzes files that belong to a cargo project.
mkdir -p .vplcrate
[ -f .vplcrate/lib.rs ] || : > .vplcrate/lib.rs
{
	echo '[package]'
	echo 'name = "vplproject"'
	echo 'version = "0.0.0"'
	echo 'edition = "2021"'
	echo 'autobins = false'
	echo ''
	echo '[workspace]'
	echo ''
	# The manifest needs a target even when there is no source file yet.
	echo '[lib]'
	echo 'name = "vplproject"'
	echo 'path = ".vplcrate/lib.rs"'
	for source in *.rs ; do
		[ -f "$source" ] || continue
		echo ''
		echo '[[bin]]'
		echo "name = \"${source%.rs}\""
		echo "path = \"$source\""
	done
} > Cargo.toml
exec rust-analyzer 2> /dev/null
END
}

# ----------------------------------------------------------------------- R --
install_r() {
	install_any r-base R r || return 1
	if ! R --vanilla --slave -e 'requireNamespace("languageserver", quietly = TRUE)' \
		&> /dev/null ; then
		R --vanilla --slave -e \
			'install.packages("languageserver", repos="https://cloud.r-project.org")' \
			>> "$LOGFILE" 2>&1 || return 1
	fi
	R --vanilla --slave -e 'requireNamespace("languageserver", quietly = TRUE)' \
		&> /dev/null || { fail "R package languageserver is not available"; return 1; }
	write_launcher r <<'END'
cd vplproject || exit 1
exec R --vanilla --slave -e 'languageserver::run()' 2> /dev/null
END
}

# rust-analyzer is not packaged by every distribution, use the official binary.
install_rust_analyzer_binary() {
	local arch
	case "$(uname -m)" in
		x86_64) arch=x86_64 ;;
		aarch64|arm64) arch=aarch64 ;;
		*) fail "No rust-analyzer binary for architecture $(uname -m)"; return 1 ;;
	esac
	local libc=gnu
	[ "$PMTYPE" = apk ] && libc=musl
	require_curl || return 1
	command -v gunzip &> /dev/null || install_any gzip &> /dev/null
	log "Downloading rust-analyzer binary"
	if ! curl -fsSL "$RUST_ANALYZER_URL/rust-analyzer-$arch-unknown-linux-$libc.gz" \
			| gunzip > /usr/local/bin/rust-analyzer ; then
		fail "Download of rust-analyzer failed"
		rm -f /usr/local/bin/rust-analyzer
		return 1
	fi
	chmod 755 /usr/local/bin/rust-analyzer
	command -v rust-analyzer &> /dev/null
}

# ------------------------------------------------ typescript / javascript --
# The global node modules directory is not the same in every distribution.
find_node_modules_with() {
	local relative="$1"
	local candidate
	for candidate in "$(npm root -g 2> /dev/null)" /usr/local/lib/node_modules \
			/usr/lib/node_modules /usr/lib64/node_modules /usr/share/node_modules ; do
		if [ -n "$candidate" ] && [ -f "$candidate/$relative" ] ; then
			echo "$candidate"
			return 0
		fi
	done
	return 1
}

# TypeScript 7 is the native port and no longer ships tsserver.js,
# which typescript-language-server still requires.
TYPESCRIPT_PACKAGE='typescript@^5'

install_typescript_server() {
	local language="$1"
	if ! command -v typescript-language-server &> /dev/null ; then
		npm_install_global "$TYPESCRIPT_PACKAGE" || return 1
		npm_install_global typescript-language-server || return 1
	fi
	require_npm || return 1
	# The LS only finds tsserver in the workspace unless the path is given.
	local node_modules
	node_modules=$(find_node_modules_with typescript/lib/tsserver.js)
	if [ -z "$node_modules" ] ; then
		npm_install_global "$TYPESCRIPT_PACKAGE" || return 1
		node_modules=$(find_node_modules_with typescript/lib/tsserver.js)
	fi
	if [ -z "$node_modules" ] ; then
		fail "tsserver not found in the global node modules"
		return 1
	fi
	# Declarations of the node API for the hover of its globals and modules.
	npm_install_global @types/node &> /dev/null
	write_launcher "$language" <<END
cd vplproject || exit 1
# The LS resolves tsserver and the type declarations from the project node_modules.
if [ ! -e node_modules ] ; then
	ln -s "$node_modules" node_modules
fi
if [ ! -f tsconfig.json ] ; then
	cat > tsconfig.json <<CONFIG
{"compilerOptions": {"allowJs": true, "checkJs": true, "target": "ES2020", "moduleResolution": "node"}, "include": ["."]}
CONFIG
fi
exec typescript-language-server --stdio 2> /dev/null
END
}

install_typescript() { install_typescript_server typescript; }
install_javascript() { install_typescript_server javascript; }

# --------------------------------------------------------------------- php --
install_php() {
	install_any php-cli php &> /dev/null || return 1
	if ! command -v intelephense &> /dev/null ; then
		npm_install_global intelephense || return 1
	fi
	write_launcher php <<'END'
cd vplproject || exit 1
exec intelephense --stdio 2> /dev/null
END
}

# --------------------------------------------------------------- web config --
install_npm_server() {
	local language="$1" package="$2" binary="$3"
	if ! command -v "$binary" &> /dev/null ; then
		npm_install_global "$package" || return 1
	fi
	command -v "$binary" &> /dev/null || {
		fail "$language language server executable is not available"
		return 1
	}
	write_launcher "$language" <<END
cd vplproject || exit 1
exec $binary --stdio 2> /dev/null
END
}

install_yaml() { install_npm_server yaml yaml-language-server yaml-language-server; }
install_html() { install_npm_server html vscode-html-languageserver-bin html-language-server; }
install_css() { install_npm_server css vscode-css-languageserver-bin css-language-server; }

# --------------------------------------------------------------------- lua --
install_lua() {
	install_any lua5.4 lua &> /dev/null || return 1
	if ! command -v lua-language-server &> /dev/null ; then
		install_any lua-language-server lua-language-server-bin &> /dev/null || return 1
	fi
	write_launcher lua <<'END'
cd vplproject || exit 1
exec lua-language-server 2> /dev/null
END
}

# -------------------------------------------------------------------- main --
while [ $# -gt 0 ] ; do
	case "$1" in
		-h|--help) usage; exit 0 ;;
		-l|--list) echo "${ALL_LANGUAGES[*]}"; exit 0 ;;
		--python-ls=*) PYTHON_LS="${1#*=}" ;;
		--jdtls-version=*) JDTLS_VERSION="${1#*=}" ;;
		--prefix=*) VPL_LS_BIN_DIR="${1#*=}" ;;
		--no-upgrade) DO_UPGRADE=no ;;
		-*) fail "Unknown option $1"; usage; exit 1 ;;
		*)
			local_language="${1,,}"
			case "$local_language" in
				c++) local_language=cpp ;;
				c#) local_language=csharp ;;
			esac
			SELECTED+=("$local_language")
			;;
	esac
	shift
done

check_root
if [ ${#SELECTED[@]} -eq 0 ] || printf '%s\n' "${SELECTED[@]}" | grep -qx -- all ; then
	SELECTED=("${ALL_LANGUAGES[@]}")
fi

umask 022
touch "$LOGFILE" && chmod 600 "$LOGFILE"
date > "$LOGFILE"
mkdir -p "$VPL_LS_BIN_DIR"

detect_package_manager

failed=()
for language in "${SELECTED[@]}" ; do
	if ! printf '%s\n' "${ALL_LANGUAGES[@]}" | grep -qx -- "$language" ; then
		warn "Unsupported language '$language'"
		failed+=("$language")
		continue
	fi
	log "Installing language server for $language"
	if "install_$language" ; then
		log "Language server for $language ready"
	else
		warn "Installation of language server for $language failed (see $LOGFILE)"
		failed+=("$language")
	fi
done

if [ ${#failed[@]} -gt 0 ] ; then
	# A language server unavailable in this system must not stop the installation.
	warn "Language servers not installed: ${failed[*]}"
fi
log "Language servers installed in $VPL_LS_BIN_DIR"
