#!/bin/sh -e

. /usr/html/axis-cgi/lib/sh-helpers.sh
. /lib/rcscripts/sh/files.sh

GDBUS_POWERD_PROP_GET="gdbus call --system 		--dest=com.axis.PowerControl.Status 		--object-path=/com/axis/PowerControl/Status 		--method=org.freedesktop.DBus.Properties.Get 		com.axis.PowerControl.Status"

GDBUS_POWERD_METHOD="gdbus call --system 		--dest=com.axis.PowerControl.Status 		--object-path=/com/axis/PowerControl/Status 		--method=com.axis.PowerControl.Status"

GDBUS_GLLDPD_METHOD="gdbus call --system 		--dest=com.axis.glldp 		--object-path=/com/axis/glldpd 		--method=com.axis.glldp"

GDBUS_POLICYKIT_LLDPCLI="gdbus call -y -d com.axis.PolicyKitSystem 		-o /com/axis/PolicyKitSystem 		-m com.axis.PolicyKitSystem.ExecuteLldpcli"

GDBUS_POWERSAVE_METHOD="gdbus call --system 		--dest=com.axis.PowerControl.PowerSave 		--object-path=/com/axis/PowerControl/PowerSave 		--method=com.axis.PowerControl.PowerSave"

INFO_AVAILABLE=

# $1 - Return variable
# $2 - Parameter to fetch from dbus
# Get value from gdbus
get_value() {
	local value

	[ $# -eq 2 ] && [ "$1" ] && [ "$2" ] ||
		error 'Incorrect call to get_value'

	value=$($GDBUS_POWERD_PROP_GET $2 2>&1) ||
		error "Failed to fetch $2: $value"

	value=${value#\(<}
	value=${value%>,\)}

	[ "$value" ] || error "Failed to get value for $2"

	[ "$2" = NbrOfDevices ] || INFO_AVAILABLE=y
	eval $1=\$value
}

# $1 - Parameter title string to print
# $2 - PoE class to translate to string
# Translate poe class number to descriptive string
print_poeclass_string() {
	[ $# -eq 2 ] && [ "$1" ] && [ "$2" ] ||
		error 'Incorrect call to print_poeclass_string'

	case $2 in
		3)
			echo "$1 IEEE802.3af"
			;
		4)
			echo "$1 IEEE802.3at"
			;
		5)
			echo "$1 Dual_IEEE802.3at (Axis PoE)"
			;
		102)
			echo "$1 Pre-IEEE802.3bt (71W)"
			;
		*)
			if [ $2 -ge $((0x11)) ] && [ $2 -le $((0x18)) ]; then
				poe_class=$(($2 & 0x0F))
				echo "$1 IEEE802.3bt single signature class $poe_class"
			elif [ $2 -ge $((0x21)) ] && [ $2 -le $((0x25)) ]; then
				poe_class=$(($2 & 0x0F))
				echo "$1 IEEE802.3bt dual signature class $poe_class"
			else
				echo "$1 N/A"
			fi
			;
	esac
}

# $1 - PoE class to translate to string
# Translate poe status to descriptive string
print_power_status() {
	local power_pfx status

	[ $# -eq 1 ] && [ "$1" ] ||
		error 'Incorrect call to print_power_status'

	power_pfx='Power Status:'

	get_value status PowerStatusString

	echo

	if [ "$status" != "''" ]; then
		echo "$power_pfx $status"
	else
		case $1 in
			0)
				echo "$power_pfx OK"
				;
			1)
				echo "$power_pfx Discrepancy (Camera is OK but operational temperature is reduced)"
				;
			2)
				echo "$power_pfx Mismatch"
				;
			*)
				echo"$power_pfx N/A"
				;
		esac
	fi
}

# 1 - System description string to split and align
# Find new lines in string and align the lines to parameter title
# Assumes the string uses the same new line characters throughout
align_and_print_description() {
	local string delim

	[ $# -eq 1 ] && [ "$1" ] ||
		error 'Incorrect call to align_and_print_description'

	string=$1

	printf '  -System Description: '
	while :; do
		# Find next new line in string
		case $string in
			*\\r\\n*)
				delim='\\r\\n'
				;
			*\\n\\r*)
				delim='\\n\\r'
				;
			*\\n*)
				delim='\\n'
				;
			*\\r*)
				delim='\\r'
				;
			*)
				# No more newline found so print last line
				# and exit loop
				echo "$string"
				break
				;
		esac

		# Print string until newline
		echo "${string%%$delim*}"
		# Remove part of string before newline
		string=${string#*$delim}
		# Align all lines to first one
		printf '                       '
	done
}

# Get info about neighbour from glldpd and print.
print_neighbour_info() {
	local info name description

	info=$($GDBUS_GLLDPD_METHOD.GetNeighbourInfo 2>&1) ||
		error "DBus call GetNeighbourInfo failed: $info"

	# Parameters are a boolean followed by two strings
	# Discard first parameter
	info=${info#*, \'}

	name=${info%%\', \'*}
	[ "$name" ] || error "Failed to get system name"
	echo "  -System Name: $name"

	description=${info#*\', \'}
	description=${description%\'\)*}
	[ "$description" ] || error "Failed to get system description"
	align_and_print_description "$description"
}

# $1 - Port priority to translate to string
# Translate priority to descriptive string
print_prio_string() {
	local prio_pfx

	[ $# -eq 1 ] && [ "$1" ] ||
		error 'Incorrect call to print_prio_string'

	prio_pfx='  -Priority:'
	case $1 in
		0)
			echo "$prio_pfx Unknown"
			;
		1)
			echo "$prio_pfx Critical"
			;
		2)
			echo "$prio_pfx High"
			;
		3)
			echo "$prio_pfx Low"
			;
		*)
			echo "$prio_pfx Invalid Value"
			;
	esac
}

# Get info about the lldp negotiation
print_negotiation_info() {
	local info prio requested

	info=$($GDBUS_GLLDPD_METHOD.GetPoeInfo 2>&1) ||
		error "DBus call GetPoeInfo failed: $info"

	# Parameters are a boolean, unsigned int, int, int, boolean, int, unsigned int.
	# Interesting parameters are requested power and priority.
	# Discard first, second and third parameter
	info=${info#(*, *, *, }

	requested=${info%%, *}
	[ "$requested" ] || error "Failed to get requested power"
	printf "  -Software Power Requested: %d.%d Watt\n" 		$(($requested/10)) $(($requested%10))

	# Discard fourth and fith parameter
	info=${info#*, *, }

	prio=${info%%, *}
	[ "$prio" ] || error "Failed to get priority"
	print_prio_string $prio
}

# Print info from negotiation log
print_failure_info() {
	local neg_type power failure_time

	while read neg_type power failure_time; do
		[ "$neg_type" ] || error "Failed to read negotiation type"
		[ "$power" ] || error "Failed to read power"
		[ "$failure_time" ] || error "Failed to failure time"

		echo "  Negotiation Type: $neg_type, Power Received: $power," 			"Time: $failure_time"
	done </lib/persistent/var/lib/powerd/negotiation_log
}

# Get total power consumption from powerd, decode the values and print
print_total_power_consumption() {
	local info current_power min_power max_power average_power

	# Fetch camera power consumption from powerd
	info=$($GDBUS_POWERD_METHOD.TotalPowerConsumption 2>&1) || {
		case $info in
			*"Total power consumption not measured"*)
				# Power is not measured for this camera
				return 0
				;
			*)
				error "Dbus call TotalPowerConsumption" 					"failed: $info"
				;
		esac
	}

	# Decode dbus response
	# Should be four double values
	local IFS=,
	set -- $info
	[ $# -eq 4 ] && [ "$1" ] && [ "$2" ] && [ "$3" ] && [ "$4" ] ||
		error 'Incorrect response from dbus call TotalPowerConsumption'

	current_power=$1
	current_power=${current_power#\(}
	current_power=$(printf "%.1f" $current_power)
	[ "$current_power" ] || error 'Failed to decode current power'

	min_power=$(printf "%.1f" $2)
	[ "$min_power" ] || error 'Failed to decode min power'

	max_power=$(printf "%.1f" $3)
	[ "$max_power" ] || error 'Failed to decode max power'

	average_power=$4
	average_power=${average_power%\)}
	average_power=$(printf "%.1f" $average_power)
	[ "$average_power" ] || error 'Failed to decode average power'

	# Do not print consumption if the average is zero since it is not in
	# use then. For example lightning pan mcu do not report power if
	# poe class is three.
	[ "$average_power" = 0.0 ] || {
		INFO_AVAILABLE=y
		echo "
Current Power Consumption: $current_power Watt
Min Power Consumption: $min_power Watt
Max Power Consumption: $max_power Watt
Average Power Consumption: $average_power Watt"
	}
}

# $1 - Device name
# Modify the names to be more descriptive.
print_pretty_name() {
	[ $# -eq 1 ] && [ "$1" ] ||
		error 'Incorrect call to print_pretty_name'

	case $1 in
		led0)
			printf "\nDevice: IR Illumination\n"
			;
		irc)
			printf "\nDevice: IR Cut Filter\n"
			;
		*)
			printf "\nDevice: $1\n"
			;
	esac
}

# $1 - Device index
# Get device power consumption from powerd, decode the values and print
print_device_power_consumption() {
	local info name current_power min_power max_power average_power

	[ $# -eq 1 ] && [ "$1" ] ||
		error 'Incorrect call to print_device_power_consumption'

	# Fetch device power information from powerd
	info=$($GDBUS_POWERD_METHOD.DevicePower $1 2>&1) ||
		error "Dbus call DevicePower failed: $info"

	# Decode dbus response
	# Should be one string followed by four double values
	local IFS=,
	set -- $info
	[ $# -eq 5 ] && [ "$1" ] && [ "$2" ] && [ "$3" ] && [ "$4" ] && [ "$5" ] ||
		error 'Incorrect response from dbus call DevicePower'

	name=$1
	name=${name#\(\'}
	name=${name%\'}
	[ "$name" ] || error 'Failed to decode device name'

	current_power=$(printf "%.1f" $2)
	[ "$current_power" ] || error 'Failed to decode device current power'

	min_power=$(printf "%.1f" $3)
	[ "$min_power" ] || error 'Failed to decode device min power'

	max_power=$(printf "%.1f" $4)
	[ "$max_power" ] || error 'Failed to decode device max power'

	average_power=$5
	average_power=${average_power%\)}
	average_power=$(printf "%.1f" $average_power)
	[ "$average_power" ] || error 'Failed to decode device average power'

	INFO_AVAILABLE=y
	print_pretty_name $name
	echo "Current: $current_power Watt
Min: $min_power Watt
Max: $max_power Watt
Average: $average_power Watt"
}

# Get info about PowerSaving
print_powersaving_info() {
	local info support mode
	echo "PowerSaving"
	info=$($GDBUS_POWERSAVE_METHOD.GetPowerSavingSupport 2>&1) ||
		error "DBus call GetPowerSavingSupport failed: $info"

	info=${info#(}
	support=${info%,)}
	[ "$support" ] || error "Failed to get powersaving support"
	echo "  -Support: $support"

	[ $support != true ] || {
		info=$($GDBUS_POWERSAVE_METHOD.GetPowerSavingMode 2>&1) ||
			error "DBus call GetPowerSavingMode failed: $info"
		info=${info#(}
		mode=${info%,)}

		[ "$mode" ] || error "Failed to get powersaving mode"
		echo "  -Mode: $mode"
	}
}


poe_report_powerd() {
	local return_value failed_negotiations number_of_devices lldp_info

	[ -x /usr/bin/powerd ] || return 1

	title 'Power Management'

	# Hardware poe class
	get_value return_value PsePoeClass
	# Hardware PoE class value -1 indicates PoE info is
	# not available for this camera.
	[ $return_value -eq -1 ] || {
		print_poeclass_string "Hardware:" $return_value

		# Software poe class
		get_value return_value LldpPoeClass
		print_poeclass_string "Software:" $return_value

		# Neighbour and negotiation information if software poe class
		# is used
		[ $return_value -lt 3 ] || {
			lldp_info=$($GDBUS_POLICYKIT_LLDPCLI show-neighbors 2>&1) ||
				error "DBus call $GDBUS_POLICYKIT_LLDPCLI failed: $lldp_info"
			echo "$lldp_info" | sed -e 's#\\n$##;s#\\n#\n#g'
			print_negotiation_info
		}

		# Power status
		get_value return_value PowerStatus
		print_power_status $return_value

		# Power requested
		get_value return_value PowerRequested
		printf "Power Requested: %.1f Watt\n" $return_value

		# Power received
		get_value return_value PowerReceived
		printf "Power Received: %.1f Watt\n" $return_value

		# Negotiation counter
		get_value failed_negotiations NegotiationCounter
		echo "Failed negotiations: $failed_negotiations"

		# Print negotiation failure info
		[ $failed_negotiations -le 0 ] || print_failure_info
	}
	# Power Saving info
	print_powersaving_info

	# Total power consumption
	print_total_power_consumption

	# Number of devices
	get_value number_of_devices NbrOfDevices

	# Print devices
	[ $number_of_devices -le 0 ] || {
		local device_index=0

		echo "Device Power Consumption:"
		while [ $device_index -lt $number_of_devices ]; do
			print_device_power_consumption $device_index

			device_index=$(($device_index + 1))
		done
	}

	[ "$INFO_AVAILABLE" ] || echo 'No power information available'
}

poe_report() {
	local hw_poe_script=/usr/libexec/poe-detect
	local sw_poe_script=/usr/libexec/poe-plus-setup
	local pse_type_file=/run/poe/pse_type
	local poet_executable=/usr/bin/poet
	local poe_class pse_type poe_output

	[ -x $hw_poe_script ] ||
		[ -x $sw_poe_script ] ||
		[ -x $poet_executable ] ||
		return 1

	title "Power over Ethernet (PoE) Detection"

	[ ! -x $hw_poe_script ] || {
		if [ -r $pse_type_file.hw ]; then
			read poe_class <$pse_type_file.hw
		else
			read poe_class <$pse_type_file
		fi

		if [ "$poe_class" ]; then
			poe_output=$poe_class
		else
			poe_output="$hw_poe was empty"
		fi

		printf "Hardware: %s\n\n" "$poe_output"
	}

	[ ! -x $sw_poe_script ] || {
		if [ -r $pse_type_file.hw ]; then
			read poe_class <$pse_type_file
			if [ "$poe_class" ]; then
				poe_output=$poe_class
			else
				poe_output="$pse_type_file was empty"
			fi

		else
			poe_output="No negotiation"
		fi

		printf "Software (LLDP): %s\n" "$poe_output"
	}

	[ ! -x $poet_executable ] || {
		poe_output=$($poet_executable --info 2>&1 || :)

		[ "$poe_output" ] ||
			poe_output="$poet_executable --info returned nothing"

		printf "\n%s\n" "$poe_output"
	}
}

if ! exists gpiolib-bitfiddle; then
	# Use poe_report for cameras without sysfs gpio handling but with PoE+.
	# Use poe_report_powerd for cameras without sysfs gpio and without PoE+
	# to display power control information.
	poe_report || poe_report_powerd ||
		error 'Both glldpd and powerd is missing on the camera'
else
	# Use poe_report_powerd for cameras with sysfs gpio handling that have
	# either PoE+ or power control
	poe_report_powerd || error 'Camera does not have powerd'
fi