#!/bin/bash

###############################################################################
# Count Me In! v1.1 for Fatdog64 - A keystroke & mouse click counter		  #
# Copyright (C) JakeSFR 2018												  #
# License: GNU GPL Version 2												  #
# Requirements: bash >= 4, libinput, yad, stdbuf, pr						  #
###############################################################################

export TEXTDOMAIN=countmein
export OUTPUT_CHARSET=UTF-8

APPNAME="Count Me In! v1.1"

# Globals
declare -A KEYS
declare -A CLICKS

TOTAL_KEYS=0
TOTAL_CLICKS=0
TOTAL_WHEEL=0

LAST_UPTIME=0
UPTIME=0
TIMESTAMP=$(date +%s)

KEYS_EXP=''
CLICKS_EXP=''

PERSISTENCE=false


# Constants
G_D="$(gettext "d")"	# days
G_H="$(gettext "h")"	# hours
G_M="$(gettext "m")"	# minutes
G_S="$(gettext "s")"	# seconds


TOOLTIP_HEAD="\
<span font-family='"'monospace'"'>\
<span font-size='"'x-large'"' background='"'gold'"'><b>$(gettext " *** ${APPNAME} *** ")</b></span>\n\
"

TOOLTIP_FOOT="\
</span>\
"


CONFDIR="${XDG_CONFIG_HOME}/countmein"
CONFFILE="${CONFDIR}/options"
CONFKEYS="${CONFDIR}/keys"
CONFCLICKS="${CONFDIR}/clicks"
CONFTOTALS="${CONFDIR}/totals"

WORKDIR="/tmp/countmein.${USER}.${$}"
ICON="/usr/share/pixmaps/themes/puppy16/mini-graph.xpm"
PIPE="${WORKDIR}/pipe"
RESET="${WORKDIR}/reset"

export APPNAME ICON RESET PIPE CONFDIR CONFFILE

# -----------------------------------------------------------------------------

run_as_root() {

	if [ $(id -u) -ne 0 ]; then
		exec gtksu "$APPNAME" "$0" "$@"
	fi

}

# -----------------------------------------------------------------------------

check_deps() {

	local DEP ALLOK=true

	if [ ! ${BASH_VERSINFO[0]} -ge 4 ]; then
		echo "$(gettext "Bash version is too old:") $BASH_VERSION"
		echo "$(gettext "Required version is >= 4")"
		ALLOK=false
	fi

	for DEP in libinput yad stdbuf pr; do
		if ! which ${DEP} >/dev/null 2>&1; then
			ALLOK=false
			echo "$(gettext "Missing dependency:") $DEP"
		fi
	done

	if [ "${ALLOK}" != 'true' ]; then
		echo "$(gettext "Aborting...")"
		exit 1
	fi

}

# -----------------------------------------------------------------------------

prepare() {

	mkdir -p "$WORKDIR"
	mkfifo "$PIPE"
	exec 3<>"$PIPE"

	trap "save_stats; pkill -P $$; exit" INT HUP TERM
	trap "rm -rf $WORKDIR" EXIT

	load_stats

}

# -----------------------------------------------------------------------------

save_stats() {

	mkdir -p "$CONFDIR"

	if [ -e "$CONFFILE" ]; then
		. "$CONFFILE"
	fi

	if [ "$PERSISTENCE" = "true" ]; then

		# Totals
		TOTAL_UPTIME=$(( ( $(date +%s) - ${TIMESTAMP} ) + ${LAST_UPTIME} ))
		echo -n "${TOTAL_KEYS}|${TOTAL_CLICKS}|${TOTAL_WHEEL}|${TOTAL_UPTIME}" > "$CONFTOTALS"

		# Keystrokes
		printf "%s\n" "${!KEYS[@]}" "${KEYS[@]}" | pr -l 999 -t2 -s'|' > "$CONFKEYS"

		# Mouse clicks
		printf "%s\n" "${!CLICKS[@]}" "${CLICKS[@]}" | pr -l 999 -t2 -s'|' > "$CONFCLICKS"

	fi

}

# -----------------------------------------------------------------------------

load_stats() {

	local KEY VALUE

	if [ -e "$CONFFILE" ]; then
		. "$CONFFILE"
	fi

	if [ "$PERSISTENCE" = "true" ] && [ -e "$CONFTOTALS" ]; then

		IFS='|' read TOTAL_KEYS TOTAL_CLICKS TOTAL_WHEEL LAST_UPTIME < "$CONFTOTALS"
		
		if [ -e "$CONFKEYS" ]; then
			while IFS='|' read KEY VALUE; do
				[ "$KEY" ] && [ "$VALUE" ] && KEYS[$KEY]="${VALUE}"
			done < "$CONFKEYS"
		fi

		if [ -e "$CONFCLICKS" ]; then
			while IFS='|' read KEY VALUE; do
				[ "$KEY" ] && [ "$VALUE" ] && CLICKS[$KEY]="${VALUE}"
			done < "$CONFCLICKS"
		fi

	fi

}

# -----------------------------------------------------------------------------

about() {

	yad --title="$(gettext "About")" \
		--window-icon="$ICON" \
		--button="gtk-ok:0" \
		--buttons-layout="center" \
		--width="352" \
		--margins="8" \
		--mouse \
		--text-align="center" \
		--text-justify="center" \
		--text="<b>${APPNAME}</b>" \
		--text-info <<< "$(echo -e "$(gettext "A keystroke & mouse click counter")\nCopyright (C) JakeSFR 2018\nLicense: GNU GPL Version 2")"

}

export -f about

# -----------------------------------------------------------------------------

statistics() {

	local WHEEL DATA_KEYS='' DATA_CLICKS='' DATA_WHEEL='' T_KEYS='' T_CLICKS='' T_WHEEL='' T_UPTIME=''

	declare -A S_KEYS
	declare -A S_CLICKS
	
	[ "${1#*KEYS}" ] && eval "S_KEYS="${1#*=}
	[ "${2#*CLICKS}" ] && eval "S_CLICKS="${2#*=}
	[ "$3" ] && WHEEL=${3} || WHEEL=0

	DATA_KEYS=$(printf "%s\n" "${!S_KEYS[@]}" "${S_KEYS[@]}" | pr -l 999 -t2 -s'|' | sort -rn -k2 -t '|')
	DATA_CLICKS=$(printf "%s\n" "${!S_CLICKS[@]}" "${S_CLICKS[@]}" | pr -l 999 -t2 -s'|' | sort -rn -k2 -t '|')
	[ ${WHEEL} -gt 0 ] && DATA_WHEEL="MOUSE_WHEEL|${WHEEL}" || DATA_WHEEL=''

	DATA_KEYS="${DATA_KEYS//KEY_/}"	# get rid of KEY_ prefix

	T_KEYS="$(printf " %-16s<b>%'-0d</b>" "$(gettext "Keystrokes:")" "$4")"
	T_CLICKS="$(printf " %-16s<b>%'-0d</b>" "$(gettext "Mouse Clicks:")" "$5")"
	T_WHEEL="$(printf " %-16s<b>%'-0d</b>" "$(gettext "Mouse Wheel:")" "$6")"
	T_UPTIME="$(printf " %-16s<b>%-0s</b>" "$(gettext "Uptime:")" "$7")"

	yad --title="$(gettext "Statistics")" \
		--window-icon="$ICON" \
		--width="288" \
		--height="512" \
		--mouse \
		--button="gtk-ok:0" \
		--buttons-layout="center" \
		--text "<span font-family='"'monospace'"'>${T_KEYS}\n${T_CLICKS}\n${T_WHEEL}\n${T_UPTIME}</span>" \
		--list \
		--no-selection \
		--dclick-action="" \
		--column="$(gettext "Key/Button"):TEXT" \
		--column="$(gettext "Strokes/Clicks"):NUM" \
		--sep-column="1" \
		--sep-value="-" \
		$(echo -e "${DATA_KEYS//|/\\n}\n-\n-\n${DATA_CLICKS//|/\\n}\n-\n-\n${DATA_WHEEL//|/\\n}")

}

export -f statistics

# -----------------------------------------------------------------------------

reset_stats() {

	yad --title="$(gettext "Reset Statistics")" \
		--window-icon="$ICON" \
		--image="gtk-dialog-warning" \
		--mouse \
		--text-align="center" \
		--justify="center" \
		--text "<b>$(gettext "This action is irreversible!\nAre you really sure?")</b>"
	
	if [ $? -eq 0 ]; then
		touch "${RESET}"
	fi

}

export -f reset_stats

# -----------------------------------------------------------------------------

toggle_persistence() {

	local TOGGLE PERSISTENCE=false

	mkdir -p "$CONFDIR"
	
	if [ -e "$CONFFILE" ]; then
		. "$CONFFILE"
	else
		echo "PERSISTENCE=$PERSISTENCE" > "$CONFFILE"
	fi
	
	if [ "$PERSISTENCE" = "false" ]; then
		TOGGLE='true'
	else
		TOGGLE='false'
	fi

	sed -i 's/PERSISTENCE=.*/PERSISTENCE='${TOGGLE}'/' "$CONFFILE"
	
	update_menu "$TOGGLE"

}

export -f toggle_persistence

# -----------------------------------------------------------------------------

update_menu() {

	case "$1" in
		'true')
			#PERSISTENCE_ICON='gtk-yes'
			PERSISTENCE_CHAR='✔'
		;;
		*)
			#PERSISTENCE_ICON='gtk-no'
			PERSISTENCE_CHAR='✘'
		;;
	esac

	cat > "$PIPE" << EOF
menu:\
$(gettext "Persistence")   ${PERSISTENCE_CHAR}!bash -c toggle_persistence!gtk-save|\
$(gettext "Reset stats")!bash -c reset_stats!gtk-refresh|\
|\
gtk-about!bash -c about|\
gtk-quit!quit
EOF

}

export -f update_menu

# -----------------------------------------------------------------------------

update_yad_tooltip() {
	
	local T_KEYS T_CLICKS T_WHEEL

	T_KEYS="$(printf "   %-20s%'-0d" "$(gettext "Keystrokes:")" $TOTAL_KEYS)"
	T_CLICKS="$(printf "   %-20s%'-0d" "$(gettext "Mouse Clicks:")" $TOTAL_CLICKS)"
	T_WHEEL="$(printf "   %-20s%'-0d" "$(gettext "Mouse Wheel:")" $TOTAL_WHEEL)"

	cat > "$PIPE" << EOF
tooltip: ${TOOLTIP_HEAD}\n\
<b>${T_KEYS}</b>\n\
<b>${T_CLICKS}</b>\n\
<b>${T_WHEEL}</b>\n\
\n\
   $(gettext "Uptime:") ${UPTIME}\n\
${TOOLTIP_FOOT}
EOF

}

# -----------------------------------------------------------------------------

update_yad_command() {

	cat > "$PIPE" << EOF
action: bash -c "statistics \"$KEYS_EXP\" \"$CLICKS_EXP\" \"$TOTAL_WHEEL\" \"$TOTAL_KEYS\" \"$TOTAL_CLICKS\" \"$TOTAL_WHEEL\" \"$UPTIME\""
EOF

}

# -----------------------------------------------------------------------------

create_tray() {
	
	yad --kill-parent \
		--no-middle \
		--notification \
		--image="$ICON" \
		--listen <"$PIPE" &

	update_menu "$PERSISTENCE"

}

# -----------------------------------------------------------------------------

refresh_timeout() {
	
	while
		echo 'REFRESH'
	do
		sleep 1
	done
}

# -----------------------------------------------------------------------------

main_loop() {

	local NULL TIMEOUT TYPE CODE CHANGED='true'

	while

		case "$TYPE" in

			'KEYBOARD_KEY')
				[ "$KEYS[$CODE]" ] && ((KEYS[$CODE]++)) || KEYS[$CODE]=1
				((TOTAL_KEYS++))
				CHANGED='true'
			;;

			'POINTER_BUTTON')
				[ "$CLICKS[$CODE]" ] && ((CLICKS[$CODE]++)) || CLICKS[$CODE]=1
				((TOTAL_CLICKS++))
				CHANGED='true'
			;;

			'POINTER_AXIS')
				((TOTAL_WHEEL++))
				CHANGED='true'
			;;

		esac

		# Refresh everything only on timeout
		if [ "$TIMEOUT" = 'REFRESH' ]; then

			UPTIME="$(eval echo $(date -ud @$(( $(date +%s) - $TIMESTAMP + $LAST_UPTIME )) +'$((%s/3600/24))${G_D} %H${G_H} %M${G_M} %S${G_S}'))"

			if [ -e "${RESET}" ]; then
				unset KEYS CLICKS
				declare -A KEYS CLICKS
				TOTAL_KEYS=0
				TOTAL_CLICKS=0
				TOTAL_WHEEL=0
				LAST_UPTIME=0
				UPTIME="0${G_D} 00${G_H} 00${G_M} 00${G_S}"
				TIMESTAMP=$(date +%s)
				CHANGED='true'
				rm -f "$CONFKEYS" "$CONFCLICKS" "$CONFTOTALS"
				rm -f "$RESET"
			fi

			# Update tooltip
			update_yad_tooltip

			# Update command, but only if something's really changed
			if [ "$CHANGED" = 'true' ]; then
				KEYS_EXP="$(declare -p KEYS)"
				CLICKS_EXP="$(declare -p CLICKS)"
				update_yad_command
				CHANGED='false'
			fi

		fi

	do

		read TIMEOUT TYPE NULL CODE NULL

	done < <( refresh_timeout & stdbuf -o0 libinput debug-events --show-keycodes | stdbuf -o0 grep -E 'released|(wheel)')

}

# -----------------------------------------------------------------------------

### START ###

run_as_root "$@"
check_deps
prepare
create_tray
main_loop

###############################################################################
