#!/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 EXTENDED_GLOB NULL_GLOB
	disable -r end
elif [ -n "${BASH_VERSION}" ]; then
	shopt -qs nullglob
	shopt -qs extglob
fi

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

trap 'PRINT_COL="${COLUMNS:-$(tput cols)}"' WINCH
PRINT_COL="${COLUMNS:-$(tput cols)}"

#
# @FUNCTION: Print error message to stderr
#
error()
{
	local msg="${*}"
	PRINT_LEN=$((${#name}+3+${#msg}))
	local PFX="${name:+${COLOR_MAGENTA}${name}:${COLOR_RESET}}"
	echo -e "${PRINT_EOL}${COLOR_RED}ERROR:${COLOR_RESET} ${PFX} ${@}" >&2
}

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

#
# @FUNCTION: Print info message to stdout
#
info()
{
	local msg="${*}"
	PRINT_LEN=$((${#name}+3+${#msg}))
	local PFX="${name:+${COLOR_YELLOW}${name}:${COLOR_RESET}}"
	echo -e "${PRINT_EOL}${COLOR_BLUE}INFO:${COLOR_RESET} ${PFX} ${@}"
}

#
# @FUNCTION: Print warn message to stdout
#
warn()
{
	local msg="${*}"
	PRINT_LEN=$((${#name}+3+${#msg}))
	local PFX="${name:+${COLOR_RED}${name}:${COLOR_RESET}}"
	echo -e "${PRINT_EOL}${COLOR_YELLOW}WARN:${COLOR_RESET} ${PFX} ${@}"
}

#
# @FUNCTION: Print begin message to stdout
#
begin()
{
	echo -en "${PRINT_EOL}"
	PRINT_EOL="\n"
	local msg="${*}"
	PRINT_LEN=$((${#name}+3+${#msg}))
	local PFX="${name:+${COLOR_MAGENTA}[${COLOR_BLUE}${name}${COLOR_MAGENTA}]${COLOR_RESET}}"
	echo -en "${PFX} ${@}"
}

#
# @FUNCTION: Print end message to stdout
#
end()
{
	local suffix
	case "${1:-0}" in
		(0) suffix="${COLOR_BLUE}[${COLOR_GREEN}ok${COLOR_BLUE}]${COLOR_RESET}";;
		(*) suffix="${COLOR_YELLOW}[${COLOR_RED}no${COLOR_YELLOW}]${COLOR_RESET}";;
	esac
	shift
	if [ -n "${PRINT_EOL}" ]; then
		PRINT_LEN=$((${PRINT_COL}-${PRINT_LEN}))
	else
		PRINT_LEN="${PRINT_COL}"
	fi
	printf "%*b\n" "${PRINT_LEN}" "${@} ${suffix}"
	PRINT_EOL=
	PRINT_LEN=0
}

#
# @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 ESC FGD BGD color
	ESC='\e[' FGD='3' BGD='4'

	for color in 0:BLACK 1:RED 2:GREEN 3:YELLOW 4:BLUE 5:MAGENTA 6:CYAN 7:WHITE; do
		eval COLOR_${color#*:}="'${ESC}${FGD}${color%:*}m'"
		eval COLOR_BG_${color#*:}="'${ESC}${BGD}${color%:*}m'"
	done
	COLOR_RESET="${ESC}0m"
	COLOR_BOLD="${ESC}1m"
	COLOR_UNDERLINE="${ESC}4m"
	COLOR_ITALIC="${ESC}3m"
	if [ "${1}" = 256 ]; then
		local i
		for i in seq 0 255; do
			eval BG_${i}="'${ESC}${BGD}${i}m'"
			eval FG_${i}="'${ESC}${FGD}${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 mode
    -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_BLUE}${PACKAGE}${COLOR_RESET} version ${COLOR_MAGENTA}${VERSION}${COLOR_RESET}"
	exit
}

#
# @FUNCTION: Validity verification of a profile type
# @ARGS: <type>
#
verify_profile()
{
	local char
	[ "${#}" = 1  ] && [ -n "${1}"   ] || die "Invalid profile type ${1}"
	for char in - /; do
		PROFILE_FILE="${PROFILE_DIR}/${1}${char}functions"
		[ -f "${PROFILE_FILE}" ] && source "${PROFILE_FILE}" && break
	done
}

#
# @FUNCTION: Print the profile to be used
# @ARGS: <type> <profile>
#
get_profile()
{
	run_cmd start_test && return
	[ -n "${DEFAULT}" ] && echo "${DEFAULT}" || echo -n "${PROFILES%% *}"
}

#
# @FUNCTION: Print the currently selected profile
# @ARGS: <type>
#
current_profile()
{
	local file="${RUNDIR}/${PACKAGE}:${1}-current"
	[ -s "${file}" ] && echo $(cat "${file}")
}

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

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

	begin "Stoping ${1}.${p} profile"
	for cmd in stop_pre stop_${p} stop_post; do
		run_cmd "${cmd}" "${p}"
		case ${?} in
			(111) ;;
			(*) ret=$((${ret}+${?}));;
		esac
	done
	end ${ret}
	[ ${ret:-0} = 0 ] || return ${ret}
	echo        > "${RUNDIR}/${PACKAGE}:${1}-current"
	echo "${p}" > "${RUNDIR}/${PACKAGE}:${1}-previous"
}

#
# @FUNCTION: Revert to the previous profile of the given type
# @ARGS: <type>
#
revert_profile()
{
	local file="${RUNDIR}/${PACKAGE}:${1}-previous" p
	[ -s "${file}" ] && p=$(cat "${file}") || return
	[ -n "${p}" ] && start_profile "${1}" "${p}"
}

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

#
# @FUNCTION: Swap files with extension .<profile> in the appropriate profile
# directory, and sym-link files appropriately.
# @ARGS: <inherited>
#
swap_files()
{
	local src dest
	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
	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 ] && echo "${2}" >"${RUNDIR}/${PACKAGE}:${1}-current" || 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_CURRENT PROFILE_FILE PROFILE_NAME PROFILE_TYPE
	set ${@/./ }
	PROFILE_NAME="${2}"
	PROFILE_TYPE="${1}"

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

	PROFILE_CURRENT=$(current_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
ARGS="$(getopt \
	-l debug,force,help,version,profile:,type,current:,list:,stop:,revert,user: \
	-o \?dfhvtc:p:l:s:r:u: -n ${PACKAGE} -s sh -- "${@}")"
[ ${?} = 0 ] || help_message 1
eval set -- "${ARGS}"

while true; do
	case "${1}" in
		(-d|--debug) 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}"
			current_profile "${2}"
			exit;;
		(-p|--profile)
			verify_profile "${2}"
			get_profile    "${2}"
			exit;;
		(-l|--list)
			verify_profile "${2}"
			echo "${PROFILES}"; exit;;
		(-s|--stop)
			verify_profile "${2}"
			stop_profile "${2}"
			exit;;
		(-r|--revert)
			verify_profile "${2}"
			revert_profile "${2}"
			exit;;
		(-u|--user) PROFILE_DIR="${HOME}/.${PACKAGE}"; shift;;
		(*) shift; break;;
	esac
done

profile "${@}"

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