#!/bin/sh -e
# -*- mode: sh; indent-tabs-mode: t -*-
#
# 2021 Dennis Camera (dennis.camera at ssrq-sds-fds.ch)
#
# This file is part of cdist.
#
# cdist is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cdist is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with cdist. If not, see <http://www.gnu.org/licenses/>.
#

postgres_user=$("${__type_explorer:?}/postgres_user")
conf_name=${__object_id:?}

tolower() { printf '%s' "$*" | tr '[:upper:]' '[:lower:]'; }

tobytes() {
	# NOTE: This function treats everything as base 2.
	#       It is not compatible with SI units.
	awk 'BEGIN { FS = "\n" }
	/TB$/ { $0 = ($0 * 1024) "GB" }
	/GB$/ { $0 = ($0 * 1024) "MB" }
	/MB$/ { $0 = ($0 * 1024) "kB" }
	/kB$/ { $0 = ($0 * 1024) "B" }
	 /B?$/ { sub(/ *B?$/, "") }
	($0*1) == $0  # is number
	' <<-EOF
	$1
	EOF
}

tomillisecs() {
	awk 'BEGIN { FS = "\n" }
	    /d$/ { $0 = ($0 * 24) "h" }
	    /h$/ { $0 = ($0 * 60) "min" }
	  /min$/ { $0 = ($0 * 60) "s" }
	/[^m]s$/ { $0 = ($0 * 1000) "ms" }
	   /ms$/ { $0 *= 1 }
	($0*1) == $0  # is number
	' <<-EOF
	$1
	EOF
}

tobool() {
	# prints either 'on' or 'off'
	case $(tolower "$1")
	in
		(t|true|y|yes|on|1)
			echo 'on' ;;
		(f|false|n|no|off|0)
			echo 'off' ;;
		(*)
			printf 'Inavlid bool value: %s\n' "$2" >&2
			return 1
			;;
	esac
	return 0
}

quote() { printf '%s\n' "$*" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/'/"; }
psql_exec() {
	su - "${postgres_user}" -c "psql postgres -twAc $(quote "$*")"
}

psql_conf_source() {
	# NOTE: SHOW/SET are case-insentitive, so this command should also be.
	psql_exec "SELECT CASE WHEN source = 'default' OR setting = boot_val THEN 'default' ELSE source END FROM pg_settings WHERE lower(name) = lower('$1')"
}
psql_conf_cmp() (
	IFS='|' read -r lower_name vartype setting unit <<-EOF
	$(psql_exec "SELECT lower(name), vartype, setting, unit FROM pg_settings WHERE lower(name) = lower('$1')")
	EOF

	should_value=$2
	is_value=${setting}

	# The following case contains special cases for special settings.
	case ${lower_name}
	in
		(archive_command)
			if test "${setting}" = '(disabled)'
			then
				# DAFUQ PostgreSQL?!
				# PostgreSQL returns (disabled) if the feature is inactive.
				# We cannot compare the values unless it is enabled, first.
				return 0
			fi
			;;
		(archive_mode|backslash_quote|constraint_exclusion|force_parallel_mode|huge_pages|synchronous_commit)
			# Although only 'on', 'off' are documented, PostgreSQL accepts all
			# the "likely" variants of "on" and "off".
			case $(tolower "${should_value}")
			in
				(on|off|true|false|yes|no|1|0)
					should_value=$(tobool "${should_value}")
					;;
			esac
			;;
	esac

	case ${vartype}
	in
		(bool)
			test -z "${unit}" || {
				# please fix the explorer if this error occurs.
				printf 'units are not supported for vartype: %s\n' "${vartype}" >&2
				exit 1
			}

			should_value=$(tobool "${should_value}")

			test "${is_value}" = "${should_value}"
			;;
		(enum)
			test -z "${unit}" || {
				# please fix the explorer if this error occurs.
				printf 'units are not supported with vartype: %s\n' "${vartype}" >&2
				exit 1
			}

			# NOTE: All enums that are currently defined are lower case, but
			#       PostgreSQL also accepts upper case spelling.
			should_value=$(tolower "$2")

			test "${is_value}" = "${should_value}"
			;;
		(integer)
			# split multiples from unit, first (e.g. 8kB -> 8, kB)
			case ${unit}
			in
				([0-9]*)
					multiple=${unit%%[!0-9]*}
					unit=${unit##*[0-9 ]}
					;;
				(*) multiple=1 ;;
			esac

			is_value=$((setting * multiple))${unit}

			if expr "${should_value}" : '-\{0,1\}[0-9]*$' >/dev/null
			then
				# default unit
				should_value=$((should_value * multiple))${unit}
			fi

			# then, do conversion
			# NOTE: these conversions work for integers only!
			case ${unit}
			in
				(B|[kMGT]B)
					# bytes
					is_bytes=$(tobytes "${is_value}")
					should_bytes=$(tobytes "${should_value}")

					test $((is_bytes)) -eq $((should_bytes))
					;;
				(ms|s|min|h|d)
					# seconds
					is_ms=$(tomillisecs "${is_value}")
					should_ms=$(tomillisecs "${should_value}")

					test $((is_ms)) -eq $((should_ms))
					;;
				('')
					# no unit
					is_int=${is_value}
					should_int=${should_value}

					test $((is_int)) -eq $((should_int))
					;;
			esac
			;;
		(real|string)
			# NOTE: reals could possibly have units, but currently there none.

			test -z "${unit}" || {
				# please fix the explorer if this error occurs.
				printf 'units are not supported with vartype: %s\n' "${vartype}" >&2
				exit 1
			}

			test "${is_value}" = "${should_value}"
			;;
	esac
)

psql_exec 'SELECT 1' >/dev/null || {
	echo 'Connection to PostgreSQL server failed' >&2
	exit 1
}

case $(psql_conf_source "${conf_name}")
in
	('')
		printf 'Invalid configuration parameter: %s\n' "${conf_name}" >&2
		exit 1
		;;
	(default)
		echo absent
		;;
	(*)
		if ! test -f "${__object:?}/parameter/value"
		then
			echo present
		elif psql_conf_cmp "${conf_name}" "$(cat "${__object:?}/parameter/value")"
		then
			echo present
		else
			echo different
		fi
		;;
esac
