#!/usr/bin/env bash

VERSION="24.1.0"

set -euo pipefail

FLAG_AUTOTYPE=0
FLAG_COPY=0
FLAG_FILEISUSER=0
FLAG_ONLYPASSWORD=0
FLAG_SQUASH=0
FLAG_TYPE=0
FLAG_HELP=0
FLAG_HOTKEYS_SUPPORTED=1
FLAG_CASE_INSENSITIVE=0

CMD_COPY="${CMD_COPY:-"wl-copy"}"
CMD_TYPE="${CMD_TYPE:-"wtype -"}"

# We expect to find these fields in pass(1)'s output
PASS_FIELD_USERNAME="${PASS_FIELD_USERNAME:-"username"}"

WOFI_PASS_AUTOTYPE="${WOFI_PASS_AUTOTYPE:-":username :tab :password"}"
WOFI_PASS_DELAY=${WOFI_PASS_DELAY:-2}
WOFI_PASS_AUTO_ENTER="${WOFI_PASS_AUTO_ENTER:-"false"}"

HOTKEY_USERNAME="Alt-u"
HOTKEY_PASSWORD="Alt-p"
HOTKEY_AUTOTYPE="Alt-a"
HOTKEY_OTP="Alt-o"
HOTKEY_USERNAME_RET=10
HOTKEY_PASSWORD_RET=11
HOTKEY_AUTOTYPE_RET=12
HOTKEY_OTP_RET=13

FIELD_OTP_TAG="<<--OTP-->>"

function _wofi() {
    wofi "${@}"
}

function _pass() {
    if [ -n "${PASSWORD_STORE_DIR:-""}" ]; then
        PASSWORD_STORE_DIR="${PASSWORD_STORE_DIR}" pass "${@}"
    else
        pass "${@}"
    fi
}

function _trim() {
    local var="${*}"
    # remove leading whitespace characters
    var="${var#"${var%%[![:space:]]*}"}"
    # remove trailing whitespace characters
    var="${var%"${var##*[![:space:]]}"}"
    printf '%s' "${var}"
}

function generate_otp() {
    local -r passname="${1}"
    pass otp "${passname}" | tail -n1 | { IFS= read -r pass; printf %s "${pass}"; }
}

function parse_fields() {
    local field_list
    local has_username=0
    local -r passname="${1}"
    mapfile -t entries < <(_pass show "${passname}" | tail -n +2 )
    local line=""
    local fields=()

    for entry in "${entries[@]}"; do
        if [[ "${entry}" == "autotype_always" ]]; then
            fields+=("${entry}")
        else
            field="$(echo "${entry}" | cut -d: -f1 -s)"
            [ -n "${field}" ] && fields+=("${field}")
        fi
    done

    field_list+=("password")
    if [ ${FLAG_FILEISUSER} -eq 1 ]; then
        has_username=1
        field_list+=( "${PASS_FIELD_USERNAME}" )
    fi
    for line in "${fields[@]}"; do
        if [ "${line}" = "${PASS_FIELD_USERNAME}" ]; then
            if [ ${has_username} -eq 0 ]; then
                has_username=1
                field_list+=( "${line}" )
            fi
        elif [ "${line}" = "otpauth" ]; then
            field_list+=( "${FIELD_OTP_TAG}" )
        elif [ "${line}" = "autotype_always" ]; then
            FLAG_AUTOTYPE=1
        else
            field_list+=( "${line}" )
        fi
    done

    if [ ${FLAG_TYPE} -eq 1 ] && [ ${has_username} -eq 1 ]; then
        field_list=("autotype" "${field_list[@]}")
    fi

    printf "%s\n" "${field_list[@]}"
}

function pass_field() {
    local -r passname="${1}"
    shift
    _trim "$(_pass show "${passname}" | tail -n+2 | grep "^${*}:.*$" | cut -d: -f1 -s --complement)"
}

function pass_get() {
    local -r passname="${1}"
    local -r field="${2}"
    if [ "${field}" = "password" ]; then
        _pass show "${passname}" | { IFS= read -r pass; printf %s "${pass}"; }
    elif [ "${field}" = "${FIELD_OTP_TAG}" ]; then
        generate_otp "${passname}"
    elif [ ${FLAG_FILEISUSER} -eq 1 ] && [ "${field}" = "${PASS_FIELD_USERNAME}" ]; then
        printf %s "${passname##*/}"
    else
        pass_field "${@}"
    fi
}

function usage() {
    cat <<EOF
    Usage:
    wofi-pass [options]

    Options:
        -a, --autotype    autotype whatever entry is chosen
        -c, --copy=[cmd]  copy to clipboard. Defaults to wl-copy if no cmd is given.
        -f, --fileisuser  use the name of the password file as username
        -h, --help        show this help message
        -s, --squash      don't show field choice if password file only contains password
        -t, --type=[cmd]  type the selection instead of copying to clipboard.
                          Defaults to wtype if no cmd is given.
        -k, --nohotkey    Disable hotkey support. By default hotkeys are supported.

    wofi-pass version ${VERSION}
EOF
}

get_config_file () {
    local config_base_dir="${XDG_CONFIG_HOME:-"${HOME}/.config"}"
    readonly config_base_dir

    config_files=("${WOFI_PASS_CONFIG:-""}"
                  "${config_base_dir}/wofi-pass/config"
                  "/etc/wofi-pass.conf")

    # return the first config file with a valid path
    for config in "${config_files[@]}"; do
        if [[ -n "${config}" && -f "${config}" ]]; then
            printf "%s" "${config}"
            return
        fi
    done
}

load_config_file () {
    config_file="$(get_config_file)"
    if [[ -n "${config_file}" ]]; then
        #shellcheck source=wofi-pass.conf
        source "${config_file}"
    fi
}

function parse_arguments() {
    OPTS="$(getopt --options ac::fhsit::k --longoptions autotype,copy::,fileisuser,help,squash,type::,nohotkey,insensitive -n 'wofi-pass' -- "${@}")"
    eval set -- "${OPTS}"
    while true; do
        case "${1}" in
            -a | --autotype) FLAG_AUTOTYPE=1; shift;;
            -c | --copy)
                FLAG_COPY=1
                [ -n "${2}" ] && CMD_COPY="${2}"
                shift 2;;
            -f | --fileisuser) FLAG_FILEISUSER=1; shift;;
            -h | --help) FLAG_HELP=1; shift;;
            -i | --insensitive) FLAG_CASE_INSENSITIVE=1; shift;;
            -s | --squash) FLAG_SQUASH=1; shift;;
            -t | --type)
                FLAG_TYPE=1
                [ -n "${2}" ] && CMD_TYPE="${2}"
                shift 2;;
            -k | --nohotkey) FLAG_HOTKEYS_SUPPORTED=0; shift;;
            --) shift; break;;
            *) break;;
      esac
    done
}

function exit_if_help_flag_set() {
    if [ ${FLAG_HELP} -eq 1 ]; then
        usage >&2
        exit 0
    fi
}

function verify_flags() {
    if [ ${FLAG_TYPE} -eq 1 ] && [ ${FLAG_COPY} -eq 1 ]; then
        printf "copy and type cannot be used at same time. Please pass only one.\n" >&2
        exit 1
    elif [ ${FLAG_TYPE} -eq 0 ] && [ ${FLAG_COPY} -eq 0 ]; then
        printf "neither -c/--copy or -t/--type passed. Defaulting to copying with %s.\n" "${CMD_COPY}" >&2
    fi
}

function get_passname_from_menu() {
    local -r pass_dir="${PASSWORD_STORE_DIR:-${HOME}/.password-store}"
    local password_files
    password_files="$(find "${pass_dir}" -name "*.gpg" | sed "s|^${pass_dir}\/\(.*\)\.gpg$|\1|" | sort)"
    readonly password_files
    local passname
    local wofi_ret
    local wofi_args=()

    wofi_args+=("--dmenu")
    if [ ${FLAG_CASE_INSENSITIVE} -eq 1 ]; then
        wofi_args+=("--insensitive")
    fi
    if [ ${FLAG_HOTKEYS_SUPPORTED} -eq 1 ]; then
        [ -n "${HOTKEY_USERNAME}" ] && wofi_args+=("-D" "key_custom_0=${HOTKEY_USERNAME}")
        [ -n "${HOTKEY_PASSWORD}" ] && wofi_args+=("-D" "key_custom_1=${HOTKEY_PASSWORD}")
        [ -n "${HOTKEY_AUTOTYPE}" ] && wofi_args+=("-D" "key_custom_2=${HOTKEY_AUTOTYPE}")
        [ -n "${HOTKEY_OTP}" ] && wofi_args+=("-D" "key_custom_3=${HOTKEY_OTP}")
    fi

    passname="$(printf '%s\n' "${password_files}" | _wofi "${wofi_args[@]}")"
    wofi_ret=${?}

    printf "%s" "${passname}"
    return ${wofi_ret}
}

function get_field_from_menu() {
    local field=""
    local field_count
    local -r passname="${1}"
    local -r hotkey="${2:-0}"

    if [ "${hotkey}" -eq ${HOTKEY_USERNAME_RET} ]; then
        field="${PASS_FIELD_USERNAME}"
    elif [ "${hotkey}" -eq ${HOTKEY_PASSWORD_RET} ]; then
        field="password"
    elif [ "${hotkey}" -eq ${HOTKEY_AUTOTYPE_RET} ] && [ ${FLAG_TYPE} -eq 1 ]; then
        field="autotype"
    elif [ "${hotkey}" -eq ${HOTKEY_OTP_RET} ]; then
        field="${FIELD_OTP_TAG}"
    fi

    if [ -n "${field}" ]; then
        printf "%s" "${field}"
        return
    fi

    mapfile -t field_list < <(parse_fields "${passname}")
    field_count=${#field_list[@]}
    readonly field_list
    readonly field_count
    if [ ${FLAG_SQUASH} -eq 1 ] && [ "${field_count}" -le 1 ]; then
        printf "password"
        FLAG_ONLYPASSWORD=1
    elif [ ${FLAG_AUTOTYPE} -ne 1 ]; then
        printf "%s" "$(printf '%s\n' "${field_list[@]}" | _wofi --dmenu)"
    fi
}

function get_output_cmd() {
    if [ ${FLAG_TYPE} -eq 1 ]; then
        printf "%s" "${CMD_TYPE}"
    else
        printf "%s" "${CMD_COPY}"
    fi
}

function press_key() {
    local -r key="${1}"
    wtype -P "${key}" -p "${key}"
}

function output_autotype() {
    local -r passname="${1}"
    local -r cmd_output="${2}"
    local password
    password="$(pass_get "${passname}" "password")"
    readonly password
    local autotype

    autotype="$(pass_get "${passname}" "autotype")"
    [ -z "${autotype}" ] && autotype="${WOFI_PASS_AUTOTYPE}"
    readonly autotype

    for word in ${autotype}; do
        case "${word}" in
            ":tab") [ ${FLAG_TYPE} -eq 1 ] && press_key "Tab" || printf "\t";;
            ":space") [ ${FLAG_TYPE} -eq 1 ] && press_key "space" || printf " ";;
            ":enter") [ ${FLAG_TYPE} -eq 1 ] && press_key "Return" || printf "\n";;
            ":delay") [ ${FLAG_TYPE} -eq 1 ] && sleep "${WOFI_PASS_DELAY}";;
            ":otp") printf "%s" "$(generate_otp "${passname}")" | ${cmd_output};;
            ":password") printf "%s" "${password}" | ${cmd_output};;
            ":username") printf "%s" "$(pass_get "${passname}" "${PASS_FIELD_USERNAME}")" | ${cmd_output};;
            ":path") printf "%s" "${passname##*/}" | ${cmd_output};;
            *) printf "%s" "$(pass_get "${passname}" "${word}")" | ${cmd_output}
        esac
    done
    [[ "${WOFI_PASS_AUTO_ENTER}" != "false" ]] && press_key "Return"
}

function output_field() {
    local cmd_output
    local -r passname="${1}"
    local -r field="${2}"
    cmd_output="$(get_output_cmd)"

    if [ ${FLAG_AUTOTYPE} -eq 1 ] || [ "${field}" = "autotype" ]; then
        # check if we are autotyping a password-only file
        if [ ${FLAG_ONLYPASSWORD} -eq 1 ]; then
            local -r password="$(pass_get "${passname}" "password")"
            printf '%s\n' "${password}" | ${cmd_output}
        else
            output_autotype "${passname}" "${cmd_output}"
        fi
    else
        pass_get "${passname}" "${field}" | ${cmd_output}
    fi
}

function main() {
    local passname
    local field
    local hotkey
    local ret

    load_config_file

    parse_arguments "${@}"

    exit_if_help_flag_set

    verify_flags

    ret=0
    passname="$(get_passname_from_menu)" || ret=${?}
    if [ ${ret} -eq 0 ] || [ ${ret} -ge 10 ] && [ ${ret} -le 13 ]; then
        hotkey=${ret}
    else
        exit 1
    fi
    [ -n "${passname}" ] || exit 1

    field="$(get_field_from_menu "${passname}" "${hotkey}")"

    output_field "${passname}" "${field}"
}

if [[ -z "${WOFI_PASS_TESTING:-""}" ]]; then
    main "${@}"
fi
