#!/bin/bash
#
# SPDX-License-Identifier: GPL-2.0-or-later
#
# SPDX-FileCopyrightText: 2016-2023 KUNBUS GmbH


# reset environment
if [ "$(/usr/bin/env | /bin/grep -Ev '^(PWD|SHLVL|_)=')" ]; then
	exec /usr/bin/env - /bin/bash "$0" "$@"
fi

CONFIG=/boot/firmware/config.txt

trap "exit 1" SIGUSR1
PROC="$$"

usage() {
	cat <<-EOF
		Usage: $(basename "$0") enable | disable | status | available <feature>
		Supported features: gui revpi-con-can
		                    var-log.mount dphys-swapfile
		                    teamviewer-revpi pimodbus-master
		                    pimodbus-slave systemd-timesyncd ssh
		                    logi-rts nodered noderedrevpinodes-server
		                    revpipyload bluetooth ieee80211 avahi
		                    external-antenna
	EOF
}

# do_gui <verb>
do_gui() {
	case "$1" in
	enable)
		/bin/systemctl set-default graphical.target
		;;
	disable)
		/bin/systemctl set-default multi-user.target
		;;
	status)
		[ "$(/bin/systemctl get-default)" != "graphical.target" ]
		return
		;;
	availstat)
		if do_gui available; then
			return 2
		fi
		do_gui status "$2"
		return
		;;
	available)
		if [ -e /usr/bin/startx ]; then
			return 1
		else
			return 0
		fi
		;;
	esac
}

rpi_revision_code() {
	# see https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#new-style-revision-codes
	code=$(grep Revision /proc/cpuinfo | cut -d' ' -f2)

	if [ -z "$code" ]; then
		echo >&2 "Unable to determine board revision code. Not running on a RevPi?. Aborting."
		# we are running in a subshell, thus cannot exit directly here
		kill -10 "$PROC"
		return
	fi

	# Convert the hexadecimal revision code to base 10
	echo $((16#${code}))
}

rpi_type() {
	code=$(rpi_revision_code)
	mask=4080

	echo $(((code & mask) >> 4))
}

is_cm1() {
	test "$(rpi_type)" -eq 6
}

is_not_cm1() {
	! is_cm1
}

is_cm3() {
	test "$(rpi_type)" -eq 10
}

is_cm4s() {
	test "$(rpi_type)" -eq 21
}

is_cm4() {
	test "$(rpi_type)" -eq 20
}

is_cm5() {
	test "$(rpi_type)" -eq 24
}

is_cm_with_wifi() {
	if is_cm4 || is_cm5 && grep -q 'DRIVER=brcmfmac' /sys/class/ieee80211/phy0/device/uevent 2>/dev/null; then
		return 0
	else
		return 1
	fi
}

is_revpi_connect() {
	is_cm3 || is_cm4s && /bin/grep -F -q 'kunbus,revpi-connect' /proc/device-tree/compatible
}

get_config_var() {
	/bin/grep -E "^[[:blank:]]*${1}=" "$CONFIG" |
		/usr/bin/cut -d= -f2- | /usr/bin/tail -1
}

clear_config_var() {
	/bin/sed -r -i -e "/^[[:blank:]]*${1}=${2}/d" "$CONFIG"
	/bin/sync
}

set_config_var() {
	if [ "$(get_config_var $1)" = "$2" ]; then
		return
	fi
	clear_config_var "$1"
	echo "$1=$2" >>"$CONFIG"
	/bin/sync
}

# do_config <verb> <variable-name> <variable-value> <available-hook>
do_config() {
	case "$1" in
	enable)
		set_config_var "$2" "$3"
		;;
	disable)
		clear_config_var "$2" "$3"
		;;
	status)
		val="$(get_config_var $2)"
		if [ -z "$val" ]; then
			echo -n "0 "
		else
			echo -n "$val "
		fi
		if [ -n "$3" ]; then
			[ "$3" != "$val" ]
			return
		else
			[ -z "$val" ]
			return
		fi
		;;
	available)
		eval "$4"
		[ $? = 1 ]
		return
		;;
	availstat)
		if do_config available "$2" "$3" "$4"; then
			return 2
		fi
		do_config status "$2" "$3" "$4"
		return
		;;
	esac
}

# do_overlay <verb> <overlay-name> <available-hook>
do_overlay() {
	case "$1" in
	enable)
		clear_config_var "dtoverlay" "$2"
		echo "dtoverlay=$2" >>"$CONFIG"
		/usr/bin/dtoverlay "$2"
		;;
	disable)
		clear_config_var "dtoverlay" "$2"
		/usr/bin/dtoverlay -r "$2"
		;;
	status)
		/bin/grep -E -q "^[[:blank:]]*dtoverlay=$2" "$CONFIG"
		[ $? = 1 ]
		return
		;;
	available)
		eval "$3"
		[ $? = 1 ]
		return
		;;
	availstat)
		if do_overlay available "$2" "$3"; then
			return 2
		fi
		do_overlay status "$2" "$3"
		return
		;;
	esac
}

avahi-daemon-post-disable() {
	# Disable the avahi socket AFTER disabling the service unit
	# Note: This is because a connected socket could interrupt stop
	/bin/systemctl disable avahi-daemon.socket
	/bin/systemctl stop avahi-daemon.socket
}
avahi-daemon-post-enable() {
	# Enable the avahi socket after enabling the service unit
	/bin/systemctl enable avahi-daemon.socket
	/bin/systemctl start avahi-daemon.socket
}

dphys-swapfile-post-disable() {
	# remove swapfile
	/sbin/dphys-swapfile uninstall
}

var-log.mount-pre-enable() {
	# disable is no longer sufficient on stretch
	/bin/systemctl unmask var-log.mount
}

var-log.mount-post-disable() {
	/bin/systemctl mask var-log.mount
}

# do_service <verb> <service>
do_service() {
	declare -f "$2-pre-$1" >/dev/null 2>&1 && eval "$2-pre-$1"
	case "$1" in
	enable)
		/bin/systemctl enable "$2"
		/bin/systemctl start "$2"
		;;
	disable)
		/bin/systemctl disable "$2"
		/bin/systemctl stop "$2"
		;;
	status)
		/bin/systemctl is-enabled "$2" >/dev/null 2>&1
		[ $? = 1 ]
		return
		;;
	available)
		/bin/systemctl status "$2" 2>&1 | /bin/grep -qE "not(-| be )found"
		return
		;;
	availstat)
		if do_service available "$2"; then
			return 2
		fi
		do_service status "$2"
		return
		;;
	esac
	ret=$?
	declare -f "$2-post-$1" >/dev/null 2>&1 && eval "$2-post-$1"
	return $ret
}

# Check if the WLAN country is set with the cfg80211 kernel module's
# ieee80211_regdom parameter. Abort if it isn't present in sysfs as the kernel
# module isn't loaded then. Print a warning when the country wasn't explicitly
# set.
#
# If the country isn't explicitly set then a fallback is used. This is fine, so
# a warning is printed instead of aborting.
#
# The value of ieee80211_regdom is 00 if no country was explicitly set.
_check_wlan_country_set() {
	local regdom_param_file=/sys/module/cfg80211/parameters/ieee80211_regdom
	if [ ! -f "$regdom_param_file" ]; then
		printf "WLAN kernel module not active. Aborting.\n" >&2
		return 1
	fi

	local regdom="$(cat "$regdom_param_file")"
	if [ "$regdom" = "00" ]; then
		printf "WARNING: WLAN country isn't set. Set the WLAN country and reboot.\n" >&2
	fi

	return 0
}

_set_wlan_state_networkmanager() {
	local state_file=/var/lib/NetworkManager/NetworkManager.state
	local action="$1"
	local enabled=false
	local radio=off

	case "$action" in
	enable)
		enabled=true
		radio=on
		;;
	disable)
		enabled=false
		radio=off
		;;
	*)
		printf "BUG: cannot pass '%s' to _set_wlan_state_networkmanager()\n" "$action" >&2
		return 1
		;;
	esac

	if systemctl -q is-active NetworkManager; then
		nmcli radio wifi "$radio"
	elif [ -f "$state_file" ]; then
		sed -i -e "s/^\(WirelessEnabled\)=.*\$/\1=$enabled/" "$state_file"
	fi
}

# do_wireless <verb> <type> (where type is "bluetooth" or "ieee80211")
do_wireless() {
	shopt -s nullglob
	if is_cm_with_wifi || grep -q "revpi-flat" "/proc/device-tree/compatible"; then
		if [ "$2" = "ieee80211" ]; then
			#built-in devices are recognizable by having a devicetree node
			IDX=(/sys/class/"$2"/*)
			if grep -q -E 'DRIVER=(brcmfmac|mwifiex_sdio)' "$IDX"/device/uevent; then
				# search for rfkill directory
				IDX=("$IDX"/rfkill*)
				# strip everything except index
				IDX="${IDX##*/rfkill}"
			fi
		else
			hci_device="/sys/class/bluetooth/hci0"

			if [ ! -L $hci_device ]; then
				# The bluetooth device is not present and
				# the device should have been brought up by revpi-bluetooth's udev rules
				# or vendor magic (devices based on CM4 and newer). Nothing we can do here,
				# so treat the interface as disabled.
				return 0
			fi

			IDX=$(cat "$hci_device"/rfkill*/index)
		fi
	fi
	shopt -u nullglob

	case "$1" in
	enable)
		if ! _check_wlan_country_set; then
			return
		fi
		[ "$IDX" ] && rfkill unblock "$IDX"
		if [ "$2" = "ieee80211" ]; then
			_set_wlan_state_networkmanager "$1"
		fi
		;;
	disable)
		# don't check whether the WLAN country is set when disabling as it
		# should always be possible to disable it
		[ "$IDX" ] && rfkill block "$IDX"
		if [ "$2" = "ieee80211" ]; then
			_set_wlan_state_networkmanager "$1"
		fi
		;;
	status)
		[ -z "$IDX" ] && return 0
		disabled=$(</sys/class/rfkill/rfkill"$IDX"/soft)
		# invert value
		((disabled))
		return
		;;
	available)
		[ -z "$IDX" ]
		return
		;;
	availstat)
		[ -z "$IDX" ] && return 2
		disabled=$(</sys/class/rfkill/rfkill"$IDX"/soft)
		# invert value
		((disabled))
		return
		;;
	esac
}

do_cm_external_antenna() {
	case "$1" in
	enable)
		clear_config_var "dtparam" "ant1"
		clear_config_var "dtparam" "ant2"
		echo "dtparam=ant2" >>"$CONFIG"
		;;
	disable)
		clear_config_var "dtparam" "ant1"
		clear_config_var "dtparam" "ant2"
		;;
	status)
		/bin/grep -E -q "^[[:blank:]]*dtparam=ant2$" "$CONFIG"
		[ $? = 1 ]
		return
		;;
	available)
		is_cm_with_wifi
		[ $? = 1 ]
		return
		;;
	availstat)
		if do_cm_external_antenna available; then
			return 2
		fi
		do_cm_external_antenna status
		return
		;;
	esac
}

if [ "$1" != enable ] &&
	[ "$1" != disable ] &&
	[ "$1" != status ] &&
	[ "$1" != available ] &&
	[ "$1" != availstat ]; then
	usage
	exit
fi

cmd="$1"
shift
while [ $# -gt 0 ]; do
	svc="$1"
	case "$svc" in
	gui)
		do_gui "$cmd"
		;;
	revpi-con-can)
		do_overlay "$cmd" "$svc" is_revpi_connect
		;;
	var-log.mount)
		do_service "$cmd" "$svc"
		;;
	dphys-swapfile)
		do_service "$cmd" "$svc"
		;;
	teamviewer-revpi)
		do_service "$cmd" "$svc"
		;;
	pimodbus-master)
		do_service "$cmd" "$svc"
		;;
	pimodbus-slave)
		do_service "$cmd" "$svc"
		;;
	systemd-timesyncd)
		do_service "$cmd" "$svc"
		;;
	ssh)
		do_service "$cmd" "$svc"
		;;
	logi-rts)
		do_service "$cmd" "$svc"
		;;
	nodered)
		do_service "$cmd" "$svc"
		;;
	noderedrevpinodes-server)
		do_service "$cmd" "$svc"
		;;
	revpipyload)
		do_service "$cmd" "$svc"
		;;
	avahi)
		do_service "$cmd" avahi-daemon
		;;
	bluetooth)
		do_wireless "$cmd" "$svc"
		;;
	ieee80211)
		do_wireless "$cmd" "$svc"
		;;
	external-antenna)
		do_cm_external_antenna "$cmd"
		;;
	*)
		usage
		exit
		;;
	esac
	echo -n "$? "
	shift
done

echo
