#!/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_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="create 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
}

_network_route() {
	local words=( $@ )

	local commands="add list remove"
	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_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)
			# TODO
			return 0
			;;
		destroy)
			_network_complete_zones
			;;
		*)
			local zone="${cmd}"
			local args="${words[@]:1}"
			_network_zone_subcommand "${zone}" ${args}
			;;
	esac
}

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

	local words=( $@ )

	local commands="config 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}
			;;
	esac
}

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

	local words=( $@ )

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

	case "${cmd}" in
		new)
			_network_complete_hooks "config"
			;;
	esac
}

_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 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
			;;
		zone)
			_network_zone ${args}
			;;
	esac
} && complete -F _network network
