#!/bin/sh

set -e

sanitize()
{
	s="${1#"${1%%[![:space:]]*}"}"
	s="${s%"${s##*[![:space:]]}"}"
	if [ "${#s}" -eq 0 ]
	then
		notify 'Invalid Input' 'Empty strings do not constitute valid input'
		exit 1
	fi
}

usage()
{
	cat <<-EOF >&2
	Usage: ${0##*/} add [-c]
	       ${0##*/} edit [-c]
	       ${0##*/} get
	       ${0##*/} rm [-c]
	EOF
	exit 1
}

xecho()
{
	printf '%s' "$@"
}

notify()
{
	if [ -t 2 ]
	then
		printf "%s: %s\n" "${0##*/}" "$2" >&2
	else
		notify-send -a "${0##*/}" "$1" "$2."
	fi
}

prompt()
{
	if [ -t 2 ]
	then
		printf '%s ' "$1"
		read -r s
	else
		s="`zenity --title=zenity --entry --text="$1"`"
	fi
	sanitize "$s"
}

xprompt()
{
	if [ -t 2 ]
	then
		printf '%s ' "$1"
		stty -echo
		trap 'stty echo' INT
		read -r s
		stty echo
		trap - INT
		echo
	else
		s="`zenity --title=zenity --password --text="$1"`"
	fi
	sanitize "$s"
}

add()
{
	shift

	while getopts 'c' opt
	do
		case $opt in
		c)
			add_c
			exit 0
			;;
		*)
			usage
			;;
		esac
	done

	readonly data="`enchive extract <"$VAULT"`"

	c="`xecho "$data" | jq -r 'keys | .[]' | osel`"

	prompt 'Password name:'
	n="$s"

	xecho "$data" \
		| jq -e --arg c "$c" --arg n "$n" '.[$c] | has($n) | not' >/dev/null || {
			notify 'Failed To Add Password' "The password ‘$n’ already exists"
			exit 1
		}

	if [ "$VAULT_2FA" = "$c" ]
	then
		xprompt 'Secret key:'; k="$s"
		prompt 'Digits:'; d="$s"
		prompt 'Period:'

		xecho "$data" \
			| jq --arg c "$c" \
			     --arg n "$n" \
			     --arg s "$k" \
			     --arg d "$d" \
			     --arg p "$s" \
				'.[$c] += {($n): {
					"secret": $s,
					"digits": ($d | tonumber),
					"period": ($p | tonumber)
				}}' \
			| enchive archive >"$VAULT"
		[ ! -t 2 ] && notify '2FA Key Added' \
			"The 2FA key ‘$n’ was added with the digit length ‘$d’ and period ‘$p’"
	else
		xprompt 'Password:'

		xecho "$data" \
			| jq --arg c "$c" --arg n "$n" --arg s "$s" '.[$c] += {($n): $s}' \
			| enchive archive >"$VAULT"
		[ ! -t 2 ] && notify 'Password Added' \
			"The password ‘$n’ was added to the category ‘$c’"
	fi
}

add_c()
{
	readonly data="`enchive extract <"$VAULT"`"

	prompt 'Category to create:'

	xecho "$data" | jq -e --arg s "$s" 'has($s) | not' >/dev/null || {
		notify 'Failed To Create Category' "The category ‘$s’ already exists"
		exit 1
	}

	xecho "$data" \
		| jq --arg s "$s" '. + {($s): {}}' \
		| enchive archive >"$VAULT"
	[ ! -t 2 ] && \
		notify 'Category Created' "The password category ‘$s’ was created"
}

get()
{
	readonly data="`enchive extract <"$VAULT"`"

	c="`xecho "$data" | jq -r 'keys | .[]' | osel`"
	o="`xecho "$data" | jq -r --arg c "$c" '.[$c] | keys | .[]' | osel`"

	xecho "$data" | if [ "$VAULT_2FA" = "$c" ]
	then
		eval "`jq -r --arg c "$c" --arg o "$o" '
		   .[$c]
		   | .[$o]
		   | "totp -d"
		   + (.digits | tostring)
		   + " -p"
		   + (.period | tostring)
		   + " "
		   + .secret
		'`" \
			| wl-copy -no \
			&& [ ! -t 2 ] \
			&& notify '2FA Code Copied To The Clipboard' \
			"The 2FA code for ‘$o’ was copied to the clipboard"
	else
		jq -r --arg c "$c" --arg o "$o" '.[$c] | .[$o]' \
			| wl-copy -no \
			&& [ ! -t 2 ] \
			&& notify 'Password Copied To The Clipboard' \
			"The password for ‘$o’ was copied to the clipboard"
	fi
}

rm_()
{
	shift

	while getopts 'c' opt
	do
		case $opt in
		c)
			rm_c
			exit 0
			;;
		*)
			usage
			;;
		esac
	done

	readonly data="`enchive extract <"$VAULT"`"

	c="`xecho "$data" | jq -r 'keys | .[]' | osel`"
	n="`xecho "$data" | jq -r --arg c "$c" '.[$c] | keys | .[]' | osel`"

	xecho "$data" \
		| jq --arg c "$c" --arg n "$n" 'del(.[$c] | .[$n])' \
		| enchive archive >"$VAULT"
	[ ! -t 2 ] && notify 'Removed Password' \
		"The password ‘$n’ was removed from the category ‘$c’"
}

rm_c()
{
	readonly data="`enchive extract <"$VAULT"`"

	c="`xecho "$data" | jq -r 'keys | .[]' | osel`"

	xecho "$data" \
		| jq -e --arg c "$c" '.[$c] | length == 0' >/dev/null || {
			notify 'Failed To Remove Category' "The category ‘$c’ is not empty"
			exit 1
		}

	xecho "$data" \
		| jq --arg c "$c" 'del(.[$c])' \
		| enchive archive >"$VAULT"
	[ ! -t 2 ] && notify 'Removed Category' "The category ‘$c’ was removed"
}

edit()
{
	shift

	while getopts 'c' opt
	do
		case $opt in
		c)
			edit_c
			exit 0
			;;
		*)
			usage
			;;
		esac
	done

	readonly data="`enchive extract <"$VAULT"`"

	c="`xecho "$data" | jq -r 'keys | .[]' | osel`"
	n="`xecho "$data" | jq -r --arg c "$c" '.[$c] | keys | .[]' | osel`"

	if [ "$VAULT_2FA" = "$c" ]
	then
		xprompt 'Secret key:'; k="$s"
		prompt 'Digits:'; d="$s"
		prompt 'Period:'

		xecho "$data" \
			| jq --arg c "$c" \
			     --arg n "$n" \
			     --arg s "$k" \
			     --arg d "$d" \
			     --arg p "$s" \
				'.[$c] += {($n): {
					"secret": $s,
					"digits": ($d | tonumber),
					"period": ($p | tonumber)
				}}' \
			| enchive archive >"$VAULT"
		[ ! -t 2 ] && notify '2FA Key Added' \
			"The 2FA key ‘$n’ was added with the digit length ‘$d’ and period ‘$p’"
	else
		xprompt 'Password:'
		xecho "$data" \
			| jq --arg c "$c" --arg n "$n" --arg s "$s" '.[$c] += {($n): $s}' \
			| enchive archive >"$VAULT"
		[ ! -t 2 ] && notify 'Password Edit' \
			"The password ‘$n’ in the category ‘$c’ was changed"
	fi
}

edit_c()
{
	readonly data="`enchive extract <"$VAULT"`"

	c="`xecho "$data" | jq -r 'keys | .[]' | osel`"
	prompt 'Category name:'
	xecho "$data" \
		| jq --arg o "$c" --arg n "$s" \
			 'with_entries(if .key == $o then .key = $n else . end)' \
		| enchive archive >"$VAULT"
	[ ! -t 2 ] && notify 'Category Edit' "The category ‘$c’ was renamed"
}

raw()
{
	shift
	enchive extract <"$VAULT" \
		| jq --arg c "$1" --arg n "$2" -r '.[$c] | .[$n]'
}

: ${VAULT_2FA:="2fa"}
: ${VAULT_HOME:=${XDG_DATA_HOME:-$HOME/.local/share}/vault}
readonly VAULT="${VAULT_HOME}/vault.sec"
export OSEL_GUI_FLAGS='-Oalphabetical'

[ $# -eq 0 ] && usage

case "$1" in
add)
	add "$@"
	;;
edit)
	edit "$@"
	;;
get)
	get
	;;
raw)
	raw "$@"
	;;
rm)
	rm_ "$@"
	;;
*)
	usage
	;;
esac