#!/usr/bin/env bash
#
# Original inspiration from https://github.com/carnager/rofi-pass/

# selector wrapper
# uses rofi if found, or dmenu if found, complains if no selector available
# passes along any options given to main script
rofi_opts=("$@")
_rofi() {
  if type rofi 1>/dev/null 2>/dev/null; then
    rofi -dmenu -no-auto-select -i "${rofi_opts[@]}" "$@" -p "Entry"
  elif type dmenu 1>/dev/null 2>/dev/null; then
    dmenu -i "${rofi_opts[@]}" "$@" -p "Entry"
  else
    exist rofi critical "rofi-gopass" || exit 0
  fi
}

# parse, see https://unix.stackexchange.com/a/331965/8541
_parse_config() {
  (grep -E "^$2=" -m 1 "$1" 2>/dev/null || printf "VAR=__UNDEFINED__\n") | head -n1 | cut -d '=' -f 2-
}

# read config file
get_config() {
  local locations=(
    "$RGP_CONFIGURATION_FILE"
    "${XDG_CONFIG_HOME:-$HOME/.config}/rofi-gopass/rofi-gopass.conf"
    "$HOME/.rofi-gopass.conf"
    "/etc/rofi-gopass.conf"
  )

  # return the first config file with a valid path
  for config in "${locations[@]}"; do
    if [[ -n "$config" && -f "$config" ]]; then
      # see if the config has been given a value
      local val
      val="$(_parse_config "$config" "$1")"
      break
    fi
  done

  # if there was a config file but no value
  # or there was no config file at all
  if [ "$val" = "__UNDEFINED__" ] || [ -z "$val" ]; then
    val="$2"
  fi
  printf -- "%s" "$val"
}

set_defaults() {
  # The location of the rofi-gopass config file
  # ROFI_PASS_CONFIGURATION_FILE="~/.config/rofi-gopass"
  # set options, leaving already set environment variables intact
  # try to read any settings from config files
  KEY_AUTOFILL="${RGP_KEY_AUTOFILL:-$(get_config KEY_AUTOFILL Return)}"
  KEY_ENTRY_OPEN="${RGP_KEY_ENTRY_OPEN:-$(get_config KEY_ENTRY_OPEN Alt+Return)}"
  KEY_FILL_USER="${RGP_KEY_FILL_USER:-$(get_config KEY_FILL_USER Alt+u)}"
  KEY_CLIP_USER="${RGP_KEY_CLIP_USER:-$(get_config KEY_CLIP_USER Ctrl+Alt+u)}"
  KEY_FILL_PASS="${RGP_KEY_FILL_PASS:-$(get_config KEY_FILL_PASS Alt+p)}"
  KEY_CLIP_PASS="${RGP_KEY_CLIP_PASS:-$(get_config KEY_CLIP_PASS Ctrl+Alt+p)}"
  KEY_ENTRYMENU_FILL="${RGP_KEY_ENTRYMENU_FILL:-$(get_config KEY_ENTRYMENU_FILL Return)}"
  KEY_ENTRYMENU_CLIP="${RGP_KEY_ENTRYMENU_CLIP:-$(get_config KEY_ENTRYMENU_CLIP Alt+Return)}"
  KEY_ENTRYMENU_SHOWFIELD="${KEY_ENTRYMENU_SHOWFIELD:-$(get_config KEY_ENTRYMENU_SHOWFIELD Alt+s)}"
  KEY_ENTRYMENU_QUIT="${RGP_KEY_ENTRYMENU_QUIT:-$(get_config KEY_ENTRYMENU_QUIT Alt+BackSpace)}"

  AUTOFILL_BACKEND="${RGP_BACKEND:-$(get_config AUTOFILL_BACKEND xdotool)}"
  AUTOFILL_CHAIN="${RGP_AUTOENTRY_CHAIN:-$(get_config AUTOFILL_CHAIN 'username :tab password')}"
  AUTOFILL_DELAY="${RGP_AUTOENTRY_DELAY:-$(get_config AUTOFILL_DELAY 30)}"
  GOPASS_USERNAME_FIELD="${RGP_GOPASS_USERNAME_FIELD:-$(get_config GOPASS_USERNAME_FIELD 'username user login')}"
}

# exit on escape pressed
# rofi returns exit code 1 on esc
exit_check() {
  [ "$1" -eq 1 ] && exit
}

# simply return a list of all passwords in gopass store
# TODO choose password store
# TODO only show website names (+ folder names), and account names for multiple accounts on one site
list_passwords() {
  gopass list --flat
}

# return password for argument passed
show_password() {
  gopass show -f --password "$1"
}

# send password to clipboard
clip_password() {
  gopass show -c "$1"
}

# attempt to return the field specified
# attempts all (space separated) fields until the
# first one successfully returned
_gp_get_field() {
  local gp_entry="$1"
  local gp_field="$2"
  local clip="$3"

  for key in $gp_field; do
    # return on first successfully returned key
    if [ -n "$clip" ]; then
      gopass show -c "$gp_entry" "$key" && break
    else
      gopass show -f "$gp_entry" "$key" && break
    fi
  done
}

# return username for argument passed
show_username() {
  _gp_get_field "$1" "${GOPASS_USERNAME_FIELD}"
}

clip_username() {
  _gp_get_field "$1" "${GOPASS_USERNAME_FIELD}" "-c"
}

show_field() {
  _gp_get_field "$1" "$2"
}

clip_field() {
  _gp_get_field "$1" "$2" "-c"
}

list_fields() {
  gopass show -f "$1" | tail -n+2
}

# invoke the dotool to type inputs
_type() {
  local tool="${AUTOFILL_BACKEND}"
  local toolmode="$1"
  local key="$2"

  "$tool" "$toolmode" --delay "${AUTOFILL_DELAY}" "$key"
}

# automatically fill out fields
# transform special chain entries into valid dotool commands
autofill() {
  local selected="${1}"
  local autoentry_chain="${2}"

  for part in $autoentry_chain; do
    case "$part" in
    ":tab") _type key Tab ;;
    ":return") _type key Return ;;
    ":space") _type key space ;;
    "username") _type type "$(show_username "$selected")" ;;
    "password") _type type "$(show_password "$selected")" ;;
    ":direct") _type type "$selected" ;;
    *) printf '%s' "$selected" ;;
    esac
  done
}

# opens a menu for the specified gopass entry, containing its individual fields
entrymenu() {
  local entry="$1"
  local deobfuscate="$2"
  local k_entrymenu_fill="${KEY_ENTRYMENU_FILL}"
  local k_entrymenu_clip="${KEY_ENTRYMENU_CLIP}"
  local k_entrymenu_showfield="${KEY_ENTRYMENU_SHOWFIELD}"
  local k_entrymenu_quit="${KEY_ENTRYMENU_QUIT}"

  local pass
  if [ "$deobfuscate" = "true" ]; then
    pass="$(show_password "$entry")"
  else
    pass="(hidden)"
  fi

  local field
  field=$(
    printf "password: %s\n%s" "$pass" "$(list_fields "$entry")" |
      _rofi \
        -kb-accept-entry "" \
        -kb-custom-1 "$k_entrymenu_fill" \
        -kb-custom-2 "$k_entrymenu_clip" \
        -kb-custom-3 "$k_entrymenu_quit" \
        -kb-custom-4 "$k_entrymenu_showfield" \
        -mesg " ᐊ $k_entrymenu_quit ᐊ | $k_entrymenu_fill: fill selection | $k_entrymenu_clip: clip selection | $k_entrymenu_showfield: reveal password"
  )
  exit_value=$?
  exit_check "$exit_value"

  # get field name
  field=${field%%:*}
  case "$exit_value" in
  "10")
    if [ "$field" = "password" ]; then
      autofill "$entry" "password"
    else
      autofill "$(show_field "$entry" "$field")" ":direct"
    fi
    exit 0
    ;;
  "11")
    if [ "$field" = "password" ]; then
      clip_password "$entry"
    else
      clip_field "$entry" "$field"
    fi
    exit 0
    ;;
  "12")
    main
    ;;
  "13")
    entrymenu "$entry" "true"
    ;;
  esac
}

main() {
  local autoentry_chain="${AUTOFILL_CHAIN}"
  local k_autofill="${KEY_AUTOFILL}"
  local k_fill_user="${KEY_FILL_USER}"
  local k_clip_user="${KEY_CLIP_USER}"
  local k_fill_pass="${KEY_FILL_PASS}"
  local k_clip_pass="${KEY_CLIP_PASS}"
  local k_submenu="${KEY_ENTRY_OPEN}"

  entry="$(
    list_passwords |
      _rofi -kb-accept-entry "" \
        -kb-custom-1 "$k_autofill" \
        -kb-custom-2 "$k_clip_user" \
        -kb-custom-3 "$k_clip_pass" \
        -kb-custom-4 "$k_fill_user" \
        -kb-custom-5 "$k_fill_pass" \
        -kb-custom-6 "$k_submenu" \
        -mesg "| $k_autofill: fill credentials | $k_submenu: open entry | $k_fill_user: fill username | $k_fill_pass: fill password | $k_clip_user: clip username | $k_clip_pass: clip password |"
  )"
  exit_value=$?

  exit_check "$exit_value"
  case "$exit_value" in
  "10")
    autofill "$entry" "$autoentry_chain"
    exit 0
    ;;
  "11")
    clip_username "$entry"
    exit 0
    ;;
  "12")
    clip_password "$entry"
    exit
    ;;
  "13")
    autofill "$entry" "username"
    exit
    ;;
  "14")
    autofill "$entry" "password"
    exit
    ;;
  "15")
    entrymenu "$entry"
    exit
    ;;
  esac
}

set_defaults
main