#!/bin/dash
# Specify the sink and source for aloopd
# This file always writes to $HOME/.aloopdrc, never to /etc/aloopd.conf
#
# Copyright (C) James Budiono 2021
# License: GPL Version 3 or later
#

### configuration
APPTITLE="Configure Aloopd Sink/Source"
APPTITLE_MAIN="$APPTITLE"
APPTITLE_SINK="Configure Output"
APPTITLE_SOURCE="Configure Input"

DEFAULT_LATENCY=500000
AVAIL_LATENCIES="
	50000 \"50ms - better audio/video sync\"
	500000 \"500ms - lighter CPU load\"
	other \"Other - specify yourself\"
"

ALOOPD_DEVICES="
	aloopd \"aloopd - exclusive output (better latency)\"
	aloopdshared \"aloopdshared - shared output (can be used for recording mixout) \"
"

########## UI helpers #########

# $1-info
msgbox() {
	if [ $DISPLAY ]; then
		Xdialog --title "$APPTITLE" --msgbox "$1" 0 0 10000
	else
		dialog --backtitle "$APPTITLE" --msgbox "$1" 0 0 > /dev/stderr
	fi
}

# $1-info $2-timeout (in ms)
infobox() {
	local timeout=${2:-10000}
	if [ $DISPLAY ]; then
		Xdialog --title "$APPTITLE" --infobox "$1" 0 0 $timeout
	else
		dialog --backtitle "$APPTITLE" --no-cancel --pause "$1" 12 60 $(( timeout/1000 )) > /dev/stderr
	fi
}

# $1-text, $2-choices, output: stdout
choices() {
	if [ $DISPLAY ]; then
		eval Xdialog --title \"$APPTITLE\" --stdout --no-tags --menubox \""$1"\" 20 100 5 $2
	else 
		eval dialog --backtitle \"$APPTITLE\" --stdout --no-tags --menu \""$1"\" 0 0 0 $2
	fi
}

# $1-text, $2-choices, output: stdout
multi_choices() {
	if [ $DISPLAY ]; then
		eval Xdialog --title \"$APPTITLE\" --stdout --no-tags --separator \" \" --checklist \""$1"\" 20 100 5 $2
	else
		eval dialog --title \"$APPTITLE\" --stdout --no-tags --separator \" \" --checklist \""$1"\" 0 0 0 $2
	fi
}

# $1-text
yesno() {
	if [ $DISPLAY ]; then
		Xdialog --title "$APPTITLE" --yesno "$1" 0 0
	else
		dialog --title "$APPTITLE" --yesno "$1" 0 0
	fi
}

# $1-prompt, $2-current value
inputbox() {
	if [ $DISPLAY ]; then
		Xdialog --title "$APPTITLE" --stdout --inputbox "$1" 0 0 "$2"
	else
		dialog --title "$APPTITLE" --stdout --inputbox "$1" 0 0 "$2"		
	fi
}

# $1-prompt
fselect() {
	if [ $DISPLAY ]; then
		Xdialog --title "$APPTITLE" --backtitle "$1" --stdout --no-buttons --fselect "$PWD" 0 0
	else
		dialog --title "$APPTITLE" --backtitle "$1" --stdout --fselect "$PWD" 10 70
	fi
}

# $1-prompt
tailbox() {
	local tmpfile=/tmp/bt-status.$$
	if [ $DISPLAY ]; then
		Xdialog --title "$APPTITLE" --no-cancel --backtitle "$1" --tailbox - 0 0
	else
		cat > $tmpfile
		dialog --title "$APPTITLE" --backtitle "$1" --textbox $tmpfile 0 0
		rm $tmpfile
	fi
}

# $1-text
splash() {
	if [ "$XPID" ]; then
		kill $XPID
		XPID=
	fi

	if [ "$1" ]; then	
		if [ $DISPLAY ]; then
			Xdialog --title "$APPTITLE" --no-buttons --infobox "$1" 0 0 1000000 &
			XPID=$!
		else
			dialog --backtitle "$APPTITLE" --infobox "$1" 0 0 > /dev/stderr
		fi
	fi
}

# $1-text
die() {
	infobox "$1"
	exit 1
}

die_unchanged() {
	die "Operation cancelled. Nothing was changed."
}

# $1-option, $* options
has_option() {
	local p inp=$1
	shift
	for p; do
		case $p in
			$inp) return 0
		esac	
	done
	return 1
}


########## tasks ############

detect_hardware() {
	SOUNDCARDS=$(LANG=C aplay -l | awk '$1~/card/ && $0!~/Loopback/ { print "\"" $0 "\" " "\"" $0 "\"" }')
	if pidof bluetoothd > /dev/null && BT_DEVICES=$(bt-device -l);
	then
		BT_DEVICES="$(echo "$BT_DEVICES" | sed '1d; s|.*(\(.*\))|"\1" "&"|')"
		SOUNDCARDS_BT="$SOUNDCARDS bluetooth \"Bluetooth devices\""
	else
		SOUNDCARDS_BT="$SOUNDCARDS"
	fi
}

get_latency() {
	local LATENCY
	LATENCY=$(choices "Choose latency" "$AVAIL_LATENCIES") || return 1
	if [ "$LATENCY" = other ]; then
		LATENCY=$(inputbox "Specify the latency in usec.

If the latency is too big you will notice audio/video out of sync.
If the latency is too small you will either hear choppy sound, or nothing at all.

500000 (500ms = 0.5 second) helps with slower machine and slower devices (e.g. Bluetooth)
50000 (50ms = 0.05 second) provides unnoticeable delay
Use larger values of you use 'aloopdshared' as as output

" "$DEFAULT_LATENCY") || return 1
	fi
	echo $LATENCY
}

# $@ - items to configure
configure() {
	while [ $1 ]; do
		case $1 in
			sink)
				APPTITLE="$APPTITLE_SINK"
				infobox "Now configuring OUTPUT device ..."
				CARD=$(choices "Choose output device" "$SOUNDCARDS_BT") || die_unchanged
				if [ "$CARD" = bluetooth ]; then
					BT_DEV=$(choices "Choose bluetooth devices for output" "$BT_DEVICES") || die_unchanged
					SINK="bluealsa_raw:SRV=org.bluealsa,DEV=$BT_DEV,PROFILE=A2DP,DELAY=0"
				else
					SINK=$(echo $CARD | sed 's/card \([0-9]*\):.*device \([0-9]*\):.*/hw:\1,\2/')
				fi
				SOURCE=$(choices "Choose aloopd device to use" "$ALOOPD_DEVICES") || die_unchanged
				LATENCY=$(get_latency) || die_unchanged
				SINK_JOB="-t $LATENCY -T -1 -S none -C $SOURCE -P $SINK"
			;;
			
			source)
				APPTITLE="$APPTITLE_SOURCE"
				infobox "Now configuring INPUT device ..."
				CARD=$(choices "Choose input device" "$SOUNDCARDS") || die_unchanged
				# I don't know how to configure BT input yet ... A2DP does not support capture
				#if [ "$CARD" = bluetooth ]; then
				#	BT_DEV=$(choices "Choose bluetooth devices for input" "$BT_DEVICES") || die_unchanged
				#	SOURCE="bluealsa_raw:SRV=org.bluealsa,DEV=$BT_DEV,PROFILE=A2DP,DELAY=0"
				#else
					SOURCE=$(echo $CARD | sed 's/card \([0-9]*\):.*device \([0-9]*\):.*/hw:\1,\2/')
				#fi
				SINK="aloopd" # always aloopd
				LATENCY=$(get_latency) || die_unchanged
				SOURCE_JOB="-t $LATENCY -T -1 -S none -C $SOURCE -P $SINK"
			;;
		esac
		shift
	done
	APPTITLE="$APPTITLE_MAIN"
}

output_config_file() {
	# if we don't have source, write and example by reversing output job
	if [ -z "$SOURCE_JOB" -a -z "$BT_DEV" ]; then
		SOURCE_JOB="#-t $LATENCY -T -1 -S none -C $SINK -P aloopd"
	fi

	# if we're outputting to bluetooh, connect to the device for convenience
	if ! [ -z "$BT_DEV" ]; then
		bt-device -c $BT_DEV
	fi

	# write output
	cat > $HOME/.aloopdrc << EOF
# For template/examples/notes, see /etc/aloopd.conf

# playback job
$SINK_JOB

# capture job - use "aloopd" only here. Never use "aloopdshared".
$SOURCE_JOB
EOF
}

########### main ############

AVAIL_ITEMS="
	sink \"Configure sink (speaker output)\" on
	source \"Configure source (microphone input)\" off
"

ITEMS=$(multi_choices "Choose what you want to configure" "$AVAIL_ITEMS") || die_unchanged
detect_hardware
configure $ITEMS
output_config_file

# Done, but offer to start aloopd service for convenience as well
if yesno "Done. File is written to $HOME/.aloopdrc

Do you want to start/restart aloopd service?
"; then
	splash "Please wait, it may take up to 10 seconds to restart aloopd ..."
	service aloopd start
	splash ""
	infobox "aloopd restarted."
fi

