#!/usr/pkg/bin/runawk

# Copyright (c) 2010-2015, Aleksey Cheusov <vle@gmx.net>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above
#       copyright notice, this list of conditions and the following
#       disclaimer in the documentation and/or other materials provided
#       with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#use "power_getopt.awk"
#use "xgetline.awk"
#use "xclose.awk"

#.begin-str help
# This program takes a dependency graph of packages and a list of
# individual packages and outputs subgraph consisting of packages that
# depend on listed packages directly or indirectly (the default).
#
# usage: pkg_subgraph_deps [OPTIONS] [files...]
# OPTIONS:
#    -h           display this screen
#    =f <file>    list of packages, one package per line
#    =p <pkgs>    list of packages separated by space character
#    -r           output dependencies
#    -x           exclude packages in the list
#    -v           invert condition
#    -n           output nodes, not edges
#    -t           produce output compatible with tsort(1) command
#    -1           output only direct dependent packages or dependencies
# Input graph is represented by list of edges and isolated nodes, e.g.
#   devel/gmake textproc/dict-client
#   devel/gmake textproc/dict-server
#   devel/libjudy
#   devel/libmaa textproc/dict-client
#   devel/libmaa textproc/dict-server
#   devel/libmaa wip/paexec
#   net/netcat
#
# Option -f or -p are mandatory.
#.end-str

BEGIN {
	if (getarg("h")){
		print_help()
		exitnow(0)
	}

	fn         = getarg("f")
	pkgs       = getarg("p")
	opt_deps   = getarg("r")
	opt_excl   = getarg("x")
	opt_invert = getarg("v")
	opt_nodes  = getarg("n")
	opt_tsort  = getarg("t")
	opt_1      = getarg("1")

	if (!fn && !pkgs){
		print "-f or -p are mandatory!" > "/dev/stderr"
		exitnow(1)
	}

	assert(!opt_1 || !opt_invert || !opt_excl, "pkg_subgraph_deps: -xv1 is not allowed!")

	if (pkgs != ""){
		split(pkgs, pkgs_arr)
		for (i in pkgs_arr)
			list [pkgs_arr [i]] = 1
	}else if (fn != ""){
		while (xgetline0(fn))
			list [$1] = 1

		xclose(fn)
	}
}

NF == 1 || (NF == 2 && $1 == $2) {
	nodes [$1] = 1
	next
}

NF == 3 {
	dep_type = $1
	$1 = $2
	$2 = $3
	NF = 2
}

NF == 2 {
	if (opt_deps){
		from = $2
		to   = $1
	}else{
		from = $1
		to   = $2
	}

	if (dep_type)
		edge_types [from, to] = edge_types [from, to] " " dep_type
	dep_type = ""

	if (edge_set [from, to]++)
		next

	edge [from, ++count [from]] = to

	nodes [from] = nodes [to] = 1

#	print "!", dep_type, from, to
	next
}

function rec (pkg,                    i,to){
	result [pkg] = 1

	if (! (pkg in count))
		return

	for (i=1; i <= count [pkg]; ++i){
		to = edge [pkg, i]

#		print "!", pkg, to
		if (opt_1){
			result_edge [pkg, to] = 0
			result [to] = 1
		}else{
			if (! (to in result))
				rec(to)
		}
	}
}

function do_print_edge (type, from, to,            v){
	if (opt_deps){
		# swap from and to
		v = to
		to = from
		from = v
	}

	if (type)
		print type, from, to
	else
		print from, to
}

function print_edge (from, to,               ok,arr,i){
#	print "?", from, to
	if (opt_1 && opt_invert){
		ok = !((from SUBSEP to) in result_edge)
	}else{
		ok = ((from in result) && (to in result))
		if (ok && opt_1){
			if (!opt_invert)
				ok = ((from SUBSEP to) in result_edge)
		}
	}

	if (ok){
#		print "ft:", from, to
		if (!((from SUBSEP to) in edge_types)){
			do_print_edge("", from, to)
		}else{
			split(edge_types [from, to], arr)
			for (i in arr){
				do_print_edge(arr [i], from, to)
			}
		}

		printed [from] = printed [to] = 1
	}
}

END {
	delete edge_set

	for (i in list){
		rec(i)
		result [i] = 1
	}
	if (opt_excl){
		for (i in list)
			delete result [i]
	}
	if (opt_invert){
		for (i in nodes){
			if (i in result)
				delete result [i]
			else
				result [i] = 1
		}
	}

	if (!opt_nodes){
		for (i in edge){
			from = substr(i, 1, index(i, SUBSEP)-1)

			print_edge(from, edge [i])
		}
	}

	for (i in nodes){
		if ((i in result) && !(i in printed)){
			if (opt_tsort)
				print i, i
			else
				print i
		}
	}
}
