#!/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"_FIBER"
				 fi
	      	else
				echo "$script_name: Invalid parameter fiber=\"$fb\" in CONFIG_PORT"$i_port"_FIBER"
		  	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
			hpKeys="port proto  iface profile"
			for k in $hpKeys; do 
				v="$port_vn[$k]";[[ -n "${!v}" ]] && echo "$k  ${!v}" >>$output
				v="$inst_vn[$k]";[[ -n "${!v}" ]] && echo "$k  ${!v}" >>$output
			done
			# print remaining keys
			for k in $port_ppsi_keys ; do
				[[ "$hpKeys" != *"$k"* ]] && (v="$port_vn[$k]";[[ -n "${!v}" ]] && echo "$k  ${!v}" >>$output)
			done
			for k in $inst_ppsi_keys ; do
				[[ "$hpKeys" != *"$k"* ]] && (v="$inst_vn[$k]";[[ -n "${!v}" ]] && echo "$k  ${!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
		hpKeys="iface"
		for k in $hpKeys; do 
			v="$port_vn[$k]";[[ -n "${!v}" ]] && echo -e "    \"$k\": \"${!v}\"," >>$output
		done
		# print remaining keys
		for k in $port_ppsi_keys; do 
			[[ "$hpKeys" != *"$k"* ]] && (v="$port_vn[$k]"; [[ -n "${!v}" ]] && echo -e "    \"$k\": \"${!v}\"," >>$output)
		done
		
		# 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

			hpKeys="port proto  iface profile"
			for k in $hpKeys; do 
				v="$inst_vn[$k]";[[ -n "${!v}" ]] && echo -e "      \"$k\":  \"${!v}\"," >>$output
			done
			# print remaining keys
			for k in $inst_ppsi_keys ; do
				[[ "$hpKeys" != *"$k"* ]] && (v="$inst_vn[$k]";[[ -n "${!v}" ]] && echo -e "      \"$k\":  \"${!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 disable_L1sync() {
	local inst=$1
	local lv
	
 	for k in l1SyncEnabled l1SyncTxCoherencyIsRequired  l1SyncRxCoherencyIsRequired \
 	         l1SyncCongruencyIsRequired logL1SyncInterval l1SyncReceiptTimeout l1SyncOptParamsEnabled; do 
 		lv="$inst[$k]"; unset ${lv}
 	done
}

function set_profile_for_PTP() {
	local inst=$1
	local lv
	
	disable_L1sync $inst
}


function set_profile_for_WR() {
	local inst=$1
	local lv
	
	disable_L1sync $inst
	# Egress and ingress latencies already integrated in WR calculation 
 	for k in egressLatency ingressLatency ; do 
 		lv="$inst[$k]"; unset ${lv}
 	done
}

function set_profile_for_HA() {
	local inst=$1
	local lv
	# L1SYNC mandatory values
 	for k in l1SyncEnabled l1SyncTxCoherencyIsRequired  l1SyncRxCoherencyIsRequired l1SyncCongruencyIsRequired ; do 
 		lv="$inst[$k]"; eval ${lv}="y"
 	done
 	lv="$inst[l1SyncOptParamsEnabled]"; eval ${lv}="n"
 	# Free parameters
 	test ! ${inst_vn[logL1SyncInterval]+_}    && (lv="$inst_vn[logL1SyncInterval]";    eval ${lv}="0") # Set default value
 	test ! ${inst_vn[l1SyncReceiptTimeout]+_} && (lv="$inst_vn[l1SyncReceiptTimeout]"; eval ${lv}="3") # Set default value
 	# Force asymmetry correction
 	lv="$inst[asymmetryCorrectionEnable]"; eval ${lv}="y" 
}

function set_instance_profile() {
		local inst=$1
		local lv="$inst[profile]"
		local value=${!lv}
		if [ "${value}" == "wr" ]; then
			eval ${lv}="wr"
			set_profile_for_WR $inst
		elif [ "${value}" == "ha" ]; then
		    eval ${lv}="ha"
		elif [ "${value}" == "custom" ]; then
		    eval ${lv}="custom"
		elif [ "${value}" == "none" ] || [ "${value}" == "ptp" ]; then
			# do nothing
		    eval ${lv}="ptp"
			set_profile_for_PTP $inst
		elif [ -n "$p" ]; then
			echo "$script_name: Invalid parameter profile=\"$p\" in ${inst}"
			eval ${lv}="ha"
		else
			# default
			eval ${lv}="ha"
		fi
		value=${!lv}
		if [ "${value}" == "ha" ]; then 
		    set_profile_for_HA  $inst
		fi
}

function build_port_ppsi_keys() { 
    local s=""
    for i in "${!port_dotc_ppsi_key_mapping[@]}"
	do
	  	value=`echo ${port_dotc_ppsi_key_mapping[$i]} | head -n1 | cut -d " " -f1`;
	  	[[ "$s" != *"$value"* ]] && s="$s $value"
	done
 	echo `echo $s | xargs -n1 | sort -u | xargs`
}

function build_inst_ppsi_keys() { 
    local s=""
    for i in "${!inst_dotc_ppsi_key_mapping[@]}"
	do
	  	value=`echo ${inst_dotc_ppsi_key_mapping[$i]} | head -n1 | cut -d " " -f1`;
	  	[[ "$s" != *"$value"* ]] && s="$s $value"
	done
 	echo `echo $s | xargs -n1 | sort -u | xargs`
}


globals_indexes='clock-class clock-accuracy clock-allan-variance domain-number priority1 priority2 time-source externalPortConfigurationEnabled'
globals_not_yet_supported='time-source'

# PHYSICAL PORT PARAMETERS
declare -A port_dotc_ppsi_key_mapping='(\
[FIBER]="delayCoefficient" \
[IFACE]="iface" \
[CONSTANT_ASYMMETRY]="constantAsymmetry" \
)'
port_dotc_keys="${!port_dotc_ppsi_key_mapping[@]}"
port_ppsi_keys=$(build_port_ppsi_keys)

# PPSI INSTANCE PARAMETERS
declare -A inst_dotc_ppsi_key_mapping='(\
[PROTOCOL_RAW]="proto raw" [PROTOCOL_UDP_IPV4]="proto udp" \
[MECHANISM_E2E]="mechanism e2e" [MECHANISM_P2P]="mechanism p2p" \
[PROFILE_PTP]="profile ptp" [PROFILE_WR]="profile wr" [PROFILE_HA]="profile ha" [PROFILE_CUSTOM]="profile custom" \
[DESIRADE_STATE_MASTER]="desiredState master" [DESIRADE_STATE_SLAVE]="desiredState slave" [DESIRADE_STATE_PASSIVE]="desiredState passive" \
[ANNOUNCE_INTERVAL]="logAnnounceInterval" [ANNOUNCE_RECEIPT_TIMEOUT]="announceReceiptTimeout" \
[MIN_DELAY_REQ_INTERVAL]="logMinDelayReqInterval" [MIN_PDELAY_REQ_INTERVAL]="logMinPDelayReqInterval" \
[ASYMMETRY_CORRECTION_ENABLE]="asymmetryCorrectionEnable" \
[BMODE_MASTER_ONLY]="masterOnly" \
[EGRESS_LATENCY]="egressLatency" [INGRESS_LATENCY]="ingressLatency" \
[L1SYNC_ENABLED]="l1SyncEnabled" [L1SYNC_INTERVAL]="logL1SyncInterval" \
[L1SYNC_RECEIPT_TIMEOUT]="l1SyncReceiptTimeout" \
[L1SYNC_OPT_PARAMS_ENABLED]="l1SyncOptParamsEnabled" [L1SYNC_OPT_PARAMS_TS_CORRECTED_TX_ENABLED]="l1SyncTimestampsCorrectedTxEnabled" \
[L1SYNC_TX_COHERENCY_IS_REQUIRED]="l1SyncTxCoherencyIsRequired" \
[L1SYNC_RX_COHERENCY_IS_REQUIRED]="l1SyncRxCoherencyIsRequired" [L1SYNC_CONGRUENCY_IS_REQUIRED]="l1SyncCongruencyIsRequired" \
)'

inst_dotc_keys="${!inst_dotc_ppsi_key_mapping[@]}"
inst_ppsi_keys=$(build_inst_ppsi_keys)

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}"
 
	for p in $port_dotc_keys; do 
			k="${port_key}_$p"
			plc="${port_dotc_ppsi_key_mapping[$p]}"
			if [ -n "${!k}" ] && [ "$plc" != "" ]; then
				v="$port_vn[${plc}]";
				if [  "$p" == "FIBER" ] ; then 
				    # Special treatment for FIBER : Retreive the delay coefficient from the DB
					fiber_num="${!k}"
		 			eval ${v}=$(get_fiber_delay_coeff $fiber_num)
				else
				  eval ${v}="${!k}"
				fi
			fi
	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
	
		inst_vn="${port_vn}inst${j_inst}"	
		declare -A $inst_vn
		inst_key="CONFIG_PORT${i_port}_INST${j_inst}"

		for p in $inst_dotc_keys; do 
			k="${inst_key}_$p"
			plc="${inst_dotc_ppsi_key_mapping[$p]}"
			if [ -n "${!k}" ] && [ "$plc" != "" ]; then
			    # Check if the string contains 2 elements separated by a space
			    OIFS=${IFS}; IFS=' ' read -a tokens <<< "${plc}"; IFS=${OIFS}
			    plc=${tokens[0]};
			    v="$inst_vn[${plc}]"
			    if [ "${#tokens[@]}" -gt "1" ] ; then
			        eval ${v}="${tokens[1]}";
			    else
					eval ${v}="${!k}"
				fi
			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
		set_instance_profile $inst_vn
		v="$inst_vn[profile]"; p_profile=${!v}
		
		# define instance name
		v="$port_vn[iface]"; p_iface=${!v}
		v="$inst_vn[proto]"; p_proto=${!v}
		v="$inst_vn[port]"; eval ${v}="${p_iface}-${j_inst_int}-${p_profile:0:2}-${p_proto}"
		
		# if extPortConfiguration enabled, get the desired state
		if [ "$CONFIG_PTP_OPT_EXT_PORT_CONFIG_ENABLED" == 'y' ] ; then
			v="$inst_vn[desiredState]";
			if [ -z "{!v}" ] ; then 
				eval ${v}="passive" # default value
			fi
			v="$inst_vn[masteronly]"; unset  ${v} # remove master only 
		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
