/* $Id: depends.c,v 1.44 2009/05/07 21:22:52 imil Exp $ */

/*
 * Copyright (c) 2009 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Emile "iMil" Heitor <imil@NetBSD.org> .
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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 AUTHOR 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 AUTHOR 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.
 *
 */

#include "pkg_dry.h"

/* order: <=1.0 >=2.0 ==1.2 -2.10
 *        ^     ^     ^     ^
 */

static const char *extchars = "<>=-";

static Plisthead *plisthead = NULL;

void
free_deptree(Deptreehead *deptreehead)
{
	Pkgdeptree *pdp;

	if (deptreehead == NULL)
		return;
	
	while (!SLIST_EMPTY(deptreehead)) {
		pdp = SLIST_FIRST(deptreehead);
		SLIST_REMOVE_HEAD(deptreehead, next);
		XFREE(pdp->depname);
		XFREE(pdp);
	}
}

/* match dependency extension following this order: <>=- */
char *
match_dep_ext(char *depname, const char *ext)
{
	char *pext, *p, *pdep = NULL;

	XSTRDUP(pext, ext);
	p = pext;

	for (; *pext != 0; pext++)
		if ((pdep = strrchr(depname, *pext)) != NULL)
			break;

	XFREE(p);
	return pdep;
}

/* basic full package format detection */
int
exact_pkgfmt(const char *pkgname)
{
	char	*p;

	if ((p = strrchr(pkgname, '-')) == NULL)
		return 0;

	p++;

	/* naive assumption, will fail with foo-100bar, hopefully, there's
	 * only a few packages needing to be fully specified
	 */
	if (!isdigit((int)*p))
		return 0;

	return 1;
}

/* end an expression with a delimiter */
char *
end_expr(char *str)
{
	Pkglist	*pkglist;
	char	*expr, *p;
	
	/* enough space to add a delimiter */
	XMALLOC(expr, (strlen(str) + 2) * sizeof(char));
	XSTRCPY(expr, str);

	/* really don't want to fight with {foo,bar>=2.0}, rely on pkg_match()
	 * XXX: this makes us load pkg_summary db in full_dep_tree()
	 */
	if (strpbrk(expr, "*?[{") != NULL) {
		SLIST_FOREACH(pkglist, plisthead, next) {
			if (pkg_match(expr, pkglist->pkgname)) {
				XFREE(expr);
				XSTRDUP(expr, pkglist->pkgname);
				break;
			}
		}
	}
	
	/* simple package/dependency  case, parse string backwards
	 *  until we find a delimiter: foo>=1.0
	 */
	if ((p = match_dep_ext(expr, extchars)) != NULL) {
		*p++ = DELIMITER; /* terminate expr */
		*p = '\0';		
	}

	return expr;
}

/* sqlite callback
 * DIRECT_DEPS or REVERSE_DEPS result, feeds a Pkgdeptree SLIST
 * Deptreehead is the head of Pkgdeptree
 */
static int
ddb_rec_direct_deps(void *param, int argc, char **argv, char **colname)
{
	int			argvlen, matchlen;
	char		*depname, *pkgname, *p;
	Pkgdeptree	*deptree, *pdp;
	Deptreehead	*pdphead = (Deptreehead *)param;

	if (argv != NULL) {

		depname = end_expr(argv[0]); /* foo| */

		argvlen = strlen(depname);
		/* dependency already recorded, do not insert on list  */
		SLIST_FOREACH(pdp, pdphead, next) {

			matchlen = strlen(pdp->matchname) + 2; /* +2 = DELIMITER + \0 */
			XMALLOC(pkgname, matchlen * sizeof(char)); 
			snprintf(pkgname, matchlen, "%s%c", pdp->matchname, DELIMITER);

			if (strncmp(pkgname, depname, argvlen) == 0) {

				XFREE(pkgname);
				XFREE(depname);

				/* proceed to next result */
				return DDB_OK;
			}
			XFREE(pkgname);
		}

		if ((p = strrchr(depname, DELIMITER)) != NULL)
			*p = '\0';

		XMALLOC(deptree, sizeof(Pkgdeptree));
		XSTRDUP(deptree->depname, argv[0]);
		XSTRDUP(deptree->matchname, depname);
		deptree->computed = 0;
		deptree->level = 0;
		/* used in LOCAL_REVERSE_DEPS / autoremove.c */
		if (argc > 1 && argv[1] != NULL)
			deptree->pkgkeep = 1;
		else
			deptree->pkgkeep = 0;
		
		SLIST_INSERT_HEAD(pdphead, deptree, next);

		XFREE(depname);
		
		return DDB_OK;
	}

	return DDB_ERR;
}

/* recursively parse dependencies: this is our central function  */
void
full_dep_tree(const char *pkgname, const char *depquery,
	Deptreehead *pdphead, const char *caller, int level)
{
	Pkgdeptree	*pdp;
	char		query[BUFSIZ];

	snprintf(query, BUFSIZ, depquery, pkgname);
	
	if (plisthead == NULL) {
		if (strcasestr(depquery, "SELECT REMOTE") != NULL) {
			/* querying remote packages, load remote packages list */
			plisthead = rec_pkglist(REMOTE_PKGS_QUERY);

			/* first package to recurse on and exact pkg name, this is an
			 * exact match due to many versions of the package
			 */
			if (exact_pkgfmt(pkgname))
				snprintf(query, BUFSIZ, EXACT_DIRECT_DEPS, pkgname);

		} else
			/* querying local packages, load local packages list */
			plisthead = rec_pkglist(LOCAL_PKGS_QUERY);

		/* first call, set recursion level to 1 */
		level = 1;
	}

	if (drydb_doquery(query, ddb_rec_direct_deps, pdphead) == 0) {
		/* record dependency level for installation */
		SLIST_FOREACH(pdp, pdphead, next)
			if (!pdp->level) {
				pdp->level = level;
			}
		SLIST_FOREACH(pdp, pdphead, next) {
			if (!pdp->computed) {
				
				/* been parsed */
				pdp->computed = 1;

#if 0
				printf("p: %s, l: %d\n", pdp->depname, pdp->level);
#endif		
				full_dep_tree(pdp->matchname, depquery,
					pdphead, __func__, level + 1);
			}

		} /* SLIST_FOREACH */
	}
	
	/* XXX: freed list is allocated for end_expr */
	if (strncmp(caller, __func__, strlen(caller)) != 0) {
		free_pkglist(plisthead);
		XFREE(plisthead);
	}
}

void
show_direct_depends(const char *pkgname)
{
	char		query[BUFSIZ];
	Pkgdeptree	*pdp;
	Deptreehead	deptreehead;
   
	if ((plisthead = rec_pkglist(REMOTE_PKGS_QUERY)) == NULL) {
		printf("empty available package list.\n");
		return;
	}
	
	if (count_samepkg(plisthead, pkgname) < 2) {
		SLIST_INIT(&deptreehead);

		if (exact_pkgfmt(pkgname))
			snprintf(query, BUFSIZ, EXACT_DIRECT_DEPS, pkgname);
		else
			snprintf(query, BUFSIZ, DIRECT_DEPS, pkgname);

		if (drydb_doquery(query, ddb_rec_direct_deps, &deptreehead) == 0) {
			printf("direct dependencies for %s\n", pkgname);
			SLIST_FOREACH(pdp, &deptreehead, next) {
				printf("\t%s\n", pdp->depname);
			}
			free_deptree(&deptreehead);
		}
	}
	free_pkglist(plisthead);
}

void
show_full_dep_tree(const char *pkgname, const char *depquery, const char *msg)
{
	Pkgdeptree	*pdp;
	Deptreehead	deptreehead; /* replacement for SLIST_HEAD() */
	int			count;

	plisthead = rec_pkglist(REMOTE_PKGS_QUERY);
	count = count_samepkg(plisthead, pkgname);
	/* free plisthead now so it is NULL for full_dep_tree() */
	free_pkglist(plisthead);
	XFREE(plisthead);

	if (count > 1)
		return;

	SLIST_INIT(&deptreehead);

	printf(msg, pkgname);
	full_dep_tree(pkgname, depquery, &deptreehead, __func__, 0);
	SLIST_FOREACH(pdp, &deptreehead, next)
		printf("\t%s\n", pdp->depname);

	free_deptree(&deptreehead);
}

