#!/bin/sh

set -eu

DBUS_NAME="org.freedesktop.Notifications"
DBUS_PATH="/org/freedesktop/Notifications"
DBUS_IFAC_DUNST="org.dunstproject.cmd0"
DBUS_IFAC_PROP="org.freedesktop.DBus.Properties"
DBUS_IFAC_FDN="org.freedesktop.Notifications"

die() {
	printf "%s\n" "${1}" >&2
	if [ -z "${DBUS_SESSION_BUS_ADDRESS:-}" ]; then
		echo "DBUS_SESSION_BUS_ADDRESS is blank. Is the D-Bus session configured correctly?" >&2
	fi
	exit 1
}

show_help() {
	# Below, each line starts with a tab character
	cat <<-EOH
	Usage: dunstctl <command> [parameters]
	Commands:
	  action                            Perform the default action, or open the
	                                    context menu of the notification at the
	                                    given position
	  close [ID]                        Close the last notification or the
	                                    notification with given ID
	  close-all                         Close all the notifications
	  context                           Open context menu
	  count [displayed|history|waiting] Show the number of notifications
	  history                           Display notification history (in JSON)
	  history-clear                     Delete all notifications from history
	  history-pop [ID]                  Pop the latest notification from
	                                    history or optionally the
	                                    notification with given ID
	  history-rm ID                     Remove the notification from
	                                    history with given ID.
	  is-paused [-e|--exit-code]        Check if pause level is greater than 0,
	                                    optionally with exit code instead of text output
	  set-paused true|false|toggle      Set the pause status
	  get-pause-level                   Get the current pause level
	  set-pause-level level             Set the pause level
	  rule name enable|disable|toggle   Enable or disable a rule by its name
	  rules [--json]                    Displays configured rules (optionally
	                                    in JSON)
	  reload [dunstrc ...]              Reload the settings of the running
	                                    instance, optionally with specific
	                                    config files (space/comma-separated)
	  debug                             Print debugging information
	  help                              Show help
	EOH
}

busctl_checked() {
	command -v busctl >/dev/null 2>/dev/null || die "Command busctl not found"
	busctl --user --json=pretty --no-pager call "${DBUS_NAME}" "${DBUS_PATH}" "${DBUS_IFAC_DUNST}" "$@"
}

dbus_send_checked() {
	dbus-send "$@" \
		|| die "Failed to communicate with dunst, is it running? Or maybe the version is outdated. You can try 'dunstctl debug' as a next debugging step."
}

method_call() {
	dbus_send_checked --print-reply=literal --dest="${DBUS_NAME}" "${DBUS_PATH}" "$@"
}

property_get() {
	dbus_send_checked --print-reply=literal --dest="${DBUS_NAME}" "${DBUS_PATH}" "${DBUS_IFAC_PROP}.Get" "string:${DBUS_IFAC_DUNST}" "string:${1}"
}

property_set() {
	dbus_send_checked --print-reply=literal --dest="${DBUS_NAME}" "${DBUS_PATH}" "${DBUS_IFAC_PROP}.Set" "string:${DBUS_IFAC_DUNST}" "string:${1}" "${2}"
}

command -v dbus-send >/dev/null 2>/dev/null || die "Command dbus-send not found"

case "${1:-}" in
	"action")
		method_call "${DBUS_IFAC_DUNST}.NotificationAction" "uint32:${2:-0}" >/dev/null
		;;
	"close")
		if [ $# -eq 1 ]; then
			method_call "${DBUS_IFAC_DUNST}.NotificationCloseLast" >/dev/null
		elif [ $# -eq 2 ]; then
			method_call "${DBUS_IFAC_FDN}.CloseNotification" "uint32:$2" >/dev/null
		else
			die "Please pass the right number of arguments. Close takes 0 or 1 arguments"
		fi
		;;
	"close-all")
		method_call "${DBUS_IFAC_DUNST}.NotificationCloseAll" >/dev/null
		;;
	"context")
		method_call "${DBUS_IFAC_DUNST}.ContextMenuCall" >/dev/null
		;;
	"count")
		[ $# -eq 1 ] || [ "${2}" = "displayed" ] || [ "${2}" = "history" ] || [ "${2}" = "waiting" ] \
			|| die "Please give either 'displayed', 'history', 'waiting' or none as count parameter."
		if [ $# -eq 1 ]; then
			property_get waitingLength   | ( read -r _ _ waiting;   printf "              Waiting: %s\n" "${waiting}" )
			property_get displayedLength | ( read -r _ _ displayed; printf "  Currently displayed: %s\n" "${displayed}" )
			property_get historyLength   | ( read -r _ _ history;   printf "              History: %s\n" "${history}")
		else
			property_get ${2}Length | ( read -r _ _ notifications; printf "%s\n" "${notifications}"; )
		fi
		;;
	"history-clear")
		method_call "${DBUS_IFAC_DUNST}.NotificationClearHistory" >/dev/null
		;;
	"history-pop")
		if [ "$#" -eq 1 ]
		then
			method_call "${DBUS_IFAC_DUNST}.NotificationShow" >/dev/null
		elif [ "$#" -eq 2 ]
		then
			method_call "${DBUS_IFAC_DUNST}.NotificationPopHistory" "uint32:${2:-0}" >/dev/null
		else
			die "Please pass the right number of arguments. History-pop takes 0 or 1 arguments"
		fi
		;;
	"history-rm")
		if [ $# -eq 2 ]; then
			method_call "${DBUS_IFAC_DUNST}.NotificationRemoveFromHistory" "uint32:${2}" >/dev/null
		else
			die "Please pass the right number of arguments. History-rm takes 1 arguments"
		fi
		;;
	"is-paused")
		exit=false
		case "${2:-}" in
			-e | --exit-code)
					exit=true
				;;
		esac
		property_get paused | {
			read -r _ _ paused
			if [ "$exit" = "false" ]; then
				printf "%s\n" "${paused}"
			elif [ "${paused}" = "false" ]; then
				exit 1
			fi
		}
		;;
	"set-paused")
		[ "${2:-}" ] \
			|| die "No status parameter specified. Please give either 'true', 'false' or 'toggle' as paused parameter."
		[ "${2}" = "true" ] || [ "${2}" = "false" ] || [ "${2}" = "toggle" ] \
			|| die "Please give either 'true', 'false' or 'toggle' as paused parameter."
		if [ "${2}" = "toggle" ]; then
			paused=$(property_get paused | ( read -r _ _ paused; printf "%s\n" "${paused}"; ))
			if [ "${paused}" = "true" ]; then
				property_set paused variant:boolean:false
			else
				property_set paused variant:boolean:true
			fi
		else
			property_set paused variant:boolean:"$2"
		fi
		;;
	"rules")
		case "${2:-}" in
			"" | --json)
				busctl_checked RuleList \
				| {
					if [ "${2:-}" = '--json' ]
					then
						cat
					else
						command -v jq >/dev/null 2>/dev/null || die "Command jq not found"
						jq --raw-output '.data[][] | ["[\(.name.data)]"], [to_entries[] | select(.key != "name") | "    \(.key) = \(.value.data)"] | join("\n")'
					fi
				} \
					|| die "Dunst is unreachable or the version is too old."
			;;
			*)
				die "Unknown format \"${2}\". Please use either \"--json\" or no option at all."
			;;
		esac
		;;
	"rule")
		[ "${2:-}" ] \
			|| die "No rule name parameter specified. Please give the rule name"
		case "${3:-}" in
			"disable")
				state=0
				;;
			"enable")
				state=1
				;;
			"toggle")
				state=2
				;;
			*)
				die "No valid rule state parameter specified. Please give either 'enable', 'disable' or 'toggle'"
				;;
		esac
		method_call "${DBUS_IFAC_DUNST}.RuleEnable" "string:${2:-1}" "int32:${state}" >/dev/null
		;;
	"get-pause-level")
		property_get pauseLevel | ( read -r _ _ paused; printf "%s\n" "${paused}"; )
		;;
	"set-pause-level")
		[ "${2:-}" ] \
			|| die "No status parameter specified. Please give a number as paused parameter."
		case "$2" in
			(*[!0123456789]*)
				die "Please give a number as paused level parameter." ;;
			([!123456789]?*)
				die "Please give a number without leading zeros as paused level parameter." ;;
			('')
				die "Please give a number as paused level parameter." ;;
		esac
		property_set pauseLevel variant:uint32:"$2"
		;;
	"help"|"--help"|"-h")
		show_help
		;;
	"debug")
		dbus-send --print-reply=literal --dest="${DBUS_NAME}" "${DBUS_PATH}" "${DBUS_IFAC_FDN}.GetServerInformation" >/dev/null 2>/dev/null \
			|| die "No notification manager is running."

		dbus-send --print-reply=literal --dest="${DBUS_NAME}" "${DBUS_PATH}" "${DBUS_IFAC_FDN}.GetServerInformation" \
			| (
					read -r name _ version _
					[ "${name}" = "dunst" ]
					printf "dunst version: %s\n" "${version}"
				) \
			|| die "Another notification manager is running. It's not dunst"

		dbus-send --print-reply=literal --dest="${DBUS_NAME}" "${DBUS_PATH}" "${DBUS_IFAC_DUNST}.Ping" >/dev/null 2>/dev/null \
			|| die "Dunst controlling interface not available. Is the version too old?"
		;;
	"history")
		busctl_checked NotificationListHistory \
			|| die "Dunst is not running or unreachable."
		;;
	"reload")
		shift
		method_call "${DBUS_IFAC_DUNST}.ConfigReload" "array:string:$(IFS=','; echo "$*")" >/dev/null
		;;
	"")
		die "dunstctl: No command specified. Use dunstctl help"
		;;
	*)
		die "dunstctl: unrecognized command '${1:-}'. Please consult the usage."
		;;
esac
