#!/usr/bin/python3
# Wrapper around cargo-tree that makes it recursively resolve all features of
# child dependencies. This is a hacky workaround for sfackler/cargo-tree#34 but
# it is incredibly slow.
#
# Ideally this whole cargo-tree logic should be part of debcargo.

import functools
import multiprocessing.pool
import os
import subprocess
import sys
import threading

scriptdir = os.path.dirname(__file__)
myname = os.path.basename(__file__)

def reverse_topo(it, f):
	seen = set()
	for item in reversed(it):
		k = f(item)
		if k not in seen:
			yield item
		seen.add(k)

@functools.lru_cache(maxsize=512)
def cargo_tree_args(*args):
	# non-dev dependencies, default feature set.
	# this is what we put in Build-Depends in Debian
	return subprocess.check_output([os.path.join(scriptdir, "cargo-tree-any")] +
		list(args) + ["--no-dev-dependencies", "--all-targets", "--prefix-depth", "-a"])

lock = threading.Lock()
want = 0
done = 0

def parse(output, stack):
	global want, done, lock
	to_recurse = []
	self = None
	with lock:
		for line in reverse_topo(output.splitlines(), lambda x: x.split(b" ", 1)[1]):
			i, pkg, ver = line.split(b" ", 2)
			i = int(i)
			pkg = pkg.decode("utf-8")
			ver = ver.decode("utf-8")
			if " " in ver:
				ver, _ = ver.split(" ", 1)
			if i == 1:
				self = (pkg, ver)
				done += 1
			elif i >= 2:
				# we recurse into each direct dependency D of $1 (depth == 2), to pick
				# up dependencies of the default feature of D. these are needed by the Debian
				# build for D, but cargo_tree_args($1) might not output them, e.g. if $1
				# depends on less-than-default features of D.
				#
				# OTOH $1 might depend on more-than-default features of D, which we would
				# omit if we only recurse() into the direct dependencies {all D}. therefore
				# we must also recurse into all transitive dependencies of $1 as output by
				# cargo_tree_args($1), which includes any more-than-default features of D
				# that are needed by $1.
				to_recurse.append((pkg, ver))
				want += 1
			else:
				continue
		sys.stderr.write("\033[K")
		stack += (self,)

		print("%s: %s/%s done, %s todo, |%s|%s" %
			(myname, done, want, want-done,
			 "|".join("%s:%s" % (d[0][0],d[1]) for d in stack[:-1]),
			 "%s:%s" % stack[-1]), end="\r", file=sys.stderr)

	if any(item in stack for item in to_recurse):
		raise ValueError("cyclic dependency, you can't package one of these: %s" % (stack + [(pkg, ver)]))
	return self, tuple((pkg, ver, stack) for pkg, ver in to_recurse)

def cargo_tree_pv(item):
	(pkg, ver, stack) = item
	output = cargo_tree_args("%s:%s" % (pkg, ver.lstrip("v")))
	return (pkg, ver), parse(output, stack)

def recurse(pool, self, to_recurse):
	items = []
	results = pool.imap_unordered(cargo_tree_pv, to_recurse)
	for item, output in results:
		items.append((item, recurse(pool, *output)))
	items.sort()
	return [self] + [o for i, out in items for o in out]

pool = multiprocessing.pool.ThreadPool()
want += 1
items = recurse(pool, *parse(cargo_tree_args(*sys.argv[1:]), ()))
sys.stderr.write("\033[K")
sys.stderr.flush()

for pkg, ver in reverse_topo(items, lambda x: x):
	print(pkg, ver)
