/* $Id: summary.c,v 1.41 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 "tools.h"
#include "pkg_dry.h"

static const struct Summary {
	const int	type;
	const char	*tbl_name;
	const char	*deps;
	const char	*conflicts;
	const char	*requires;
	const char	*provides;
} sumsw[] = {
	{
		LOCAL_SUMMARY,
		"LOCAL_PKG",
		"LOCAL_DEPS",
		"LOCAL_CONFLICTS",
		"LOCAL_REQUIRES",
		"LOCAL_PROVIDES"
	},
	{
		REMOTE_SUMMARY,
		"REMOTE_PKG",
		"REMOTE_DEPS",
		"REMOTE_CONFLICTS",
		"REMOTE_REQUIRES",
		"REMOTE_PROVIDES"
	},
};

static struct Columns {
	int		num;
	char	**name;
} cols;

typedef struct Insertlist {
	char	*field;
	char	*value;
	SLIST_ENTRY(Insertlist) next;
} Insertlist;

SLIST_HEAD(, Insertlist) inserthead;

static char	**fetch_summary(char *url);
static void	freecols(void);
static void	free_insertlist(void);
static void	prepare_insert(int, struct Summary);
int			colnames(void *, int, char **, char **);

char		**commit_list = NULL;
char		*cur_repo = NULL;
int			commit_idx = 0;
int			query_size = BUFSIZ;

/* remote summary fetch */
static char **
fetch_summary(char *url)
{
	/* from pkg_install/files/admin/audit.c */
	Dlfile	*file;
	char	*decompressed_input;
	size_t	decompressed_len;
	char	**out;

	file = download_file(url);

	if (decompress_buffer(file->buf, file->size, &decompressed_input,
			&decompressed_len)) {
		
		out = splitstr(decompressed_input, "\n");
		
		XFREE(file->buf);
		XFREE(file);

		return out;
	}

	XFREE(file->buf);
	XFREE(file);

	return NULL;
}

/* progress percentage */
static void
progress(char c)
{
	const char	*alnum = ALNUM;
	int 		i, alnumlen = strlen(alnum);
	float		percent = 0;
	
	for (i = 0; i < alnumlen; i++)
		if (c == alnum[i])
			percent = ((float)(i + 1)/ (float)alnumlen) * 100;
	
	printf("updating database: %.f%%\r", percent);
}

/* check if the field is PKGNAME */
static int
chk_pkgname(char *field)
{
	if (strncmp(field, "PKGNAME=", 8) == 0 ||
		strncmp(field, "CONFLICTS=", 10) == 0)
		return 1;

	return 0;
}

/* returns value for given field */
static char *
field_record(const char *field, char *line)
{
	char *pfield;
	
	if (strncmp(field, line, strlen(field)) == 0) {
		pfield = strchr(line, '=');
		trimcr(pfield++);
		
		return pfield;
	}

	return NULL;
}

static void
freecols()
{
	int i;
	
	for (i = 0; i < cols.num; i++)
		XFREE(cols.name[i]);

	XFREE(cols.name);
}

static void
free_insertlist()
{
	Insertlist *pi;

	while (!SLIST_EMPTY(&inserthead)) {
		pi = SLIST_FIRST(&inserthead);
		SLIST_REMOVE_HEAD(&inserthead, next);
		XFREE(pi->field);
		XFREE(pi->value);
		XFREE(pi);
	}
}

/* sqlite callback, fill cols.name[] with available columns names */
int
colnames(void *unused, int argc, char **argv, char **colname)
{
	int			i = 0;
	static int	colcount = 0;
	
	colcount++;

	cols.num = colcount;
	XREALLOC(cols.name, colcount * sizeof(char *));

	for (i = 0; i < argc; i++)
		if (argv[i] != NULL && strncmp(colname[i], "name", 4) == 0)
			XSTRDUP(cols.name[colcount - 1], argv[i]);

	return DDB_OK;
}

/* for now, values are located on a SLIST, build INSERT line with them */
static void
prepare_insert(int pkgid, struct Summary sum)
{
	char		*commit_query;
	Insertlist	*pi;

	if (sum.type == REMOTE_SUMMARY)
		query_size = (query_size + strlen(cur_repo)) * sizeof(char);

	XMALLOC(commit_query, query_size);
	
	snprintf(commit_query, BUFSIZ, "INSERT INTO %s ( PKG_ID,", sum.tbl_name);

	/* insert fields */
	SLIST_FOREACH(pi, &inserthead, next)
		snprintf(commit_query, query_size,
			"%s\"%s\",", commit_query, pi->field);

	/* insert REPOSITORY field */
	if (sum.type == REMOTE_SUMMARY)
		snprintf(commit_query, query_size, "%s\"REPOSITORY\")", commit_query);
	else
		commit_query[strlen(commit_query) - 1] = ')';
	
	snprintf(commit_query, query_size, "%s VALUES ( %d, ",
		commit_query, pkgid);

	/* insert values */
	SLIST_FOREACH(pi, &inserthead, next)
		snprintf(commit_query, query_size,
			"%s\"%s\",", commit_query, pi->value);

	/* insert repository URL if it's a remote pkg_summary */
	if (sum.type == REMOTE_SUMMARY) {
		snprintf(commit_query, query_size, "%s\"%s\");",
			commit_query, cur_repo);
	} else {
		commit_query[strlen(commit_query) - 1] = ')';
		strcat(commit_query, ";");
	}

	/* append query to commit list */
	commit_idx++;
	XREALLOC(commit_list, (commit_idx + 1) * sizeof(char *));
	commit_list[commit_idx] = commit_query;
}

static void
child_table(int pkgid, const char *table, char *val)
{
	char buf[BUFSIZ];
	
	snprintf(buf, BUFSIZ,
		"INSERT INTO %s (PKG_ID,%s_PKGNAME) VALUES (%d,\"%s\");",
		table, table, pkgid, val);
	
	/* append query to commit_list */
	commit_idx++;
	XREALLOC(commit_list, (commit_idx + 1) * sizeof(char *));
	XSTRDUP(commit_list[commit_idx], buf);
}

static void
update_col(struct Summary sum, int pkgid, char *line)
{
	int			i;
	char		*val, *p, buf[BUFSIZ];
	Insertlist	*insert;

	/* DEPENDS */
	if ((val = field_record("DEPENDS", line)) != NULL)
		child_table(pkgid, sum.deps, val);
	/* REQUIRES */
	if ((val = field_record("REQUIRES", line)) != NULL)
		child_table(pkgid, sum.requires, val);
	/* PROVIDES */
	if ((val = field_record("PROVIDES", line)) != NULL)
		child_table(pkgid, sum.provides, val);
	
	for (i = 0; i < cols.num; i++) {
		snprintf(buf, BUFSIZ, "%s=", cols.name[i]);

		val = field_record(cols.name[i], line);

		/* XXX: handle that later */
		if (strncmp(cols.name[i], "DESCRIPTION", 11) == 0)
			continue;
		
		if (val != NULL && strncmp(buf, line, strlen(buf)) == 0) {
			/* nasty little hack to prevent double quotes */
			if (strchr(line, '"') != NULL)
				for (p = line; *p != '\0'; p++)
					if (*p == '"')
						*p = '`';
				

			XMALLOC(insert, sizeof(Insertlist));
			XSTRDUP(insert->field, cols.name[i]);
			XSTRDUP(insert->value, val);

			SLIST_INSERT_HEAD(&inserthead, insert, next);

			/* update query size */
			query_size += strlen(insert->field) + strlen(insert->value) + 5;
			/* 5 = strlen(\"\",) */
		}
	}
}

static void
insert_summary(struct Summary sum, char **summary)
{
	int			i;
	static int	pkgid = 1;
	char		*pkgname, **psum, query[BUFSIZ];
	const char	*alnum = ALNUM;
	Insertlist	*insert;

	printf( "\e[?25l" ); /* hide the cursor */

	if (summary == NULL) {
		drydb_close();
		errx(EXIT_FAILURE, "could not open read summary");
	}
	
	snprintf(query, BUFSIZ, "PRAGMA table_info(%s);", sum.tbl_name);

	/* record columns names to cols */
	drydb_doquery(query, colnames, NULL);

	SLIST_INIT(&inserthead);

	XMALLOC(commit_list, sizeof(char *));
	/* begin transaction */
	XSTRDUP(commit_list[0], "BEGIN;");
	
	psum = summary;
	/* main pkg_summary analysis loop */
	while (*psum != NULL) {
		/* CONFLICTS may appear before PKGNAME... */
		if ((pkgname = field_record("CONFLICTS", *psum)) != NULL) {
			snprintf(query, BUFSIZ,
				"INSERT INTO %s (PKG_ID,%s_PKGNAME) VALUES (%d,\"%s\");",
				sum.conflicts, sum.conflicts, pkgid, pkgname);

			/* append query to commit_list */
			commit_idx++;
			XREALLOC(commit_list, (commit_idx + 1) * sizeof(char *));
			XSTRDUP(commit_list[commit_idx], query);

			psum++;
			continue; /* there may be more */
		}

		/* PKGNAME record, should always be true  */
		if ((pkgname = field_record("PKGNAME", *psum)) != NULL) {

			XMALLOC(insert, sizeof(Insertlist));
			XSTRDUP(insert->field, "PKGNAME");
			XSTRDUP(insert->value, pkgname);

			SLIST_INSERT_HEAD(&inserthead, insert, next);

			/* nice little counter */
			progress(pkgname[0]);
		}

		psum++;

		/* browse entries following PKGNAME and build the SQL query */
		while (*psum != NULL && !chk_pkgname(*psum)) {
			update_col(sum, pkgid, *psum);
			psum++;
		}

		/* build INSERT query */
		prepare_insert(pkgid, sum);

		/* next PKG_ID */
		pkgid++;

		/* free the SLIST containing this package's key/vals */
		free_insertlist();

		/* reset max query size */
		query_size = BUFSIZ;
		
	} /* while *psum != NULL */

	commit_idx++;
	XREALLOC(commit_list, (commit_idx + 2) * sizeof(char *));
	XSTRDUP(commit_list[commit_idx], "COMMIT;");
	commit_list[commit_idx + 1] = NULL;

	/* do the insert */
	for (i = 0; commit_list[i] != NULL; i++)
		drydb_doquery(commit_list[i], NULL, NULL);

	progress(alnum[strlen(alnum) - 1]); /* XXX: nasty. */
	
	free_list(commit_list);
	commit_idx = 0;

	/* reset pkgid */
	if (sum.type == LOCAL_SUMMARY)
		pkgid = 1;

	printf( "\n\e[?25h" ); /* show the cursor again */
}

void
update_db(int which, char **pkgkeep)
{
	int			i;
	Plisthead	*plisthead;
	Pkglist		*pkglist;
	char		**summary = NULL, **prepos, buf[BUFSIZ];

	plisthead = rec_pkglist(KEEP_LOCAL_PKGS);

	/* dropping and recreating the tables is faster than updating */
	drydb_doquery(DROP_LOCAL_TABLES, NULL, NULL);
	if (which == REMOTE_SUMMARY)
		drydb_doquery(DROP_REMOTE_TABLES, NULL, NULL);
	drydb_doquery(CREATE_DRYDB, NULL, NULL);
	
	for (i = 0; i < 2; i++) {

		switch (sumsw[i].type) {
		case LOCAL_SUMMARY:
			/* generate summary locally */
			summary = exec_list(PKGTOOLS "/pkg_info -Xa", NULL);
			printf("processing local summary...\n");

			insert_summary(sumsw[i], summary);
			free_list(summary);

			break;
		case REMOTE_SUMMARY:
			if (which == LOCAL_SUMMARY)
				continue;
			/* loop through PKG_REPOS */
			for (prepos = pkg_repos; *prepos != NULL; prepos++) {
				snprintf(buf, BUFSIZ, "%s/%s.bz2", *prepos, PKG_SUMMARY);
				/* load remote pkg_summary */
				summary = fetch_summary(buf);
				printf("processing remote summary (%s)...\n", *prepos);

				cur_repo = *prepos;
				insert_summary(sumsw[i], summary);
				free_list(summary);
			}

			/* remove empty rows (duplicates) */
			drydb_doquery(DELETE_EMPTY_ROWS, NULL, NULL);
			break;
		}

		/* restore keep list */
		if (sumsw[i].type == LOCAL_SUMMARY) {
			if (plisthead != NULL) {
				SLIST_FOREACH(pkglist, plisthead, next) {
					snprintf(buf, BUFSIZ, KEEP_PKG, pkglist->pkgname);
					drydb_doquery(buf, NULL, NULL);
				}
				free_pkglist(plisthead);
				XFREE(plisthead);
			}

			/* insert new keep list */
			if (pkgkeep != NULL)
				/* installation: mark the packages as "keep" */
				pkg_keep(KEEP, pkgkeep);
		}

	}
	/* columns name not needed anymore */
	if (cols.name != NULL)
		freecols();

}

