#!/bin/bash

# Adam Wujek & Jean-Claude BAU @CERN
# script to assembly ppsi.conf based on dot-config configuration
#
# Parameters:
#     -j generate JSON format of ppsi.conf 
#     -o file Overwrite the default output file name
#     -i file Overwrite the default input dot-config file  
#     -p file Overwrite the default input psi-pre file
#
 
PRE_FILE="/wr/etc/ppsi-pre.conf"
OUTPUT_FILE="/etc/ppsi.conf"
DOTCONFIG_FILE="/wr/etc/dot-config"
unset JSON_FORMAT

#decode script parameters

while getopts jo:i:p: option
do
	case "${option}" in
		"j") JSON_FORMAT=1;;
		"o") OUTPUT_FILE=${OPTARG};;
		"i") DOTCONFIG_FILE=${OPTARG};;
		"p") PRE_FILE=$OPTARG;;
	esac
done

if [ -f "$DOTCONFIG_FILE" ]; then
    . "$DOTCONFIG_FILE"
else
    # if dot-config not available remove ppsi's config
    rm $OUTPUT_FILE
    exit 1
fi

script_name="$0"

#
# This function retreive the fiber delay coefficient from 
# the CONFIG_FIBER${1}_PARAMS parameter
# Parameter :
#   $1 = fiber index 
# Return the fiber delay coefficient 
function get_fiber_delay_coeff() { 
	    local dc=0
	    local fb=$1 	
	    if [ -n "$fb" ]; then # check if fiber parameter exists
	    	if [[ "$fb" =~ ^-?[0-9]+$ ]]; then # check if fiber parameter is an integer 
				printf -v fb "%02d" $fb
				local fiber_param=$(eval "echo \$CONFIG_FIBER${fb}_PARAMS")
			     if [ -n "$fiber_param" ]; then # check if the fiber exists
					IFS='=' read -a fpa <<< "$fiber_param"
					dc=${fpa[1]}
				 else
					echo "$script_name: Unknown fiber=\"$fb\" in CONFIG_PORT"$i_port"_PARAMS"
				 fi
	      	else
				echo "$script_name: Invalid parameter fiber=\"$fb\" in CONFIG_PORT"$i_port"_PARAMS"
		  	fi
	    fi
	    echo "$dc" 
} 

#
# Decode ppsi.conf 'pre' file. It is used to define or override global keys. 
# Expected file format :
#   - must define key/value pairs separated with a space character : 'key value'
#   - empty line are allowed 
#   - a comment start at the beginning of the line with the character '#'
# Parameter: $1 file_to_open
#
function decode_pre_file(){
	filename="$1"
    # read globals entries 
	while IFS='' read -r line  ; do
		if [[ -n "$line" ]] && [[ ${line:0:1} != "#" ]] ; then
 			IFS=' ' read var1 var2 <<< $line	
 			if  [[ -n $var1 ]] && [[ -n $var2 ]] ; then	
 				globals[$var1]=$var2
    		fi
    	fi
	done < "$filename"
}

#
# Generate the ppsi.conf file using the default text format:
#   - define key/value pairs separated with a space character : 'key value'
#   - empty line are allowed 
#   - a comment start at the beginning of the line with the character '#'
#   - port instances are separated by a empty line
#
# Parameter : $1 output_file_name 
#             $2 pre_file_name
function gen_ppsi_conf() { 

	# Read destination - stdout if not defined
	output=${1:-/dev/stdout}

	echo "# Autogenerated file, please don't edit." >$output 
	echo "# This file will be overwritten at next boot." >>$output
	echo -e "\n# Globals\n" >>$output
	
	# globals
	for key in ${globals_indexes}; do
		prefix=""
		if [[ $key = *"${globals_not_yet_supported}"* ]] ; then
			prefix="# Parameter not yet supported // " 
		fi
		value=${globals[$key]}
		if [ -n "$value" ]; then 
			echo "${prefix}$key $value"  >>$output
		fi
	done
	echo -e "\n\n# Port instances\n" >>$output
	
	local size
	# Physical ports 
	for i_port in {01..18}; do # scan all the physical ports

		port_vn="port${i_port}"
		
		# PPSI instances
		for j_inst in {01..02}; do  # scan all the ppsi instances for a given port

			inst_vn="${port_vn}inst${j_inst}"
			eval array=\( \${${inst_vn}[@]} \)
			size=${#array[@]}
			if [ "${size}" == "0" ] ; then
				continue
			fi
			v="$inst_vn[name]" ; echo "port  ${!v}" >>$output
			v="$inst_vn[proto]"; echo "proto ${!v}" >>$output
			v="$port_vn[iface]"; echo "iface ${!v}" >>$output
			v="$inst_vn[masteronly]"; [[ -n "${!v}"  ]] && echo "masterOnly  ${!v}" >>$output
			v="$inst_vn[vlan]" ; [[ -n "${!v}"       ]] && echo "vlan  ${!v}" >>$output
			v="$inst_vn[dm]"   ; [[ "${!v}" != "e2e" ]] && echo "dm    ${!v}" >>$output
			v="$inst_vn[prof]" ; echo "profile  ${!v}" >>$output
			v="$inst_vn[dstate]"; [[ -n "${!v}" ]] &&  echo "desiredState  ${!v}" >>$output
			v="$inst_vn[rx]";     echo "ingressLatency    ${!v}" >>$output
			v="$inst_vn[tx]";     echo "egressLatency     ${!v}" >>$output
			v="$port_vn[dcoeff]"; echo "delayCoefficient  ${!v}" >>$output
			v="$port_vn[asym]";   echo "constantAsymmetry ${!v}" >>$output
			for p in $inst_opt_parameters ; do 
				plc=$(inst_opt_parameter_to_instance_index ${p})
				v="$inst_vn[$plc]"
				[[ -n "${!v}" ]] && echo "$plc    ${!v}" >>$output
			done
			echo -n -e "\n\n" >>$output
		done
	done
}

#
# Generate the ppsi.conf file using a JSON format:
#   {
#  		"globals": {
#			"clock-class" : 187,
#			...
#	    },
#       "ports" : [
#	      {
#           "iface" : "wri1",
#            ...
#	        "instances" : [
#		        {
#			       "name" : "wri1-i1",
#			       ...
#               },
#               {  ... } 
#           ]
#         },
#         { ... }
#       ]
#  }
#	
# Parameter : $1 output_file_name 
#             $2 pre_file_name
function gen_ppsi_conf_json() { 
	# Read destination - stdout if not defined
	output=${1:-/dev/stdout}
	
	# start globals
	echo -e "{\n  \"globals\": {\n" >$output
	
	# globals
	unset comma_g
	for key in ${globals_indexes}; do
		if [[ $key = *"${globals_not_yet_supported}"* ]] ; then
			continue
		fi
		value=${globals[$key]}
		if [ -n "$value" ]; then 
			[[ -n "${comma_g}" ]] && echo ","   >>$output
			echo -e -n "    \"$key\": \"$value\""  >>$output
			comma_g=1
		fi
	done
	
	# end globals
	echo -e -n "\n  }" >>$output

	# start ports
	echo -e ",\n  \"ports\": [" >>$output
	
	local size
	# Physical ports 
	for i_port in {01..18}; do # scan all the physical ports

		port_vn="port${i_port}"
		# start port
		if [  $i_port == "01" ] ; then
			echo -e "  {" >>$output
		else
			echo -e ",\n  {" >>$output
		fi
		v="$port_vn[iface]";  echo -e "    \"iface\": \"${!v}\"," >>$output
		v="$port_vn[dcoeff]"; echo -e "    \"delayCoefficient\": \"${!v}\"," >>$output
		v="$port_vn[asym]";   echo -e "    \"constantAsymmetry\": \"${!v}\"," >>$output
		
		# start instances
		echo -e "    \"instances\": [" >>$output

		# PPSI instances
		for j_inst in {01..02}; do  # scan all the ppsi instances for a given port

			inst_vn="${port_vn}inst${j_inst}"
			eval array=\( \${${inst_vn}[@]} \)
			size=${#array[@]}
			if [ "${size}" == "0" ] ; then
				continue
			fi

			# start instance /instances
			if [  $j_inst == "01" ] ; then
				# start instances
				echo -e "    {" >>$output
			else
				echo -e ",\n    {" >>$output
			fi

			v="$inst_vn[name]" ; echo -e "      \"port\":  \"${!v}\"," >>$output
			v="$inst_vn[rx]";    echo -e "      \"ingressLatency\": \"${!v}\"," >>$output
			v="$inst_vn[tx]";    echo -e "      \"egressLatency\":  \"${!v}\"," >>$output
			v="$inst_vn[proto]"; echo -e "      \"proto\": \"${!v}\"," >>$output
			v="$inst_vn[masteronly]"; [[ -n "${!v}"  ]] && echo -e "      \"masterOnly\":  \"${!v}\"," >>$output
			v="$inst_vn[vlan]" ; [[ -n "${!v}"       ]] && echo -e "      \"vlan\":  \"${!v}\"," >>$output
			v="$inst_vn[dm]"   ; [[ "${!v}" != "e2e" ]] && echo -e "      \"dm\":    \"${!v}\"," >>$output
			v="$inst_vn[prof]" ; echo -e -n "      \"profile\":  \"${!v}\"" >>$output
			v="$inst_vn[dstate]"; [[ -n "${!v}" ]] &&  echo -e -n ",\n      \"desiredState\": \"${!v}\"" >>$output

			for p in $inst_opt_parameters ; do 
				plc=$(inst_opt_parameter_to_instance_index ${p})
				v="$inst_vn[$plc]"
				[[ -n "${!v}" ]] && echo -e -n ",\n      \"$plc\": \"${!v}\"" >>$output
			done

			# end instance
			echo -e -n "\n    }" >>$output
		done
		
		# end instances
		echo -e "\n    ]" >>$output
		
		# end port
		echo -e -n "  }" >>$output
		
	done

	# end ports
	echo -e "\n  ]" >>$output

	# end main block
	echo "}" >>$output
}

function inst_opt_parameter_to_instance_index() {
	 echo `echo $1 | tr '[:upper:]_' '[:lower:]-'`
}

globals_indexes='clock-class clock-accuracy clock-allan-variance domain-number priority1 priority2 time-source externalPortConfigurationEnabled'
globals_not_yet_supported='time-source'
port_indexes='fiber asym dcoeff iface'
instance_indexes='rx tx proto prof dm monitor dstate masteronly'
inst_opt_parameters='ANNOUNCE_INTERVAL ANNOUNCE_RECEIPT_TIMEOUT SYNC_INTERVAL MIN_DELAY_REQ_INTERVAL MIN_PDELAY_REQ_INTERVAL L1SYNC_INTERVAL L1SYNC_RECEIPT_TIMEOUT'
echo "${x}" | tr '_' '-' | sed -e "s/\b\(.\)/\u\1/g"
# update instance_indexes with inst_opt_parameters
instance_indexes="$instance_indexes `echo ${inst_opt_parameters} | tr '[:upper:]' '[:lower:]'`"

declare -A globals

# Read specific configuration : Still use the old format - to discuss
[[ "$PRE_FILE" != "" ]] && [[ -f $PRE_FILE ]] && decode_pre_file "$PRE_FILE"
	

# Use default value of clock class if not overwritten or empty string 
if [ -v CONFIG_PTP_OPT_OVERWRITE_CLOCK_CLASS ] && [ -n "$CONFIG_PTP_OPT_CLOCK_CLASS" ]; then
	globals[clock-class]="$CONFIG_PTP_OPT_CLOCK_CLASS"
else
	if [ "$CONFIG_TIME_GM" = "y" ]; then
		globals[clock-class]=6
	fi
	if [ "$CONFIG_TIME_FM" = "y" ]; then
		globals[clock-class]=52
	fi
	if [ "$CONFIG_TIME_BC" = "y" ]; then
		globals[clock-class]=248
	fi
fi

if [ -n "$CONFIG_PTP_OPT_CLOCK_ACCURACY" ]; then
	globals[clock-accuracy]="$CONFIG_PTP_OPT_CLOCK_ACCURACY"
fi
if [ -n "$CONFIG_PTP_OPT_CLOCK_ALLAN_VARIANCE" ]; then
	globals[clock-allan-variance]="$CONFIG_PTP_OPT_CLOCK_ALLAN_VARIANCE"
fi

if [ -n "$CONFIG_PTP_OPT_DOMAIN_NUMBER" ]; then
	globals[domain-number]="$CONFIG_PTP_OPT_DOMAIN_NUMBER"
fi

if [ -n "$CONFIG_PTP_OPT_ANNOUNCE_INTERVAL" ]; then
	globals[announce-interval]="$CONFIG_PTP_OPT_ANNOUNCE_INTERVAL"
fi

if [ -n "$CONFIG_PTP_OPT_SYNC_INTERVAL" ]; then
	globals[sync-interval]="$CONFIG_PTP_OPT_SYNC_INTERVAL"
fi

if [ -n "$CONFIG_PTP_OPT_PRIORITY1" ]; then
	globals[priority1]="$CONFIG_PTP_OPT_PRIORITY1"
fi

if [ -n "$CONFIG_PTP_OPT_PRIORITY2" ]; then
	globals[priority2]="$CONFIG_PTP_OPT_PRIORITY2"
fi

if [ -v CONFIG_PTP_OPT_OVERWRITE_TIME_SOURCE ] && [ -n "$CONFIG_PTP_OPT_TIME_SOURCE" ]; then
	globals[time-source]="$CONFIG_PTP_OPT_TIME_SOURCE"
else
	if [ "$CONFIG_TIME_GM" = "y" ]; then
		globals[time-source]=16 # ATOMIC_CLOCK
	fi
	if [ "$CONFIG_TIME_FM" = "y" ]; then
		globals[time-source]=160 # INTERNAL_OSCILLATOR
	fi
	if [ "$CONFIG_TIME_BC" = "y" ]; then
		globals[time-source]=160 # INTERNAL_OSCILLATOR
	fi
fi

if [ -n "$CONFIG_PTP_OPT_EXT_PORT_CONFIG_ENABLED" ]; then
	globals[externalPortConfigurationEnabled]="$CONFIG_PTP_OPT_EXT_PORT_CONFIG_ENABLED"
fi

for i_port in {01..18}; do # scan all the physical ports

	port_vn="port${i_port}"
	declare -A $port_vn

	#remove leading zero from i_port (params has numbers with leading zero,
	#interface names are without leading zero)
	i_port_int=$(expr $i_port + 0)
	
	# parse parameters
	port_key="CONFIG_PORT${i_port}_PARAMS"
	port_key_value=${!port_key}

	# save pairs into array
	IFS=',' read -a pair_array <<< "${port_key_value}"
	for pair in ${pair_array[@]}
	do
		# split pairs
		IFS='=' read param value <<< "$pair"
		case "$param" in
		"fiber")
		 	v="$port_vn[fiber]"
		 	eval ${v}="$value"
		 	v="$port_vn[dcoeff]"
		 	eval ${v}=$(get_fiber_delay_coeff $value)
		 	;;
		"asym")
		 	v="$port_vn[asym]"
		 	eval ${v}="$value"
		 	;;
		"iface")
		 	v="$port_vn[iface]"
		 	eval ${v}="$value"
		 	;;
		*)
			echo "$script_name: Invalid parameter $param in ${port_key}" ;;
		esac
	done
 
	for j_inst in {01..02}; do  # scan all the ppsi instances for a given port

		#remove leading zero from i_port (params has numbers with leading zero,
		#interface names are without leading zero)
		j_inst_int=$(expr $j_inst + 0)
	
		inst_count="CONFIG_PORT${i_port}_INSTANCE_COUNT_`expr ${j_inst_int} - 1`"
		inst_count_value=$(eval "echo \$${inst_count}")
		if [ -n "${inst_count_value}" ]; then
			break # number of defined instances reached 	
		fi

		# Set default values
		tx=0
		rx=0
		
		inst_vn="${port_vn}inst${j_inst}"	
		declare -A $inst_vn
		inst_key="CONFIG_PORT${i_port}_INST${j_inst}"
		inst_key_value=${!inst_key}
	    IFS=',' read -a inst_pair_array <<< "${inst_key_value}"

		for pair in ${inst_pair_array[@]}
		do
			# split pairs
			IFS='=' read param value <<< "$pair"
			case "$param" in
			"tx") 
			    v="$inst_vn[tx]"
			    eval ${v}="$value"
			    ;;
			"rx") 
			    v="$inst_vn[rx]"
			    eval ${v}="$value"
			    ;;
			"proto")
				 v="$inst_vn[proto]" 
				 eval ${v}="${value,,}"
				 ;;
			"ext" | "prof")
				 v="$inst_vn[prof]" 
				 eval ${v}="${value,,}"
				 ;;
			"dm")
				 v="$inst_vn[dm]" 
				 eval ${v}="${value,,}"
				 ;;
			"monitor")		
				continue;; # read by SNMP directly from the config
			*)
				echo "$script_name: Invalid parameter $param in ${inst_key}" ;;
			esac
		
		done
		for p in $inst_opt_parameters; do 
			k="${inst_key}_$p"
			plc=$(inst_opt_parameter_to_instance_index ${p})
			if [ -n "$k" ]; then
				v="$inst_vn[${plc}]"; eval ${v}="${!k}"
			fi
		done
	
		v="$inst_vn[proto]"
		if [ ! -n "${!v}" ]; then 
			eval ${v}="raw" # proto not defined. Set it to raw by default
		fi		
		
		# set the profile
		v="$inst_vn[prof]"
		p_prof=${!v}
		if [ "${p_prof}" == "wr" ]; then
			eval ${v}="whiterabbit"
		elif [ "${p_prof}" == "ha" ]; then
		    eval ${v}="highaccuracy"
		elif [ "${p_prof}" == "none" ] || [ "${p_prof}" == "ptp" ]; then
			# do nothing
		    eval ${v}="ptp" >> $OUTPUT_FILE
		    p_prof="ptp"
		elif [ -n "$p" ]; then
			echo "$script_name: Invalid parameter prof=\"$p\" in ${inst_key}"
			eval ${v}="highaccuracy"
			p_prof="ha"
		else
			# default
			eval ${v}="highaccuracy"
			p_prof="ha"
		fi
		
		# define instance name
		v="$port_vn[iface]"; p_iface=${!v}
		v="$inst_vn[proto]"; p_proto=${!v}
		v="$inst_vn[name]"; eval ${v}="${p_iface}-${j_inst_int}-${p_prof}-${p_proto}"
		
		# if extPortConfiguration enabled, get the desired state
		if [ "$CONFIG_PTP_OPT_EXT_PORT_CONFIG_ENABLED" == 'y' ] ; then
			v="$inst_vn[dstate]"; 
			eval ${v}="passive" # default value
			k="${inst_key}_DESIRADE_STATE_SLAVE";  [ "${!k}" == 'y' ] && eval ${v}="slave"		
			k="${inst_key}_DESIRADE_STATE_MASTER"; [ "${!k}" == 'y' ] && eval ${v}="master"
		else
			v="$inst_vn[masteronly]"; 
			k="${inst_key}_BMODE_MASTER_ONLY";  [ "$k" == 'y' ] && eval ${v}="y"					
		fi
		
		# set delay mechanism
		v="$inst_vn[dm]"
		p=${!v}
		if [ "${p}" = "p2p" -o   "${p}" = "e2e" ]; then
			# do nothing
			true
		elif [ -n "${p}" ]; then
			echo "$script_name: Invalid parameter dm=\"${p}\" in ${inst_key}"
		fi

		# add vlans
		if [ "$CONFIG_VLANS_ENABLE" = "y" ]; then
			unset ppsi_vlans;
			unset port_mode_access;
			unset port_mode_trunk;
			unset port_mode_unqualified;
			unset port_mode_disabled;
	
			# check port mode
			port_mode_access=$(eval "echo \$CONFIG_VLANS_PORT"$i_port"_MODE_ACCESS")
			port_mode_trunk=$(eval "echo \$CONFIG_VLANS_PORT"$i_port"_MODE_TRUNK")
			port_mode_unqualified=$(eval "echo \$CONFIG_VLANS_PORT"$i_port"_MODE_UNQUALIFIED")
			port_mode_disabled=$(eval "echo \$CONFIG_VLANS_PORT"$i_port"_MODE_DISABLED")
	
			# check port mode
			if [ "$port_mode_access" = "y" ]; then
				ppsi_vlans=$(eval "echo \$CONFIG_VLANS_PORT"$i_port"_VID")
				# use "&> /dev/null" to avoid error when $ppsi_vlans
				# is not a number
				if [ "$ppsi_vlans" -ge 0 ]  &> /dev/null \
				    && [ "$ppsi_vlans" -le 4094 ] &> /dev/null; then
					v="$inst_vn[vlan]"; eval ${v}="$ppsi_vlans"
				else
					echo "$script_name: Wrong value \"$ppsi_vlans\" in CONFIG_VLANS_PORT"$i_port"_VID"
					continue;
				fi
			fi
	
			if [ "$port_mode_trunk" = "y" ] \
			    || [ "$port_mode_disabled" = "y" ] \
			    || [ "$port_mode_unqualified" = "y" ]; then
				ppsi_vlans=$(eval "echo \$CONFIG_VLANS_PORT"$i_port"_VID")
				if [ -n "$ppsi_vlans" ]; then
					mod_vlans=${ppsi_vlans//;/,}
					v="$inst_vn[vlan]"; eval ${v}="$mod_vlans"
				fi
			fi
		fi
	
	done # scan all the ppsi instances in a port
done # scan all the physical ports

if [ -v JSON_FORMAT ] ; then
	gen_ppsi_conf_json  ${OUTPUT_FILE} ${PRE_FILE}
else
	gen_ppsi_conf ${OUTPUT_FILE} ${PRE_FILE}
fi
