#!/bin/sh
#
# Header: hprofile
# Aythor:
# 	Copyright (c) 2003-2004 Martin Aspeli <optilude@gmx/net>
# 	Copyright (c) 2014-2016 tokiclover <tokiclover@gmail.com>
# License: GPL-2
#

if [ -n "${ZSH_VERSION}" ]; then
	emulate sh
	setopt SH_WORD_SPLIT
	setopt NULL_GLOB
	disable -r end
	NULLCMD=:
elif [ -n "${BASH_VERSION}" ]; then
	shopt -qs nullglob
fi

PACKAGE=hprofile
VERSION=6.2.0
:	${CONFDIR:=/etc/${PACKAGE}}
:	${RUNDIR:=${TMPDIR:-/tmp}}
NULL=/dev/null
PROFILE_DIR="${CONFDIR}"
name="${PACKAGE}"

DEBUG()
{
	if yesno ${PROFILE_DEBUG}; then
		printf "${__print_eol__}${name}: debug: ${@}${__eol_print__}" >&2
	fi
	return 0
}

trap '__print__col=${COLUMNS:-$(tput cols)}' WINCH
__print__col="${COLUMNS:-$(tput cols)}"
__eol_print__="\n"

#
# @FUNCTION: Print error message to stderr
#
error()
{
	local message="${*}"
	__print_len__=$((${#name}+3+${#message}))
	local prefix="${name:+${COLOR_FG_magenta}${name}}"
	printf "${__print_eol__}${prefix} ${COLOR_FG_red}error${COLOR_RESET}: ${message}${__eol_print__}" >&2
}

#
# @FUNCTION: Print error message to stderr & exit
#
die()
{
	local ret=${?}; error "${@}"; exit ${ret}
}

#
# @FUNCTION: Print info message to stdout
#
info()
{
	local message="${*}"
	__print_len__=$((${#name}+3+${#message}))
	local prefix="${name:+${COLOR_FG_yellow}${name}}"
	printf "${__print_eol__}${prefix} ${COLOR_FG_blue}info${COLOR_RESET}: ${message}${__eol_print__}"
}

#
# @FUNCTION: Print warn message to stdout
#
warn()
{
	local message="${*}"
	__print_len__=$((${#name}+3+${#message}))
	local prefix="${name:+${COLOR_FG_red}${name}}"
	printf "${__print_eol__}${prefix} ${COLOR_FG_yellow}warning${COLOR_RESET}: ${message}${__eol_print__}" >&2
}

#
# @FUNCTION: Print begin message to stdout
#
begin()
{
	local message="${*}"
	printf "${__print_eol__}"
	__eol_print__=
	__print_eol__="\n"
	__print_len__=$((${#name}*2+6+${#message}))
	local prefix="${name:+${COLOR_FG_magenta}{${COLOR_FG_blue}${name}${COLOR_FG_magenta}}${COLOR_RESET}}"
	printf "${prefix} ${message}"
}

#
# @FUNCTION: Print end message to stdout
#
end()
{
	local suffix ret="${1:-0}"
	shift
	local message="${*}"
	case "${ret}" in
		(0) suffix="${COLOR_FG_blue}[${name}] {${COLOR_FG_green}ok${COLOR_FG_blue}}${COLOR_RESET}";;
		(*) suffix="${COLOR_FG_yellow}[${name}] {${COLOR_FG_red}no${COLOR_FG_yellow}}${COLOR_RESET}";;
	esac
	__print_len__=$((${COLUMNS}-${__print_len__}))
	printf "%*b\n" "${__print_len__}" "${message} ${suffix}"
	__eol_print__="\n"
	__print_eol__=
	__print_len__=0
	return "${ret}"
}

#
# @FUNCTION: YES or NO helper
#
yesno()
{
	case "${1:-NO}" in
	(0|[Dd][Ii][Ss][Aa][Bb][Ll][Ee]|[Oo][Ff][Ff]|[Ff][Aa][Ll][Ss][Ee]|[Nn][Oo])
		return 1;;
	(1|[Ee][Nn][Aa][Bb][Ll][Ee]|[Oo][Nn]|[Tt][Rr][Uu][Ee]|[Yy][Ee][Ss])
		return 0;;
	(*)
		return 2;;
	esac
}

#
# @FUNCTION: Colors handler
#
eval_colors()
{
	local e f b c
	e='\e[' f='3' b='4'
	DEBUG "function=eval_colors( $@ )"

	for c in 0:black 1:red 2:green 3:yellow 4:blue 5:magenta 6:cyan 7:white; do
		eval COLOR_FG_${c#*:}="'${e}1;${f}${c%:*}m'"
		eval COLOR_BG_${c#*:}="'${e}1;${b}${c%:*}m'"
		eval COLOR_FG_${c%:*}="'${e}${f}${c%:*}m'"
		eval COLOR_BG_${c%:*}="'${e}${b}${c%:*}m'"
	done

	COLOR_RESET="${e}0m"
	COLOR_BOLD="${e}1m"
	COLOR_UNDERLINE="${e}4m"
	COLOR_ITALIC="${e}3m"

	if [ "${1}" = 256 ]; then
		local i
		for i in seq 0 255; do
			eval COLOR_BG_${i}="'${e}48;5;${i}m'"
			eval COLOR_FG_${i}="'${e}38;5;${i}m'"
		done
	fi
}

if [ -t 1 ] && yesno "${PRINT_COLOR:-Yes}"; then
	eval_colors
fi


help_message()
{
	cat <<-EOH
  usage: ${PACKAGE} [OPTIONS] <TYPE>[.<PROFILE>]

    -d, --debug              Enable debug output
    -x, --trace              Enable shell trace
    -t, --type               Print all known profiles types
    -c, --current=<type>     Print the current profile <type>
    -p, --profile=<type>     Print the profile that would be used
    -l, --list=<type>        Print all available <type> profiles
    -s, --stop=<type>        Stop the current <type> profile
    -u, --user=<user>        Use a user profile instead of system wide
    -h, --help               Print this help message
    -v, --version            Print pkgname-version_string
    -r, --revert=<type>      Revert to the previous known <type> profile
    -f, --force              Apply profile regardless of the current one

  <type>                     Switch to the currently valid <type> profile
  <type>.<profile>           Switch to the given <type>.<profile> profile
EOH
${1:+exit ${1}}
}

version_message()
{
	echo -e "${COLOR_FG_bluee}${PACKAGE}${COLOR_RESET} version ${COLOR_FG_magenta}${VERSION}${COLOR_RESET}"
	exit
}

#
# @FUNCTION: Validity verification of a profile type
# @ARGS: <type>
#
verify_profile()
{
	local char val var
	DEBUG "function=verify_profile( $@ )"

	if ! [ "${#}" = 1  -a -n "${1}"   ]; then
		die "Invalid profile type \`${1}'"
	fi

	PROFILE_STATE_FILE=${RUNDIR}/${PACKAGE}:${1}
	if [ -r ${PROFILE_STATE_FILE} ]; then
		. ${PROFILE_STATE_FILE}
	else
		var=${RUNDIR}/${PACKAGE}:${1}-current
		if [ -r ${var} ]; then
			read val <${var}
			PROFILE_CURRENT="${val}"
			rm -r ${var}
		fi
		var=${RUNDIR}/${PACKAGE}:${1}-previous
		if [ -r ${var} ]; then
			read val <${var}
			PROFILE_CURRENT="${val}"
			rm -f ${var}
		fi
	fi

	for char in - /; do
		PROFILE_FILE="${PROFILE_DIR}/${1}${char}functions"
		if [ -r "${PROFILE_FILE}" ]; then
			if . "${PROFILE_FILE}"; then
				PROFILE_TYPE="${1}"
				return 0
			fi
		fi
	done
	return 1
}

#
# @FUNCTION: Print the profile to be used
# @ARGS: <type> <profile>
#
profile_default()
{
	DEBUG "function=profile_default( $@ )"
	PROFILE_DEFAULT="$(run_cmd start_test 2>${NULL})"
	if [ -z "${PROFILE_DEFAULT}" ]; then
		set -- "${PROFILES}"
		printf "${1}"
	else
		printf "${PROFILE_DEFAULT}"
	fi
}

#
# @FUNCTION: run script passed as positional parameter 1
# @ARGS: <script> <args>
#
run_cmd()
{
	DEBUG "function=run_cmd( $@ )"
	local CMD="${1}"
	shift
	if command -v ${CMD} >${NULL} 2>&1; then
		DEBUG "command=${CMD}( ${@} )"
		eval ${CMD} "${@}"
	else
		return 111
	fi
}

#
# @FUNCTION: Stop the current profile of the given type
# @ARGS: <inherited>|<type> <profile>
#
stop_profile()
{
	local cmd ret
	DEBUG "function=stop_profile( $@ )"
	[ -n "${PROFILE_CURRENT}" ] || return 0

	begin "Stoping ${1}.${PROFILE_CURRENT} profile"
	for cmd in stop_pre stop_${PROFILE_CURRENT} stop_post; do
		run_cmd "${cmd}" "${PROFILE_CURRENT}"
		case ${?} in
			(111) ;;
			(*) ret=$((${ret}+${?}));;
		esac
	done
	end ${ret}
	[ ${ret:-0} = 0 ] || return ${ret}
	PROFILE_PREVIOUS="${PROFILE_CURRENT}"
	PROFILE_CURRENT=
}

#
# @FUNCTION: Print all known profile types
#
profile_type()
{
	local f t o="${PWD}"
	DEBUG "function=profile_type( $@ )"
	cd ${PROFILE_DIR}
	for f in *-functions */functions; do
		t="${f%/functions}"
		printf "${t%-*} "
	done
	cd "${o}"
	echo
	exit
}

#
# @FUNCTION: Save current profile
#
profile_save()
{
	printf "PROFILE_CURRENT='${PROFILE_CURRENT}'\nPROFILE_PREVIOUS='${PROFILE_PREVIOUS}'" >${PROFILE_STATE_FILE}
}
#
# @FUNCTION: Swap files with extension .<profile> in the appropriate profile
# directory, and sym-link files appropriately.
# @ARGS: <inherited>
#
swap_files()
{
	local src dest
	DEBUG "function=swap_files( $@ )"
	for src in $(find "${PROFILE_DIR}/${1}" -name "*.${2}"); do
		dest="${src#${PROFILE_DIR}/${1}}"; dest="${dest%.${2}}"
		if [ -e "${dest}" ] && [ ! -h "${dest}" ]; then
			if ! diff "${dest}" "${src}" >${NULL}; then
				mv -f "${dest}" "${dest}\~" ||
				{ error "Failed to back up ${dest} file"; continue; }
			fi
		fi
		ln -fs "${src}" "${dest}" || error "Failed to restore ${src} to ${dest}"
	done
}

#
# @FUNCTION: Apply profile
# @ARGS: <type> <profile>
#
start_profile()
{
	local cmd ret
	DEBUG "function=start_profile( $@ )"
	begin "Starting ${1}.${2} profile"
	for cmd in start_pre start_${2} start_post; do
		run_cmd "${cmd}" "${2}"
		case ${?} in
			(111) ;;
			(*) ret=$((${ret}+${?}));;
		esac
	done
	end ${ret}
	# Save profile
	[ ${ret:-0} = 0 ] && PROFILE_CURRENT="${2}" || return ${ret}
	# Sym-link files if available
	if [ -d "${PROFILE_DIR}/${1}" ]; then
		swap_files "${1}" "${2}" || error "Failed to swap ${1}.${2} files"
	fi
}


profile()
{
	local PROFILE_DEFAULT PROFILE_FILE PROFILE_NAME PROFILE_TYPE
	DEBUG "function=profile( $@ )"
	set ${@/./ }
	PROFILE_NAME="${2}"
	PROFILE_TYPE="${1}"
:	${PROFILE_DEFAUL:-$DEFAULT}

	[ -n "${PROFILE_TYPE}" ] || die "Empty/null profile type"
	verify_profile "${PROFILE_TYPE}" || die "Invalid profile type"
	[ -n "${PROFILE_NAME}" ] || PROFILE_NAME="${PROFILE_DEFAULT:-$(profile_default)}"
	[ -n "${PROFILE_NAME}" ] || die "Could not find ${PROFILE_NAME} profile ${PROFILE_TYPE}"

	if [ "${PROFILE_NAME}" = "${PROFILE_CURRENT}" ]; then
		yesno "${PROFILE_FORCE:-0}" || exit 0
	fi

	 stop_profile "${PROFILE_TYPE}" "${PROFILE_NAME}"
	start_profile "${PROFILE_TYPE}" "${PROFILE_NAME}"
}

[ ${#} -ge 1 ] || help_message 2
PROFILE_DEBUG=false
ARGS="$(getopt \
	-l debug,force,help,trace,version,profile:,type,current:,list:,stop:,revert,user: \
	-o \?dfhvtc:p:l:s:r:u:x -n ${PACKAGE} -s sh -- "${@}")"
[ ${?} = 0 ] || help_message 1
eval set -- "${ARGS}"

while true; do
	case "${1}" in
		(-d|--debug) PROFILE_DEBUG=true ; shift;;
		(-x|--trace) set -x ; shift;;
		(-f|--force) PROFILE_FORCE=1; shift;;
		('-?'|-h|--help) help_message 0;;
		(-v|--version) version_message;;
		(-t|--type)	profile_type;;
		(-c|--current)
			verify_profile  "${2}"
			echo "${PROFILE_CURRENT}"
			exit;;
		(-p|--profile)
			verify_profile "${2}"
			profile_default "${2}"
			exit;;
		(-l|--list)
			verify_profile "${2}"
			echo "${PROFILES}"; exit;;
		(-s|--stop)
			verify_profile "${2}"
			stop_profile "${2}"
			exit;;
		(-r|--revert)
			verify_profile "${2}"
			if [ -n "${PROFILE_PREVIOUS}" ]; then
				start_profile "${2}" "${PROFILE_PREVIOUS}"
			fi
			exit;;
		(-u|--user) PROFILE_DIR="${HOME}/.${PACKAGE}"; shift;;
		(*) shift; break;;
	esac
done

trap profile_save EXIT
profile "${@}"

#
# vim:fenc=utf-8:ft=sh:ci:pi:sts=0:sw=4:ts=4:
#
