#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <locale.h>
#include <sys/file.h>
#include <sys/ioctl.h>

#include "config.h"
#include "AVLTree.h"
#include "error.h"
#include "conffile.h"
#include "keepers.h"
#include "status.h"
#include "xargs.h"
#include "asktty.h"
#include "intl.h"

struct undo_record {
	symbol_t name;
	struct undo_record* prev;
};

static AVLTree *keeplist, *postpone;
static struct undo_record *undo_stack = NULL;

static struct option long_options[] = {
	{"help", 0, 0, 'h'},
	{"verbose", 0, 0, 'v'},
	{"version", 0, 0, 'V'},
	{"quiet", 0, 0, 'q'},
	{"force", 0, 0, 'f'},
	{"mark-only", 0, 0, 'm'},
	{"upgrade", 0, 0, 'u'},
	{"keeperfile", 1, 0, 'k'},
	{"no-keeperfile", 0, 0, 'n'},
	{"show-keepers", 0, 0, 'a'},
	{"show-orphans", 0, 0, 's'},
	{"show-depends", 1, 0, 'd'},
	{"show-dependents", 1, 0, 'e'},
	{"show-providers", 1, 0, 'p'},
	{"show-related", 1, 0, 'r'},
	{"use-tasks", 0, 0, 't'},
	{"option", 1, 0, 'o'},
	{"config", 1, 0, 'c'},
	{"ignore-default-rules", 0, 0, 'i'},
	{0, 0, 0, 0}
};

enum {
	EAllDepends,
	EOrphansOnly
};

enum {
	ENoAlternatives,
	ECommittedAlternatives,
	EAllAlternatives
};

/* Find out the number of columns supported by the tty.
 * For non-tty's the default is 80 columns.
 */
int columns(void) {
#ifdef TIOCGWINSZ
	struct winsize w;
	if (!ioctl(STDOUT_FILENO, TIOCGWINSZ, (char*)&w) && w.ws_col)
		return w.ws_col;
#endif
	return 80;
}

/* Print a message if Verbose mode is on. */
int Vprintf(char *fmt, ...) {
	va_list ap;
	int n = 0;

	if(Verbose) {
		va_start(ap, fmt);
		n = vprintf(fmt, ap);
		va_end(ap);
	}
	return n;
}


/* Print a title and a set of symbols nicely on tty.
 * If the set is empty, nothing is printed.
 */
int show_set(AVLTree *set, char *fmt, ...) {
	va_list ap;
	AVLNode *p;
	int width = columns() - 2, n = width;

	if(set->head) {
		va_start(ap, fmt);
		vprintf(fmt, ap);
		va_end(ap);

		for(p = set->head; p; p = p->next) {
			if(n + strlen((symbol_t)p->item) >= width)
				n = printf("\n ");
			n += printf(" %s", (symbol_t)p->item);
		}
		printf("\n");
		return 1;
	}
	return 0;
}

static void showinfo(symbol_t pkg) {
	AVLTree t;
	AVLNode n;
	AVLInitTree(&t, (AVLCompare)strcmp);
	AVLInitNode(&n, pkg);
	AVLInsertTopNode(&t, &n);
	xargs(&t, InfoCmd);
}

/* A two-key sort function for optimizing packet processing
 * order for the interactive engine.
 */
int pkgsortcmp(struct package *a, struct package *b) {
	int k = b->orphan_depends - a->orphan_depends;
	return k ? k : strcasecmp(a->name, b->name);
}

/* Add a package and all its dependents to the keep list. */
void add_keeplist(struct package *pkg) {
        AVLNode *p;

	if(!AVLInsert(keeplist, pkg->name))
		return;

	/* Add all direct depends on keeper list */
	for(p = pkg->depends.head; p; p = p->next)
		add_keeplist(pkg_find(p->item));

	/* If this packge is a meta package and there is exactly one
	 * installed package providing it, add the provider to the
	 * keeper list. If there are multiple providers of the meta
	 * package, the user will have to answer some questions.
	 * In Force-mode we add all providers, since we don't know
	 * which one(s) the user would like to keep.
	 */
	if(!pkg->installed && (Force || pkg->provider_count == 1))
		for(p = pkg->providers.head; p; p = p->next)
			add_keeplist(pkg_find(p->item));
}

/* Add all packages on the keepers and postpone lists, including their
 * dependencies, to the keeplist.
 */
void build_keeplist(void) {
	AVLNode *c;
	struct package *pkg;

	AVLFreeNodes(keeplist);
	for(c = packages->head; c; c = c->next) {
		pkg = (struct package *)c->item;
		if(pkg->keep || AVLSearch(keepers, pkg->name) || AVLSearch(postpone, pkg->name))
			add_keeplist(pkg);
	}
}

/* Remove packages from keeper list if they are no longer installed. */
void prune_keepers(void) {
	AVLNode *c, *p;
	struct package *pkg;

	for(c = keepers->head; c; c = p) {
		p = c->next;
		pkg = pkg_find(c->item);
		if(!pkg || !(pkg->installed || pkg->task)) {
			Vprintf(_("Package was removed: %s\n"), (symbol_t)c->item);
			AVLDeleteNode(keepers, c);
		}
	}
}

/* Do a graph search for all dependencies of package <name>. The
 * <visited> list has a dual purpose. It serves as a loop detection
 * mechanism in the graph search. In the end it contains the result
 * of the search. Initially <visited> should be empty.
 *
 *  <include_providers> indicates how to deal with meta packages:
 *    ENoAlternatives           Don't include providers of meta packages
 *                              as dependencies.
 *    ECommittedAlternatives    If there is a single package that provides
 *                              a meta package, include it as a dependency.
 *    EAllAlternatives          Include all installed providers of a meta
 *                              package as dependencies.
 *
 *  <orphans_only> indicates whether to include all dependecies or only
 *  those that are potentially orphans:
 *    EAllDepends               Include all dependencies.
 *    EOrphansOnly              Include only potential orphans.
 *
 */
int all_depends(symbol_t name, AVLTree *visited, int include_providers, int orphans_only) {
	struct package *pkg;
	AVLNode *p;
	int count;

	if((orphans_only && AVLSearch(keeplist, name)) || !AVLInsert(visited, name))
		return 0;

	pkg = pkg_find(name);
	if(!pkg)
		return 0;
	count = pkg->installed ? 1 : 0;

	for(p = pkg->depends.head; p; p = p->next)
		count += all_depends(p->item, visited, include_providers, orphans_only);

	if(include_providers && (include_providers == EAllAlternatives
	|| pkg->provider_count == 1))
		for(p = pkg->providers.head; p; p = p->next)
			count += all_depends(p->item, visited, include_providers, orphans_only);

	return count;
}

/* Do a graph search for all dependents of package <name>. The
 * <visited> list has a dual purpose. It serves as a loop detection
 * mechanism in the graph search. In the end it contains the result
 * of the search. Initially <visited> should be empty.
 *
 *  <include_provides> indicates how to deal with meta packages:
 *    ENoAlternatives           Don't include meta packages as dependents.
 *    ECommittedAlternatives    If the package is the only one that provides
 *                              a meta package, include the meta package as
 *                              a dependent.
 *    EAllAlternatives          Always include meta packages as dependents
 *                              regardles of how many other packages provide
 *                              them.
 *
 */
void all_dependents(symbol_t name, AVLTree *visited, int include_provides) {
	AVLNode *p;
	struct package *pkg;
	if(AVLInsert(visited, name)) {
		pkg = pkg_find(name);
		for(p = pkg->dependents.head; p; p = p->next)
			all_dependents(p->item, visited, include_provides);
		if(include_provides)
			for(p = pkg->provides.head; p; p = p->next)
				if(include_provides == EAllAlternatives
				|| pkg_find(p->item)->provider_count == 1)
					all_dependents(p->item, visited, include_provides);
	}
}

/* add_provider(pkg, pkg) adds package <pkg> to the providers lists of
 * all meta packages that it provides. The routine traverses the graph
 * of provides links and adds the package to each node.
 *
 * We collapse any provider chains so that the providers list of
 * package X is guaranteed to contain only installed packages that
 * really provide X.  This simplifies the dependency handling for
 * alternative packages.
 *
 * We also detect problems such as the following:
 *
 *     A     Installed packages B and C depend on each other and both
 *    / \    claim to provide A. A now appears to have two alternative
 *   B===C   dependencies, whereas in truth both B and C are required.
 *
 * We break the above into one of the following (non-deterministic):
 *
 *     A       A
 *    /         \
 *   B===C   B===C
 *
 */
void add_provider(struct package *pkg, struct package *ppkg) {
        AVLNode *c, *p;
	AVLTree depends, dependents;

	if(AVLSearch(&ppkg->providers, pkg->name))
		return;

	if(ppkg->provider_count) {
		/* Remove multiple alternative dependencies to a dependency loop */
		AVLInitTree(&depends, (AVLCompare)symcmp);
		AVLInitTree(&dependents, (AVLCompare)symcmp);
		all_depends(pkg->name, &depends, ENoAlternatives, EAllDepends);
		all_dependents(pkg->name, &dependents, ENoAlternatives);
		for(c = ppkg->providers.head; c; c = p) {
			p = c->next;
			if(AVLSearch(&depends, c->item)) {
				/* pkg depends on something already on provider list. Don't add pkg. */
			        pkg = NULL;
				break;
			}
			if(AVLSearch(&dependents, c->item)) {
				/* Something on the provider list depends on pkg. Remove it. */
				AVLDeleteNode(&ppkg->providers, c);
				ppkg->provider_count--;
			}
		}
	}

	if (pkg) {
		AVLInsert(&ppkg->providers, pkg->name);
		ppkg->provider_count++;

		for(c = ppkg->provides.head; c; c = c->next)
	        	add_provider(pkg, pkg_find(c->item));
	}
}

/* remove_provider(pkg, pkg) removes package <pkg> from the providers
 * lists of all meta packages that it provides. The routine traverses
 * the graph of "provides" links and removes the package from each
 * node.
 *
 * This routine gets called when debfoster has decided to remove a
 * particular package. If, as a result of removing the package, any
 * other package X becomes the last remaining alternative that
 * provides a dependency Y that is on keeplist, X and all its
 * dependencies will also be added to the keeplist. This locks them as
 * required dependencies.
 */
void remove_provider(struct package *pkg, struct package *ppkg) {
        AVLNode *p;

	if(!(p = AVLSearch(&ppkg->providers, pkg->name)))
		return;

	AVLDeleteNode(&ppkg->providers, p);
	ppkg->provider_count--;

	if(!ppkg->installed && ppkg->provider_count == 1
	&& AVLSearch(keeplist, ppkg->name))
		add_keeplist(pkg_find(ppkg->providers.head->item));

	for(p = ppkg->provides.head; p; p = p->next)
	        remove_provider(pkg, pkg_find(p->item));
}

/* Initialize list of installed packages. Reads the dpkg status file
 * and builds the dependents and providers lists for each package.
 */
void init_packages(int ignore_defaults) {
	AVLNode *c, *p;
	struct package *pkg, *ppkg;

	/* Read dpkg status */
	readstatus();

	/* build provider and dependent lists for each package */
	for(c = packages->head; c; c = c->next) {
		pkg = (struct package *)c->item;
		if(pkg->installed) {
			if(ignore_defaults) { /* clear keep and nokeep hints */
				pkg->keep = 0;
				pkg->nokeep = 0;
			}
			add_provider(pkg, pkg);
			for(p = pkg->depends.head; p; p = p->next) {
				ppkg = pkg_find(p->item);
				AVLInsert(&ppkg->dependents, pkg->name);
			}
		}
	}
}

/* Count the number of orphans that would be committed to keeplist if
 * <pkg> were to be marked as a keeper.
 */
void count_orphan_depends(struct package *pkg) {
	AVLTree visited;
	AVLInitTree(&visited, (AVLCompare)symcmp);
	pkg->orphan_depends = all_depends(pkg->name, &visited, ECommittedAlternatives, EOrphansOnly);
}

/* --show-providers option */
void show_providers(symbol_t name) {
	struct package *pkg;

	if(!(pkg = pkg_find(name))) {
		printf(_("Not a dependency: \"%s\"\n"), name);
		return;
	}

	if(pkg->installed)
		printf(_("%s is an installed package.\n"), pkg->name);
	else if(pkg->providers.head)
		show_set(&pkg->providers, _("Dependency %s is met by:"), name);
	else
		printf(_("Dependency %s is not met by any package.\n"), pkg->name);
}

/* --show-depends option */
void show_depends(symbol_t name) {
	struct package *pkg;
	AVLTree visited;
	AVLNode *c, *p;

	if(!(pkg = pkg_find(name)) || !(pkg->installed || pkg->task)) {
		printf(_("Not an installed package: \"%s\"\n"), name);
		return;
	}

	AVLInitTree(&visited, (AVLCompare)strcasecmp);
	all_depends(name, &visited, ECommittedAlternatives, EAllDepends);

	for(c = visited.head; c; c = p) {
		p = c->next;
		if(c->item == name || !pkg_find(c->item)->installed)
			AVLDeleteNode(&visited, c);
	}
	if(!show_set(&visited, _("Package %s depends on:"), name))
		printf(_("Package %s has no depends.\n"), name);
}

/* --show-dependents option */
void show_dependents(symbol_t name) {
	struct package *pkg;
	AVLTree dependents;
	AVLNode *c, *p;
	int others = 0, count = 0;

	if(!(pkg = pkg_find(name))) {
		printf(_("Not an installed package: \"%s\"\n"), name);
		return;
	}

	AVLInitTree(&dependents, (AVLCompare)symcmp);
	all_dependents(name, &dependents, ECommittedAlternatives);

	for(c = dependents.head; c; c = p) {
		p = c->next;
		if (!AVLSearch(keepers, c->item)) {
			others |= pkg_find(c->item)->keep;
			AVLDeleteNode(&dependents, c);
		} else
			count++;
	}
	if (!show_set(&dependents, _("The following %d packages on keeper list rely on %s:"), count, name))
		printf(_("Packages on keeper list do not rely on %s.\n"), name);
	printf(_("Packages kept by default rules %s %s.\n"), others ? _("rely on") : _("do not rely on"), name);
}

/* Find a set of orphan packages that are dependencies of orphan
 * package <name> and not dependencies of any other package that is
 * not also a dependency of <name>. The set consists with a high
 * probability of packages that are closely related together and can
 * be removed as a group if the user so wishes.
 */
int related_set(symbol_t name, AVLTree *set) {
	AVLTree ancestors, eliminate;
	AVLNode *c, *p;
	struct package *pkg, *ppkg;
	int count = 0;

	AVLInitTree(&ancestors, (AVLCompare)symcmp);
	AVLInitTree(&eliminate, (AVLCompare)symcmp);

	all_depends(name, set, ECommittedAlternatives, EOrphansOnly);
	AVLInsert(&ancestors, name);
	for(c = set->head; c; c = c->next)
		all_dependents(c->item, &ancestors, ECommittedAlternatives);
	AVLDelete(&ancestors, name);

	for(c = ancestors.head; c; c = c->next) {
		if(!AVLSearch(set, c->item) && !AVLSearch(nokeepers, c->item)) {
			ppkg = pkg_find(c->item);
			if(ppkg->installed && !ppkg->nokeep)
				all_depends(c->item, &eliminate, ECommittedAlternatives, EOrphansOnly);
		}
	}

	for(c = set->head; c; c = p) {
		p = c->next;
		if(c->item == name
		|| AVLSearch(&eliminate, c->item)
		|| AVLSearch(nokeepers, c->item)
		|| (pkg = pkg_find(c->item),
			!pkg->installed || pkg->keep || pkg->nokeep))
			AVLDeleteNode(set, c);
		else
			count++;
	}

	return count;
}

/* --show-related option */
void show_related(symbol_t name) {
	struct package *pkg;
	AVLTree related;
	int count;

	if(!(pkg = pkg_find(name)) || !(pkg->installed || pkg->task)) {
		printf(_("Not an installed package: \"%s\"\n"), name);
		return;
	}

	AVLInitTree(&related, (AVLCompare)strcasecmp);
	count = related_set(name, &related);
	if(!show_set(&related, _("The following %d packages are brought in by %s:"), count, name))
		printf(_("No packages are brought in by %s.\n"), name);
}

/* Push a symbol onto undo stack */
void push_undo(symbol_t name) {
	struct undo_record *undo = xmalloc(sizeof(struct undo_record));
	undo->name = name;
	undo->prev = undo_stack;
	undo_stack = undo;
}

/* Pop a symbol from the undo stack. Returns zero if stack is empty. */
symbol_t pop_undo(void) {
	struct undo_record *undo = undo_stack;
	if(undo) {
		symbol_t name = undo->name;
		undo_stack = undo->prev;
		return name;
	}
	return 0;
}

int main(int argc, char **argv) {
	AVLTree *installs, *removes, *orphans, *related;
	AVLNode *c, *p;
	struct package *pkg, *ppkg;
	int i, option_index, ignore_defaults = 0, show_option = 0, nokeeperfile = 0, upgrade = 0, mark_only = getuid();
	int related_count;
	symbol_t show_param = 0, base, undo_entry;

	setlocale (LC_ALL, "");
	bindtextdomain (PACKAGE, LOCALEDIR);
	textdomain (PACKAGE);

	/* global trees */
	packages  = AVLAllocTree((AVLCompare)pkgcmp);
	keepers   = AVLAllocTree((AVLCompare)strcasecmp);
	nokeepers = AVLAllocTree((AVLCompare)strcasecmp);

	/* local trees */
	postpone  = AVLAllocTree((AVLCompare)strcasecmp);
	keeplist  = AVLAllocTree((AVLCompare)symcmp);
	installs  = AVLAllocTree((AVLCompare)strcasecmp);
	removes   = AVLAllocTree((AVLCompare)strcasecmp);
	orphans   = AVLAllocTree((AVLCompare)pkgsortcmp);
	related   = AVLAllocTree((AVLCompare)strcasecmp);

	readconfig(NULL);

	while((i = getopt_long(argc, argv, "vVhqfmuc:k:niasd:e:p:r:to:", long_options, &option_index)) != EOF) {
		option_t opt;
		char *s;
		switch(i) {
			case '?':
			case 'h':
				printf(_("Usage: %s [-ck FILE] [-adefhinopqrsvV] package1 package2-\n"), argv[0]);
				printf(_("Installs package1, deinstalls package2\n\n"));
				printf(_("-v, --verbose                  Be a loudmouth\n"));
				printf(_("-V, --version                  Show version and copyright information\n"));
				printf(_("-h, --help                     Show this message\n"));
				printf(_("-q, --quiet                    Silently build keeper file\n"));
				printf(_("-f, --force                    Force system to conform to keeper file\n"));
				printf(_("-m, --mark-only                Do not install or delete packages\n"));
				printf(_("-u, --upgrade                  Try to upgrade dependencies\n"));
				printf(_("-c, --config FILE              Specify configuration file\n"));
				printf(_("-k, --keeperfile FILE          Specify keeper file\n"));
				printf(_("-n, --no-keeperfile            Don't read keeper file\n"));
				printf(_("-i, --ignore-default-rules     Ignore default rules\n"));
				printf(_("-a, --show-keepers             Show packages on keeper list\n"));
				printf(_("-s, --show-orphans             Show orphaned packages\n"));
				printf(_("-d, --show-depends PACKAGE     Show all depends of PACKAGE\n"));
				printf(_("-e, --show-dependents PACKAGE  Show dependents of PACKAGE\n"));
				printf(_("-p, --show-providers PACKAGE   Show packages providing PACKAGE\n"));
				printf(_("-r, --show-related PACKAGE     Show packages brought in by PACKAGE\n"));
				printf(_("-t, --use-tasks                Make tasks visible as packages\n"));
				printf(_("-o, --option OPT=VAL           Override any configuration option\n"));
				printf(_("\nSee also: debfoster(8)\n"));
			case 'V':
				printf(_("debfoster %s -- Copyright (C) 2000,2001 Wessel Dankers.\n"), VERSION);
                                printf(_("Distributed under the GNU General Public License.\n"));
				exit(i=='?');
			case 'v':
				Verbose = !Verbose;
			break;
			case 'q':
				Quiet = !Quiet;
			break;
			case 'f':
				Force = !Force;
			break;
			case 'm':
				mark_only = 1;
			break;
			case 'u':
				upgrade = 1;
			break;
			case 'k':
				find_option("KeeperFile")->set(optarg);
			break;
			case 'c':
				readconfig(optarg); /* Overrides any previous config options */
			break;
			case 'i':
				ignore_defaults = 1; /* Ignore keep and nokeep hints in packages */
			break;
			case 'n':
				nokeeperfile = 1; /* Start clean, don't read keeper file */
			break;
			case 'o':
				s = strchr(optarg, '=');
				if(s > optarg) {
					*s++ = '\0';
					if((opt = find_option(optarg))) {
						opt->set(s);
						break;
					}
				}
				error_exit(ERROR_USER, _("%s: Invalid config option -- \"%s\"\n"), argv[0], optarg);
			break;
			case 'd':
			case 'e':
			case 'p':
			case 'r':
				show_param = symbol(optarg);
			case 'a':
			case 's':
				show_option = i;
				mark_only = 1;
			break;
			case 't':
				UseTasks = !UseTasks;
			break;
		}
	}

	openkeepers(show_option && argc == optind); /* Readonly, if --show-xxx option and no extra args */
	if(!nokeeperfile)
		readkeepers();

	for(i=optind; i<argc; i++) {
		char *e;
		int add;
		symbol_t name;

		if(*argv[i]) {
			e = strchr(argv[i], '\0');
			if(*--e == '-') {
				*e = '\0';
				add = 0;
			} else {
				add = 1;
			}
			if(*argv[i]) {
				name = symbol(argv[i]);
				if (add) {
					AVLInsert(keepers, name);
					AVLInsert(installs, name);
					AVLDelete(nokeepers, name);
					AVLDelete(removes, name);
				} else {
					AVLDelete(keepers, name);
					AVLDelete(installs, name);
					AVLInsert(nokeepers, name);
					AVLInsert(removes, name);
				}
				continue;
			}
		}
		error_exit(ERROR_USER, _("Not a package: \"%s\"\n"), argv[i]);
	}

	/* Sync keeper file */
	if(optind < argc)
		writekeepers();

	/* Force option will attempt to make the system conform to the keepers file.
	 * This may include installing new packages as well as removing packages.
	 */
	if(Force) {
		for(c = keepers->head; c; c = c->next)
			AVLInsert(installs, c->item);
	}

	tty_init();

	init_packages(ignore_defaults);

	/* Print information */
	switch(show_option) {
	case 'a': /* --show-keepers option */
		if(!show_set(keepers, _("The following packages are on keeper list:")))
			printf(_("The keeper list is empty.\n"));
		show_set(nokeepers, _("The following packages are marked as non-keepers:"));
		return 0;
	case 'd': /* --show-depends option */
		show_depends(show_param);
		return 0;
	case 'e': /* --show-dependents option */
		show_dependents(show_param);
		return 0;
	case 'p': /* --show-providers option */
		show_providers(show_param);
		return 0;
	case 'r': /* --show-related option */
		AVLDelete(keepers, show_param);
		build_keeplist();
		show_related(show_param);
		return 0;
	}

	if(installs->head && !mark_only) {
		/* Prune install list */
		for(c = installs->head; c; c = p) {
			p = c->next;
			pkg = pkg_find(c->item);
			if(pkg && pkg->installed) {
				if(!upgrade)
					AVLDeleteNode(installs, c);
			} else 
				Vprintf(_("Installing package: %s\n"), (symbol_t)c->item);
		}

		/* Add all dependencies to install list (--upgrade) */
		if(upgrade) {
			AVLTree install_depends;
			AVLInitTree(&install_depends, (AVLCompare)symcmp);
			for(c = installs->head; c; c = c->next)
				if(pkg_find(c->item))
					all_depends(c->item, &install_depends, ECommittedAlternatives, EAllDepends);
			while((c = install_depends.head)) {
				if(pkg_find(c->item)->installed)
					AVLInsert(installs, c->item);
				AVLDeleteNode(&install_depends, c);
			}
		}

		/* Install packages  */
		if(installs->head) {
			/* Save a list of packages installed prior to invoking InstallCmd */
			for(c = packages->head; c; c = c->next) {
				pkg = (struct package *)c->item;
				if(pkg->installed)
					AVLInsert(keeplist, pkg->name);
			}

			i = xargs(installs, InstallCmd);
			if(i) return i;

			/* Read dpkg status */
			init_packages(ignore_defaults);

			/* find out which packages actually were installed */
			AVLFreeNodes(installs);
			for(c = packages->head; c; c = c->next) {
				pkg = (struct package *)c->item;
				if(!AVLSearch(keeplist, pkg->name)) {
					AVLInsert(installs, pkg->name);
					AVLDelete(removes, pkg->name);
				}
			}
		}
	}

	if(!mark_only)
		prune_keepers();

	build_keeplist();

	for(;;) {
		/* Build a list of all packages that are not anchored either
		 * by the default rules or the keeper list and sort it so that
		 * the package with the most orphans dangling from it comes
		 * up first.
		 */
		AVLFreeNodes(orphans);
		for(c = packages->head; c; c = c->next) {
			pkg = (struct package *)c->item;
			if ((pkg->installed || pkg->task) && !AVLSearch(keeplist, pkg->name)) {
				count_orphan_depends(pkg);
				AVLInsert(orphans, pkg);
			}
		}

		i = 0;
		while((c = orphans->head)) {
	        	pkg = (struct package *)c->item;

			/* Has this one found a home? */
			if(AVLSearch(keeplist, pkg->name)) {
				AVLDeleteNode(orphans, c);
				continue;
			}

			/* Re-shuffle the pack for optimal ordering. */
			AVLUnlinkNode(orphans, c);
			AVLInitNode(c, pkg);

			/* Handle GuessDepends here. Guesses don't go into keeper file. */
			if(!ignore_defaults && (base = guessbase(pkg->name)) &&
					(ppkg = pkg_find(base)) && ppkg->installed) {
				if(AVLSearch(keeplist, ppkg->name)) {
					add_keeplist(pkg);
					continue;
				}
				if(AVLSearch(removes, ppkg->name)) {
					/* Base package was deleted. Set nokeep hint. */
					pkg->nokeep = 1;
				} else {
					/* Don't know yet. Reposition after the base package. */
					pkg->orphan_depends = ppkg->orphan_depends;
				}
			} else
				count_orphan_depends(pkg); /* Count may change due to user choises */

			AVLInsertNode(orphans, c);
			if(orphans->head != c)
				continue;

			/* Re-shuffle complete. We're still go. */
			AVLDeleteNode(orphans, c);

			/* If the package was just installed by debfoster it's a keeper.
			 * This catches packages that were installed by apt because the user
			 * gave a regular expression on the command line.
			 */
			if(AVLSearch(installs, pkg->name)) {
				AVLInsert(keepers, pkg->name);
				add_keeplist(pkg);
				AVLDelete(nokeepers, pkg->name);
				continue;
			}

			if(Force || pkg->nokeep || AVLSearch(nokeepers, pkg->name)) {
				AVLInsert(removes, pkg->name);
				remove_provider(pkg, pkg);
				continue;
			}

			/* Ignore a task if it doesn't bring at least two packages in */
			if(pkg->task && !pkg->installed) {
				if(related_set(pkg->name, related) < 2)
					continue;
				AVLFreeNodes(related);
			}

			if(Quiet) {
				AVLInsert(keepers, pkg->name);
				add_keeplist(pkg);
				Vprintf(_("Keeping package: %s\n"), pkg->name);
				continue;
			}

			if(show_option == 's') {  /* Treat --show-orphans option as skip */
				AVLInsert(postpone, pkg->name);
				add_keeplist(pkg);
				continue;
			}

			related_count = related_set(pkg->name, related);
			show_set(related, _("\n%s is keeping the following %d packages installed:"), pkg->name, related_count);

			do {
				switch((i = tty_ask(_("ynpshiuqx?"), _("Keep %s? [Ynpsiuqx?], [H]elp: "), pkg->name))) {
					case 4: /* h */
                                                printf("\n");
						printf(_("  Yes        Keep %s. [default]\n"), pkg->name);
						printf(_("  No         Delete %s.\n"), pkg->name);
						printf(_("  Prune      Delete %s and the packages it is keeping installed.\n"), pkg->name);
						printf(_("  Skip       Skip this question.\n"));
						printf(_("  Help       Print this message.\n"));
						printf(_("  Info or ?  Show information about %s.\n"), pkg->name);
						printf(_("  Undo       Undo last response.\n"));
						printf(_("  Quit       Exit without removing packages.\n"));
						printf(_("  Exit       Remove unwanted packages and exit.\n"));
                                                printf("\n");
                                                i = -1;
					break;
					case 9: /* ? */
                                        case 5: /* i */
						if(pkg->installed)
							showinfo(pkg->name);
						else if(pkg->task) {
							/* Tasksel has no command line option for printing task info
							 * so we just show the packages that belong to it.
							 */
							show_set(&pkg->depends,
								_("This is a task containing the following installed packages:"));
						}
                                                i = -1;
					break;
					case 2: /* p */
						for(c = related->head; c; c = c->next)
							AVLInsert(nokeepers, c->item);
					case 1: /* n */
						AVLInsert(nokeepers, pkg->name);
						AVLInsert(removes, pkg->name);
						remove_provider(pkg, pkg);
					break;
					case 0: /* y */
						AVLInsert(keepers, pkg->name);
						add_keeplist(pkg);
					break;
					case 3: /* s */
						AVLInsert(postpone, pkg->name);
						add_keeplist(pkg);
					break;
					case 6: /* u */
						if(!undo_stack) {
							printf(_("\aNothing to undo.\n"));
							i = -1;
							break;
						} 
						while((undo_entry = pop_undo())
						&& undo_entry != symbol("MARK")) {
							AVLDelete(keepers, undo_entry);
							AVLDelete(nokeepers, undo_entry);
							AVLDelete(removes, undo_entry);
							AVLDelete(postpone, undo_entry);
						}
						AVLFreeNodes(orphans);
					break;
					case 7: /* q */
						closekeepers();
						return 0;
					case 8: /* x */
						AVLFreeNodes(orphans);
					break;
				}
			} while(i < 0);

			if(i < 4) { /* Yes, No, Purge or Skip */
				push_undo(symbol("MARK"));
				push_undo(pkg->name);
				if(i == 2) { /* Purge */
					for(c = related->head; c; c = c->next)
						push_undo(c->item);
				}
			}
			AVLFreeNodes(related);
		}

		if(show_option == 's') { /* --show-orphans option */
			if(!show_set(postpone, _("The following packages have been orphaned:")))
				printf(_("There are no orphaned packages.\n"));
			return 0;
		}

		/* Undo? Start over. */
		if(i == 6) { /* Undo */
			init_packages(ignore_defaults);
			build_keeplist();
			continue;
		}

		/* Save keeper file */
		writekeepers();

		if(!mark_only) {
			for(c = removes->head; c; c = p) {
				p = c->next;
				pkg = pkg_find(c->item);
				if (!pkg || !pkg->installed) {
					if(!pkg || !pkg->task)
						Vprintf(_("Package is not installed: %s\n"), (symbol_t)c->item);
					AVLDeleteNode(removes, c);
				} else if(AVLSearch(keeplist, pkg->name)) {
					Vprintf(_("Package is a dependency: %s\n"), pkg->name);
					AVLDeleteNode(removes, c);
				} else
					Vprintf(_("Removing package: %s\n"), pkg->name);
			}
			/* Remove packages */
			if(removes->head) {
				i = xargs(removes, RemoveCmd);
				if(i) return i;
				AVLFreeNodes(removes);
			}
		}

		break; /* done! */
	}

	closekeepers();

	return 0;
}
