#!/bin/bash

# Copyright (C) 2019 IoT.bzh
# Authors:
#    Stephane Desneux <stephane.desneux@iot.bzh>
# Released under the Apache 2.0 license

set -e

# project vars
VERSION="1.0.0"
REGISTRY_PREFIX="agl"
GLOBAL_CONFIG=/etc/pr-tools/registry.conf
GLOBAL_CONFIG_DIR=${GLOBAL_CONFIG}.d
CORE_INFO=/etc/platform-info/core

# script vars
SCRIPTNAME=$(basename $BASH_SOURCE)
DEBUG=false
SHOWVER=false
HELP=false
FULLHELP=false
DUMP_ALL=false
DUMP_KEYS=false
EXTRA_CONFIG=

# the associative array to store keys
declare -A REGISTRY

function info() { echo "$@" >&2; }
function error() { echo "ERROR: $@" >&2; }
function warning() { echo "WARNING: $@" >&2; }
function fatal() { echo "FATAL ERROR: $@" >&2; exit 2; }
function debug() { $DEBUG && echo "[$BASHPID] $@" >&2 || true; }

function usage() {
	cat <<EOF >&2
Usage: $SCRIPTNAME [options] [<key>]
Version: $VERSION

$SCRIPTNAME retrieves and outputs the values for the requested configuration key.

Options:
   -h|--help|--fullhelp: get help
   -V|--version: get version
   -d|--debug: enable debug
   -c|--config <file>: extra configuration file to load (may be used multiple times)
   -a|--all: dump the full registry (this can be used in bash: source <($SCRIPTNAME -a) )
   -k|--keys: dump all defined keys

EOF

	$FULLHELP || return 0

	cat <<EOF >&2
Default configuration files are:
   $GLOBAL_CONFIG
   $GLOBAL_CONFIG_DIR/*
   kernel command line options starting by '$REGISTRY_PREFIX.'

Configuration file syntax:
* shell style
* empty lines are allowed
* comments start with '#' until the end of line
* variables are named with 3 parts
   - prefix: '$REGISTRY_PREFIX'
   - domain: defines when variable will be set
     this can be:
        - 'common'
        - a CPU architecture: 'aarch64','x86_64','arm' ...
		- a SoC vendor: 'Renesas', 'Intel' ...
		- a board name: 'kingfisher-h3ulcb-r8a7795', 'minnowboard-turbot' ...
     architectures, vendors, board names are set by platform-hardware-info package
   - name: the variable name (1 or more alphanumeric characters + underscore: [a-zA-Z0-9_]+)
* to set a variable, the syntax is:
    [name]=[value]
  or if some spaces are required in the value:
    [name]="[value]"
  or if it's just a boolean flag set to true:
    [name]
* examples:
    $REGISTRY_PREFIX.common.verbose
    $REGISTRY_PREFIX.common.debug=true

    $REGISTRY_PREFIX.common.root_password=fbgUx1ap3tCTPBT2
    $REGISTRY_PREFIX.common.binder_loglevel=4
    $REGISTRY_PREFIX.common.welcome_message="Hi there, you're welcome !!!"

    # to restrict to a given architecture/vendor/board:
    $REGISTRY_PREFIX.aarch64.enable_option_foo=true
    $REGISTRY_PREFIX.x86_64.disable_option_bar=true

    $REGISTRY_PREFIX.renesas.e3_emulation=true
    $REGISTRY_PREFIX.intel.xeon_for_embedded=true
    $REGISTRY_PREFIX.amd.r1000=true

    $REGISTRY_PREFIX.kingfisher-h3ulcb-r8a7795.btwilink=off
    $REGISTRY_PREFIX.minnowboard-turbot.builtin_audio=off
EOF
}

# parse command line options
tmp=$(getopt -o c:akdVh --long config:,all,keys,debug,version,help,fullhelp -n $(basename $SCRIPTNAME) -- "$@") || {
	error "Invalid arguments."
	usage
	exit 1
}
eval set -- $tmp
while true; do
	case "$1" in 
		-a|--all) DUMP_ALL=true; shift;;
		-k|--keys) DUMP_KEYS=true; shift;;
		-c|--config) 
			[[ -r "$2" ]] && EXTRA_CONFIG+=" $2" || fatal "Invalid config file $2"
			shift 2
			;;
		-d|--debug) DEBUG=true; shift;;
		-V|--version) SHOWVER=true; shift;;
		-h|--help) HELP=true; shift;; 
		--fullhelp) HELP=true; FULLHELP=true; shift;; 
		--) shift; break;;
		*) fatal "Internal error";;
	esac
done

function dump_registry() {
	for k in $(dump_keys); do 
		echo "$k=${REGISTRY[$k]@Q}"
	done
}

function dump_keys() {
	for k in ${!REGISTRY[@]}; do
		echo "$k"
	done | sort
}

function getkey() {
	debug "getkey $1"
	[[ -v "REGISTRY[$1]" ]] && echo "${REGISTRY[$1]}"
}

function parse_config() {
	debug "parse_config"
	local cond key def 
	while read -r line; do 
		debug "line: $line"
		[[ "$line" =~ ^($REGISTRY_PREFIX)\.(common|$HW_CPU_ARCH|$HW_SOC_VENDOR|$HW_BOARD_MODEL)\.([a-zA-Z0-9_]+)([[:space:]]*=[[:space:]]*(.*)[[:space:]]*)?$ ]] || continue
#		debug "   regexp matches"
		cond=${BASH_REMATCH[2]}
#		debug "      cond='$cond'"
		key=${BASH_REMATCH[3]}
#		debug "      key='$key'"

		if [[ -z "${BASH_REMATCH[4]}" ]]; then
			def=true
		else
			def=${BASH_REMATCH[5]}
			[[ "$def" =~ ^\"(.*)\"$ ]] && def=${BASH_REMATCH[1]}
		fi
		REGISTRY[$key]=$def
		debug "      $key='${REGISTRY[$key]}'"
	done < <(sed -e 's/#.*$//g' -e '/^[[:space:]]*$/d')
}

$SHOWVER && { echo $VERSION; exit 0; }
$HELP && { usage $FULLHELP; exit 1; }

[[ -r $CORE_INFO ]] && . $CORE_INFO || warning "Unable to find core info file $CORE_INFO. Hardware-specific keys will not be handled."

# load global config file
[[ -r $GLOBAL_CONFIG ]] && {
	debug "loading global config from $GLOBAL_CONFIG"
	parse_config <$GLOBAL_CONFIG
}

# load all files in GLOBAL_CONFIG_DIR
if [[ -d "$GLOBAL_CONFIG_DIR" ]]; then
	for x in $(find $GLOBAL_CONFIG_DIR -maxdepth 1 -type f | sort); do
		debug "loading config from $x"
		parse_config <$x
	done
else
	warning "$GLOBAL_CONFIG_DIR doesn't exist. Skipping parsing on global config dir files."
fi

# also parse kernel command line
debug "Loading config from kernel command line"
parse_config < <(cat /proc/cmdline | sed 's/[ ]\+/\n/g')

# load extra config files
for cfg in $EXTRA_CONFIG; do
	debug "Loading extra config $cfg"
	parse_config <$cfg
done

$DUMP_ALL && { dump_registry; exit 0; }
$DUMP_KEYS && { dump_keys; exit 0; }

[[ "$#" -eq 0 ]] && { error "No key provided on command line"; usage; exit 1; }
[[ "$#" -gt 1 ]] && { error "More than one key provided on command line"; usage; exit 1; }
getkey $1 

