#!/bin/sh

. /usr/local/bin/telem/functions # for logg function
. /usr/local/bin/telem/modem_functions

logg "StartPPP"

GENPATH=/usr/local/bin/ppp/
CHECKUSB=/usr/local/bin/telem/check-usb.sh

sim_try_count=1
#sim_try_max=5
sim_id=1

modem_errors=0
modem_errors_cycle=9
modem_errors_reboot=18
modem_io_errors=0
modem_io_errors_max=9
#############################################################
#############################################################
#### tiemouts
timeout_terminate=10
timeout_default=5
timeout_connect=180
# from HE910 AT reference guide (R4)
timeout_clip=180
timeout_cops=180
timeout_cpin=20
timeout_gmi=5
timeout_gmm=5
timeout_gmr=5
timeout_gsn=20
timeout_cgatt=180
timeout_csq=5
#############################################################
noPIN=false
modem_log="/var/log/modem.log"
modem_full_log="/var/log/modem-full.log"
modem_full_log1="/var/log/modem-full.1.log"
#############################################################

powerCycleModem() {
	noPIN=false
	[ -z "$@" ] || logg "$@"
	modem_reset_lock
}

resetModem() {
	noPIN=false
	[ -z "$@" ] || logg "$@"
	modem_reset_lock at
}

hasConfSIM1() {
	[ -f "${sim1conf}" ]
}

hasConfSIM2() {
	[ -f "${sim2conf}" ]
}

sourceSIM1() {
	. "${sim1conf}"
}

sourceSIM2() {
	. "${sim2conf}"
}

sourceConf() {
	# defaults
	APN="-"
	PIN="-"
	# Select wireless network (Telit specific)
	# 12 - GSM Digital Cellular System(GERAN aka GSM EDGE aka GPRS only)
	# 22 - UTRAN aka UMTS Terrestrial Radio Access Network aka 3G only
	# 25 - 3GPP systems(both GERAN and UTRAN)(factory default)
	NETWORK="25"
	OPERATOR="-"

	sourceSIM${sim_id}
	# override sim${sim_id}.conf value, if PIN was not required in previous run
	$noPIN && PIN="-"
}

checkState() {
	hasConfSIM${sim_id} || {
		logg "sim${sim_id}.conf is missing"
		return 1
	}
	canSwitchSIM || return 0
	insertedSIM${sim_id} || {
		logg "SIM${sim_id} is not available according to GPIO MODEM_SIM${sim_id}"
		return 1
	}
}

switchSIM() {
	canSwitchSIM || return
	local new_sim_id=1
	[ "${sim_id}" == "1" ] && new_sim_id=2
	# Reselect current SIM just in case if other SIM is not available or not configured
	selectSIM${sim_id}
	# Check if SIM is inserted
	insertedSIM${new_sim_id} || {
		logg "Can't switch to SIM${new_sim_id}, because SIM is not available according to GPIO MODEM_SIM${new_sim_id}"
		return 1
	}
	# Cancel SIM switch if configuration file is missing
	hasConfSIM${new_sim_id} || {
		logg "Can't switch to SIM${new_sim_id}, because configuration is missing"
		return 1
	}
	# Everything OK, do the switch
	logg "Switch to SIM${new_sim_id}"
	selectSIM${new_sim_id}
	sim_id=${new_sim_id}
	noPIN=false
	# reset SIM try count
	sim_try_count=1
	return 0
}

initSIM() {
	# Check if we can control multiple SIMs
	canSwitchSIM || return
	# Always start from SIM1
	sim_id=1
	selectSIM1
	# Switch over if needed
	insertedSIM1 && hasConfSIM1 || switchSIM
}

generateFiles() {
	# update USB_MODEM variable
	[ -f /var/local/telem/usb-modem-vidpid ] && . /var/local/telem/usb-modem-vidpid
	case "$USB_MODEM" in
		#12d1:1c25) . "${GENPATH}huawei.sh"
		12d1:*)    . "${GENPATH}genchat.sh"
		;;
		1bc7:0021) . "${GENPATH}telit.sh"
		;;
		2c7c:*)    . "${GENPATH}genchat.sh"
		;;
		1e0e:*)    . "${GENPATH}genchat.sh"
		;;
		*)
			# by default find scripts by usb id
			# ex: match pattern is "*12d1?1c25.sh" for device 12d1:1c25
			fname="$(find "${GENPATH}" -name "*$(echo "$USB_MODEM" | tr : ? ).sh" | head -n1)"
			[ -f "$fname" ] && . "$fname" || logg "Modem configuration not found for usb id $USB_MODEM"
		;;
	esac
}

doOnMissingDev() {
	modem_errors="$((modem_errors+1))"
	if [ "${modem_errors}" -eq "${modem_errors_cycle}" ]; then
		powerCycleModem "Failed to connect $modem_errors times, Power cycle modem"
	fi
	if [ "${modem_errors}" -gt "${modem_errors_reboot}" ]; then
		logg "Unable to connect to modem, do reboot"
		reboot; sleep 60
		return
	fi
	$CHECKUSB
}

resolve1() {
	if [ "${modem_errors}" -gt "${modem_errors_reboot}" ]; then
		logg "Unable to connect to modem, do reboot"
		reboot; sleep 60
		return 0
	fi
	return 1
}

resolve2() {
	case "${modem_errors}" in
		3|12) resetModem "Failed to connect ${modem_errors} times, reset modem"
		;;
		6|15) powerCycleModem "Failed to connect ${modem_errors} times, power cycle modem"
		;;
		9) switchSIM
		;;
		*) return 1
	esac
	return 0
}

resolve3() {
	# try to solve(trivial) PIN issues
	grep -qF -- '+CPIN: READY' "${modem_log}" && noPIN=true || noPIN=false
}

doOnModemError() {
	modem_errors="$((modem_errors+1))"
	# log last error from temporary $modem_log to messages
	logg "$(
		tail -n15 "${modem_log}" | \
		awk 'BEGIN{RS="\n|\r"}/send.*|^(at|AT)/{snd=$0;next}match($0,/CM[E|S] ERROR: .+/){print substr($0,RSTART,RLENGTH)", last command:",snd}'
	)"

	# Try to fix the issue
	resolve1 || resolve2 || resolve3
	
	if grep -qF -- 'Input/output error' "${modem_log}"; then
		logg "PPPD IO errors: ${modem_io_errors}"
		modem_io_errors="$((modem_io_errors+1))"
		# potentially unrecoverable?
		powerCycleModem
	fi
	if [ "${modem_io_errors}" -gt "${modem_io_errors_max}" ]; then
		logg "Modem Input/output errors problem, do reboot"
		touch /var/local/telem/errors/modem-io
		reboot; sleep 60
	fi

	$CHECKUSB
}

logtmp() {
	echo "$@" >> "${modem_log}"
}

resetLogtmp() {
	cat "${modem_log}" >> "${modem_full_log}"
	rm "${modem_log}" 2>/dev/null
	# simple rotation
	if [ ! -z "$(find "${modem_full_log}" -size +500k 2> /dev/null)" ]; then
		mv "${modem_full_log}" "${modem_full_log1}"
	fi
}

modem_connected_test() {
	modem_tty_cmd_wait
	2> /dev/null chat -t2 'ABORT' 'ERROR' 'ABORT' '%CMATT: 0' '' 'AT%CMATT?' 'OK' 'AT%PCONI' 'OK' '' < "${MODEM_TTY_CMD}" > "${MODEM_TTY_CMD}"
}

modem_connected_test_3x() {
	modem_connected_test && return
	sleep 1
	modem_connected_test && return
	sleep 2
	modem_connected_test
}

fake_pppd() {
	call_chat '/etc/ppp/peers/modem_chat' >> "${modem_log}" || return 1
	ip link set ppp0 up >> "${modem_log}" || return 1
	sleep 5
	modem_tty_cmd_ok        || return 1
	modem_connected_test_3x || return 1

	udhcpc -S -i ppp0 >> "${modem_log}" || return 1 # TODO: use custom script
	inet="$(ip -o addr show ppp0 | awk -F'[ \t/]+' '/inet /{print $4}')"
	/etc/ppp/ip-up "ppp0" "" "" "${inet}" "" "MODEM" # TODO: call from udhcpc
	sleep 5
	while :; do
		modem_tty_cmd_ok || break
		modem_connected_test_3x || break
		sleep 20
	done
	/etc/ppp/ip-down "ppp0" "" "" "${inet}" "" "MODEM"
	return 1
}

ser() {
	local USB_MODEM="nothing"
	[ -f /var/local/telem/usb-modem-vidpid ] && . /var/local/telem/usb-modem-vidpid
	case "$USB_MODEM" in
		2cd2:*)
			serport="${MODEM_TTY_CMD}"
			fake='fake_'
		;;
		*)
			serport="${MODEM_TTY}"
			fake=
		;;
	esac
}

#############################################################
#### main part

initSIM

powerCycleModem "For starters, power cycle modem"

while true; do
	ser
	if [ -e "${serport}" ]; then
		# is everything okay
		checkState || {
			# if switching fails, then sleep longer
			switchSIM || sleep 60
			continue
		}
		# load variables
		sourceConf "${sim_id}"
		# generate modem_init and modem_chat
		generateFiles

		logg "Using SIM${sim_id}, try $sim_try_count"
		logg "Port found: '$serport'"

		pppd_cmd="$serport 115200 call modem_init"

		rm -f /var/local/telem/was_connected 2>/dev/null
		resetLogtmp
		logtmp "========================================"
		logtmp "New PPP session @ $(date)"
		rm /var/local/telem/kill-pppd 2>/dev/null
		# wait for modem just in case if it was recently reset
		wait_for_modem || logg "Waited for modem"
		logg "Starting PPPD ($pppd_cmd)"
		${fake}pppd $pppd_cmd

		# error handling
		pppd_ret=$?
		logg "PPPD stopped, exit status: $pppd_ret"

		sim_try_count="$((sim_try_count+1))"
		if [ -e /var/local/telem/was_connected ]; then
			modem_errors=0
			modem_io_errors=0
			rm -f /var/local/telem/was_connected
			switchSIM
		else
			doOnModemError
		fi
	else
		logg "Error, not valid port! (${serport})"
		doOnMissingDev
		sleep 10
	fi
	sleep 5
done
