#!/bin/ksh93

export POSIXLY_CORRECT=1

typeset -r VERSION='1.0' FPROG=${.sh.file} PROG=${FPROG##*/} SDIR=${FPROG%/*} \
	GRAFANA="/usr/share/grafana/bin/grafana"

X='/etc/default/grafana'
[[ -e /etc/sysconfig/grafana-server ]] && X='/etc/sysconfig/grafana-server'

unset DEFAULTS; typeset -r DEFAULTS=(
	[HOME]="${GRAFANA%/*/*}"					# install dir
	[CFG]='/etc/grafana/grafana.ini'
	[PROVDIR]='/etc/grafana/provisioning'
	[PLUGDIR]='/var/lib/grafana/plugins'
	[DATADIR]='/var/lib/grafana/data'
	[LOGDIR]='/var/log/grafana'
	[REPO]='https://grafana.com/api/plugins'
	[SVC]="$X"
)

function showUsage {
    [[ -n $1 ]] && X='-?' ||  X='--man'
    getopts -a "${NAME}" "${ print ${USAGE} ; }" OPT $X
}
function getValue {
	typeset -n RES=$1
	typeset P=$2 K="$3" E="$4" V="$5"
	if [[ $K == $P ]]; then
		if [[ $E == '=' ]]; then
			RES="$V"			# key = value
		elif [[ ${E:0:1} == '=' ]]; then
			RES="${E:1}"		# key =value
		fi
		# else:					# key bla
		return 0
	fi
	if [[ "${K:0:${#P}}=" == "${P}=" ]]; then
		if [[ ${#K} == ${#P} ]]; then
			RES="$E"			# key= value
		else
			RES="${K:${#P}+1}"	# key=value
		fi
		return 0
	fi
	return 1
}

# key .. what we lookup in ini files, val .. key used to store the value
typeset -Ar KMAP=(
	['data']='DATADIR' ['logs']='LOGDIR' ['plugins']='PLUGDIR'
	['provisioning']='PROVDIR'
)
function readCfg {
	[[ -f $2 ]] || return 1
	typeset -n M="$1"	# property map
	integer S=0			# in paths section flag
	typeset T X
	while read K E V R ; do
		# skip empty lines and comments
		[[ -z $K || ${K:0:1} == ';' || ${K:0:1} == '#' ]] && continue
		if (( S == 0 )); then
			[[ $K == '[paths]' ]] && S=1
			continue
		fi
		# new non-paths section => turn off reading (actually we could break
		# because proper configs should have each section only once).
		[[ ${K:0:1} == '[' && ${K:1} != 'aths]' ]] && S=0 && continue
		# stuff we want
		T=
		for X in ${!KMAP[@]} ; do
			if getValue T "$X" "$K" "$E" "$V" ; then
				[[ -n $T ]] && M[${KMAP["$X"]}]="$T"
				break
			fi
		done
	done<"$2"
}
function getSystemDefault {
	[[ -f ${DEFAULTS[SVC]} ]] || return 0
	typeset -n V=$1
	typeset K
	# only key=value supported, i.e. no quoting and no whitespaces
	while read K ; do
		[[ ${K:0:10} == 'CONF_FILE=' ]] && V="${K:10}" && return
	done<${DEFAULTS[SVC]}
	V=
}

function doMain {
	typeset BASEDIR INI D T
	typeset -a ARGS CENV
	typeset -A CFG
	[[ $1 == 'cli' ]] && shift
	[[ -z ${OPT[HOME]} ]] && BASEDIR=${DEFAULTS[HOME]} || BASEDIR=${OPT[HOME]}
	ARGS+=( '--homepath' "${BASEDIR}" )		# per default it would use CWD
	readCfg CFG "${BASEDIR}/conf/defaults.ini"
	readCfg CFG "${BASEDIR}/conf/custom.ini"
	if [[ -z ${OPT[CFG]} ]]; then
		[[ -n ${CONF_FILE} ]] && INI="${CONF_FILE}" || getSystemDefault INI
		[[ -z ${INI} ]] && INI=${DEFAULTS[CFG]}
	else
		INI=${OPT[CFG]}
	fi
	[[ -n ${INI} ]] && ARGS+=( '--config' "${INI}" ) && readCfg CFG "${INI}"
	(( OPT[DBG] )) && ARGS+=( '--debug' )
	(( OPT[INSECURE] )) && ARGS+=( '--insecure' )
	D="${OPT[REPO]}"
	[[ -z $D && -n ${GF_PLUGIN_REPO} ]] && export GF_PLUGIN_REPO
	[[ -n $D ]] && ARGS+=( '--repo' "$D" )
	D="${OPT[PURL]}"
	[[ -z $D && -n ${GF_PLUGIN_URL} ]] && export GF_PLUGIN_URL
	[[ -n $D ]] && ARGS+=( '--pluginUrl' "${OPT[PURL]}" )
	D="${OPT[PDIR]}"
	if [[ -z $D ]]; then
		if [[ -n ${GF_PLUGIN_DIR} ]]; then
			export GF_PLUGIN_DIR
		else 
			D="${CFG[PLUGDIR]}"
			[[ -z $D ]] && D="${DEFAULTS[PLUGDIR]}"
		fi
	fi
	if [[ -n $D ]]; then
		ARGS+=( '--pluginsDir' "$D" )
		OPT[OVR]+=" cfg:default.paths.plugins=$D"
	fi
	[[ -n ${CFG[PROVDIR]} ]] && D="${CFG[PROVDIR]}" || D="${DEFAULTS[PROVDIR]}"
	OPT[OVR]+=" cfg:default.paths.provisioning=$D"
	D="${CFG[DATADIR]}"
	[[ -z $D ]] && D="${DEFAULTS[DATADIR]}"
	OPT[OVR]+=" cfg:default.paths.data=$D"
	D="${CFG[LOGDIR]}"
	[[ -z $D ]] && D="${DEFAULTS[LOGDIR]}"
	OPT[OVR]+=" cfg:default.paths.logs=$D"
	[[ -n ${OPT[OVR]} ]] && ARGS+=( '--configOverrides' "${OPT[OVR]:1}" )
	[[ -x ${GRAFANA} ]] && { T= D='cli'; } || { T='-cli' D=; }
	${DRY} exec ${GRAFANA}$T $D "${ARGS[@]}" "$@"
}

# Server honors the config file and args - so no need to manually parse anything
if [[ ${PROG} == 'grafana-server' ]]; then
	[[ -x ${GRAFANA} ]] && exec  ${GRAFANA} server "$@"
	exec ${GRAFANA}-server "$@"
fi
# We do not try to fix bogus/wrong help messages for grafana invocation.
# CLI option handling by grafana is a real mess - they probably never heard of
# POSIX/UNIX Utility Argument Syntax and re-evented/use square wheels - FWIW:
# see https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html
if [[ ${PROG} == 'grafana' ]]; then
	if [[ $1 == 'server' || $1 == 'help' || $1 == 'h' ]]; then
		[[ -x ${GRAFANA} ]] && X= || X='-server'
		[[ -n $X && $1 == 'server' ]] && shift
		exec  ${GRAFANA}$X "$@"
	fi
	[[ $1 == 'cli' ]] && NAME='grafana cli' && shift
fi
[[ -z ${NAME} ]] && NAME="${PROG}"
typeset -r NAME

# The cli part - see pkg/cmd/grafana-cli/commands/cli.go
USAGE="[-?${VERSION}"' ]
[-copyright?Copyright (c) 2025 Jens Elkner. All rights reserved.]
[-license?CDDL 1.0]
[+NAME?'"${NAME}"' - a simple wrapper around grafana.]
[+DESCRIPTION?Since \bgrafana\b CLI tool ignores the \b--config=\b\afile\a option completely, and thus does not honor any pathes in the \bpaths\b section of the related ini file, this script sources and parses either the given or default ini file (if available), deduces the proper pathes to use (CLI arguments have precedence over config file), and finally invokes the grafana tool with the corresponding CLI arguments.]
[+OPERANDS?\acmd\a is the command to execute. To check which commands are supported, use the command \bhelp\b. To find out, which \aargument\as the command supports, specify \bhelp\b as argument.]
[h:help?Show this help and exit.]
[n:dry?Just show, what would be done, but do not do it.]
[F:functions?Show a list of all functions defined within this script.]
[T:trace]:[functionList?A comma separated list of the names of functions to trace during execution.]
[+?]
[=11:config]:[file:='"${DEFAULTS[CFG]}"' ?The grafana config file. If not given, this wrapper checks whether the environment variable \bCONF_FILE\b is set. If so its value will be used, the value of \bCONF_FILE\b in  \b'"${DEFAULTS[SVC]}"'\b otherwise. If it is still not set, the default value will be used. To mimic no config file, use \b--config=/dev/null\b .]
[=12:configOverrides]:[list?Allows one to overwrite certain settings otherwise read from the config files. One should not use this kludge but rather adjust the config file for consistency reasons.]
[=13:debug?Enable debug logging.]
[=14:homepath]:[path:='"${GRAFANA%/*/*}"' ?The working directory grafana should use to load its \bconf/defaults.ini\b and store additional data. Depending on the command it might not get honored.]
[=15:insecure?Skip TLS verification (insecure).]
[=16:pluginsDir]:[path:='"${DEFAULTS[PLUGDIR]}"' ?The \apath\a to the Grafana plugin directory [\bGF_PLUGIN_DIR\b]].]
[=17:pluginUrl]:[URL?Full \aURL\a to the plugin zip file instead of downloading the plugin from grafana.com/api [\bGF_PLUGIN_URL\b]].]
[=18:repo]:[URL:='"${DEFAULTS[REPO]}"' ?The \aURL\a to the plugin repository [\bGF_PLUGIN_REPO\b]].]
[v:version?Print the grafana version and exit.]
[+NOTES?The option \b-F\b, \b-T\b and \b-n\b are wrapper specific and may vanish when grafana gets a proper optarg implementation/handling.]
[+?Options given on the CLI have precedence over the corresponding settings found in the grafana ini and defaults file.] 
[+?The grafana binary uses the current working directory as \bhomepath\b if it is not explicitly specified. But for convenience this wrapper always sets the \bhomepath\b either to the specified argument or if not specified to the default one mentioned above. So if you want to mimic the exact same behavior as the binary, use \b--homepath=.\b .]
[+?Some options support environment variables, too (name enclosed in square brackets). So if the related option is not specified, it gets checked, whether the mentioned env variable is set. If so it will be passed to the binary as is, otherwise the default value will be used and submitted via option.]
\n\n
[ \acmd\a [ \aargument\a... ] ]
'
unset DRY OPT ; DRY= X="${ print ${USAGE} ; }"
typeset -A OPT
while getopts -a "${NAME}" "${X}" OPTION ; do
    case "${OPTION}" in
        F) typeset +f ; exit 0 ;;
        T)  if [[ ${OPTARG} == 'ALL' ]]; then
                typeset -ft ${ typeset +f ; }
                set -x
            else
                typeset -ft ${OPTARG//,/ }
            fi
            ;;
        h) showUsage ; exit 0 ;;
        n) DRY='print --' ;;
		11) OPT[CFG]="${OPTARG}" ;;
		12) OPT[OVR]+=" ${OPTARG}" ;;
		13) OPT[DGB]=1 ;;
		14) OPT[HOME]="${OPTARG}" ;;
		15) OPT[INSECURE]=1 ;;
		16) OPT[PDIR]="${OPTARG}" ;;
		17) OPT[PURL]="${OPTARG}" ;;
		18) OPT[REPO]="${OPTARG}" ;;
		v) ${GRAFANA} -v && return 0 || return 1 ;;
		*) $0 '--long' ; exit 1;;
    esac
done
IDX=$(($OPTIND-1))
shift $IDX
unset X IDX OPTION

doMain "$@"
