#! /bin/bash

# genzaptelconf: generate as smartly as you can:
#		/etc/zaptel.conf
#		/etc/asterisk/zapata-channels.conf (to be #include-d into zapata.conf)
#	update:
#		/etc/default/zaptel (list of modules to load)
#
# $Id:$
#	

# The script uses a number of bash-specific features
# TODO: either ditch them or convert to perl

# /etc/default/zaptel may override the following variables
lc_country=us
#method=ls
#method=ks
base_exten=6000


ZAPCONF_FILE=/etc/zaptel.conf
ZAPATA_FILE=/etc/asterisk/zapata-auto.conf
ZAPTEL_BOOT=/etc/sysconfig/zaptel
exten_base_dir=/etc/asterisk/extensions-phones.d
exten_defs_file=/etc/asterisk/extensions-defs.conf
ztcfg_cmd=ztcfg

# a temporary directory. Created when the switch -r is parsed on getopts
# and deleted in the end on update_extensions_defs
tmp_dir=

# read default configuration from /etc/default/zaptel
if [ -r $ZAPTEL_BOOT ]; then . $ZAPTEL_BOOT; fi

# work around a bug (that was already fixed) in the installer:
if [ "$lc_country" = '' ]; then lc_country=us; fi

ALL_MODULES="wctdm wcfxo wcfxs pciradio tor2 torisa wct1xxp wct4xxp wcte11xp wcusb ztd_eth ztdummy"
force_stop_ast=yes
do_detect=no
do_unload=no
verbose=no
rapid_extens=''
# global: current extension number in extensions list. Should only be 
# changed in print_pattern:
rapid_cur_exten=1 
# set the TRUNK in extensidialplan dialplan defs file  rapid_conf_mode=no

die() {
	echo "$@" >&2
	exit 1
}

say() {
	if [ "$verbose" = no ]; then
		return
	fi
	echo "$@"   >&2
}

run_ztcfg() {
	if [ "$verbose" = no ]; then
		$ztcfg_cmd "$@"
	else
		say "Reconfiguring identified channels"
		$ztcfg_cmd -vv "$@"
	fi
}

do_update() {
	sed -i.bak "s/^$1=.*\$/$1=\"$2\"/" ${ZAPTEL_BOOT}
	if ! grep -q "^$1=" ${ZAPTEL_BOOT}; then
		echo "$1=\"$2\"" >> ${ZAPTEL_BOOT}
	fi
}

update_extensions_defs() {
	if [ "$rapid_conf_mode" = 'yes' ]
	then
		say "DEBUG: Updating dialplan defs file $exten_defs_file"
		if [ "`echo $tmp_dir/fxo_* | grep -v '*'`" != '' ]
		then
			trunk_nums=`cat $tmp_dir/fxo_* | sort -n | xargs`
			say "Configuring TRUNK to be [first of] zaptel channels: $trunk_nums"
			trunk_dev=`echo $trunk_nums| sed -e 's/ /\\\\\\&/g' -e 's/[0-9a-zA-Z]\+/Zap\\\\\\/&/g'`
			echo >&2 sed -i "s/^TRUNK.*=>.*/TRUNK => $trunk_dev/" $exten_defs_file
			sed -i "s/^TRUNK.*=>.*/TRUNK => $trunk_dev/" $exten_defs_file
			if ! grep -q "^TRUNK =>" $exten_defs_file; then
				trunk_dev=`echo $trunk_nums| sed -e 's/ /&/g' -e 's/[0-9a-zA-Z]*/Zap\\/&/g'`
				echo "TRUNK => $trunk_dev" >> $exten_defs_file
			fi
		else
			say "Warning: No FXO channel for trunk. Moving on."
		fi
		if [ "`echo $tmp_dir/fxs_* | grep -v '*'`" != '' ]
		then
			fxs_nums=`cat $tmp_dir/fxs_* | sort -n | xargs`
			zap_nums=`grep '^[^;].*Zap/\${CHAN_ZAP_' $exten_base_dir/*.conf | \
				sed -e 's/.*Zap\/\${CHAN_ZAP_\([0-9]*\)}.*/\1/' | sort -u | xargs`
			say "Configuring channels: $fxs_nums as channel placeholders: $zap_nums"
			j=1
			for i in $zap_nums
			do
				chan=`echo $fxs_nums | awk "{print \\$$i}"`
				if [ "$chan" = '' ]
				then
					# if the result is empty, we probably got past the last one.
					# bail out.
					say "Warning: No FXS channel for CHAN_ZAP_$i. Moving on"
					break
				fi
				say "DEBUG: setting channel $chan to placeholder $i"
				if grep -q "^CHAN_ZAP_$i " $exten_defs_file
				then
					sed -i -e "s/^CHAN_ZAP_$i .*/CHAN_ZAP_$i => Zap\/$chan/" $exten_defs_file
				else
					echo "CHAN_ZAP_$i => Zap/$chan" >> $exten_defs_file
				fi
			done
		fi	
		# cleaning up the temp dir
		if [ -d "$tmp_dir" ]; then rm -rf "$tmp_dir"; fi
	fi
}

usage() {
	program=`basename $0`

	echo >&2 "$program: generate zaptel.conf"
	echo >&2 "usage:"
	echo >&2 " $program [-v] [-m k|l|g] [-c <country_code>] [-r |-e <base_exten>] "
	echo >&2 " $program [-v] -l"
	echo >&2 " $program -u"
	echo >&2 " $program -h (this screen)"
	echo >&2 ""
	echo >&2 "Options:"
	echo >&2 "  -c CODE: set the country code (default: $lc_country)"
	echo >&2 "  -e NUM: set the base extension number (default: $base_exten)"
	echo >&2 "  -m: set signalling method (gs/ks/ls, default: $method)"
	echo >&2 "  -l: output a list of detected channels instead of zaptel.conf"
	echo >&2 "  -u: Unload zaptel modules"
	echo >&2 "  -v: verbose"
	echo >&2 "  -r: rapid configuration mode: configure Zaptel FXS channels from "
	echo >&2 "      existing Rapid extension files. FXOs will all be TRUNK "
}

# $1: channel number
print_pattern() {
	local chan=$1
	local sig=$2 #fxs/fxo
	local mode=$3
	case "$mode" in
	zaptel)	echo "${sig}$method=$chan" ;;
	list) echo $chan $sig;;
	zapata)
		echo "signalling=${sig}_$method"
		if [ "$sig" = 'fxo' ]
		then
		echo "; Note: this is an extension. Create a ZAP extension in AMP for Channel $chan"
        echo "context=from-internal"
		echo "group=1"
			# to preconfigure channel 1's extension to 550, set
			# chan_1_exten=550
			# in, e.g, /etc/default/zaptel
		  var_name=`echo chan_${chan}_exten`
			cfg_exten=`echo ${!var_name} | tr -d -c 0-9`
		  var_name=`echo chan_${chan}_vmbox`
			cfg_vmbox=`echo ${!var_name} | tr -d -c 0-9`
		  var_name=`echo chan_${chan}_cntxt`
			cfg_cntxt=`echo ${!var_name} | tr -d -c 0-9`
			
			# if option -E was given, get configuration from current extension
			if [ "$rapid_conf_mode" = 'yes' ]
			then
				rap_exten=`echo $rapid_extens |awk "{print \\$$rapid_cur_exten}"`
				if [ "$rap_exten" != '' ]
				then
					rap_cfgfile="$exten_base_dir/$rap_exten.conf"
					if [ -r "$rap_exten" ]
					then
						cfg_exten=$rap_exten
						# the vmbox is the third parameter to stdexten
						rap_vmbox=`grep '^[^;].*Macro(stdexten' $rap_exten | cut -d, -f3 \
							| cut -d')' -f1 | tr -d -c '0-9@a-zA-Z'`
						if [ "$rap_vmbox" ]!= '' ; then cfg_vmbox=$rap_vmbox; fi
					fi
				fi
				rapid_cur_exten=$(($rapid_cur_exten + 1))
			fi
			
			if [ "$cfg_exten" = '' ]
			then # No extension number set for this channel
				exten=$(($chan+$base_exten))
			else # use the pre-configured extension number
				exten=$cfg_exten
			fi
			# is there any real need to set 'mailbox=' ?
			if [ "x$cfg_vmbox" = x ]
			then # No extension number set for this channel
				vmbox=$exten
			else # use the pre-configured extension number
				vmbox=$cfg_vmbox
			fi
			# echo "callerid=\"Channel $chan\" <$exten>"
			# echo "mailbox=$exten"
		else # we have may have set it. So reset it:
			echo "; Note: this is a trunk. Create a ZAP trunk in AMP for Channel $chan"
			echo "context=from-zaptel"
			echo "group=0"
			# echo "callerid=\"\" <0>"
			# echo "mailbox="
		fi

		echo "channel => $chan"
		echo ""

		# Keep a note of what channels we have identified
		say "DEBUG: adding to channels list: channel: $chan, sig: $sig"
		case "$sig" in
		fxs)
			echo $chan >$tmp_dir/fxo_$chan
			say "DEBUG: FXO list now contains: `cat $tmp_dir/fxo_* |xargs`"
			;;
		fxo)
			echo $chan >$tmp_dir/fxs_$chan
			say "DEBUG: FXS list now contains: `cat $tmp_dir/fxs_* |xargs`"
			;;
		esac
		;;
	esac
	
}

# the number of channels from /proc/zaptel
# must always print a number as its output.
count_proc_zap_lines() {
	# if zaptel is not loaded there are 0 channels:
	if [ ! -d /proc/zaptel ]; then echo '0'; return; fi
	
	(
		for file in `echo /proc/zaptel/* |grep -v '\*'`
		do sed -e 1,2d $file # remove the two header lines
		done
	) | wc -l # the total number of lines
}

load_modules() {
	say "Test Loading modules:"
	for i in zaptel $ALL_MODULES
	do
		if [ "$i" = ztdummy ]; then
			continue	# No hardware to detect
		fi
		lines_before=`count_proc_zap_lines`
		args="${i}_args"
		eval "args=\$$args"
		# a module is worth listing if it:
		# a. loaded successfully, and
		# b. added channels lines under /proc/zaptel/*
		if /sbin/modprobe $i $args 2> /dev/null && \
			[ $lines_before -lt `count_proc_zap_lines` ] 
		then
			probed_modules="$probed_modules $i"
			say "	ok	$i	$args"
		else
			say "	- 	$i	$args"
		fi
	done
}


unload_modules() {
	if
		pids="$(pgrep asterisk)"
		[ "$pids" != '' ]
	then
		die "Before unloading -- STOP asterisk (pids=$pids)."
	fi
	say "Unloading zaptel modules:"
	modlist=''
	for i in $ALL_MODULES zaptel
	do
		if lsmod | grep "^$i  *" > /dev/null; then
			modlist="$modlist $i"
			say -n "$i "
		fi
	done
	/sbin/modprobe -r $modlist
	say ''
}

detect() {
	unload_modules
	load_modules
	modlist="$probed_modules"
	#for i in $ALL_MODULES
	#do
	#	if lsmod | grep "^$i  *" > /dev/null; then
	#		modlist="$modlist $i"
	#	fi
	#done
	modlist="$(echo $modlist)"		# clean spaces
	if [ "$modlist" = '' ]; then
		modlist=ztdummy			# Fallback
	fi
	say "Updating '${ZAPTEL_BOOT}'"
	do_update ZAPTEL_MODS  "$modlist"
}

check_tdm_sigtype() {
	chan_num=$1
	sig_type=$2
	mode=$3
	
	case "$sig_type" in
	fxs)chan_sig_type=fxo;;
	fxo)chan_sig_type=fxs;;
	esac

#	print_pattern $chan_num $chan_sig_type $mode
	
	$ztcfg_cmd -c <(print_pattern $chan_num $chan_sig_type zaptel) 2>/dev/null  \
		|| return 1
	if head -c1 /dev/zap/$chan_num >/dev/null 2>/dev/null
	then 
		print_pattern $chan_num $chan_sig_type $mode
		return 0
	else
		return 1
	fi	
}

# output a list of extensions that need a channel
get_rapid_extens() {
	if [ "$rapid_conf_mode" = 'yes' ]
	then
		rapid_extens=`grep -l '^[^;].*Zap/\${CHAN_ZAP_' $exten_base_dir/*.conf 2>/dev/null | \
			rev | cut -d/ -f1 | cut -d. -f2- | rev | xargs`
		say "Need to configure extensions: $rapid_extens"
	fi
}

genconf() {
	local mode=$1

	# reset FXO list (global)
	say "DEBUG: resetting channels lists"
	rm -f $tmp_dir/fx{s,o}_*

	if [ "$mode" = 'zapata' ]
	then
		rem_char=';'
	else
		rem_char='#'
	fi

	spanlist=`echo /proc/zaptel/* |  grep -v '\*'`

	#if [ "$spanlist" == "" ]; then
	#	die "No zapata interfaces in /proc/zaptel"
	#fi


	case "$mode" in 
		zaptel)
			cat <<EOF
# Autogenerated by $0 -- do not hand edit
# Zaptel Configuration File
#
# This file is parsed by the Zaptel Configurator, ztcfg
#

# It must be in the module loading order

EOF
			;;
		zapata)
			cat <<EOF
; Autogenerated by $0 -- do not hand edit
; Zaptel Channels Configurations (zapata.conf)
;
; This is not intended to be a complete zapata.conf. Rather, it is intended 
; to be #include-d by /etc/zapata.conf that will include the global settings
;
callerid=asreceived
EOF
		;;
	esac

	# For each line in the spanlist: see if it represents a channel.
	# if it does, test that the channel is usable.
	# we do that by configuring it (using ztcfg with a 1-line config file)
	# and then trying to read 1 byte from the device file.
	#
	# The '<(command)' syntax creates a temporary file whose content is is the
	# output of 'command'.
	#
	# This approach failed with the T1 card we have: the read operation simply 
	# hung. 
	#
	# Another problem with such an approach is how to include an existing 
	# configuration file. For instance: how to include some default settings.
	#
	# Maybe an 'include' directive should be added to zaptel.conf ?
	#cat $spanlist | 
	for procfile in $spanlist
	do
		# the first line is the title line. It states the model name
		# the second line is empty
		title=`head -n 1 $procfile`
		echo ""
		echo "$rem_char $title"
		
		# The rest of the lines are per-channel lines
		sed -e 1,2d $procfile | \
		while read line
		do 
			# in case this is a real channel. 
			chan_num=`echo $line |awk '{print $1}'`
			case "$line" in
			*WCT1/*) # we're yet to detect when a channel is active
				echo "$rem_char channel $chan_num, WCT1, unhandled for now"
				;;
			*WCTDM/*) 
				# this can be either FXS or FXO
				check_tdm_sigtype $chan_num fxs $mode || \
				check_tdm_sigtype $chan_num fxo $mode || \
				echo "$rem_char channel $chan_num, WCTDM, inactive."
				;;
			*WRTDM/*) 
				# this can be either FXS or FXO
				check_tdm_sigtype $chan_num fxs $mode || \
				check_tdm_sigtype $chan_num fxo $mode || \
				echo "$rem_char channel $chan_num, WRTDM, inactive."
				;;
			*WCFXO/*) 
				check_tdm_sigtype $chan_num fxo $mode || \
				echo "$rem_char channel $chan_num, WCFXO, inactive."
				;;
			'') ;;		# Empty line (after span header)
			*) echo "$rem_char ??: $line";;
			esac
		done
	done

	if [ "$mode" = 'zaptel' ]
	then
		cat <<EOF

# Global data

EOF
		echo "loadzone	= $loadzone"
		echo "defaultzone	= $defaultzone"
	fi
	
	if [ "$mode" = 'zapata' ]
	then 
		update_extensions_defs 
	fi
}

while getopts 'c:de:E:hlm:rsuv' arg
do
	case "$arg" in
		e) # guarantee that it is a number:
			new_base_exten=`echo $OPTARG | tr -d -c 0-9`
			if [ "x$new_base_exten" != x ]; then base_exten=$new_base_exten; fi
			;;
		c) lc_country=`echo $OPTARG | tr -d -c a-z` ;;
		u) do_unload=yes ;;
		v) verbose=yes ;;
		l) mode='list' ;;
		m) 
		  case "$OPTARG" in 
			k|l|g)method=${OPTARG}s ;;
			*) echo >&2 "unknown signalling method ${OPTARG}s, defaulting to \"$method\"";;
			esac
			;;
		r) 
			rapid_conf_mode=yes 
			tmp_dir=`mktemp -d` || die "$0: failed to create temporary directory. Aborting"
			;;
		*) echo >&2 "unknown parameter -$arg, Aborting"; usage; exit 1;;
	esac
done
shift $(( $OPTIND-1 ))
if [ $# != 0 ]; then
	echo >&2 "$0: too many parameters"
	usage
	exit 1
fi

case "$lc_country" in
	# the list was generated from the source of zaptel:
	#grep '{.*[0-9]\+,.*"[a-z][a-z]"' zonedata.c | cut -d'"' -f 2 | xargs |tr ' ' '|'
	us|au|fr|nl|uk|fi|es|jp|no|at|nz|it|gr|tw|cl|se|be|il)
	:;;
*)
  lc_country=us
  echo >&2 "unknown country-code $lc_country, defaulting to \"us\"";;
esac
# any reason for loadzone and defaultzone to be different? If so, this is
# the place to make that difference
loadzone=$lc_country
defaultzone=$loadzone

# Choose reasonable default for signaling method (by country)
if [ "" = "$method" ]; then
	case "$lc_country" in
	il) method=ls	;;
	*) method=ks	;;
	esac
fi

# make sure asterisk is not in our way
if [ "$force_stop_ast" = 'yes' ]
then
#  /etc/init.d/asterisk stop 1>&2
   /usr/sbin/amportal stop 1>&2
else
	if ps auxww |grep asterisk | grep -v -- -r | grep -q -v grep
	then
	  echo >&2 "Asterisk is already running. Configuration left untouched"
		echo >&2 "You can use the option -s to shut down Asterisk for the"
		echo >&2 "duration of the detection."
		exit 1
	fi
fi

if [ "$do_unload" = yes ]
then
	unload_modules
	exit
fi

if [ "$do_detect" = yes ]
then
	detect
fi

if [ "$mode" = list ]; then
	genconf list
else
	get_rapid_extens
	echo "Generating  '${ZAPCONF_FILE}'"
	mv "${ZAPCONF_FILE}" "${ZAPCONF_FILE}.bak"
	genconf zaptel > "${ZAPCONF_FILE}"
	echo "Generating  '${ZAPATA_FILE}'"
	mv "${ZAPATA_FILE}" "${ZAPATA_FILE}.bak"
	genconf zapata > "${ZAPATA_FILE}"
	run_ztcfg
fi

if [ "$force_stop_ast" = 'yes' ]
then
#  /etc/init.d/asterisk start 1>&2
	/etc/init.d/zaptel stop
	/etc/init.d/zaptel start
	/usr/sbin/amportal start 1>&2
fi

# if in verbose mode: verify that asterisk is running
if [ "$verify" != 'no' ]
	then
	say "Checking channels configured in Asterisk:"
	sleep 1 # give it some time. This is enough on our simple test server
	/usr/local/sbin/ast-cmd cmd "zap show channels"
fi
