#!/bin/zsh
# vim: ft=zsh noet ts=4 sts=4 sw=4

zmodload zsh/stat

: ${SVDIR:=/run/service}

zparseopts -D \
	h=H -help=H \
	-all-up=ALL_UP \
	-all-down=ALL_DOWN \
	-new-down=NEW_DOWN \
	-lock+:=LOCK \
	f+:=F -filter+:=F

if [[ -n $H ]]; then cat <<END
usage: ${0:t} [options] [logfile]

options:
  -h --help            this help
  -f --filter PATTERN  consider only services of given pattern
     --lock   TIMEOUT  perform locking on ZSVDIR
     --all-up          turn all (filtered) services on
     --all-down        turn all (filtered) services off
     --new-down        turn all (filtered) services that haven't been
	                   initialized yet off
END
	exit 0
fi

if (( $#ALL_UP + $#ALL_DOWN + $#NEW_DOWN > 1 )); then
	printf '%s\n' "Only one of following options is allowed:" \
		"  --all-up --all-down --new-down"
	exit 2

fi

if [[ -n $1 ]]; then
	exec >>$1 2>&1
	shift
	zmodload zsh/datetime zsh/system
	print -r - "$(strftime '%Y-%m-%d %H:%M:%S' $EPOCHSECONDS)  started: ${(qqq)0}" "${(qqq)@}  (pid: $$)"
	PS4='+%B${SECONDS} ${sysparams[pid]} %N:%i>%b '
	set -x
fi

service_patterns=()
for pat in $F; do
	case $pat in
		(-f);;
		(--filter);;
		(*) service_patterns+=( $pat );;
	esac
done

typeset -A remove

autoload -Uz zsv_config
zsv_config
zsv_parse

if (($#LOCK)); then
	LOCK_TIMEOUT=$LOCK[-1]
	if ! [[ $LOCK_TIMEOUT =~ '^[0-9]+$' ]]; then
		printf 'Incorrect lock timeout: "%s"\n' $LOCK_TIMEOUT
		exit 2
	fi
	exec 3> ${ZSVDIR}.lock
	if ! flock -w $LOCK_TIMEOUT 3; then
		exit 3
	fi
fi

for d in $ZSVDIR/*(N/); do
	[[ $d == $ZSVDIR/_* ]] && continue
	if (($#service_patterns)); then
		for pat in $service_patterns; do
			if [[ $d:t == $~pat ]]; then
				remove[$d:t]=$d
				break
			fi
		done
	else
		remove[$d:t]=$d
	fi
done

sv_mkdir=${SVDIR%%/}
typeset -gA svdir_stat
zstat -L -H svdir_stat $sv_mkdir && \
while (( $svdir_stat[mode] >> 12 == 10 )); do  # symlink
	sv_mkdir=$svdir_stat[link]
	zstat -L -H svdir_stat $sv_mkdir || break
done
mkdir -p $sv_mkdir || exit $?

for name in $svtab; do
	unset "remove[$name]"
	if (($#service_patterns)); then
		pat_matches=0
		for pat in $service_patterns; do
			if [[ $name == $~pat ]]; then
				pat_matches=1
				break
			fi
		done
		(($pat_matches)) || continue
		unset pat pat_matches
	fi
	zsv_eval $ZSVDIR/$name
	zsv_dir=$zsv_svdir/$name

	if (($#NEW_DOWN)); then
		if ! [[ -d $ZSVDIR/_ovr/$name ]]; then
			mkdir -p $zsv_dir $ZSVDIR/_ovr/$name
			touch $ZSVDIR/_ovr/$name/down
		fi
	else
		mkdir -p $zsv_dir $ZSVDIR/_ovr/$name
		if (($#ALL_UP)); then
			[[ -e $ZSVDIR/_ovr/$name/down ]] && rm $ZSVDIR/_ovr/$name/down
		elif (($#ALL_DOWN)); then
			touch $ZSVDIR/_ovr/$name/down
		fi
	fi

	# flags
	if (( ${flags[M]} )); then  # Manual
		touch $zsv_dir/down
	else
		# check if the service should be up
		if [[ ! -e $ZSVDIR/_ovr/$name/down ]] && \
				$handler[cond] && ! [[ -f ${SVDIR}/down ]] ; then
			rm -f $zsv_dir/down
			sv up $zsv_dir
		else
			touch $zsv_dir/down
			sv down $zsv_dir
		fi
	fi

	# run
	for f in run check finish control/{u,d,o,p,c,h,a,i,q,1,2,t,k,x}; do
		if zsv_has_handler $f:t; then
			[[ -L $zsv_dir/$f ]] && rm $zsv_dir/$f
			mkdir -p $zsv_dir/$f:h
			ln -s ${commands[zsv.exec]} $zsv_dir/$f
		else
			rm -f $zsv_dir/$f
		fi
	done

	# log
	if [[ $handler[log] != '-' ]]; then
		mkdir -p $zsv_dir/log
		[[ -L $zsv_dir/log/run ]] && rm $zsv_dir/log/run
		ln -s ${commands[zsv.exec]} $zsv_dir/log/run
	elif [[ -e $zsv_dir/log ]]; then
		rm -rf $zsv_dir/log
	fi

	# make symlink
	[[ -L ${SVDIR}/$name ]] && rm ${SVDIR}/$name
	ln -s ../zsv/$name ${SVDIR}
done

# for name in $svtab; do
# 	[[ -L ${SVDIR}/$name ]] && rm ${SVDIR}/$name
# 	ln -s ../zsv/$name ${SVDIR}
# done

if (($#remove)); then
	rm -f ${SVDIR}/${^remove:t}
	sv_exit=( $^remove/supervise/control(N) )
	(($#sv_exit)) && sv exit $sv_exit:h:h
	rm -rf $remove
fi

broken=( ${SVDIR}/*(-@N) )
(($#broken)) && rm -f $broken

# make runsvdir re-read the directories
killall --signal CONT --user ${USER:-root} runsvdir &>/dev/null
touch ${SVDIR}/.zsv