#!/usr/bin/env bash
#      _                                  _     _            _              _    _
#   __| |_ __ ___   ___ _ __  _   _      | |__ | |_   _  ___| |_ ___   ___ | |_ | |__
#  / _` | '_ ` _ \ / _ \ '_ \| | | |_____| '_ \| | | | |/ _ \ __/ _ \ / _ \| __|| '_ \
# | (_| | | | | | |  __/ | | | |_| |_____| |_) | | |_| |  __/ || (_) | (_) | |_ | | | |
#  \__,_|_| |_| |_|\___|_| |_|\__,_|     |_.__/|_|\__,_|\___|\__\___/ \___/ \__||_| |_|
#
# Author: Nick Clyde (clydedroid)
# dmenu support by: Layerex
#
# A script that generates a dmenu menu that uses bluetoothctl to
# connect to bluetooth devices and display status info.
#
# Inspired by networkmanager-dmenu (https://github.com/firecat53/networkmanager-dmenu)
# Thanks to x70b1 (https://github.com/polybar/polybar-scripts/tree/master/polybar-scripts/system-bluetooth-bluetoothctl)
#
# Depends on:
#   Arch repositories: dmenu, bluez-utils (contains bluetoothctl)

# Constants
divider="---------"
goback="Back"

# Checks if bluetooth controller is powered on
power_on() {
	if bluetoothctl show | grep -F -q "Powered: yes"; then
		return 0
	else
		return 1
	fi
}

# Toggles power state
toggle_power() {
	if power_on; then
		bluetoothctl power off
		show_menu
	else
		if rfkill list bluetooth | grep -F -q 'blocked: yes'; then
			rfkill unblock bluetooth && sleep 3
		fi
		bluetoothctl power on
		show_menu
	fi
}

# Checks if controller is scanning for new devices
scan_on() {
	if bluetoothctl show | grep -F -q "Discovering: yes"; then
		echo "Scan: on"
		return 0
	else
		echo "Scan: off"
		return 1
	fi
}

# Toggles scanning state
toggle_scan() {
	if scan_on; then
		kill "$(pgrep -F -f "bluetoothctl scan on")"
		bluetoothctl scan off
		show_menu
	else
		bluetoothctl scan on &
		echo "Scanning..."
		sleep 5
		show_menu
	fi
}

# Checks if controller is able to pair to devices
pairable_on() {
	if bluetoothctl show | grep -F -q "Pairable: yes"; then
		echo "Pairable: on"
		return 0
	else
		echo "Pairable: off"
		return 1
	fi
}

# Toggles pairable state
toggle_pairable() {
	if pairable_on; then
		bluetoothctl pairable off
		show_menu
	else
		bluetoothctl pairable on
		show_menu
	fi
}

# Checks if controller is discoverable by other devices
discoverable_on() {
	if bluetoothctl show | grep -F -q "Discoverable: yes"; then
		echo "Discoverable: on"
		return 0
	else
		echo "Discoverable: off"
		return 1
	fi
}

# Toggles discoverable state
toggle_discoverable() {
	if discoverable_on; then
		bluetoothctl discoverable off
		show_menu
	else
		bluetoothctl discoverable on
		show_menu
	fi
}

# Checks if a device is connected
device_connected() {
	device_info=$(bluetoothctl info "$1")
	if echo "$device_info" | grep -F -q "Connected: yes"; then
		return 0
	else
		return 1
	fi
}

# Toggles device connection
toggle_connection() {
	if device_connected "$1"; then
		bluetoothctl disconnect "$1"
		# device_menu "$device"
	else
		bluetoothctl connect "$1"
		# device_menu "$device"
	fi
}

# Checks if a device is paired
device_paired() {
	device_info=$(bluetoothctl info "$1")
	if echo "$device_info" | grep -F -q "Paired: yes"; then
		echo "Paired: yes"
		return 0
	else
		echo "Paired: no"
		return 1
	fi
}

# Toggles device paired state
toggle_paired() {
	if device_paired "$1"; then
		bluetoothctl remove "$1"
		device_menu "$device"
	else
		bluetoothctl pair "$1"
		device_menu "$device"
	fi
}

# Checks if a device is trusted
device_trusted() {
	device_info=$(bluetoothctl info "$1")
	if echo "$device_info" | grep -F -q "Trusted: yes"; then
		echo "Trusted: yes"
		return 0
	else
		echo "Trusted: no"
		return 1
	fi
}

# Toggles device connection
toggle_trust() {
	if device_trusted "$1"; then
		bluetoothctl untrust "$1"
		device_menu "$device"
	else
		bluetoothctl trust "$1"
		device_menu "$device"
	fi
}

# Prints a short string with the current bluetooth status
# Useful for status bars like polybar, etc.
print_status() {
	if power_on; then
		printf ''

		mapfile -t paired_devices < <(bluetoothctl paired-devices | grep -F Device | cut -d ' ' -f 2)
		counter=0

		for device in "${paired_devices[@]}"; do
			if device_connected "$device"; then
				device_alias="$(bluetoothctl info "$device" | grep -F "Alias" | cut -d ' ' -f 2-)"

				if [ $counter -gt 0 ]; then
					printf ", %s" "$device_alias"
				else
					printf " %s" "$device_alias"
				fi

				((counter++))
			fi
		done
		printf "\n"
	else
		echo ""
	fi
}

# A submenu for a specific device that allows connecting, pairing, and trusting
device_menu() {
	device=$1

	# Get device name and mac address
	device_name="$(echo "$device" | cut -d ' ' -f 3-)"
	mac="$(echo "$device" | cut -d ' ' -f 2)"

	# Build options
	if device_connected "$mac"; then
		connected="Connected: yes"
	else
		connected="Connected: no"
	fi
	paired=$(device_paired "$mac")
	trusted=$(device_trusted "$mac")
	options="$connected\n$paired\n$trusted\n$divider\n$goback\nExit"

	# Open dmenu menu, read chosen option
	chosen="$(echo -e "$options" | run_dmenu "$device_name")"

	# Match chosen option to command
	case $chosen in
		"" | "$divider")
			echo "No option chosen."
			;;
		"$connected")
			toggle_connection "$mac"
			;;
		"$paired")
			toggle_paired "$mac"
			;;
		"$trusted")
			toggle_trust "$mac"
			;;
		"$goback")
			show_menu
			;;
	esac
}

# Opens a dmenu menu with current bluetooth status and options to connect
show_menu() {
	# Get menu options
	if power_on; then
		power="Power: on"

		# Human-readable names of devices, one per line
		# If scan is off, will only list paired devices
		devices=$(bluetoothctl devices | sed -nE 's|^Device ([A-Z0-9:]*) (.*)|\2|p')

		# Get controller flags
		scan=$(scan_on)
		pairable=$(pairable_on)
		discoverable=$(discoverable_on)

		# Options passed to dmenu
		options="$devices\n$divider\n$power\n$scan\n$pairable\n$discoverable\nExit"
	else
		power="Power: off"
		options="$power\nExit"
	fi

	# Open dmenu menu, read chosen option
	chosen="$(echo -e "$options" | run_dmenu "Bluetooth")"

	# Match chosen option to command
	case $chosen in
		"" | "$divider")
			echo "No option chosen."
			;;
		"$power")
			toggle_power
			;;
		"$scan")
			toggle_scan
			;;
		"$discoverable")
			toggle_discoverable
			;;
		"$pairable")
			toggle_pairable
			;;
		*)
			device=$(bluetoothctl devices | grep -F "$chosen")
			# Open a submenu if a device is selected
			if [[ $device ]]; then device_menu "$device"; fi
			;;
	esac
}

original_args=("$@")

# dmenu command to pipe into. Extra arguments to dmenu-bluetooth are passed through to dmenu. This
# allows the user to set fonts, sizes, colours, etc.
run_dmenu() {
	bemenu "${original_args[@]}" --fn 'IBM Plex Sans 15' -i -l 10 -c -W 0.4 -B 3 -p "$1" --bdr="#$GLOBAL_ACCENT" --tf="#$GLOBAL_ACCENT" --hf="#$GLOBAL_ACCENT"
}

case "$1" in
	--status)
		print_status
		;;
	*)
		show_menu
		;;
esac
