#!/usr/pkg/bin/runawk

# Copyright (c) 2008-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.

############################################################

#env "LC_ALL=C"

#use "power_getopt.awk"
#use "alt_assert.awk"
#use "braceexpand.awk"
#use "psu_funcs.awk"
#use "glob.awk"
#use "pkgsrc-dewey.awk"
#use "xgetline.awk"

############################################################
#.begin-str help
# pkg_summary2deps - converts pkg_summary to the dependency graph
#   by analysing pkgname:../../pkg/path and 
#   {alter,native}:../../pkg/path entries
#   in DEPENDS and BUILD_DEPENDS fields.
# Format of the output:
#   package1
#   package2 package3
#   ...
# Here package3 depends on package2, package1 has no dependencies.
# usage: pkg_summary2deps [OPTIONS] [files...]
# OPTIONS:
#   -h    display this help
#   -p    output PKGPATH
#   -n    output PKGNAME
#   -d    analyse DEPENDS
#   -D    analyse BUILD_DEPENDS
#   -A    analyse both DEPENDS and BUILD_DEPENDS
#   -a    output all matched dependencies separated by `|' symbol,
#         not only the first one
#   -c    analyse CONFLICTS
#   -t    produce output compatible with tsort(1) command
#   -r    do not strip versions from PKGNAME, imply -n
#   -R    do not strip versions from dependencies, imply -n
#   -s    strict mode
#   -l    output to stderr the error messages
#         compatible with pkg_lint_summary, e.g.,
#         d: not_found foo-[0-9]*:../../devel/foo <- lang/bar bar
#   -2    separate DEPENDS and BUILD_DEPENDS and prepand output lines with
#         "DEPENDS" or "BUILD_DEPENDS"
#   =P <pkgpaths>
#         find dependencies for specified PKGPATHs.
#         If pkgpaths starts with `/' or `.', it is treated as a
#         filename that contains PKGPATHs, one per line. Otherwise
#         pkgpaths is a list of PKGPATHs separated by space or comma.
#   =N <pkgnames>
#         find dependencies for specified PKGNAMEs.
#         If pkgnames starts with `/' or `.', it is treated as a
#         filename that contains PKGNAMEs, one per line. Otherwise
#         pkgnames is a list of PKGNAMEs separated by space or comma.
#   -X    exit with non-zero exit status if missed
#         DEPENDS, BUILD_DEPENDS or CONFLICTS were found
#.end-str
############################################################

# comment
#   pkgpath    - PKGPATH=<...>
#   assigns    - ASSIGNMENTS=<...>
#   pkgbase    - PKGNAME=<...> with pkg version stripped
#   g_basecnt2path   - array: pkgbase, g_basecnt2path_cnt [base] -> PKGPATH:ASSIGNMENTS
#   g_pair2name   - array: PKGPATH:ASSIGNMENTS,PKGNAME -> pkgname
#   g_paircnt2namedeps    - array: PKGPATH:ASSIGNMENTS,PKGNAME , [1..3] , g_paircnt2namedeps_cnt [PKGPATH:ASSIGNMENTS. [1..3]] ->
#                e.g. suse32_base-10.0|suse32_base-10.0nb*
#   allbases   - set: PKGBASE

function warn_alt (pkgpath, pkgname, deps, deps_cnt,             i,res){
	res = ""
	for (i=1; i <= deps_cnt; ++i){
		if (res != "")
			res = res "|"
		res = res deps [i]
	}
	warn(pkgpath, pkgname, res)
}

function warn (pkgpath, pkgname, depname){
	if (mode_lint)
		printf "d: not_found %s <- %s %s\n",
			depname, pkgpath, pkgname > "/dev/stderr"
	else
		printf "Cannot find dependency %s for package %s (%s)\n",
			depname, pkgpath, pkgname > "/dev/stderr"
}

function warn_conflicts (\
	pkgpath, pkgname,
	conflict_path, conflict_name, conflict_spec)
{
	if (mode_lint)
		printf "c: conflict %s %s %s <- %s %s\n",
		conflict_spec, conflict_path, conflict_name, pkgpath, pkgname > "/dev/stderr"
	else
		printf "%s (%s) conflicts (%s) with %s (%s)\n",
			pkgpath, pkgname, conflict_spec, conflict_path, conflict_name > "/dev/stderr"
}

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

	keep_ver = getarg("r")
	keep_depver = getarg("R")

	out_pkgname = keep_ver || keep_depver || getarg("n")
	out_pkgpath = getarg("p")

	mode_tsort  = getarg("t")
	mode_strict = getarg("s")
	mode_lint   = getarg("l")

	dep_depends   = getarg("d")
	dep_builddep  = getarg("D")
	opt_conflicts = getarg("c")

	show_all_deps = getarg("a")

	opt_exit_status = getarg("X")

	diff_deps = !mode_tsort && getarg("2")

	all_packages = 1

	pkgs_PKGPATHs = getarg("P", "<fake>")
	if (pkgs_PKGPATHs ~ /^[.\/]/){
		all_packages = 0
		while (xgetline0(pkgs_PKGPATHs)){
			PKGPATHs [$1] = 0
		}
		close(pkgs_PKGPATHs)
	}else{
		gsub(/,/, " ", pkgs_PKGPATHs)
		if (pkgs_PKGPATHs != "<fake>"){
			all_packages = 0
			split(pkgs_PKGPATHs, arr)
			for (i in arr){
				PKGPATHs [arr [i]] = 0
			}
		}
	}

	pkgs_PKGNAMEs = getarg("N", "<fake>")
	if (pkgs_PKGNAMEs ~ /^[.\/]/){
		all_packages = 0
		while (xgetline0(pkgs_PKGNAMEs)){
			PKGNAMEs [$1] = 0
		}
		close(pkgs_PKGNAMEs)
	}else{
		gsub(/,/, " ", pkgs_PKGNAMEs)
		if (pkgs_PKGNAMEs != "<fake>"){
			all_packages = 0
			split(pkgs_PKGNAMEs, arr)
			for (i in arr){
				PKGNAMEs [arr [i]] = 0
			}
		}
	}

	if (getarg("A")){
		dep_builddep = dep_depends = 1
	}

	if (!opt_conflicts){
		if (!out_pkgname && !out_pkgpath){
			print "Either -p or -n must be applied" > "/dev/stderr"
			exit 1
		}
	}
	if (!dep_depends && !dep_builddep && !opt_conflicts){
		print "Either -d, -D, -c or -A must be applied" > "/dev/stderr"
		exit 1
	}
}

/^ASSIGNMENTS=/ {
	assigns = substr($0, 13)
	next
}

/^PKGPATH=/ {
	pkgpath = substr($0, 9)
	next
}

/^PKGNAME=/ {
	pkgbase = pkgname = substr($0, 9)
	sub(/-[^-]+$/, "", pkgbase)
	next
}

dep_depends && /^DEPENDS=/ {
	depends = depends " " substr($0, 9)
	next
}

dep_builddep && /^BUILD_DEPENDS=/ {
	build_depends = build_depends " " substr($0, 15)
	next
}

opt_conflicts && /^CONFLICTS=/ {
	conflicts = conflicts " " substr($0, 11)
	next
}

function process_deps (id,               deps,i){
	for (i=1; i <= NF; ++i){
		if ($i ~ /[{]/)
			$i = braceexpand($i)

		gsub(/:[^ ]*/, "", $i)
		gsub(/ /, "|", $i)

		g_paircnt2namedeps [pair, id, ++g_paircnt2namedeps_cnt [pair, id]] = $i
	}
}

NF == 0 {
	if (pkgpath == ""){
		pkgpath = "none"
	}

	assert(pkgname != "", "Cannot find pkgname")

	if (assigns)
		pkgpath = pkgpath ":" assigns

	pair = pkgpath ";" pkgname

	g_basecnt2path [pkgbase, ++g_basecnt2path_cnt [pkgbase]] = pair
	g_pair2name [pair] = pkgname
	allbases [pkgbase] = 1

	if (all_packages || (pkgpath in PKGPATHs) || (pkgname in PKGNAMEs)){
		$0 = depends
		process_deps(1)

		$0 = build_depends
		process_deps(2)

		$0 = conflicts
		process_deps(3)
	}

	conflicts = depends = build_depends = ""
	assigns = pkgpath = pkgbase = pkgname = ""
	next
}

function print_nodep_node (node){
	if (mode_tsort)
		print node, node
	else
		print node
}

function find_by_glob (pattern, ver,           base_i,i,pair){
	for (base_i in allbases){
		if (glob(base_i, pattern)){
			for (i=1; i <= g_basecnt2path_cnt [base_i]; ++i){
				pair = g_basecnt2path [base_i, i]
				if (!mode_strict ||
					pattern_match(pkgname2version(pair), ver))
				{
					found [++foundcnt] = pair
				}
			}
		}
	}
}

function find_pkg (pattern,           base,basecnt,ver,j,curr){
	base = pkgname2pkgbase(pattern)
	ver  = pkgname2version(pattern)

	if (base ~ /[*?\[\]]/){
		# glob pattern
		find_by_glob(base, ver)
	}else if (base in allbases){
		# plain PKGBASE
		basecnt = g_basecnt2path_cnt [base]
		for (j=1; j <= basecnt; ++j){
			curr = g_basecnt2path [base, j]
			if (!mode_strict || pkgmatch(curr, pattern))
				found [++foundcnt] = curr
		}
	}
}

function pkgmatch (pkg, pattern){
	if (pattern ~ /[<>].*[*?\[\]]/)
		gsub(/[*?]/, "0", pattern)

	pkg     = pkgname2version(pkg)
	pattern = pkgname2version(pattern)

	return pattern_match(pkg, pattern)
}

function pkgmatch_all (pair, matched_pkg, id,
					   depscnt,j,k,depname,deps,all_matched)
{
	depscnt = g_paircnt2namedeps_cnt [pair, id]
	for (j=1; j <= depscnt; ++j){
		deps = g_paircnt2namedeps [pair, id, j]
		if (deps ~ /[|]/)
			continue

		if (pkgname2pkgbase(matched_pkg)		\
			!= pkgname2pkgbase(deps))
			continue

		if (!pkgmatch(matched_pkg, deps))
			return 0
	}

	return 1
}

function name2base (pair){
	gsub(/-[^|-]*([|]|$)/, "|", pair)
	return substr(pair, 1, length(pair)-1)
}

function pair2path (pair){
	gsub(/;[^|]*/, "", pair)
	return pair
}

function pair2name (pair){
	gsub(/[^;|]*;/, "", pair)
	return pair
}

function pair2xxx (pair)
{
	if (pair == "")
		return ""

	if (!out_pkgname){
		return pair2path(pair)
	}else if (!out_pkgpath){
		pair  = pair2name(pair)

		if (!keep_ver)
			pair = name2base(pair)

		return pair
	}else{
		if (!keep_ver)
			pair = name2base(pair)

		return pair
	}
}

function print_pair (prepand, left, right, deps){
	left  = pair2xxx(left)

	# node
	if (right == ""){
		if (left in already_printed)
			return

		already_printed [left] = 1

		if (mode_tsort)
			print left, left
		else
			print left

		return
	}

	# edge
	right = pair2xxx(right)
	if ((left SUBSEP right SUBSEP prepand) in already_printed)
		return

	already_printed [left, right, prepand] = 1
	already_printed [left] = 1
	already_printed [right] = 1

	if (keep_depver)
		print prepand left, right, "(", final_deps [triple], ")"
	else
		print prepand left, right
}

END {
	# building dependency graph
	for (pair in g_pair2name){
		path = pair2path(pair)
		name = g_pair2name [pair]
		for (id=1; id <= 3; ++id){
			depscnt = g_paircnt2namedeps_cnt [pair, id]
			for (i=1; i <= depscnt; ++i){
				deps = g_paircnt2namedeps [pair, id, i]

				altdepscnt = split(deps, arr, /[|]/)
				for (k=1; k <= altdepscnt; ++k){
					depname = arr [k]
					if (depname ~ /[<>].*[*?\[\]]/)
						gsub(/[*?]/, "0", depname)

					foundcnt = 0
					find_pkg(depname)

					if (foundcnt == 1 && !mode_strict && id != 3){
						final_deps [id, pair, found [1]] = depname
						break
					}

					matched = ""
					for (j=1; j <= foundcnt; ++j){
						f = found [j]
						if (depname ~ /[<>].*(\[|\]|[*])/){
							continue
						}

						if (!mode_strict || pkgmatch_all(pair, g_pair2name [f], id)){
							if (id != 3){
								if (matched == ""){
									matched = f
									matched_name = g_pair2name [f]
								}else if (show_all_deps){
									matched = matched "|" f
								}else if (pkgname_gt_pkgname(g_pair2name [f],
															 matched_name))
								{
									matched = f
									matched_name = g_pair2name [f]
								}
							}else if (f != pair){
								warn_conflicts(path, name, pair2path(f),
											   g_pair2name [f], depname)
								ex = 1
							}
						}
					}

					delete found
					foundcnt = 0

					if (id == 3)
						continue

					if (!matched){
						if (k == altdepscnt){
							if (altdepscnt > 1)
								warn_alt(path, name, arr, altdepscnt)
							else
								warn(path, name, depname)

							ex = 1
						}

						continue
					}

					final_deps [id, pair, matched] = depname
					break
				}
			}
		}
	}

	# output PKGNAMESs/PKGPATHs
	prepand = ""
	for (triple in final_deps){
		split(triple, arr, SUBSEP)

		pair1 = arr [3]
		pair2 = arr [2]
		path1 = pair2path(pair1)
		path2 = pair2path(pair2)

		id = arr [1]
		if (diff_deps){
			if (id == 1) prepand = "DEPENDS "
			else if (id == 2) prepand = "BUILD_DEPENDS "
			else abort("bad id")
		}

		# right part
		right = pair2

		# left part
		path1_cnt = split(pair1, pair1_arr, /[|]/)
		left = ""
		for (i=1; i <= path1_cnt; ++i){
			p1 = pair1_arr [i]
			l = p1

			if (l != ""){
				already_printed [l] = 1
				if (left == "")
					left = l
				else
					left = left "|" l
			}
		}
		if (left != ""){
			print_pair(prepand, left, right, final_deps [triple])
		}

		already_printed [pair2] = 1
	}

	# printing packages with no dependencies
	if (dep_builddep || dep_depends){
		for (pair in g_pair2name){
			path = pair2path(pair)

			if (pair in already_printed)
				continue

			name = pair2name(pair)
			if (!all_packages && !(path in PKGPATHs) && !(name in PKGNAMEs))
				continue

			# main action
			if (pair != "")
				print_pair("", pair, "", "")
		}
	}

	# exiting
	if (opt_exit_status)
		exitnow(ex)
}
