#!/bin/bash

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

set -e

# project vars
VERSION="1.0.0"
SCRIPTS_DIR=/libexec/pr-tools/detect

# script vars
SCRIPTNAME=$(basename $BASH_SOURCE)
DEBUG=false
SHOWVER=false
HELP=false
SHELL_OUT=/dev/stdout
JSON_OUT=
STEP=
declare -A __keys

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 <option>

Options:
   -h|--help|--fullhelp: get help
   -V|--version: get version
   -d|--debug: enable debug
   -s|--step <name|dir>: set detection step by name or by directory (mandatory)
      If a name is given, a folder named $SCRIPTS_DIR/<name> is used for detection scripts.
      If a directory is given, it's used directly.
      Common names are: 'core','devices'
   -o|--output-file <file>: generate output file in SHELL format (default: stdout)
   -j|--json-file <file>: generate output file in JSON format (optional)

EOF
}

tmp=$(getopt -o h,V,d,s:,o:,j: --long help,version,debug,step:,output-file:,json-file: -n "$SCRIPTNAME" -- "$@") || {
	error "Invalid arguments."
	usage
	exit 1
}
eval set -- $tmp
while true; do
	case "$1" in
		-s|--step) STEP=$2; shift 2;;
		-o|--output-file) SHELL_OUT=$2; shift 2;;
		-j|--json-file) JSON_OUT=$2; shift 2;;
		-d|--debug) DEBUG=true; shift;;
		-V|--version) SHOWVER=true; shift;;
		-h|--help) HELP=true; shift;; 
		--) shift; break;;
		*) fatal "Internal error";;
	esac
done

# ------------------------ SH/JSON output funcs ------------------------

# usage:
# OBJ1=$(out_object json <<EOF
# key1
# value1
# key2
# value2
# EOF)
# 
# ARR1=$(out_object json <<EOF
# item1
# item2
# item3
# EOF)
#
# VAL1=$(out_value json "foo")

function out() { echo -n "$@"; }
function out_object() {
	# expected stdin stream is:
	# --------------
	# key
	# value
	# key
	# value
	# ...
	# --------------
	local sep=""
	local k
	local fmt=${1:-bash}
	case $fmt in
		bash)
			while read x; do
				[[ -z "$k" ]] && { k="$x"; continue; }
				out "$sep${k}="
				out_value $fmt "$x"
				sep=$'\n'
				k=
			done
			out "$sep"
			;;
		json)
			out "{"
			while read x; do
				[[ -z "$k" ]] && { k="$x"; continue; }
				out "$sep\"${k}\":"
				out_value $fmt "$x"
				sep=","
				k=
			done
			out "}"
			;;
	esac
}

function out_array() {
	# expected stdin stream is:
	# --------------
	# value
	# value
	# ...
	# --------------
	local sep=""
	local fmt=${1:-bash}
	case $fmt in
		bash)
			while read x; do
				out "$sep"
				out_value $fmt "$x"
				sep=" "
			done
			;;
		json)
			out "["
			while read x; do
				out $sep
				out_value $fmt "$x"
				sep=","
			done
			out "]"
			;;
	esac
}

function out_value() {
	# string
	# number
	# object
	# array
	# 'true'
	# 'false'
	# 'null'

	local fmt=${1:-bash}
	x=$2

	# litterals
	if [[ "$x" =~ ^(true|false|null)$ ]]; then
		out "$x"
	# number
	elif [[ "$x" =~ ^[+-]?[0-9]+(\.[0-9]+)?$ ]]; then
		out "$x"
	# object
	elif [[ "$x" =~ ^\{.*\}$ ]]; then
		out "$x"
	# array
	elif [[ "$x" =~ ^\[.*\]$ ]]; then
		out "$x"
	# string
	else
		out "\"$(sed 's/\("\)/\\\1/g' <<<$x)\""
	fi
}

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

function addkey() {
	local k=$1
	shift
	[[ -z "$k" ]] && return 1
	debug "Add key $k=$@"
	__keys[$k]="$@"
}

function readkey() { [[ -f $1 ]] && cat $1 || echo "unknown"; }

function __generate_output() {
	fmt=$1

	# force $HW_CPU_ARCH $HW_SOC_VENDOR and $HW_BOARD_MODEL to be lowercase (if defined)
	# these values are used by platform-hardware-config to run some scriptlets conditionnaly
	# to arch, vendor and board model.
	for x in cpu_arch soc_vendor board_model; do
		[[ -n "${__keys[$x]}" ]] && __keys[$x]=${__keys[$x],,}
	done
	
	for x in ${!__keys[@]}; do
		case $fmt in
			bash)
				echo HW_${x^^}
				;;
			*)
				echo $x
				;;
		esac

		echo ${__keys[$x]}
	done | out_object $fmt
}

function __smackadd() {
	[[ -f "$1" ]] && [[ $(command -v chsmack) ]] && { debug "Adding SMACK label for $1"; chsmack -a System:Shared $1; } || echo "No smack rules added"
}

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

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

[[ -n "$STEP" && -d "$SCRIPTS_DIR/$STEP" ]] && STEP="$SCRIPTS_DIR/$STEP" 
[[ ! -d "$STEP" ]] && fatal "Invalid step directory or name '$STEP'"

debug "Resolved scripts folder: STEP=$STEP"

# run all fragments in specified fragment dir
for x in $(ls $STEP); do
	info "Executing $x"
	. $STEP/$x
	if [[ "$x" =~ ^[0-9]+(FB|firstboot)_ ]]; then
		info "Removing script $x (firstboot only)"
		rm $STEP/$x
	fi
done

# generate outputs
[[ -n "$SHELL_OUT" ]] && {
	info "Generating shell format in $SHELL_OUT"
	__generate_output bash >$SHELL_OUT
	__smackadd $SHELL_OUT
}

[[ -n "$JSON_OUT" ]] && {
	info "Generating json format in $JSON_OUT"
	__generate_output json >$JSON_OUT
	__smackadd $JSON_OUT
}


