#!/bin/bash

# Code belongs to https://github.com/carnager/rofi-pass/
# Copyright (C) 2019 carnager

# rofi wrapper. Add custom settings here.
_rofi() {
  rofi -dmenu -no-auto-select -i "$@" -theme /themes/dmenu
}

# default settings
backend=xdotool
dotool_delay=20
daemon_wait=2
autotype_delay=2
key_autotype="Return"
key_usertype="Alt+2"
key_passtype="Alt+3"
key_actions="Alt+a"
key_clipboard="Alt+1"
key_fieldtype="Return"

# read config file
get_config_file() {
  configs=("$ROFI_PASS_CONFIG"
    "$HOME/.config/rofi-pass/rofi-gopass.conf"
    "/etc/rofi-gopass.conf")

  # return the first config file with a valid path
  for config in "${configs[@]}"; do
    # '! -z' is needed in case ROFI_PASS_CONFIG is not set
    if [[ ! -z "${config}" && -f "${config}" ]]; then
      printf "%s" "$config"
      return
    fi
  done
}

# Make sure ESC will always end the programm.
# Call this function with "exit_check $?" after each rofi call.
exit_check() {
  exit_value=$1
  if [[ "${exit_value}" == "1" ]]; then
    exit
  fi
}

clipboard() {
  local entry
  local key
  local value
  entry="${1}"
  key="${2}"
  value="$(gopass show "${entry}" "${key}")"
  printf '%s' "${value}" | xclip -sel clip
  notify-send "rofi-gopass" "Copied ${key} to clipboard\nClearing in 45 seconds."
  (
    sleep 45
    printf '%s' "" | xclip
    printf '%s' "" | xclip -selection clipboard | notify-send "rofi-gopass" "Clipboard cleared"
  ) &
  exit
}

_ydotoold() {
  if ! pgrep -x "ydotoold" >/dev/null; then
    # ydotoold blocks the terminal, so we need to background it.
    # Sadly this way we never know when the process finished starting up.
    # Until ydotoold receives proper daemonizing we add a sleep value here.
    ydotoold &
    sleep "${daemon_wait}"
  fi
}

_dotool() {
  local mode
  local key
  mode="${1}"
  key="${2:-null}"
  case "${mode}" in
  "type")
    case "${backend}" in
    "xdotool") xdotool type --delay "${dotool_delay}" --file - ;;
    "ydotool")
      _ydotoold
      ydotool type --delay "${dotool_delay}" --file -
      ;;
    esac
    ;;
  "key")
    case "${backend}" in
    "xdotool") xdotool key "${key}" ;;
    "ydotool")
      _ydotoold
      ydotool key "${key}"
      ;;
    esac
    ;;
  esac
}

list_passwords() {
  gopass list --flat
}

autopass() {
  local entry
  local autotype
  entry="${1}"
  autotype="$(gopass show "${entry}" autotype)"
  autotype="${autotype:-username :tab pass}"

  for word in ${autotype}; do
    case "$word" in
    ":tab") _dotool key Tab ;;
    ":space") _dotool key " " ;;
    ":delay") sleep "${autotype_delay}" ;;
    ":enter") _dotool key enter ;;
    "pass") printf '%s' "$(gopass show --password "${entry}")" | _dotool type ;;
    *) printf '%s' "$(gopass show "${entry}" "${word}")" | _dotool type ;;
    esac
  done
}

list_keys() {
  # gopass has no option to only list keys, so we need to build the list ourselves.
  local entry
  local keys
  entry="${1}"
  keys="$(gopass show "${entry}")"
  printf '%s\n' "${keys}" | while read -r line; do
    if [[ "${line}" == *": "* ]]; then
      printf '%s\n' "${line%: *}"
    fi
  done
}

edit_key() {
  local entry
  local keys
  entry="${1}"
  keys="$(list_keys "${entry}")"
  key_name=$(printf '%s\n' "${keys}" | _rofi -mesg "Enter new key or chose existing one")
  exit_check $?
  value_name=$(printf '%s' "" | _rofi -mesg "Enter Value for key \"${key_name}\"")
  exit_check $?
  if [[ -z "${key_name}" ]]; then
    printf '%s' "${value_name}" | gopass insert -a "${entry}" "${key_name}"
  else
    printf '%s' "${value_name}" | gopass insert "${entry}" "${key_name}"
  fi
}

# For dangerous operations call this function first. You can provide a message as argument.
# Example: confirm "Are you sure you want to delete entry?"
confirm() {
  local message
  message="${1}"
  confirm_content=(
    "Yes"
    "No")

  confirm_menu=$(printf '%s\n' "${confirm_content[@]}" | _rofi -mesg "${message}")
  exit_check $?
  case "${confirm_menu}" in
  "Yes") : ;;
  "No") exit ;;
  esac
}

custom_type() {
  local entry
  local keys
  entry="${1}"
  keys="$(list_keys "${entry}")"
  key_name=$(printf '%s\n' "${keys}" | _rofi -kb-accept-entry "" -no-custom -kb-custom-1 "${key_clipboard}" -kb-custom-2 "${key_fieldtype}" -mesg "${key_clipboard}: Copy to Clipboard | ${key_fieldtype}: Type Field")
  local exit_value=$?
  exit_check "${exit_value}"
  case "${exit_value}" in
  "10") clipboard "${entry}" "${key_name}" ;;
  "11")
    printf '%s' "$(gopass show "${entry}" "${key_name}")" | _dotool type
    exit
    ;;
  esac
}

do_menu() {
  local entry
  entry="${1}"
  action_menu_content=(
    "< Go Back"
    "---"
    "Show Fields"
    "Add/Edit Keys"
    "Generate New Password"
    "Delete Entry"
  )

  action_menu="$(printf '%s\n' "${action_menu_content[@]}" | _rofi -no-custom -mesg "Selected Entry: ${entry}" -p '> ')"
  exit_value=$?
  exit_check "${exit_value}"

  case "${action_menu}" in
  "< Go Back") main ;;
  "Show Fields") custom_type "${entry}" ;;
  "Add/Edit Keys") edit_key "${entry}" ;;
  "Delete Entry")
    confirm "Delete ${entry}?"
    gopass rm -f "${entry}"
    ;;
  "Generate New Password")
    confirm "Generate a new password for ${entry}?"
    gopass generate -f "${entry}"
    ;;
  esac
}

main() {
  entry="$(list_passwords | _rofi -kb-accept-entry "" -kb-custom-1 "${key_autotype}" -kb-custom-2 "${key_usertype}" -kb-custom-3 "${key_passtype}" -kb-custom-4 "${key_actions}" -mesg "${key_autotype}: Autotype | ${key_usertype}: Type User | ${key_passtype}: Type Pass | ${key_actions}: More Actions")"
  exit_value=$?
  exit_check "${exit_value}"
  case "${exit_value}" in
  "10")
    autopass "${entry}"
    exit
    ;;
  "11")
    printf '%s' "$(gopass show "${entry}" username)" | _dotool type
    exit
    ;;
  "12")
    printf '%s' "$(gopass show --password "${entry}")" | _dotool type
    exit
    ;;
  esac
  do_menu "${entry}"
}

main