#!/usr/bin/bash

[ ${BASH_VERSINFO:-0} -ge 4 ] || { printf "This script requires bash >= 4.0!\n" >&2; exit 1; }
command -v ps   &>/dev/null || { printf "This script requires ps!\n"   >&2; exit 1; }
command -v grep &>/dev/null || { printf "This script requires grep!\n" >&2; exit 1; }
command -v tr   &>/dev/null || { printf "This script requires tr!\n"   >&2; exit 1; }
[ -n "$(ps ux ww | grep -e pulseaudio -e pipewire)" ] || {
	printf "This script supports PipeWire and PulseAudio, but neither appears to be running!\n" >&2
	exit 1
}
command -v pactl &>/dev/null || {
	printf "This script requires pactl!\n" >&2
	printf "(it will work on either PulseAudio or PipeWire)\n" >&2
	exit 1
}
trap "exit 0" SIGINT

sinks=()
sink_list=""
while IFS= read -r line; do
	[ "${line#[Ss]ink}" != "$line" ] && sink="$line" || {
		sink="${sink#*[Ss]ink}"
		sink="${sink#${sink%%[^#:[:space:]]*}}"
		description="${line#*[Dd]escription}"
		description="${description#${description%%[^:[:space:]]*}}"
		[ -n "$sink" ] && [ -z "${sink//[0-9]/}" ] || {
			printf "Failed to get sink number for %s!\n" "$description" >&2
			exit 1
		}
		sinks[$sink]=
		sink_list="$sink_list$sink: $description"$'\n'
	}
done < <(pactl list sinks | grep -e "^[Ss]ink[:[:space:]]" -e "^[[:space:]]*[Dd]escription[:[:space:]]")
printf "%s\n\n" "$sink_list"
sink=""
while [ -z "${sinks["$sink"]+0}" ]; do
	printf "\r\033[A\033[KSelect a sink: "
	IFS= read -r sink
done

print_volume() {
	local volume=$(get_volume)00000
	volume=0000$(($volume/65536))
	volume=${volume::-3}.${volume: -3}
	volume=${volume#${volume%%[^0]*}}
	[ ${volume::1} != . ] || volume=0$volume
	printf "%s%%\n" $volume
}

step_base=
printf "\n"
while [ -z "$step_base" ] || [ -n "${step_base//[0-9]/}" ] || [ $step_base -eq 0 ]; do
	printf "\r\033[A\033[KEnter a step size: "
	IFS= read -r step_base
done
step_base=${step_base#${step_base%%[^0]*}}
step_base=$(
	{ command -v awk &>/dev/null && awk "BEGIN {x=65536/100*ARGV[1]; print int(x)+((x%1 > 0.5) ? 1 : 0)}" $step_base; } ||
	{ command -v python3 &>/dev/null && python3 -c "import sys; print(round(65536/100*float(sys.argv[1])));" $step_base; } ||
	printf "%d" $(((65536*$step_base+50)/100))
)
step=$step_base
expected=0
last_time=$(date +'%s')
streak=0

get_volume() {
	# get-sink-volume doesn't exist on older pactl versions
	pactl list sinks | grep -e "^[Ss]ink[:[:space:]]" -e "^[[:space:]]*[Vv]olume[:[:space:]]" | while IFS= read -r line; do
		[ "${line#[Ss]ink}" != "$line" ] || continue
		[ "${line//[^0-9]/}" = "$sink" ] || continue
		IFS= read -r line
		printf "%s\n" "${line//[[:space:]]/}" | tr ":/," $'\n' | while IFS= read -r line; do
			[ -n "$line" ] && [ -z "${line//[0-9]/}" ] || continue
			printf "%s\n" $line
			break
		done
		break
	done
}

set_volume() {
	pactl set-sink-volume $sink "$1"
}

floor_ceil() {
	if [ $1 -eq 0 ]; then
		set_volume $(($step_base*($(get_volume)/$step_base)))
	else
		set_volume $(($step_base*(($(get_volume)+$step_base-1)/$step_base)))
	fi
	step=$step_base
	expected=$(get_volume)
	last_time=$(date +'%s')
}

up() {
	local last
	[ $(get_volume) -eq $expected ] && [ $(($(date +'%s')-$last_time)) -le 180 ] || {
		streak=3
		floor_ceil 1
		print_volume
		return
	}
	[ $streak -ge 0 ] && streak=$(($streak+1)) || { streak=1; step=$(($step/2)); }
	if [ $streak -eq 3 ]; then
		last=$(get_volume)
		floor_ceil 1
		last=$(($(get_volume)-$last)); last=${last#-}
		[ $last -ge $((65536/1000)) ] || set_volume \+$step
	else
		set_volume \+$step
	fi
	expected=$(get_volume)
	last_time=$(date +'%s')
	print_volume
}

down() {
	local last
	[ $(get_volume) -eq $expected ] && [ $(($(date +'%s')-$last_time)) -le 180 ] || {
		streak=-3
		floor_ceil 0
		print_volume
		return
	}
	[ $streak -le 0 ] && streak=$(($streak-1)) || { streak=-1; step=$(($step/2)); }
	if [ $streak -eq -3 ]; then
		last=$(get_volume)
		floor_ceil 0
		last=$(($(get_volume)-$last)); last=${last#-}
		[ $last -ge $((65536/1000)) ] || set_volume -$step
	else
		set_volume -$step
	fi
	expected=$(get_volume)
	last_time=$(date +'%s')
	print_volume
}

trap "stty +echo 2>/dev/null; exit 0" SIGINT
printf "\nRunning - use the arrow keys!\n"
stty -echo 2>/dev/null
while IFS= read -rs -n 1 key; do
	while IFS= read -rs -n 1 -t 0.005 key2; do
		key="$key$key2"
	done
	[ "${key//$'\033'/}" != "$key" ] || continue
	key="${key#*$'\033'}"$'\033'
	while true; do
		key2="${key#*$'\033'}"
		key="${key%%$'\033'*}"
		case $'\033'"$key" in
			$'\033[A'|$'\033OA')
				up
			;;
			$'\033[B'|$'\033OB')
				down
			;;
		esac
		[ -n "$key2" ] && key="$key2" || break
	done
done
