#!/bin/sh
#
# Copyright (C) 2017 openwrt-ssr
# Copyright (C) 2017 yushi studio <ywb94@qq.com>
#
# This is free software, licensed under the GNU General Public License v3.
# See /LICENSE for more information.
#

ORIG_ARG1="${1:-}"
ORIG_ARG2="${2:-}"
ORIG_ARG3="${3:-}"
[ -f /lib/functions.sh ] && . /lib/functions.sh
. $IPKG_INSTROOT/etc/init.d/shadowsocksr
LOCK_FILE="/var/lock/ssr-switch.lock"
PROBE_SID=""

cycle_time=60
switch_time=3
probe_socks_port=10800
direct_fail_count=0
direct_fail_limit=3
probe_url="$(uci_get_by_type server_subscribe url_test_url)"
[ -n "$probe_url" ] || probe_url="https://www.google.com/generate_204"
PROBE_INSTANCE_KEY="ssr-switch-probe-local"

DEFAULT_SERVER="$(uci_get_by_type global global_server)"
CURRENT_SERVER="$DEFAULT_SERVER"
ENABLE_SERVER="nil"

cleanup_probe_socks() {
	ps_list | grep -v "grep" | grep -E "tcp-http-ssr-switch-local\\.json|ss-${PROBE_INSTANCE_KEY}/config\\.yaml|tuic-${PROBE_INSTANCE_KEY}/config\\.yaml" | awk '{print $1}' | xargs kill >/dev/null 2>&1
	sleep 1
	ps_list | grep -v "grep" | grep -E "tcp-http-ssr-switch-local\\.json|ss-${PROBE_INSTANCE_KEY}/config\\.yaml|tuic-${PROBE_INSTANCE_KEY}/config\\.yaml" | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1
	rm -f "$TMP_PATH/tcp-http-ssr-switch-local.json" 2>/dev/null
	rm -rf "$TMP_PATH/ss-${PROBE_INSTANCE_KEY}" "$TMP_PATH/tuic-${PROBE_INSTANCE_KEY}" 2>/dev/null
	rm -f /tmp/ssr-switch-probe.log 2>/dev/null
}

start_probe_socks() {
	local sid="$1"
	local server_type

	server_type="$(uci_get_by_name "$sid" type)"
	[ "$server_type" = "clash" ] && return 1
	[ "$server_type" = "tuic" ] && return 1
	[ "$server_type" = "socks5" ] && return 1

	cleanup_probe_socks
	LOCAL_SERVER="$sid"
	local_config_file="$TMP_PATH/tcp-http-ssr-switch-local.json"
	_local="2"
	local_enable=0
	tmp_local_port=
	mode="tcp,udp"

	if [ "$server_type" = "v2ray" ]; then
		export SSR_SWITCH_PROBE=1
		gen_config_file "$LOCAL_SERVER" "$server_type" 4 0 "$probe_socks_port" >/tmp/ssr-switch-probe.log 2>&1 || return 1
		unset SSR_SWITCH_PROBE
		local xray_bin
		xray_bin="$(first_type xray)"
		[ -x "$xray_bin" ] || return 1
		"$xray_bin" run -c "$local_config_file" >>/tmp/ssr-switch-probe.log 2>&1 &
	else
		export SSR_SWITCH_PROBE=1
		export SSR_SWITCH_PROBE_INSTANCE_KEY="$PROBE_INSTANCE_KEY"
		start_local_with_port "$probe_socks_port" >/tmp/ssr-switch-probe.log 2>&1 || return 1
		unset SSR_SWITCH_PROBE_INSTANCE_KEY
		unset SSR_SWITCH_PROBE
	fi

	sleep 2
	return 0
}

probe_http_ok() {
	case "$1" in
		[1-5][0-9][0-9])
			return 0
			;;
	esac
	return 1
}

check_direct_probe() {
	local code

	code="$(curl -k -sS -L -o /dev/null \
		--connect-timeout "$switch_time" \
		--max-time "$switch_time" \
		-w '%{http_code}' \
		"$probe_url" 2>/dev/null || true)"
	probe_http_ok "$code"
}

check_proxy_via_socks() {
	local try_count
	local code
	local proxy_host

	try_count="$(uci_get_by_type global switch_try_count 3)"
	for i in $(seq 1 "$try_count"); do
		for proxy_host in "127.0.0.1:$probe_socks_port" "[::1]:$probe_socks_port"; do
			code="$(curl -k -sS -L -o /dev/null \
				--connect-timeout "$switch_time" \
				--max-time "$switch_time" \
				--socks5-hostname "$proxy_host" \
				-w '%{http_code}' \
				"$probe_url" 2>/dev/null || true)"
			probe_http_ok "$code" && return 0
		done
		sleep 1
	done
	return 1
}

test_proxy() {
	local sid="$1"
	[ "$(uci_get_by_name "$sid" type)" = "clash" ] && return 1
	[ "$(uci_get_by_name "$sid" type)" = "tuic" ] && return 1
	start_probe_socks "$sid" || {
		cleanup_probe_socks
		return 1
	}
	check_proxy_via_socks
	local ret=$?
	cleanup_probe_socks
	return $ret
}

search_proxy() {
	local sid="$1"
	[ "$(uci_get_by_name "$sid" switch_enable 0)" = "1" ] || return 1
	[ "$(uci_get_by_name "$sid" type)" = "clash" ] && return 1
	[ "$(uci_get_by_name "$sid" type)" = "tuic" ] && return 1
	[ "$sid" = "$CURRENT_SERVER" ] && return 1
	[ "$ENABLE_SERVER" != "nil" ] && return 0

	if test_proxy "$sid"; then
		ENABLE_SERVER="$sid"
		return 0
	fi
	return 1
}

select_proxy() {
	config_load "$NAME"
	ENABLE_SERVER="nil"
	config_foreach search_proxy servers
}

set_main_server() {
	local sid="$1"
	[ -n "$sid" ] || return 1
	uci set shadowsocksr.@global[0].global_server="$sid"
	uci commit shadowsocksr
	return 0
}

switch_proxy() {
	set_main_server "$1" || return 1
	/etc/init.d/shadowsocksr restart "$1"
	return 0
}

start() {
	while [ "1" = "1" ]; do
		sleep "0000$cycle_time"
		run_once
	done
}

run_once() {
	DEFAULT_SERVER="$(uci_get_by_type global global_server)"
	[ "$DEFAULT_SERVER" = "nil" ] && {
		direct_fail_count=0
		return 0
	}
	[ "$(uci_get_by_name "$DEFAULT_SERVER" type)" = "clash" ] && {
		direct_fail_count=0
		cleanup_probe_socks
		return 0
	}
	[ "$(uci_get_by_name "$DEFAULT_SERVER" type)" = "tuic" ] && {
		direct_fail_count=0
		cleanup_probe_socks
		return 0
	}
	[ "$(uci_get_by_name "$DEFAULT_SERVER" kcp_enable 0)" = "1" ] && {
		direct_fail_count=0
		cleanup_probe_socks
		return 1
	}

	CURRENT_SERVER="$DEFAULT_SERVER"

	if check_direct_probe; then
		direct_fail_count=0
		return 0
	fi

	direct_fail_count=$((direct_fail_count + 1))
	[ "$direct_fail_count" -lt "$direct_fail_limit" ] && return 0
	direct_fail_count=0

	if test_proxy "$CURRENT_SERVER"; then
		return 0
	fi

	echolog "Current server error, try to switch another server."
	select_proxy
	if [ "$ENABLE_SERVER" != "nil" ]; then
		CURRENT_SERVER="$ENABLE_SERVER"
		switch_proxy "$CURRENT_SERVER"
		echolog "Switch to $(uci_get_by_name "$CURRENT_SERVER" alias) proxy!"
	else
		switch_proxy "$CURRENT_SERVER"
		echolog "Try restart current server."
	fi
	return 0
}

probe_once() {
	local sid="$1"
	[ -n "$sid" ] || {
		echo "missing_sid"
		return 1
	}
	cleanup_probe_socks
	if ! start_probe_socks "$sid"; then
		echo "probe_start:fail"
		return 1
	fi
	echo "probe_start:ok"
	echo "probe:curl_ipv4"
	curl -k -sS -L -o /dev/null \
		--connect-timeout "$switch_time" \
		--max-time "$switch_time" \
		--socks5-hostname "127.0.0.1:$probe_socks_port" \
		-w 'code=%{http_code}\n' \
		"$probe_url" 2>/dev/null || true
	echo "probe:curl_ipv6"
	curl -g -k -sS -L -o /dev/null \
		--connect-timeout "$switch_time" \
		--max-time "$switch_time" \
		--socks5-hostname "[::1]:$probe_socks_port" \
		-w 'code=%{http_code}\n' \
		"$probe_url" 2>/dev/null || true
	cleanup_probe_socks
	return 0
}

main() {
	case "${ORIG_ARG1:-}" in
		probe)
			PROBE_SID="${ORIG_ARG2:-}"
			probe_once "$PROBE_SID"
			;;
		once)
			run_once
			;;
		start)
			[ -n "$ORIG_ARG2" ] && cycle_time="$ORIG_ARG2"
			[ -n "$ORIG_ARG3" ] && switch_time="$ORIG_ARG3"
			[ -f "$LOCK_FILE" ] && exit 2
			touch "$LOCK_FILE"
			start
			;;
		*)
			[ -n "$ORIG_ARG1" ] && cycle_time="$ORIG_ARG1"
			[ -n "$ORIG_ARG2" ] && switch_time="$ORIG_ARG2"
			[ -f "$LOCK_FILE" ] && exit 2
			touch "$LOCK_FILE"
			start
			;;
	esac
}

if [ "${0##*/}" = "ssr-switch" ]; then
	main "$@"
fi
