#!/bin/bash
###############################################################################
#                                                                             #
# IPFire.org - A linux based firewall                                         #
# Copyright (C) 2014 Michael Tremer                                           #
#                                                                             #
# 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 3 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, see <http://www.gnu.org/licenses/>.       #
#                                                                             #
###############################################################################
# network(8) completion

_network_find_on_cmdline () {
	local word subcommand c=0
	while [ ${c} -lt ${cword} ]; do
		word="${words[c]}"
		for subcommand in ${1}; do
			if [ "${subcommand}" = "${word}" ]; then
				echo "${subcommand}"
				return
			fi
		done
		((c++))
	done
}

_network_complete_hooks() {
	local type="${1}"

	COMPREPLY=( $(compgen -W "$(network raw list-hooks "${type}")" -- "${cur}") )
}

_network_complete_ports() {
	COMPREPLY=( $(compgen -W "$(network raw list-ports)" -- "${cur}") )
}

_network_complete_zones() {
	COMPREPLY=( $(compgen -W "$(network raw list-zones)" -- "${cur}") )
}

_network_color() {
	local words=( $@ )

	local commands="set reset"
	local cmd="$(_network_find_on_cmdline "${commands}")"
	if [[ -z "${cmd}" ]]; then
		COMPREPLY=( $(compgen -W "${commands}" -- "${cur}") )
		return 0
	fi
}

_network_description() {
	local words=( $@ )

	local commands="edit show"
	local cmd="$(_network_find_on_cmdline "${commands}")"
	if [[ -z "${cmd}" ]]; then
		COMPREPLY=( $(compgen -W "${commands}" -- "${cur}") )
		return 0
	fi
}
_network_device() {
	local words=( $@ )

	local commands="list $(network raw list-devices)"
	local cmd="$(_network_find_on_cmdline "${commands}")"
	if [[ -z "${cmd}" ]]; then
		COMPREPLY=( $(compgen -W "${commands}" -- "${cur}") )
		return 0
	fi

	local args="${words[@]:1}"
	case "${cmd}" in
		list)
			return 0
			;;
		*)
			_network_device_subcommand ${args}
			;;
	esac
}

_network_device_subcommand() {
	local words=( $@ )

	local commands="discover identify monitor status unlock ussd"
	local cmd="$(_network_find_on_cmdline "${commands}")"
	if [[ -z "${cmd}" ]]; then
		COMPREPLY=( $(compgen -W "${commands}" -- "${cur}") )
		return 0
	fi

	case "${cmd}" in
		ussd)
			# TODO
			;;
	esac
}

_network_dhcpd() {
	local proto="${1}"
	shift

	local words=( $@ )

	local commands="edit reload restart show start stop subnet"
	local cmd="$(_network_find_on_cmdline "${commands}")"
	if [[ -z "${cmd}" ]]; then
		COMPREPLY=( $(compgen -W "${commands}" -- "${cur}") )
		return 0
	fi

	local args="${words[@]:1}"
	case "${cmd}" in
		subnet)
			_network_dhcpd_subnet "${proto}" ${args}
			;;
	esac
}

_network_dhcpd_subnet() {
	local proto="${1}"
	shift

	local words=( $@ )

	local commands="new remove show $(network raw list-dhcpd-subnets "${proto}")"
	local cmd="$(_network_find_on_cmdline "${commands}")"
	if [[ -z "${cmd}" ]]; then
		COMPREPLY=( $(compgen -W "${commands}" -- "${cur}") )
		return 0
	fi

	local subnet="${words[0]}"
	local args="${words[@]:1}"
	case "${cmd}" in
		new)
			: # TODO
			;;
		remove)
			: # TODO
			;;
		[0-9]*)
			_network_dhcpd_subnet_subcommand "${proto}" "${subnet}" ${args}
			;;
	esac
}

_network_dhcpd_subnet_subcommand() {
	local proto="${1}"
	local subnet="${2}"
	shift 2

	local words=( $@ )

	local commands="edit options range show"
	local cmd="$(_network_find_on_cmdline "${commands}")"
	if [[ -z "${cmd}" ]]; then
		COMPREPLY=( $(compgen -W "${commands}" -- "${cur}") )
		return 0
	fi

	local args="${words[@]:1}"
	case "${cmd}" in
		edit)
			: # TODO
			;;
		options)
			_network_dhcpd_subnet_subcommand_options "${proto}" "${subnet}" ${args}
			;;
		range)
			_network_dhcpd_subnet_subcommand_range "${proto}" "${subnet}" ${args}
			;;
	esac
}

_network_dhcpd_subnet_subcommand_options() {
	local proto="${1}"
	local subnet="${2}"
	shift 2

	local options option
	for option in $(network raw list-dhcpd-subnet-options "${proto}"); do
		options="${options} ${option}="
	done

	COMPREPLY=( $(compgen -W "${options}" -- "${cur}") )
}

_network_dhcpd_subnet_subcommand_range() {
	local proto="${1}"
	local subnet="${2}"
	shift 2

	local words=( $@ )

	local commands="new remove"
	local cmd="$(_network_find_on_cmdline "${commands}")"
	if [[ -z "${cmd}" ]]; then
		COMPREPLY=( $(compgen -W "${commands}" -- "${cur}") )
		return 0
	fi

	case "${cmd}" in
		new)
			COMPREPLY=( $(compgen -W "--end= --start=" -- "${cur}") )
			;;
		remove)
			COMPREPLY=( $(compgen -W "$(network raw list-dhcpd-ranges-of-subnet "${proto}" "${subnet}")" \
				-- "${cur}") )
			;;
	esac
}

_network_dns_server() {
	local words=( $@ )

	local commands="add list remove update"
	local cmd="$(_network_find_on_cmdline "${commands}")"
	if [[ -z "${cmd}" ]]; then
		COMPREPLY=( $(compgen -W "${commands}" -- "${cur}") )
		return 0
	fi

	case "${cmd}" in
		remove)
			COMPREPLY=( $(compgen -W "$(network raw list-dns-servers)" \
				-- "${cur}") )
			;;
	esac
}

_network_port() {
	local words=( $@ )

	local commands="new destroy $(network raw list-ports)"
	local cmd="$(_network_find_on_cmdline "${commands}")"
	if [[ -z "${cmd}" ]]; then
		COMPREPLY=( $(compgen -W "${commands}" -- "${cur}") )
		return 0
	fi

	local args="${words[@]:1}"
	case "${cmd}" in
		new)
			_network_complete_hooks "port"
			;;
		destroy)
			_network_complete_ports
			;;
		*)
			local args="${words[@]:1}"
			_network_port_subcommand ${args}
			;;
	esac
}

_network_port_subcommand() {
	local words=( $@ )

	local commands="color create description down edit identify remove status up"
	local cmd="$(_network_find_on_cmdline "${commands}")"
	if [[ -z "${cmd}" ]]; then
		COMPREPLY=( $(compgen -W "${commands}" -- "${cur}") )
		return 0
	fi

	local args="${words[@]:1}"
	case "${cmd}" in
		color)
			_network_color ${args}
			;;
		description)
			_network_description ${args}
			;;
	esac

}

_network_route() {
	local words=( $@ )

	local commands="static"
	local cmd="$(_network_find_on_cmdline "${commands}")"
	if [[ -z "${cmd}" ]]; then
		COMPREPLY=( $(compgen -W "${commands}" -- "${cur}") )
		return 0
	fi

	case "${cmd}" in
		static)
			local args="${words[@]}"
			_network_route_static ${args}
			;;
	esac

}

_network_route_static() {
	local words=( $@ )

	local commands="add list remove reload"
	local cmd="$(_network_find_on_cmdline "${commands}")"
	if [[ -z "${cmd}" ]]; then
		COMPREPLY=( $(compgen -W "${commands}" -- "${cur}") )
		return 0
	fi

	case "${cmd}" in
		add)
			if [[ "${cur}" = -* ]]; then
				COMPREPLY=( $(compgen -W "--blackhole --gateway= --mtu= \
					--prohibit --unreachable" -- "${cur}") )
			fi
			;;
		list)
			# TODO auto-complete options like --protocol here
			COMPREPLY=( $(compgen -W "--protocol=" -- "${cur}") )
			;;
	esac
}

_network_settings() {
	local words=( $@ )

	local key keys
	for key in $(network raw list-settings); do
		keys="${keys} ${key}="
	done
	COMPREPLY=( $(compgen -W "${keys}" -- "${cur}") )
}

_network_vpn() {
	local words=( $@ )

	local commands="ipsec security-policies"
	local cmd="$(_network_find_on_cmdline "${commands}")"
	if [[ -z "${cmd}" ]]; then
		COMPREPLY=( $(compgen -W "${commands}" -- "${cur}") )
		return 0
	fi


	local args="${words[@]:1}"
	case "${cmd}" in
		ipsec)
			_network_vpn_ipsec ${args}
			;;
		security-policies)
			_network_vpn_security_policies ${args}
			;;
		esac
}

_network_vpn_ipsec() {
	local words=( $@ )

	local commands="connection"
	local cmd="$(_network_find_on_cmdline "${commands}")"
	if [[ -z "${cmd}" ]]; then
		COMPREPLY=( $(compgen -W "${commands}" -- "${cur}") )
		return 0
	fi


	local args="${words[@]:1}"
	case "${cmd}" in
		connection)
			_network_vpn_ipsec_connection ${args}
			;;
		esac
}

_network_vpn_ipsec_connection() {
	local words=( $@ )

	local commands="destroy new $(network raw list-ipsec-connections)"
	local cmd="$(_network_find_on_cmdline "${commands}")"
	if [[ -z "${cmd}" ]]; then
		COMPREPLY=( $(compgen -W "${commands}" -- "${cur}") )
		return 0
	fi


	local args="${words[@]:1}"
	case "${cmd}" in
		destroy)
			:
			;;
		new)
			:
			;;
		*)
			if network raw ipsec-connection-exists ${cmd}; then
				_network_vpn_ipsec_connection_subcommands ${cmd} ${args}
			fi
			;;
		esac
}

_network_vpn_ipsec_connection_subcommands() {
	local connection=${1}
	shift
	local words=( $@ )

	local commands="authentication color description down inactivity-timeout local mode peer remote security-policy show up zone"
	local cmd="$(_network_find_on_cmdline "${commands}")"
	if [[ -z "${cmd}" ]]; then
		COMPREPLY=( $(compgen -W "${commands}" -- "${cur}") )
		return 0
	fi


	local args="${words[@]:1}"
	case "${cmd}" in
		authentication)
			_network_vpn_ipsec_connection_subcommands_authentication ${connection} ${args}
			;;
		color)
			_network_color ${args}
			;;
		description)
			_network_description ${args}
			;;
		local)
			_network_vpn_ipsec_connection_subcommands_local_remote ${connection} "local" ${args}
			;;
		mode)
			_network_vpn_ipsec_connection_subcommands_mode ${args}
			;;
		remote)
			_network_vpn_ipsec_connection_subcommands_local_remote ${connection} "remote" ${args}
			;;
		security-policy)
			_network_vpn_ipsec_connection_subcommands_security_policy ${args}
			;;
		zone)
			_network_vpn_ipsec_connection_subcommands_zone "${connection}" ${args}
			;;
	esac
}

_network_vpn_ipsec_connection_subcommands_authentication() {
	local connection=${1}
	shift
	local words=( $@ )

	local commands="mode pre-shared-key"
	local cmd="$(_network_find_on_cmdline "${commands}")"
	if [[ -z "${cmd}" ]]; then
		COMPREPLY=( $(compgen -W "${commands}" -- "${cur}") )
		return 0
	fi


	local args="${words[@]:1}"
	case "${cmd}" in
		mode)
			_network_vpn_ipsec_connection_subcommands_authentication_mode ${connection} ${args}
			;;
			esac
}

_network_vpn_ipsec_connection_subcommands_local_remote() {
	local connection=${1}
	local type=${2}
	shift 2
	local words=( $@ )

	local commands="id prefix"
	local cmd="$(_network_find_on_cmdline "${commands}")"
	if [[ -z "${cmd}" ]]; then
		COMPREPLY=( $(compgen -W "${commands}" -- "${cur}") )
		return 0
	fi


	local args="${words[@]:1}"
	case "${cmd}" in
		prefix)
			_network_vpn_ipsec_connection_subcommands_prefix "${connection}" "${type}" ${args}
			;;
			esac
}

_network_vpn_ipsec_connection_subcommands_prefix() {
	:
}

_network_vpn_ipsec_connection_subcommands_authentication_mode() {
	local connection=${1}
	shift
	local words=( $@ )

	local commands="PSK"
	local cmd="$(_network_find_on_cmdline "${commands}")"
	if [[ -z "${cmd}" ]]; then
		COMPREPLY=( $(compgen -W "${commands}" -- "${cur}") )
		return 0
	fi
}

_network_vpn_ipsec_connection_subcommands_mode() {
	local words=( $@ )

	local commands="transport tunnel"
	local cmd="$(_network_find_on_cmdline "${commands}")"
	if [[ -z "${cmd}" ]]; then
		COMPREPLY=( $(compgen -W "${commands}" -- "${cur}") )
		return 0
	fi
}

_network_vpn_ipsec_connection_subcommands_security_policy() {
	local words=( $@ )

	local commands="$(network raw list-vpn-security-policies-all)"
	local cmd="$(_network_find_on_cmdline "${commands}")"
	if [[ -z "${cmd}" ]]; then
		COMPREPLY=( $(compgen -W "${commands}" -- "${cur}") )
		return 0
	fi
}

_network_vpn_ipsec_connection_subcommands_zone() {
	local connection="${1}"
	shift

	local words=( $@ )

	# XXX TODO find zones that can be attached here
}

_network_vpn_security_policies() {
	local words=( $@ )

	local commands="destroy new $(network raw list-vpn-security-policies-all)"
	local cmd="$(_network_find_on_cmdline "${commands}")"
	if [[ -z "${cmd}" ]]; then
		COMPREPLY=( $(compgen -W "${commands}" -- "${cur}") )
		return 0
	fi


	local args="${words[@]:1}"
	case "${cmd}" in
		destroy)
			:
			;;
		new)
			:
			;;
		*)
			if network raw vpn-security-policy-exists ${cmd}; then
				_network_vpn_security_policies_subcommands ${cmd} ${args}
			fi
			;;
		esac
}

_network_vpn_security_policies_subcommands() {
	local policy=${1}
	shift
	local words=( $@ )

	local commands="ciphers compression group-types integrities key-exchange lifetime pfs pseudo-random-functions show"
	local cmd="$(_network_find_on_cmdline "${commands}")"
	if [[ -z "${cmd}" ]]; then
		COMPREPLY=( $(compgen -W "${commands}" -- "${cur}") )
		return 0
	fi


	local args="${words[@]:1}"
	case "${cmd}" in
		ciphers)
			_network_vpn_security_policies_subcommands_ciphers ${policy} ${args}
			;;
		compression)
			_network_vpn_security_policies_subcommands_compression ${policy} ${args}
			;;
		group-types)
			_network_vpn_security_policies_subcommands_group_types ${policy} ${args}
			;;
		integrities)
			_network_vpn_security_policies_subcommands_integrities ${policy} ${args}
			;;
		pseudo-random-functions)
			_network_vpn_security_policies_subcommands_pseudo_random_functions ${policy} ${args}
			;;
		key-exchange)
			_network_vpn_security_policies_subcommands_key_exchange ${policy} ${args}
			;;
		pfs)
			_network_vpn_security_policies_subcommands_pfs ${policy} ${args}
			;;
		esac
}

_network_vpn_security_policies_subcommands_ciphers() {
	:
}

_network_vpn_security_policies_subcommands_compression() {
	:
}

_network_vpn_security_policies_subcommands_group_types() {
	:
}

_network_vpn_security_policies_subcommands_integrities() {
	:
}

_network_vpn_security_policies_subcommands_pseudo_random_functions() {
	:
}

_network_vpn_security_policies_subcommands_key_exchange() {
	:
}

_network_vpn_security_policies_subcommands_pfs() {
	:
}

_network_zone() {
	local words=( $@ )

	local commands="new destroy $(network raw list-zones)"
	local cmd="$(_network_find_on_cmdline "${commands}")"
	if [[ -z "${cmd}" ]]; then
		COMPREPLY=( $(compgen -W "${commands}" -- "${cur}") )
		return 0
	fi


	local args="${words[@]:1}"
	case "${cmd}" in
		new)
			_network_zone_new ${args}
			;;
		destroy)
			_network_complete_zones
			;;
		*)
			local zone="${cmd}"
			local args="${words[@]:1}"
			_network_zone_subcommand "${zone}" ${args}
			;;
	esac
}

_network_zone_new() {
	local words=( $@ )
	local cmd=${words[@]:0:1}

	# Suggest useful zone names
	if [[ -z "${cmd}" ]]; then
		local commands="$(network raw list-next-free-zones)"
		COMPREPLY=( $(compgen -W "${commands}" -- "${cur}") )

	# If a valid zone name was entered, we can move on
	elif network raw zone-name-is-valid ${cmd}; then
		local args="${words[@]:1}"
		_network_complete_hooks zone ${args}
	fi

	return 0
}

_network_zone_subcommand() {
	local zone="${1}"
	shift

	local words=( $@ )

	local commands="color config description disable down edit enable identify port rename status up"
	local cmd="$(_network_find_on_cmdline "${commands}")"
	if [[ -z "${cmd}" ]]; then
		COMPREPLY=( $(compgen -W "${commands}" -- "${cur}") )
		return 0
	fi

	local args="${words[@]:1}"
	case "${cmd}" in
		config)
			_network_zone_subcommand_config "${zone}" ${args}
			;;
		port)
			_network_zone_subcommand_port "${zone}" ${args}
			;;
		color)
			_network_color ${args}
			;;
		description)
			_network_description ${args}
			;;
	esac
}

_network_zone_subcommand_config() {
	local zone="${1}"
	shift

	local words=( $@ )

	local commands="destroy list new $(network raw list-zone-config-hids ${zone})"

	local cmd="$(_network_find_on_cmdline "${commands}")"
	if [[ -z "${cmd}" ]]; then
		COMPREPLY=( $(compgen -W "${commands}" -- "${cur}") )
		return 0
	fi

	local args="${words[@]:1}"
	case "${cmd}" in
		new)
			_network_complete_hooks "config"
			;;
		destroy)
			_network_zone_subcommand_config_destroy ${zone} ${args}
			;;
		*)
			if network raw zone-config-id-is-valid ${zone} ${cmd} || network raw zone-config-hid-is-valid ${zone} ${cmd}; then
				_network_zone_subcommand_config_subcommand ${zone} ${args}
			fi
			;;
	esac
}

_network_zone_subcommand_config_subcommand() {
	local zone="${1}"
	shift

	local words=( $@ )

	local commands="edit"

	local cmd="$(_network_find_on_cmdline "${commands}")"
	if [[ -z "${cmd}" ]]; then
		COMPREPLY=( $(compgen -W "${commands}" -- "${cur}") )
		return 0
	fi
}

_network_zone_subcommand_config_destroy() {
	local zone="${1}"
	shift

	local words=( $@ )

	local commands="$(network raw list-zone-config-ids ${zone})"

	local cmd="$(_network_find_on_cmdline "${commands}")"
	if [[ -z "${cmd}" ]]; then
		COMPREPLY=( $(compgen -W "${commands}" -- "${cur}") )
		return 0
	fi
}

_network_zone_subcommand_port() {
	local zone="${1}"
	shift

	local words=( $@ )

	local commands="attach detach $(network raw list-ports-of-zone "${zone}")"
	local cmd="$(_network_find_on_cmdline "${commands}")"
	if [[ -z "${cmd}" ]]; then
		COMPREPLY=( $(compgen -W "${commands}" -- "${cur}") )
		return 0
	fi

	case "${cmd}" in
		attach)
			COMPREPLY=( $(compgen -W "$(network raw list-free-ports "${zone}")" \
				-- "${cur}") )
			;;
		detach)
			COMPREPLY=( $(compgen -W "$(network raw list-ports-of-zone "${zone}")" \
				-- "${cur}") )
			;;
		*)
			local port="${cmd}"
			local args="${words[@]:1}"
			_network_zone_subcommand_port_subcommand "${zone}" "${port}" ${args}
			;;
	esac
}

_network_zone_subcommand_port_subcommand() {
	local zone="${1}"
	local port="${2}"
	shift 2

	local words=( $@ )

	local commands="edit"
	local cmd="$(_network_find_on_cmdline "${commands}")"
	if [[ -z "${cmd}" ]]; then
		COMPREPLY=( $(compgen -W "${commands}" -- "${cur}") )
		return 0
	fi

	case "${cmd}" in
		edit)
			# TODO auto-complete the zone-port hook
			;;
	esac
}

_network() {
	local cur prev words cword
	_init_completion || return

	local cmd i
	for (( i=1; i < ${#words[@]} - 1; i++ )); do
		[[ "${words[i]}" = -* ]] && continue
		cmd="${words[i]}" && break
	done

	if [[ -z "${cmd}" ]]; then
		case "${cur}" in
			-*)
				COMPREPLY=( $(compgen -W "--debug" -- "${cur}") )
				;;
			*)
				COMPREPLY=( $(compgen -W "device dhcpv4 dhcpv6 dns-server \
					help hostname port reset route settings status vpn zone" \
					-- "${cur}") )
				;;
		esac

		return 0
	fi

	local args="${words[@]:i+1}"
	case "${cmd}" in
		device)
			_network_device ${args}
			;;
		dhcpv[64])
			_network_dhcpd "${cmd/dhcpv/ipv}" ${args}
			;;
		dns-server)
			_network_dns_server ${args}
			;;
		port)
			_network_port ${args}
			;;
		route)
			_network_route ${args}
			;;
		settings)
			_network_settings ${args}
			;;
		start|stop|status)
			# start, stop and status optionally take a zone
			_network_complete_zones
			;;
		vpn)
			_network_vpn ${args}
			;;
		zone)
			_network_zone ${args}
			;;
	esac
} && complete -F _network network
