#!/bin/bash

# Compatibility executable script for applications running dmenu, which
# runs wofi in dmenu mode instead. Note: In many cases the arguments won't
# be compatible and this will fail, however the primary way scripts use
# dmenu is just for simple prompts, using the `-p` flag. and wofi does fully
# support this usage.

# Wrapper script for menu application independent prompts. This script will pick 
# the appropriate installed prompt tool (dmenu/rofi/wofi) and use run it accordingly
# to picked usage option(s).

help() {
  cat << EOF
usage: menuprompt
Menuprompt is a wrapper script for menu application independent prompt (dmenu/rofi/wofi)

options:
  -h, --help                show this help message and exit
  -p, --prompt [MESSAGE]    Prompt to be displayed in the menu
  -a, --app [MENU_APP]      Overrides the app picked by default, must be one of supported apps
  -v, --verbose             Show debug information (printed to stderr to retain pipeable stdout output)
  -vv, --very-verbose       Show even more debug information
  --dry-run                 Don't actually run the final menu command (only useful with --verbose)

actions:
  -c, --choose
      Basic pick prompt, picking from piped newline separated options
      The picked option will be printed to stdout.
      usage: printf 'No\nYes' | menuprompt -c -p 'Pick one: '
  -yn, --yesno
      Essentially an alias for printf 'No\nYes' | menuprompt -p "\$1", however instead of printing the result
      to stdout, this instead exits with code 0 on success, 1 on failure, or 2 on getting other input. This
      allows for easily combining it with other applications through chaining.
      example: menuprompt --yesno && poweroff or menuprompt -yn -p "Do you really want to quit?" && killall Hyprland
  -P, --password
      Password prompt - hides the input characters
      example: menuprompt -P -p "Enter password:" | xargs -I {} echo "Hah, got your password: {}"

supported menu applications and their actions:
  dmenu:
    - choose: supported
    - yesno: supported
    - password: supported, but requires password patch for -P argument
  wofi:
    - choose: supported
    - yesno: supported
    - password: supported, however setting --prompt won't have any effect
  rofi:
    - choose: supported
    - yesno: supported
    - password: not supported - no option for hidden input


passing custom arguments to menu app:
    Note that when passing extra arguments, you should always explicity specify the menu application to use
    as it's not guaranteed that all supported args will support given extra argument(s), nor that if they do
    their support is consistent and the same argument does the same thing.

    examples:
        Make dmenu run on the bottom of the screen:
        printf "A\nB\nC" menuprompt --app dmenu --choose -- -b

        Run wofi with 20% width on the yesno prompt:
        menuprompt --yesno -p "Do you really want to quit?" --app wofi -- --width=20%


exit codes:
  0:    Menu app ran successfully, action completed
  1-2:  Action specific
  3:    User error - invalid usage
  4:    Unable to pick menu app (no usable menu app installed?)
  5:    Internal menuprompt error, report this!
EOF
}

pick_menu_app() {
  if [ -n "$MENU_APP" ]; then
    if command -v "$MENU_APP" >/dev/null; then
      echo "$MENU_APP"
      return 0
    else
      >&2 echo "Specified menu app by (set by \$MENU_APP) isn't a valid command \`$MENU_APP\`"
      exit 3
    fi
  fi

  if [ "$XDG_SESSION_TYPE" = "x11" ]; then
    if command -v dmenu >/dev/null; then
      echo "dmenu"
    elif command -v rofi >/dev/null; then
      echo "rofi"
    else
      >&2 echo "Menu application for x11 not found, neither dmenu nor rofi available."
      exit 3
    fi

  elif [ "$XDG_SESSION_TYPE" = "wayland" ]; then
    if command -v wofi >/dev/null; then
      echo "wofi"
    else
      >&2 echo "Menu application for wayland not found, wofi not available."
      exit 3
    fi

  else
    >&2 echo "Unknown session type, expected \$XDG_SESSION_TYPE to be \`wayland\` or \`x11\`, got \`$XDG_SESSION_TYPE\`."
    exit 3
  fi
}

# Runs picked menu application with 
# $1    pipe STDIN if set to "1", runs normally if "0"
# $2    app to use (result of pick_menu_app)
# $3    prompt to be shown (can be empty)
# $4-   extra args to pass to given menu app
runmenu() {
  pipe_stdin="$1"
  app="$2"
  prompt="$3"
  shift 3

  if [ "$VERBOSITY" -ge 2 ]; then
    if [ $# -gt 0 ]; then
      >&2 echo "Making menu command for app: \`$app\`, with prompt: \`$prompt\` and extra args: \`$*\`"
    else
      >&2 echo "Making menu command for app: \`$app\`, with prompt: \`$prompt\` (no extra args)"
    fi
  fi

  if [ "$app" = "dmenu" ] && [ "$pipe_stdin" -eq 0 ]; then
    >&2 echo "Dmenu can only be used with piped STDIN (forgot --choose?)"
    exit 3
  fi

  args=()
  case "$app" in
    dmenu)
      args+=("-i")
      ;;
    wofi)
      args+=("--dmenu")
      ;;
    rofi)
      args+=("-dmenu")
      ;;
    *)
      >&2 echo "Unsupported menu app: $app"
      exit 4
      ;;
  esac

  if [ -n "$prompt" ]; then
    if [ "$app" = "wofi" ] && [ "$pipe_stdin" -eq 0 ]; then
      >&2 echo "WARNING: wofi prompt can only be displayed with piped STDIN (forgot --choose?)"
    fi
    args+=("-p" "$prompt")
  fi

  if [ "$VERBOSITY" -ge 1 ]; then
    >&2 echo "Generated command: \`$app ${args[*]} $*\`"
  fi

  if [ "$DRY_RUN" -ne 1 ]; then
    if [ "$pipe_stdin" -eq 1 ]; then
      cat | $app "${args[@]}" "$@"
    elif [ "$pipe_stdin" -eq 0 ]; then
      $app "${args[@]}" "$@"
      return $?
    else
      >&2 echo "Internal error, pipe_stdin set to invalid value: \`$pipe_stdin\`"
      exit 5
    fi
  else
    return 0
  fi
}

# $1    app to use (result of pick_menu_app)
# $2    prompt to be shown (can be empty)
# $2-   extra arguments to pass to given menu app
password() {
  app="$1"
  prompt="$2"
  shift 2

  if [ "$VERBOSITY" -ge 2 ]; then
    >&2 echo "Prompting for password..."
  fi

  if [ "$app" = "dmenu" ]; then
    if ! runmenu "0" "$app" "$prompt" -P "$@"; then
      >&2 echo "To use dmenu for passwords, you need to apply the password patch."
      exit 3
    fi
  elif [ "$app" = "wofi" ]; then
    runmenu "0" "$app" "$prompt" -P "$@"
  elif [ "$app" = "rofi" ]; then
    >&2 echo "Rofi doesn't support password/hidden input."
    exit 3
  else
    >&2 echo "Unsupported menu application picked: \`$app\`"
    exit 4
  fi
}

# $1    app to use (result of pick_menu_app)
# $2    prompt to be shown (can be empty)
# $3-   extra arguments to pass to the menu app
yesno() {
  app="$1"
  prompt="$2"
  shift 2

  if [ "$VERBOSITY" -ge 2 ]; then
    >&2 echo "Prompting for yesno..."
  fi

  temp="$(printf "No\nYes" | runmenu "1" "$app" "$prompt" "$@")"

  if [ "$VERBOSITY" -ge 2 ]; then
    >&2 echo "Yesno prompt answer: \`$temp\`"
  fi

  if [ "$temp" = "Yes" ]; then
    return 0
  elif [ "$temp" = "No" ]; then
    return 1
  else
    return 2
  fi
}

#@  - extra args to pass to picked menu app
run() {
  app="$(pick_menu_app)"

  if [ "$VERBOSITY" -ge 2 ]; then
    >&2 echo "ACTION: \`$ACTION\`"
    >&2 echo "PROMPT_ARG: \`$PROMPT_ARG\`"
    >&2 echo "picekd app: \`$app\`"
    >&2 echo "Extra args: \`$*\`"
  fi

  if [ -z "$ACTION" ]; then
    runmenu "0" "$app" "$PROMPT_ARG" "$@"
    return $?
  elif [ "$ACTION" = "choose" ]; then
    runmenu "1" "$app" "$PROMPT_ARG" "$@"
    return $?
  elif [ "$ACTION" = "yesno" ]; then
    yesno "$app" "$PROMPT_ARG" "$@"
    return $?
  elif [ "$ACTION" = "password" ]; then
    password "$app" "$PROMPT_ARG" "$@"
    return $?
  fi

  >&2 echo "Huh? How did you get here?"
  >&2 echo "Rerun this command with the --verbose flag and submit a report with the captured output."
  exit 5
}


ACTION=""
PROMPT_ARG=""
MENU_APP="${MENU_APP-""}"
VERBOSITY=0
DRY_RUN=0
while [ "$1" ]; do
  case "$1" in
    # options
    -h|--help)
      help
      exit 0
      ;;
    -a|--app)
      MENU_APP="$2"
      shift 2
      ;;
    -p|--prompt)
      PROMPT_ARG="$2"
      shift 2
      ;;
    -v|--verbose)
      VERBOSITY=1
      shift
      ;;
    -vv|--very-verbose)
      VERBOSITY=2
      shift
      ;;
    --dry-run)
      DRY_RUN=1
      shift
      ;;

    # actions
    -c|--choose)
      if [ -n "$ACTION" ]; then
        >&2 echo "Action already set, can't specify multiple actions"
        exit 3
      fi
      ACTION="choose"
      shift
      ;;
    -yn|--yesno)
      if [ -n "$ACTION" ]; then
        >&2 echo "Action already set, can't specify multiple actions"
        exit 3
      fi
      ACTION="yesno"
      shift
      ;;
    -P|--password)
      if [ -n "$ACTION" ]; then
        >&2 echo "Action already set, can't specify multiple actions"
        exit 3
      fi
      ACTION="password"
      shift
      ;;
    --)
      shift
      run "$@"
      exit $?
      ;;

    # err
    *) 
      >&2 echo "Unrecognized argument: $1"
      >&2 echo "use -h or --help for help"
      exit 4
      ;;
  esac
done

run