#!/bin/sh

. /lib/rcscripts/sh/error.sh
. /lib/rcscripts/sh/rc-std2parse.sh
. /lib/rcscripts/sh/files.sh

LOG="logger -s -t${0##*/}[$$]"
LOG_I="$LOG -pinfo"
LOG_E="$LOG -perr"
PROFILES_COUNT=-1

# Dynamic configuration containing ids of existing pipelines.
CONF_FILE=/etc/dynamic/mcast-always.conf

LOCK_FILE=/var/lock/mcast-always

# Check that a session id is only made of figures.
is_session_id() {
	[ $# -eq 1 ] && [ "$1" ] || {
		$LOG_E "is_session_id: missing or empty argument."
		return 1
	}
	case $1 in
		*[!0-9]*)
			$LOG_E "is_session_id: invalid argument."
			return 1
			;
	esac
}

# Set CM_PORT in the range provided by the user. Since the error conditions
# are unlikely to happen, and to avoid a complex port lookup, we simply ask
# the user to set a port manually.
# $1 is a shift in port numbering (video: 0 audio:2)
set_cm_port() {
	local used_port ports_needed i=0

	case $1 in
		0|2)
			;
		*)
			$LOG_E "set_cm_port: invalid argument."
			return 1
			;
	esac

	# The RTP port is always even (RTCP odd).
	CM_PORT=$(($STD2_NETWORK_RTP_STARTPORT + $1 + $CHANNEL * 4))
	[ $(($CM_PORT % 2)) -eq 0 ] || CM_PORT=$(($CM_PORT + 1))

	# Verify that the port is within range
	[ $CM_PORT -lt $STD2_NETWORK_RTP_ENDPORT ] || {
		$LOG_E "Automatic port setting failed. Please set the" \
			"video port manually for channel $CHANNEL."
		return 1
	}

	# Verify that the new port is not used by another channel.
	while [ $i -lt $STD2_NETWORK_RTP_NBROFRTPGROUPS ]; do
		[ $i -ne $CHANNEL ] || {
			i=$(($i + 1))
			continue
		}

		case $MEDIA_TYPE in
			video)
				eval used_port=\$STD2_NETWORK_RTP_R${i}_VIDEOPORT
				;
			audio)
				eval used_port=\$STD2_NETWORK_RTP_R${i}_AUDIOPORT
				;
		esac

		[ $CM_PORT -ne $used_port ] || {
			$LOG_E "Port conflict between channels $CHANNEL and" \
				"$i. Please set the port manually for" \
				"channel $CHANNEL."
			return 1
		}
		i=$(($i + 1))
	done
}

# DBUS variables and functions
# DBUS functions display specific dbus errors in case of failure.
DBUS="dbus-send --system --dest=com.axis.Streamer --print-reply --type=method_call"
DOBJ_PATH=/com/axis/Streamer/RTP
METHOD_PREFIX=com.axis.Streamer.RTP

session_create() {
	# media=$1, profile=$2, group=$3, port=$4 ttl=$5

	[ $# -eq 5 ] || {
		$LOG_E "session_create: wrong number of arguments."
		return 1
	}
	case $1 in
		audio)
			mediaopt="audio=1&video=0"
			;
		video)
			mediaopt="audio=0&video=1"
			;
		*)
			$LOG_E "session_create: invalid media type $1"
			return 1
			;
	esac

	# Leave the rest of arguments validation to $DBUS.

	# Tell the streamer to create a multicast pipeline.
	$DBUS $DOBJ_PATH $METHOD_PREFIX.CreatePipeline \
		string:"/axis-media/media.amp" \
		string:"$2&camera=$CAMERA&$mediaopt" \
		string:"$3" \
		int32:$4 \
		int32:$5 2>&1
}

session_play() {
	is_session_id "$1" &&
		$DBUS $DOBJ_PATH/Pipeline/$1 $METHOD_PREFIX.Pipeline.Play >/dev/null 2>&1
}

session_destroy() {
	is_session_id "$1" &&
		$DBUS $DOBJ_PATH/Pipeline/$1 $METHOD_PREFIX.Pipeline.Destroy >/dev/null 2>&1
}

# Start and stop continuous multicast.
start_continuous_multicast() {
	local cm_profile_name cm_group trackID creation_msg session_id cm_profile

	# Set variables necessary to build a pipeline.
	case $MEDIA_TYPE in
		video)
			eval cm_profile=\$STD2_NETWORK_RTP_R${CHANNEL}_ALWAYSMULTICASTPROFILE
			eval cm_group=\$STD2_NETWORK_RTP_R${CHANNEL}_VIDEOADDRESS
			eval CM_PORT=\$STD2_NETWORK_RTP_R${CHANNEL}_VIDEOPORT
			# videoport == 0 means automatic port setting.
			[ $CM_PORT -ne 0 ] || set_cm_port 0

			media=video
			;
		audio)
			eval cm_profile=\$STD2_NETWORK_RTP_R${CHANNEL}_ALWAYSMULTICASTPROFILE
			eval cm_group=\$STD2_NETWORK_RTP_R${CHANNEL}_AUDIOADDRESS
			eval CM_PORT=\$STD2_NETWORK_RTP_R${CHANNEL}_AUDIOPORT
			[ $CM_PORT -ne 0 ] || set_cm_port 2

			media=audio
			;
	esac
	eval ttl=\$STD2_NETWORK_RTP_R${CHANNEL}_TTL

	# Create the multicast pipeline using the DBus API.
	creation_msg=$(session_create $media "$cm_profile" "$cm_group" "$CM_PORT" $ttl) || {
		$LOG_E $creation_msg
		return 1
	}
	# If session_create is successful, stdout contains our precious pipeline id.
	session_id=$(echo $creation_msg | sed -rne 's|^.*Pipeline/([^"]*)"$|\1|p')

	session_play $session_id || {
		$LOG_E "Could not play $MEDIA_TYPE RTP pipeline."
		return 1
	}
	# Save the session id to stop the stream when necessary.
	# Only copy the CONF_FILE if it exist.
	if [ -f $CONF_FILE ]; then
		cp $CONF_FILE $CONF_FILE.tmp || {
			session_destroy $session_id || $LOG_E "Destroy of" \
				"session '$session_id' failed"
			$LOG_E "Failed to copy '$CONF_FILE'. Destroy session"
			return 1
		}
	fi
	echo "$CHANNEL $MEDIA_TYPE id$session_id" >> $CONF_FILE.tmp || {
		session_destroy $session_id || $LOG_E "Destroy of" \
			"session '$session_id' failed"
		$LOG_E "Failed to save multicast session '$session_id'" \
			"to configuration file. Destroy session."
		return 1
	}

	fsynced_write_or_cleanup $CONF_FILE.tmp $CONF_FILE &&
	$LOG_I "multicast ($MEDIA_TYPE $cm_profile) started at" \
		"$cm_group:$CM_PORT camera $CAMERA" || {
		session_destroy $session_id || $LOG_E "Destroy of" \
			"session '$session_id' failed"
		$LOG_E "Failed to save multicast session to configuration" \
			"file. Destroy '$session_id'."
		return 1
	}
}

stop_continuous_multicast() {
	local id

	# Destroy pipeline.
	[ -r $CONF_FILE ] || {
		$LOG_E "stop_continuous_multicast: " \
			"$CONF_FILE is not readable."
		return 1
	}
	id=$(sed -rne "s|^$CHANNEL $MEDIA_TYPE id(.*)$|\1|p" $CONF_FILE) || {
		$LOG_E "could not retrieve session id from $CONF_FILE."
		return 1
	}
	# This can only fail if no pipeline exist, can happen when restarting
	# monolith. Thus it does not matter if this fails or succeedes, the conf
	# should always be deleted
	session_destroy $id || $LOG_E "Failed to destroy multicast session $id."

	# Delete session infos from configuration.
	[ -w $CONF_FILE ] || {
		$LOG_E "$CONF_FILE is not writable," \
			"Always Multicast infos can not be deleted."
		return 1
	}
	sed -i -re "/^$CHANNEL $MEDIA_TYPE.*/d" $CONF_FILE || {
		$LOG_E "stop_continuous_multicast: " \
			"could not delete session infos."
		return 1
	}
}

# We never know if a call from parhand means start or stop. It means "toggle".
toggle_continuous_multicast() {
	local enabled grep_res

	# Look if toggle means start or stop!
	case $MEDIA_TYPE in
		video)
			eval enabled=\$STD2_NETWORK_RTP_R${CHANNEL}_ALWAYSMULTICASTVIDEO
			;
		audio)
			eval enabled=\$STD2_NETWORK_RTP_R${CHANNEL}_ALWAYSMULTICASTAUDIO
			;
	esac

	grep -q "$CHANNEL[[:blank:]]\+$MEDIA_TYPE[[:blank:]]\+id" $CONF_FILE
	grep_res=$?

	case $enabled in
		yes)
			# Channel already started || start channel.
			[ $grep_res -eq 0 ] || start_continuous_multicast
			;
		no)
			# Channel already stopped || stop channel.
			[ $grep_res -ne 0 ] || stop_continuous_multicast
			;
	esac
}

# This script is called from mcast-always initscript (options start/stop) or
# as a parhand parameter setter (option toggle).
# This script is called for exactly one channel and one media type at a time.
CHANNEL=$2
case $CHANNEL in
	*[!0-9]*)
		error "Invalid channel '$CHANNEL'"
		;
esac

# The media type is video or audio
MEDIA_TYPE=$3
case $MEDIA_TYPE in
	audio|video)
		;
	*)
		error "Invalid media type '$MEDIA_TYPE'"
		;
esac

# Retrieve the global RTP configuration variables.
# Note that std2parse has its own error handling, exiting if necessary.
std2parse /etc/sysconfig/rtp.conf
# Look for allview.
std2parse /etc/sysconfig/image_global.conf
eval source=\$STD2_IMAGE_I${CHANNEL}_SOURCE
if [ $source = "quad" ]; then
	CAMERA=all
else
	CAMERA=$(($CHANNEL + 1))
fi


(flock 9 || exit 1
	case $1 in
		start)
			start_continuous_multicast
			;
		stop)
			stop_continuous_multicast
			;
		toggle)
			#Called from parhand
			toggle_continuous_multicast
			;
		*)
			$LOG_E "Usage: $0 {start|stop|toggle} <channel> <audio|video>"
			;
	esac
) 9>$LOCK_FILE