#!/usr/bin/env zsh
#
# A script that uses rook to provide autotyping to windows.
# Requires:
# - ripgrep, because some things are just easier with it
# - xdotool, for teh tap-tap-tap
# - rofi, for offering selections if there are multiple matches
# - xprop, because Luakit needs hacks to work properly. If you don't use Luakit,
#   you can (should?) delete the hacks -- it'll probably make things more robust.
# - yad, for providing info dialogs
#
# How this works: it grabs the window and uses rook to match against a record in
# the DB, returning the best match. The first line is the autotype sequence
# defined for the record; this may be the default, or one customized for the
# window. The script than parses out the key/value pairs into a associative
# array, and then parses the autotype sequence emitting keystrokes using
# xdotool. That's it.
#
# The big caveat here is that KeepassXC autotype sequences can be complex, and
# have a lot of keywords. Looking at the case switch near the bottom, you'll see
# that only a few are defined. It should be easy to add to this list -- it's not
# rocket science -- and I will as I need them; however, these cover 96% of what
# I need.
# 
# FIXME the sequences aren't rendered in some cases, e.g. TOTP fields. BACKSPACE, etc
# TODO Can we be smarter and read fields from e.g. a selected web page?
# TODO Check the client. If it's something like luakit, at least make sure we're in insert mode.

# The delay between character strokes, in ms
STROKE=150
## Get the window under the cursor
WID=$(xdotool getactivewindow)
## Search for the window title in the DB
NAME=$(xdotool getwindowname $WID)
# LUAKIT hack, because its behavior can be really stupid
APP=$(xprop -id ${WID} WM_CLASS | rg Luakit)
typeset -A TOKENS

function _rook() {
  rook "$@"
}
if type keyctl>/dev/null; then
  rp=$(keyctl list @s | grep rookpin | cut -d: -f1)
  if [[ -n "$rp" ]]; then
    function _rook() {
      rook -k "$@"
    }
  fi
fi

# TESTING:
#NAME="Login to paypal"
#function _rook() { cd ~/workspace/rook && go run . --socket rook.sock }

typeset -a LNS
typeset -a ENTRIES
while read -r line; do
  LNS+=("$line")
  E=$(<<<"$line" rg '^\d+\s+([^\s]*\s+.*$)' -or '$1') && ENTRIES+=("$E") && printf "added %s\n" "$E"
done < <(_rook match "$NAME")

if [[ ${#ENTRIES[@]} -gt 1 ]]; then
  E=$(printf "%s\n" "${ENTRIES[@]}" | rofi -dmenu -i -p "Which entry?")
elif [[ ${#ENTRIES[@]} -eq 0 ]]; then
  printf "nothing found\n"
  exit 1
else
  E="${ENTRIES[1]}"
fi

test -z "$E" && (printf "No entry chosen\n" | yad --text-info --button=Ok:0 --escape-ok ; exit 1)

################################################
# Parse out the key/values
################################################
# This escapes any special regexp characters in the string
E="$(<<<"$E" sed 's/[][\.|$(){}?+*^]/\\&/g')"
# This replaces any spaces with the regexp space character class
E="$(<<<"$E" | rg '\s+' -r '\s+')"
while read -r line; do
  if [[ $(<<<"$line" rg "^\d+\s+$E\$") ]]; then
    INREC=1
  elif [[ $INREC -eq 1 ]]; then
    <<<"$line" rg -q '^\d+\s+[^\s]*\s+[^\s]+' && break
    [[ -z "$SEQ" ]] && SEQ="$line" && continue
    if [[ -n "${line#*:}" ]]; then
      key=$(<<<${line%:*} tr '[a-z]' '[A-Z]')
      TOKENS+=(${key} ${line#*:})
    fi
  fi
done < <(printf "%s\n" "$LNS[@]")

[[ ${#TOKENS[@]} -eq 0 ]] && (printf "parsed no autotype data for\n%s" "${E}" | yad --text-info --button=Ok:0 --escape-ok ; exit 1) 
# Give the user a second to release any control keys
[[ ${#ENTRIES[@]} -eq 1 ]] && sleep 1
################################################
# Parse & execute the autotype sequence
################################################
typeset -a TUPLE
while [[ -n "$SEQ" ]]; do
  TUPLE=("${(@f)$(<<<$SEQ rg -o '(\{?[\w\s\d]+\}?)(.*)' -r $'${1}\n${2}')}")
  K="$(<<<${TUPLE[1]} rg -o '\{([\w\s\d]+)\}' -r '${1}')"
  if [[ -n "$K" ]]; then
    DELAY=$(<<<$K rg '^DELAY (\d+)' -or '$1')
    if [[ -n "$DELAY" ]]; then
      sleep $(dc -e "2k ${DELAY} 1000 /p")
      # LUAKIT This is a hack for Luakit, because it doesn't autofocus input fields.
      # A delay is almost always after an form submission and new page load, when
      # Luakit goes back into command mode. We key in "i" to go back into insert mode
      # and then backspace in case a field was already selected.
      [[ -n "$APP" ]] && xdotool key i && xdotool key BackSpace
    else
      case "$K" in
        TAB)
          xdotool key Tab
          ;;
        ENTER)
          # FIXME Enter doesn't seem to work properly, always, with web forms. Is there a Return?
          xdotool key Enter
          ;;
        BACKSPACE)
          xdotool key BackSpace
          ;;
        SPACE)
          xdotool key Space
          ;;
        ESC)
          xdotool key Escape
          ;;
        *)
          TXT=$(<<<"${TOKENS[$K]}" xargs)
          xdotool type --delay $STROKE "${TXT}"
          ;;
      esac
    fi
  else
    xdotool type --delay $DELAY "${SEQ}"
  fi
  SEQ=${TUPLE[2]}
done
