#! /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:
#		With '-M' /etc/modules (list of modules to load)
#
# Copyright (C) 2005 by Xorcom <support@xorcom.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#
# If you have any technical questions, contact 
# Tzafrir Cohen <tzafrir.cohen@xorcom.com>
#	

# The script uses a number of bash-specific features
# TODO: either ditch them or convert to perl
# Don't override variables here. 
# Override them in /etc/default/zaptel (debian) or /etc/sysconfig/zaptel 
# (redhat/centos)

# /etc/default/zaptel may override the following variables
VERSION=0.5.10
rcsid='$Id: genzaptelconf 3793 2008-02-04 23:00:48Z tzafrir $'
lc_country=us
# set to: ls, ks or gs for (Loopstart, Kewlstart and GroundStart)
#         on FXS channels (FXO signalling).
fxs_default_start=ks
base_exten=6000
# If set: no context changes are made in zapata-channels.conf
#context_manual=yes
context_lines=from-pstn      # context into which PSTN calls go
context_phones=from-internal # context for internal phones calls.
# The two below apply to input and output ports of the Xorcom Astribank:
context_input=astbank-input
context_output=astbank-output # useless, but helps marking the channels :-)
# TODO: what about PRI/BRI?
# If set: no group changes are made in zapata-channels.conf
#group_manual=yes
group_phones=5 # group for phones
group_lines=0  # group for lines
# Set fxs_immediate to 'yes' to make all FXS lines answer immediately.
fxs_immediate=no

ZAPCONF_FILE=${ZAPCONF_FILE:-/etc/zaptel.conf}
ZAPCONF_FILE_SYSTEM=$ZAPCONF_FILE
ZAPATA_FILE=${ZAPATA_FILE:-/etc/asterisk/zapata-channels.conf}
ZAPSCAN_FILE=${ZAPSCAN_FILE:-/etc/asterisk/zapscan.conf}
ZAPTEL_BOOT_DEBIAN=${ZAPTEL_BOOT_DEBIAN:-/etc/default/zaptel}
ZAPTEL_BOOT_FEDORA=${ZAPTEL_BOOT_FEDORA:-/etc/sysconfig/zaptel}
MODLIST_FILE=/etc/modules
MODLIST_FILE_FEDORA=/etc/sysconfig/zaptel
exten_base_dir=/etc/asterisk/extensions-phones.d
exten_defs_file=/etc/asterisk/extensions-defs.conf
# perl utilities:
xpp_sync=/usr/sbin/xpp_sync
zt_registration=/usr/sbin/zt_registration
# how long to wait for /dev/zap/ctl to appear? (seconds)
DEVZAP_TIMEOUT=${DEVZAP_TIMEOUT:-20}
ZTCFG=${ZTCFG:-/sbin/ztcfg}
# BRI/PRI spans will be in an additional per-span group whose number
# is SPAN_GROUP_BASE + span-number
SPAN_GROUP_BASE=10
# set to "yes" to make BRI NT spans set overlapdial (handy for ISDN phones
# and other devices).
brint_overlap=no

# a temporary directory to store whatever we need to remember.
#
# The main loop of genconf is run in a sub-process.
tmp_dir=

# A list of all modules:
# - the list of modules which will be probed (in this order) if -d is used
# - The module that will be deleted from /etc/modules , if -d -M is used
ALL_MODULES="wct4xxp wcte12xp wcte11xp wct1xxp wanpipe tor2 torisa qozap vzaphfc zaphfc ztgsm wctdm24xxp wctdm opvxa1200 wcfxo pciradio wcusb xpp_usb"

# The name of the variable in /etc/sysconfig/zaptel into which to set 
# the list of detected modules.
modules_var=MODULES
# On SuSE with the rpm package:
#modules_var=ZAPTEL_MODULES

# What signalling to give to ZapBRI channels?
# bri:       bri_net; bri_cpe           (Bristuffed Asterisk. No multi- support)
# bri_ptmpi: bri_net_ptmp; bri_cpe_ptmp (Bristuffed Asterisk, multi- support)
# pri:       pri_net; pri_cpe           (Recent Asterisk. Experimental)
#ZAPBRI_SIGNALLING=bri
ZAPBRI_SIGNALLING=bri_ptmp
#ZAPBRI_SIGNALLING=pri
zapconf_def_termtype=te

# A command to stop / start asterisk. Must support parameters "start"
# and "stop" . This is the executable:
ZAPCONF_ASTERISK_SCRIPT=/etc/init.d/asterisk
#
# Should you need to pass extra arguments:
ZAPCONF_ASTERISK_CMD=$ZAPCONF_ASTERISK_SCRIPT

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

if [ ! -x "$ZTCFG" ]; then
	# Work around a bug in the rpm package: ztcfg should be in 
	# /sbin as it may be required for starting a network interface
	if [ -x /usr/sbin/ztcfg ]; then
		ZTCFG=/usr/sbin/ztcfg
	else
		echo >&2 "ztcfg is not on found, do you have zaptel properly installed?"
		exit_cleanup 1
	fi
fi

XPP_SYNC=auto # sync mode. can be set to '0' or '1' or HOST explicitly.

# it is safe to use -c twice: the last one will be used.
ztcfg_cmd="$ZTCFG -c $ZAPCONF_FILE"

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

force_stop_ast=no
do_detect=no
do_unload=no
do_module_list=no
verbose=no
do_restart=yes
fxsdisable=no
do_gen_zapscan=no

span_te_timing_counter=1

case "$ZAPBRI_SIGNALLING" in
bri)      ZAPBRI_NET=bri_net;      ZAPBRI_CPE=bri_cpe ;;
pri)      ZAPBRI_NET=pri_net;      ZAPBRI_CPE=pri_cpe ;;
bri_ptmp) ZAPBRI_NET=bri_net_ptmp; ZAPBRI_CPE=bri_cpe_ptmp ;;
*) 
	die "Incorrect value for ZAPBRI_SIGNALLING ($ZAPBRI_SIGNALLING). Abortring"
	;;
esac

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

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

# exit (exit value is the optional $1), and clean up after us
exit_cleanup() {
	if [ -d "$tmp_dir" ]; then
		# don't fail but don't hide error if directory is not
		# empty
		rmdir "$tmp_dir" || : 
	fi
	exit $1
}

# Wait for udev to generate /dev/zap/ctl, if needed:
wait_for_zapctl() {
	# if device file already exists, or if zaptel has failed to load: 
	# no point waiting.
	if [ -c /dev/zap/ctl ] || ! grep -q zaptel /proc/modules ; then
	  return
	fi
	say "Waiting for /dev/zap/ctl to be generated"
	devzap_found=0
	for i in `seq $DEVZAP_TIMEOUT`; do
		sleep 1
		if [ -c /dev/zap/ctl ]; then
		  devzap_found=1
			break
		fi
	done
	if [ "$devzap_found" != 1 ]; then
		say "Still no /dev/zap/ctl after $devzap_timeout seconds."
		echo >&2 "No /dev/zap/ctl: cannot run ztcfg. Aborting."
	fi
}

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

update_module_list_debian() {
	say "Updating Debian modules list $MODLIST_FILE."
	del_args=`for i in $ALL_MODULES ztdummy
	do
		echo "$i" | sed s:.\*:-e\ '/^&/d':
	done`
	add_args=`for i in $*
	do
		echo "$i" | sed s:.\*:-e\ '\$a&':
	done`
	
	sed -i.bak $del_args "$MODLIST_FILE"
	for i in $*
	do
		echo "$i"
	done >> "$MODLIST_FILE"
}

update_module_list_fedora() {
	say "Updating modules list in zaptel init config $MODLIST_FILE_FEDORA."
	sed -i.bak -e "/^$modules_var=/d" "$MODLIST_FILE_FEDORA"
	echo "$modules_var=\"$*\"" >> "$MODLIST_FILE_FEDORA"
}

update_module_list() {
  if   [ -f "$MODLIST_FILE" ]; then
		update_module_list_debian "$@"
	elif [ -f "$MODLIST_FILE_FEDORA" ]; then
		update_module_list_fedora "$@"
	else
	  die "Can't find a modules list to update. Tried: $MODLIST_FILE, $MODLIST_FILE_FEDORA. Aborting"
	fi
}



zap_reg_xpp() {
	if [ ! -d /proc/xpp ]; then return; fi

	# Get a list of connected Astribank devices, sorted by the name of
	# the USB connector. That order is rather arbitrary, but will not
	# change without changes to the cabling.
	xbusses=`sed -e '/STATUS=connected/!d' -e 's/ *STATUS=.*//' -e 's/ *CONNECTOR=//' /proc/xpp/xbuses | sort -t: -k 2 | cut -d: -f1`
	say "Zaptel registration order:"
	say "$xbusses"

	# get a list of XPDs that were not yet registered as zaptel spans.
	# this will be the case if you set the parameter zap_autoreg=0 to
	# the module xpp
	# Append /dev/null to provide a valid file name in case of an empty pattern.
	xbusses_pattern=`echo $xbusses| sed -e 's|XBUS-[0-9]*|/proc/xpp/&/XPD-*/zt_registration|g'`' /dev/null'
	xpds_to_register=`grep -l 0 $xbusses_pattern`
	for file in $xpds_to_register; do
		echo 1 >$file
	done
}

# Initialize the Xorcom Astribank (xpp/)
xpp_startup() {
	# do nothing if the module xpp was not loaded, or if no 
	# Astribanks connected:
	if [ ! -d /proc/xpp ]; then return 0; fi
	if ! grep -q 'STATUS=connected' /proc/xpp/xbuses; then return 0; fi

	echo "Waiting for Astribank devices to initialize:"
	cat /proc/xpp/XBUS-[0-9]*/waitfor_xpds 2>/dev/null || true
	
	# overriding locales for the above two, as perl can be noisy
	# when locales are missing.
	# No register all the devices if they didn't auto-register:
	LC_ALL=C $zt_registration on

	# this one could actually be run after ztcfg:
	LC_ALL=C $xpp_sync "$XPP_SYNC"
}



usage() {
	program=`basename $0`

	echo >&2 "$program: generate zaptel.conf and zapata.conf"
	echo >&2 "(version $VERSION, $rcsid)"
	echo >&2 "usage:"
	echo >&2 " $program [-sRdv] [-m k|l|g] [-c <country_code>] [-e <base_exten>] [-F]"
	echo >&2 " $program [-sRdv] -l"
	echo >&2 " $program -su"
	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 "  -F: Don't print FXSs in zapata.conf"
	echo >&2 "  -l: output a list of detected channels instead of zaptel.conf"
	echo >&2 "  -d: Perform hardware detection"
	echo >&2 "  -u: Unload zaptel modules (will not restart Asterisk)."
	echo >&2 "  -v: verbose"
	echo >&2 "  -s: Stop Asterisk before running, and start it at the end."
	echo >&2 "  -R: Don't restart asterisk in the end."
	echo >&2 "  -z: also generate zapscan.conf for the asterisk GUI."
}

# print /etc/asterisk/zapscan.conf for the Asterisk-GUI
# $1: port type. Currently only fxs/fxo . Note that this is the type, and
#                not the signalling (which would be the fxo fir an fxs port.
# $2: port number. Probably a range of ports is also allowed.
print_zapscan_port () {
	if [ "$do_gen_zapscan" != 'yes' ]; then return 0; fi

	echo "
[$2]
port=$1
"	>>$zapscan_file
}

# $1: channel number
print_pattern() {
	local astbank_type=''
	local reset_values=""
	OPTIND=1
	while getopts 'a:' arg
	do
		case "$arg" in
			a) case "$OPTARG" in input|output) astbank_type=$OPTARG;;esac ;;
		esac
	done
	shift $(( $OPTIND-1 ))


	local chan=$1
	local sig=$2 #fxs/fxo
	local mode=$3
	local method
	
	if [ "$sig" = 'fxs' ]; then
		# Coutries in which we need to use busydetect:
		# United Arab Emirats, Israel, Slovenia
		case "$lc_country" in
		ae|il|si) 
			method=ls
			;;
		*)
			method=ks
			;;
		esac
	else
		method="$fxs_default_start"
	fi
	case "$sig" in
	fxs) sig_name=FXO;;
	fxo) sig_name=FXS;;
	esac
	case "$mode" in
	list)
		echo $chan $sig_name $astbank_type;;
	files)
		# sadly, both input ports and output ports go into the same span as 
		# the FXS ports. Thus we need to separate between them. See also 
		# the zapata.conf section:
		
		echo ";;; line=\"$line\""                      >> $zapata_file
		
		if [ "$astbank_type" != '' ]; 
			then echo "# astbanktype: $astbank_type" >>$zaptel_file; 
		fi
		echo "${sig}$method=$chan" >>$zaptel_file
		# zap2amp will rewrite those from zaptel.conf and hints there
		if [ "$fxsdisable" = 'yes' ] && [ "$sig" = 'fxo' ]; then return; fi
			
		echo "signalling=${sig}_$method" >>$zapata_file
		if [ "$sig" = 'fxo' ]
		then
			# 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 [ "$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>"     >> $zapata_file
			reset_values="$reset_values callerid"
			echo "mailbox=$exten"                          >> $zapata_file
			reset_values="$reset_values mailbox"
			if [ "$group_manual" != "yes" ]
			then 
				echo "group=$group_phones"                   >> $zapata_file
				reset_values="$reset_values group"
			fi
			if [ "$context_manual" != "yes" ]
			then
				if [ "$astbank_type" != '' ];
				then 
					context_var_name=context_$astbank_type
					echo context=${!context_var_name}          >> $zapata_file
				else
					echo "context=$context_phones"             >> $zapata_file
				fi
				reset_values="$reset_values context"
			fi
		else # this is an FXO (trunk/phone: FXO signalling)
		  # we have may have set it. So reset it:
			echo "callerid=asreceived"                     >> $zapata_file
			if [ "$group_manual" != "yes" ]
			then 
				echo "group=$group_lines"                    >> $zapata_file
			fi
			if [ "$context_manual" != "yes" ]
			then 
				echo "context=$context_lines"                >> $zapata_file
				reset_values="$reset_values context"
			fi
			if [ "$lc_country" = 'uk' ]
			then
			  echo "cidsignalling=v23"                     >> $zapata_file
			  case $line in 
			  *WCFXO*) echo "cidstart=history"             >> $zapata_file;;
			  *)       echo "cidstart=polarity"            >> $zapata_file;;
			  esac
				reset_values="$reset_values cidsignalling cidstart"
			fi
			case "$line" in
			*XPP_FXO*) 
				if [ "$xpp_fxo_rxgain" != '' ]; then
					echo "rxgain=$xpp_fxo_rxgain"             >> $zapata_file
					reset_values="$reset_values rxgain"
				fi
				;;
			esac
			# if kewlstart is not used, busydetect has to be employed:
			if [ "$method" = 'ls' ]
			then
			  echo 'busydetect=yes'                     >> $zapata_file
				reset_values="$reset_values busydetect"
			fi
		fi

		if [ "$astbank_type" = 'input' ] || \
			( [ "$fxs_immediate" = 'yes' ] && [ "$sig" = "fxo" ] )
		then 
		  echo 'immediate=yes'                      >> $zapata_file
			reset_values="$reset_values immediate"
		fi
		echo "channel => $chan"                          >> $zapata_file
		reset_zapata_entry $zapata_file $reset_values
		echo ""                                          >> $zapata_file

		print_zapscan_port "$sig" "$chan"
		;;
	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 $ALL_MODULES
	do
		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 
		then
		  check=0
		  case "$i" in
			xpp_usb) check=`grep 'STATUS=connected' 2>/dev/null /proc/xpp/xbuses | wc -l` ;;
			*) if [ $lines_before -lt `count_proc_zap_lines` ]; then check=1; fi ;;
			esac
			if [ "$check" != 0 ]
			then
			  probed_modules="$probed_modules $i"
			  say "	ok	$i	$args"
		  else
			  say "	- 	$i	$args"
				rmmod $i
		  fi
		else
			say "	- 	$i	$args"
		fi
	done
}

# recursively unload a module and its dependencies, if possible.
# where's modprobe -r when you need it?
# inputs: module to unload.
# returns: the result from 
unload_module() {
	module="$1"
	line=`lsmod 2>/dev/null | grep "^$1 "`
	if [ "$line" = '' ]; then return; fi # module was not loaded

	set -- $line
	# $1: the original module, $2: size, $3: refcount, $4: deps list
	mods=`echo $4 | tr , ' '`
	# xpp_usb keeps the xpds below busy, and hence must be removed
	# before them:
	case "$module" in xpd_*) mods="xpp_usb $mods";; esac
	for mod in $mods; do
		# run in a subshell, so it won't step over our vars:
		(unload_module $mod) 
		# TODO: the following is probably the error handling we want:
		# if [ $? != 0 ]; then return 1; fi
	done
	rmmod $module
}

unload_modules() {
	say "Unloading zaptel modules:"
	unload_module zaptel
	say ''
}

# sleep a while until the xpp modules fully register
wait_for_xpp() {
	if [ -d /proc/xpp ]
	then
		# wait for the XPDs to register:
		# TODO: improve error reporting and produce a messagee here
		cat /proc/xpp/XBUS-*/waitfor_xpds 2>/dev/null >/dev/null  || true
	fi
}

detect() {
	unload_modules
	temporary_zapconf save
	load_modules
	temporary_zapconf restore
	modlist="$probed_modules"
	modlist="$(echo $modlist)"		# clean spaces
	if [ "$do_module_list" = yes ]
	then
		update_module_list "$modlist"
	fi
	if echo $modlist | grep -q xpp_usb; then wait_for_xpp; fi
}

# The module configuration generated by zaptel includes a totally useless 
# automatic run of ztcfg after modules loading. As a workaround for that, 
# we provide an empty zaptel.conf temporarily.
# 
# At hardware detection time we shouldn't really touch zaptel.conf . So we 
# must keep a copy of it somewhere.
#
# I use ZAPCONF_FILE_SYSTEM rather than ZAPCONF_FILE, as the bogus modprobe 
# entries will not be initiated by us.
#
# ZAPCONF_FILE_TMP is a "global", as it needs to be preserved between the
# call to 'save' and to 'restore'
ZAPCONF_FILE_TMP=
temporary_zapconf() {
  case "$1" in
	save)
	  say "Temporarily moving zaptel.conf aside to work around broken modprobe.conf"
		ZAPCONF_FILE_TMP=`mktemp /tmp/genzaptelconf-zaptel.conf-XXXXXX` || die "Error creating temporary zaptel.conf"
		if [ -f $ZAPCONF_FILE_SYSTEM ];  then
			cp -a $ZAPCONF_FILE_SYSTEM $ZAPCONF_FILE_TMP
		fi
		echo -n >$ZAPCONF_FILE_SYSTEM
		;;
	restore)
		# restore original zaptel.conf:
		if [ "$ZAPCONF_FILE_TMP" = '' ]; then return; fi
		mv $ZAPCONF_FILE_TMP $ZAPCONF_FILE_SYSTEM
		ZAPCONF_FILE_TMP=''
		;;
	esac
}

# run after the channel's entry. Used to reset all the values 
reset_zapata_entry() {
	conf_file="$1"; shift
	
	for arg in "$@"; do
		case "$arg" in
		busydetect)      echo "$arg=no"      >>$conf_file;;
		context)         echo "$arg=default" >>$conf_file;;
		immediate)       echo "$arg=no"      >>$conf_file;;
		overlapdial)     echo "$arg=no"      >>$conf_file;;
		rxgain)          echo "$arg=0"       >>$conf_file;;
		txgain)          echo "$arg=0"       >>$conf_file;;
		*)               echo "$arg="        >>$conf_file;;
		esac
	done
}


# we need to preserve the permissions of existing configurations files.
# However we also don't want to be left with incomplete configurations in 
# case we are stopped in the middle. Thus we create a temporary file and
# mv it to the original file only when done.
#
# This function gives the temporary file the permissions of the original,
# or some sane defaults.
#
# The temporary file is generated here, and ths its name is passed through
# a variable whose name is passed as a parameter (new_var).
#
# $1: orig_cfg_file
# $2: new_var
gen_tmp_conf() {
	orig_cfg_file=$1
	new_var=$2

	# assign to new_var by reference:
	eval $new_var=`mktemp /tmp/genzaptelconf-conf-XXXXXX` \
		|| die "Unable to create temporary config file for $orig_cfg_file"
	if [ -r "$orig_cfg_file" ]; then
		chown --reference="$orig_cfg_file" ${!new_var} 2>/dev/null
		chmod --reference="$orig_cfg_file" ${!new_var} 2>/dev/null  
	else
		chmod 644 ${!new_var}
	fi
}

# Extract information from one digital channel (one line in a /proc/zaptel 
# file). Information is saved to $tmp_dir/span_foo variables.
# FIXME: detection should move to when we know the number of channels
# and hence can tell between an E1 and a T1.
detect_digital_channel() {
	line="$1"
	chan_num="$2"
	span_num="$3"

	# PRI/BRI channel
	# Rather than identifying cards by the header line, we identify 
	# them by the channel names. This is shorter. 
	# This also allows us to count the channel numbers and check if 
	# we have a E1 span, a T1 span or a BRI span.

	# span_lastd is always the one before last 
	# channel. span_bchan is the last:
	echo $chan_num      >$tmp_dir/span_end

	if [ "`cat $tmp_dir/span_begin`" != "-1" ]
	then
		return #we already configured this span.
	fi

	# Now we need to give initial configuration to the span
	echo $chan_num      >$tmp_dir/span_begin
	echo $span_num      >$tmp_dir/span_num

	case "$line" in
	*ZTHFC*/* | *ztqoz*/* | *XPP_BRI_*/*)
		# BRI channel
		echo 'ccs'          >$tmp_dir/span_framing
		echo 'euroisdn'     >$tmp_dir/span_switchtype
		if [ "`cat $tmp_dir/span_termtype`" = 'nt' 2>/dev/null ]
		then
			echo $ZAPBRI_NET >$tmp_dir/span_signalling 
		else
			echo $ZAPBRI_CPE >$tmp_dir/span_signalling
		fi
		;;
	*ztgsm*/*)
		# Junghanns's GSM cards. 
		echo 'ccs'          >$tmp_dir/span_framing
		#Does this mean anything?
		echo 'gsm'          >$tmp_dir/span_signalling
		;;
	*TE[24]/* | *WCT1/* | *Tor2/* | *TorISA/* | *WP[TE]1/* | \
		*R[124]T1/* | *XPP_[TEJ]1_*)
		# FIXME: handle cwain around here.  
		# name: *cwain[12]/* . Always E1.

		# PRI span (E1/T1)
		echo 'esf'       >$tmp_dir/span_framing
		echo 'b8zs'      >$tmp_dir/span_coding
		echo 'national'  >$tmp_dir/span_switchtype
		if [ "`cat $tmp_dir/span_termtype`" = 'nt' 2>/dev/null ]
		then
			echo pri_net >$tmp_dir/span_signalling 
		else
			echo pri_cpe >$tmp_dir/span_signalling
		fi
		# an example of country-specific setup. This is probably not accurate
		# Contributions are welcome
		case "$lc_country" in 
		nl)
			# (Just an example for per-country info)
			echo 'ccs'       >$tmp_dir/span_framing
			echo 'ami'       >$tmp_dir/span_coding
			#echo 'crc4'      >$tmp_dir/span_yellow
			#echo 'euroisdn'  >$tmp_dir/span_switchtype
			#echo 'pri_cpe'   >$tmp_dir/span_signalling
			;;
		il|de|au)
			echo 'ccs'       >$tmp_dir/span_framing
			echo 'hdb3'      >$tmp_dir/span_coding
			echo 'crc4'      >$tmp_dir/span_yellow
			echo 'euroisdn'  >$tmp_dir/span_switchtype
			;;
		cl)
			echo 'ccs'       >$tmp_dir/span_framing
			echo 'hdb3'      >$tmp_dir/span_coding
			#echo 'crc4'      >$tmp_dir/span_yellow
			echo 'national'  >$tmp_dir/span_switchtype
			;;
		esac
	;;
	esac
}

# read information from the $tmp_dir/span_foo files and generate 
# configuration for the span and its channels.
write_digital_config() {
	# if the current file we checked was not of a digital span, we have
	# nothing to do:
	if [ "`cat $tmp_dir/span_begin`" = -1 ]; then return; fi

	# read files to variables:
	for suffix in num begin end timing lbo framing \
		coding switchtype signalling yellow termtype
	do
		eval span_$suffix=`cat $tmp_dir/span_$suffix 2>/dev/null`
	done

	# exactly the same logic is used in asterisk's chan_zap.c.
	# also not that $(( )) is bash-specific
	case "$((1+ $span_end - $span_begin))" in
	2|3|24) #ztgsm, BRI or T1
		dchan=$span_end
		bchans="$span_begin-$(($span_end-1))"
		;;
	31) #E1
		dchan="$(($span_begin+15))"
		bchans="$span_begin-$(($span_begin+14)),$(($span_begin+16))-$span_end"
		if [ "$span_framing" = 'esf' ]; then
			# don't leave an E1 span with defective defaults:
			span_framing=ccs
			span_coding=hdb3
			span_switchtype=euroisdn
			span_yellow=crc4
		fi
		;;
	esac
	if [ "$span_yellow" != '' ]; then span_yellow=",$span_yellow"; fi
	# Let's assume that a TE span should get the clock from the remote unit,
	# and NT spans should provide timing. Just as a sane default.
	# If we have several TE spans, the first will have priority 1, 
	# second: 2, etc.
	case "$span_signalling" in *_cpe*)
		span_timing=$span_te_timing_counter
		span_te_timing_counter=$(($span_te_timing_counter + 1))
		;;
	esac
	case "$mode" in
	files)
		echo span=$span_num,$span_timing,$span_lbo,$span_framing,$span_coding$span_yellow >> $zaptel_file
		# leave a comment in zaptel.conf that allows to tell if
		# this span is TE or NT:
		if [ "$span_termtype" != '' ]
		then echo "# termtype: $span_termtype" >>$zaptel_file
		fi

		echo bchan=$bchans >>$zaptel_file
		echo dchan=$dchan  >>$zaptel_file
		if [ "$fxsdisable" = 'yes' ] && [ "$span_termtype" = 'nt' ]; then return; fi
		# You should not send content to zapata.conf below this line
		
		span_group=$(($SPAN_GROUP_BASE + $span_num))
		
		if [ "$span_termtype" != '' ]
		then
			# an ISDN card's span that we know if it is in NT mode or TE mode.
			# NT is the same as FXS for us and TE is the same as FXO
			if [ "$span_termtype" = 'nt' ]
			then
				echo "callerid=\"Channels $span_begin - $span_end\" <$span_begin>" >> $zapata_file
				reset_values="$reset_values callerid"
				#echo "mailbox=$exten"
				if [ "$brint_overlap" = 'yes' ]
				then
					echo "overlapdial=yes"         >> $zapata_file
					reset_values="$reset_values overlapdial"
				fi
				if [ "$group_manual" != "yes" ]
				then 
					echo "group=$group_phones,$span_group"     >> $zapata_file
					reset_values="$reset_values group"
				fi
				if [ "$context_manual" != "yes" ]
				then 
					echo "context=$context_phones" >> $zapata_file
				fi
			else 
				#echo "mailbox="
				if [ "$group_manual" != "yes" ]
				then 
					echo "group=$group_lines,$span_group"      >> $zapata_file
					reset_values="$reset_values group"
				fi
				if [ "$context_manual" != "yes" ]
				then 
					echo "context=$context_lines"  >> $zapata_file
					reset_values="$reset_values context"
				fi
			fi
		fi
		echo "switchtype = $span_switchtype" >> $zapata_file
		echo "signalling = $span_signalling" >> $zapata_file
		echo "channel => $bchans"            >> $zapata_file
		reset_zapata_entry $zapata_file $reset_values
		reset_values=
		;;
	list)
		echo "### BRI/PRI: $span_termtype" 
		echo "$bchans Data"
		echo "$dchan Control"
		#echo BRI/PRI: chans: $bchans, control: $dchan
		;;
	esac
}

# This is where the actual detection configuration detection work happens.
genconf() {
	local mode=$1
	local reset_values=""

# 	spanlist=`echo /proc/zaptel/* |  grep -v '\*'`
# 	spanlist=$(for i in `for i in  /proc/zaptel/*; do if [ -f $i ]; then echo $i |  cut -f 4 -d / ; fi; done | sort -n`; do echo -n "/proc/zaptel/$i "; done)
# 	spanlist=(cd /proc/zaptel; ls | sort -n | sed 's|^|/proc/zaptel/|')
	spanlist=`ls /proc/zaptel/ 2>/dev/null | sort -n | sed 's|^|/proc/zaptel/|'`

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


	case "$mode" in 
	files)
		if [ "$do_gen_zapscan" = 'yes' ]; then
			gen_tmp_conf "$ZAPSCAN_FILE" zapscan_file
			cat <<EOF >$zapscan_file
; zapscan.conf: information about detected zaptel channels
; (currently: analog only)
;
; Automatically generated by $0 -- Please do not edit.

EOF
		fi
		gen_tmp_conf "$ZAPTEL_FILE" zaptel_file
		gen_tmp_conf "$ZAPATA_FILE" zapata_file
		cat <<EOF >$zaptel_file
# 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
		cat <<EOF >$zapata_file
; 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
;
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'.
	#
	# 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
		span_num=`basename $procfile`
		# the first line is the title line. It states the model name
		# the second line is empty
		title=`head -n 1 $procfile`
		# stuff that needs to be remembered accross lines (for PRI/BRI support)
		case "$mode" in
		list) echo "### $title";;
		files)
			echo ""         >>$zaptel_file
			echo "# $title" >>$zaptel_file
			echo ""         >>$zapata_file
			echo "; $title" >>$zapata_file
			;;
		esac
		echo '-1'  >$tmp_dir/span_begin
		echo '-1'  >$tmp_dir/span_end
		echo '0'   >$tmp_dir/span_timing
		echo '0'   >$tmp_dir/span_lbo
		echo ''    >$tmp_dir/span_framing
		echo 'ami' >$tmp_dir/span_coding
		echo ''    >$tmp_dir/span_switchtype
		echo ''    >$tmp_dir/span_signalling
		if [ "$zapconf_def_termtype" != '' ]
		then
			echo "$zapconf_def_termtype"    >$tmp_dir/span_termtype
		fi

		# Check if ZapBRI cards are in TE or NT mode
		if   echo $title | egrep -q '((quad|octo)BRI PCI ISDN Card.* \[NT\]\ |octoBRI \[NT\] |HFC-S PCI A ISDN.* \[NT\] |Xorcom .* (BRI|T1|E1)_NT)'
		then
			echo 'nt' >$tmp_dir/span_termtype
		elif echo $title | egrep -q '((quad|octo)BRI PCI ISDN Card.* \[TE\]\ |octoBRI \[TE\] |HFC-S PCI A ISDN.* \[TE\] |Xorcom .* (BRI|T1|E1)_TE)'
		then
			echo 'te' >$tmp_dir/span_termtype
		fi

		# 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
			*WCTDM/* | *\ WRTDM/* | *OPVXA1200/*) 
				# TDM400P/2400P and similar cards (Sangoma A200, OpenVox A1200)
				# this can be either FXS or FXO
				maybe_fxs=0
				maybe_fxo=0
				$ztcfg_cmd -c <(echo fxoks=$chan_num) &>/dev/null && maybe_fxs=1
				$ztcfg_cmd -c <(echo fxsks=$chan_num) &>/dev/null && maybe_fxo=1
				if [ $maybe_fxs = 1 ] && [ $maybe_fxo = 1 ]
				then 
				  # An installed module won't accept both FXS and FXO signalling types:
					# this is an empty slot.
					# TODO: I believe that the Sangoma A20x will reject both and thus 
					# print nothing here. 
				  echo "# channel $chan_num, WCTDM, no module." >> $zaptel_file
					continue
				fi
				
				if [ $maybe_fxs = 1 ]; then print_pattern $chan_num fxo $mode; fi
				if [ $maybe_fxo = 1 ]; then print_pattern $chan_num fxs $mode; fi
				;;
			*WCFXO/*) 
				# X100P
				print_pattern $chan_num fxs $mode || \
				echo "# channel $chan_num, WCFXO, inactive." >>$zaptel_file
				;;
			*WCUSB/*)
				print_pattern $chan_num fxo $mode
				;;
			*XPP_FXO/*)
				# Astribank FXO span
				print_pattern $chan_num fxs $mode
				;;
			*XPP_FXS/*)
				# Astribank FXS span (regular port)
				print_pattern $chan_num fxo $mode
				;;
			*' FXO'/*)
				# FXO module (probably Rhino)
				print_pattern $chan_num fxs $mode
				;;
			*' FXS'/*)
				# FXS module (probably Rhino)
				print_pattern $chan_num fxo $mode
				;;
			*' ---'/*)
				# no module (probably Rhino)
				continue
				;;
			*XPP_OUT/*)
				# Astribank FXS span (output port)
				print_pattern -a output $chan_num fxo $mode
				;;
			*XPP_IN/*)
				# Astribank FXS span (input port)
				print_pattern -a input $chan_num fxo $mode
				;;
			*ZTHFC*/* | *ztqoz*/* |*ztgsm/* |*TE[24]/* | \
				*WCT1/*|*Tor2/* | *TorISA/* | \
				*XPP_BRI_*/* | *WP[TE]1/* | *R[124]T1/* | \
				*XPP_[TE]1*/* )
				detect_digital_channel "$line" "$chan_num" "$span_num"
				;;
			'') ;;		# Empty line (after span header)
			*) 
				case "$mode" in
				list) echo "# ??: $line";;
				files)
					echo "# ??: $line" >>$zaptel_file
					echo "; ??: $line" >>$zapata_file
				esac
				;;
			esac
		done
		# end of part in sub-process.
		
		write_digital_config
	done

	if [ "$mode" = 'files' ]
	then
		cat <<EOF >> ${zaptel_file}

# Global data

loadzone	= $loadzone
defaultzone	= $defaultzone
EOF
	fi
	
	if [ "$mode" = 'files' ]; then
		mv ${ZAPCONF_FILE} ${ZAPCONF_FILE}.bak 2>/dev/null
		mv $zaptel_file ${ZAPCONF_FILE}
		mv ${ZAPATA_FILE} ${ZAPATA_FILE}.bak 2>/dev/nullk
		mv $zapata_file ${ZAPATA_FILE}
		if [ "$do_gen_zapscan" = 'yes' ]; then
			mv $ZAPSCAN_FILE $ZAPSCAN_FILE.bak 2>/dev/null 
			mv $zapscan_file $ZAPSCAN_FILE
		fi

		zapata_file_name=`basename $ZAPATA_FILE`
		if ! grep -q "^#include.*$zapata_file_name" \
			/etc/asterisk/zapata.conf
		then
			say "Note: generated $ZAPATA_FILE not included in zapata.conf"
			say "To fix:  echo '#include $zapata_file_name' >>/etc/asterisk/zapata.conf"
		fi
	fi
}

while getopts 'c:de:Fhlm:MRsuvz' 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` ;;
		d) do_detect=yes ;;
		F) fxsdisable=yes;;
		u) do_unload=yes ; force_stop_ast=yes ;;
		v) verbose=yes ;;
		l) mode='list' ;;
		M) do_module_list=yes; do_detect=yes ;;
		s) force_stop_ast=yes ;;
		R) do_restart=no ;;
		z) do_gen_zapscan=yes ;;
		h) usage; exit 0;;
		*) 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

tmp_dir=`mktemp -d /tmp/genzaptelconf-dir-XXXXXX` || \
	die "$0: failed to create temporary directory. Aborting"


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|sg|il|br|hu|lt|pl|za|pt|ee|mx|in|de|ch|dk|cz|cn):;;
	*) 
		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

# make sure asterisk is not in our way
if [ "$force_stop_ast" = 'yes' ]
then
	$ZAPCONF_ASTERISK_CMD stop 1>&2
else
  # if asterisk is running and we wanted to detect modules
	# or simply to unload modules, asterisk needs to go away.
	if ( [ "$do_unload" = yes ] || [ "$do_detect" = yes ] ) && \
		pidof asterisk >/dev/null 
	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_cleanup 1
	fi
fi

if [ "$do_unload" = yes ]
then
	unload_modules
	exit_cleanup $?
fi

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

if [ "$zapconf_use_perl" = yes ]; then
	#redefine genconf to use perl utilities:
	genconf() {
		case "$1" in 
		list) zaptel_hardware ;;
		files) zapconf ;;
		esac
	}
fi

if [ "$mode" = list ]; then
	genconf list
else
	#zap_reg_xpp
	xpp_startup
	wait_for_zapctl
	say "Generating '${ZAPCONF_FILE} and ${ZAPATA_FILE}'"
	genconf files
	run_ztcfg
fi

if [ "$tmp_dir" != '' ]
then
	rm -rf "$tmp_dir"
fi

if [ "$force_stop_ast" != 'yes' ] || [ "$do_restart" != 'yes' ]
then
	exit_cleanup 0
fi

if [ -x "$ZAPCONF_ASTERISK_SCRIPT" ]
then
	$ZAPCONF_ASTERISK_CMD start 1>&2
fi

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

# vim:ts=8:
