#!/bin/bash

if ((BASH_VERSINFO[0] < 4)); then
	echo "This script needs at least bash 4."
	missing_deps=1
fi
for dep in curl jq; do
	if ! command -v $dep &>/dev/null; then
		echo "$dep is required but not found."
		missing_deps=1
	fi
done
[[ $missing_deps ]] &&
	exit 1

if [[ -z $SYNCTHING_API_KEY ]]; then
	: "${SYNCTHING_CONFIG_FILE:="$HOME/.config/syncthing/config.xml"}"
	apikey_regex='^\s+<apikey>([^<]+)</apikey>$'
	apikey_line="$(grep -E "$apikey_regex" "$SYNCTHING_CONFIG_FILE")"
	[[ $apikey_line =~ $apikey_regex ]] &&
		SYNCTHING_API_KEY=${BASH_REMATCH[1]}
fi

if [[ -z $SYNCTHING_API_KEY ]]; then
	echo "No API key in env. Set one of the variables SYNCTHING_API_KEY or SYNCTHING_CONFIG_FILE and try again..."
	exit 1
fi

: "${SYNCTHING_ADDRESS:="localhost:8384"}"

COLOR_PURPLE='\e[95m'
COLOR_GRAY='\e[90m'
COLOR_GREEN='\e[92m'
COLOR_BLUE='\e[34m'
COLOR_RED='\e[31m'
COLOR_RESET='\e[0m'

RECENT_CHANGES_LIMIT=5
LOG_ENTRIES_LIMIT=15
LOG_MAX_AGE=300 # seconds

declare -A api_cache=()
function get_api_response() { # $0 api_name
	if [[ -z ${api_cache["$1"]} ]]; then
		[[ $DEBUG ]] && echo -e "${COLOR_GRAY}CALLING API: $1${COLOR_RESET}" >&2
		api_cache["$1"]="$(curl --silent --insecure -L -H "X-API-Key: $SYNCTHING_API_KEY" "http://$SYNCTHING_ADDRESS/rest/$1")"
	fi
	RESULT="${api_cache["$1"]}"
	# using stdout and piping directly to jq would jump over the cache... not sure why :/
	[[ $RESULT == "CSRF Error" ]] && return 1
	return 0
}

function jq_arg() {
	echo "$1" | jq -r "$2"
}

function call_jq() { # $0 api_name jq_commands
	get_api_response "$1" &&
		RESULT="$(jq_arg "$RESULT" "$2")"
}

function format_time() {
	echo "${COLOR_GRAY}$(echo "$1" | cut -d'.' -f1)${COLOR_RESET}"
}

function get_messages() { # $0 api_name jq_commands message_color_control_code max_age_in_seconds
	call_jq "$1" "$2"
	RESULT="$(jq_arg "$RESULT" '.when + " " + .message' | tail -n "$LOG_ENTRIES_LIMIT")"
	local message_color="$3"
	local max_age="${4:-0}"
	local min_timestamp="$(($(date +%s) - max_age))"
	local result=
	local formatted_line=
	while IFS= read -r line; do
		[[ -z $line ]] && continue
		when="$(echo "$line" | cut -d' ' -f1)"
		message="$(echo "$line" | cut -d' ' -f2-)"
		timestamp="$(date --date="$when" +%s)"
		formatted_line="$(format_time "$when") ${message_color}$message${COLOR_RESET}"$'\n'
		[[ $max_age -gt 0 ]] && [[ $timestamp -lt $min_timestamp ]] &&
			continue
		result+="$formatted_line"
	done <<< "$RESULT"
	[[ -z $result ]] &&
		result="$formatted_line" # take the last log line in any case
	RESULT="${result%$'\n'}"
}

[[ $1 == -v ]] && VERBOSE=true

if ! call_jq "system/status" '.myID'; then
	echo "Error from Syncthing API: $RESULT"
	echo "You should probably check and change the variables SYNCTHING_API_KEY or SYNCTHING_CONFIG_FILE."
	exit 1
elif [[ -z $RESULT ]]; then
	echo "Empty response from Syncthing API."
	echo "You should probably check and change the variables SYNCTHING_ADDRESS, SYNCTHING_API_KEY or SYNCTHING_CONFIG_FILE."
	exit 1
fi
local_device_id="$RESULT"
call_jq "system/config" '.devices | map(select(.deviceID == "'"$local_device_id"'"))[] | .name'
local_device_name="$RESULT"
echo -n "Local device: $local_device_name"
[[ $VERBOSE ]] && echo -en " ${COLOR_GRAY}($local_device_id)${COLOR_RESET}"
echo $'\n'

echo "Devices:"
call_jq "system/config" '.devices[] | .deviceID'
for device_id in $RESULT; do
	[[ $device_id == "$local_device_id" ]] && continue
	call_jq "system/config" '.devices | map(select(.deviceID == "'"$device_id"'"))[]'
	device_config="$RESULT"
	device_name="$(jq_arg "$device_config" '.name')"
	echo -n "$device_name: "
	call_jq "system/connections" '.connections["'"$device_id"'"]'
	device_status="$RESULT"
	status="${COLOR_PURPLE}disconnected${COLOR_RESET}"
	if [ "$(jq_arg "$device_status" '.paused')" == "true" ]; then
		status="${COLOR_GRAY}paused${COLOR_RESET}"
	elif [ "$(jq_arg "$device_status" '.connected')" == "true" ]; then
		status="${COLOR_GREEN}$(jq_arg "$device_status" '.type')${COLOR_RESET}"
	fi
	echo -en "$status"
	[[ $VERBOSE ]] && echo -en " ${COLOR_GRAY}($device_id)${COLOR_RESET}"
	echo
done

echo -e "\nFolders:"
call_jq "system/config" '.folders[] | .id'
for folder_id in $RESULT; do
	call_jq "system/config" '.folders | map(select(.id == "'"$folder_id"'"))[]'
	folder_config="$RESULT"
	folder_label="$(jq_arg "$folder_config" '.label')"
	if [ "$folder_label" ]; then
		[[ $VERBOSE ]] && folder_label+=" ${COLOR_GRAY}($folder_id)${COLOR_RESET}"
	else
		folder_label="$folder_id"
	fi
	echo -en "$folder_label: "
	folder_status=
	need_bytes=0
	folder_paused="$(jq_arg "$folder_config" '.paused')"
	[[ $folder_paused == true ]] && folder_status="paused"
	if [[ -z $folder_status ]]; then
		call_jq "db/status?folder=$folder_id" '.state'
		folder_status="$RESULT"
		call_jq "db/status?folder=$folder_id" '.needBytes'
		need_bytes="$RESULT"
		need_bytes_formatted=
		[[ $need_bytes -gt 0 ]] &&
			need_bytes_formatted="$(numfmt --to=iec-i --suffix=B "$need_bytes")"
	fi
	case "$folder_status" in
		paused)
			folder_status="${COLOR_GRAY}$folder_status${COLOR_RESET}"
			;;
		idle)
			[[ $need_bytes -eq 0 ]] &&
				folder_status="${COLOR_GREEN}up to date${COLOR_RESET}" ||
				folder_status="${COLOR_RED}out of sync${COLOR_RESET}"
			;;
		scanning|syncing)
			folder_status="${COLOR_BLUE}$folder_status${COLOR_RESET}"
			;;
	esac
	[[ $need_bytes -gt 0 ]] && folder_status+=" ($need_bytes_formatted)"
	echo -e "$folder_status"
done

echo -e "\nRecent changes:"
call_jq "events/disk?limit=$RECENT_CHANGES_LIMIT&timeout=1" '.[] | .id'
for event_id in $RESULT; do
	call_jq "events/disk?limit=$RECENT_CHANGES_LIMIT&timeout=1" '. | map(select(.id == '"$event_id"'))[]'
	event="$RESULT"

	when="$(jq_arg "$event" '.time')"
	path="$(jq_arg "$event" '.data.path')"

	folder_id="$(jq_arg "$event" '.data.folderID')"
	call_jq "system/config" '.folders | map(select(.id == "'"$folder_id"'"))[].label'
	folder_label="${RESULT:-$folder_id}"

	action="$(jq_arg "$event" '.data.action')"
	action_color='?'
	case "$action" in
		added) action_color=${COLOR_GREEN}+ ;;
		deleted) action_color=${COLOR_RED}- ;;
		modified) action_color=${COLOR_BLUE}\# ;;
	esac

	device_id_prefix="$(jq_arg "$event" '.data.modifiedBy')"
	call_jq "system/config" '.devices | map(select(.deviceID | startswith("'"$device_id_prefix"'")))[].name'
	device_name="${RESULT:-$device_id_prefix}"

	echo -e "$(format_time "$when") ${COLOR_GRAY}$device_name${COLOR_RESET} $folder_label ${action_color}$path${COLOR_RESET}"
done

if [[ $VERBOSE ]]; then
	get_messages "system/log" '.messages[]?' '' "$LOG_MAX_AGE"
	echo -e "\nLast log entries:"
	echo -e "$RESULT"
fi
get_messages "system/error" '.errors[]?' "$COLOR_RED"
if [[ $RESULT ]]; then
	echo -e "\nERRORS:"
	echo -e "$RESULT"
fi

if [[ $DEBUG ]]; then
	echo -e "\ncached responses:"
	for k in "${!api_cache[@]}"; do
		echo "$k"
	done
fi
