/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
 *
 * Copyright (C) 2007 Andreas Obergrusberger <tradiaz@yahoo.de>
 * Copyright (C) 2008-2010 Valeriy Lyasotskiy <onestep@ukr.net>
 * Copyright (C) 2010-2011 Jonathan Conder <jonno.conder@gmail.com>
 *
 * Licensed under the GNU General Public License Version 2
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include "pk-backend-alpm.h"
#include "pk-alpm-error.h"
#include "pk-alpm-packages.h"
#include "pk-alpm-transaction.h"

#include <syslog.h>

static off_t transaction_dcomplete = 0;
static off_t transaction_dtotal = 0;

static alpm_pkg_t *dpkg = NULL;
static GString *dfiles = NULL;

static alpm_pkg_t *tpkg = NULL;
static GString *toutput = NULL;

static PkBackendJob* pkalpm_current_job = NULL;
const gchar *pkalpm_dirname = NULL;

static gchar *
pk_alpm_resolve_path (PkBackendJob *job, const gchar *basename)
{
	g_return_val_if_fail (job != NULL, NULL);
	g_return_val_if_fail (basename != NULL, NULL);
	g_return_val_if_fail (pkalpm_dirname != NULL, NULL);

	return g_build_filename (pkalpm_dirname, basename, NULL);
}

static gboolean
pk_alpm_pkg_has_basename (PkBackend *backend, alpm_pkg_t *pkg, const gchar *basename)
{
	g_return_val_if_fail (pkg != NULL, FALSE);
	g_return_val_if_fail (basename != NULL, FALSE);

	if (g_strcmp0 (alpm_pkg_get_filename (pkg), basename) == 0)
		return TRUE;

	return FALSE;
}

static void
pk_alpm_transaction_download_end (PkBackendJob *job)
{
	g_return_if_fail (dpkg != NULL);

	pk_alpm_pkg_emit (job, dpkg, PK_INFO_ENUM_FINISHED);

	/* tell DownloadPackages what files were downloaded */
	if (dfiles != NULL) {
		g_autofree gchar *package_id = pk_alpm_pkg_build_id (dpkg);
		pk_backend_job_files (job, package_id, &dfiles->str);
		g_string_free (dfiles, TRUE);
	}

	dpkg = NULL;
	dfiles = NULL;
}

static void
pk_alpm_transaction_download_start (PkBackendJob *job, const gchar *basename)
{
	PkBackend *backend = pk_backend_job_get_backend (job);
	PkBackendAlpmPrivate *priv = pk_backend_get_user_data (backend);
	const alpm_list_t *i;

	g_return_if_fail (basename != NULL);

	/* continue or finish downloading the current package */
	if (dpkg != NULL) {
		if (pk_alpm_pkg_has_basename (backend, dpkg, basename)) {
			if (dfiles != NULL) {
				g_autofree gchar *path = NULL;
				path = pk_alpm_resolve_path (job, basename);
				g_string_append_printf (dfiles, ";%s", path);
			}
			return;
		}
		pk_alpm_transaction_download_end (job);
		dpkg = NULL;
	}

	/* figure out what the next package is */
	for (i = alpm_trans_get_add (priv->alpm); i != NULL; i = i->next) {
		alpm_pkg_t *pkg = (alpm_pkg_t *) i->data;

		if (pk_alpm_pkg_has_basename (backend, pkg, basename)) {
			dpkg = pkg;
			break;
		}
	}

	if (dpkg == NULL)
		return;

	pk_alpm_pkg_emit (job, dpkg, PK_INFO_ENUM_DOWNLOADING);

	/* start collecting files for the new package */
	if (pk_backend_job_get_role (job) == PK_ROLE_ENUM_DOWNLOAD_PACKAGES) {
		g_autofree gchar *path = NULL;
		path = pk_alpm_resolve_path (job, basename);
		dfiles = g_string_new (path);
	}
}

static void
pk_alpm_transaction_dlcb (void *ctx, const gchar *filename, alpm_download_event_type_t type, void *data)
{
	guint percentage = 100, sub_percentage = 100;
	alpm_download_event_completed_t *completed = data;
	alpm_download_event_progress_t *progress = data;

	PkBackendJob* job;
	g_assert (pkalpm_current_job);
	job = pkalpm_current_job;

	g_return_if_fail (filename != NULL);
	switch (type) {
	case ALPM_DOWNLOAD_INIT:
		pk_backend_job_set_status (job, PK_STATUS_ENUM_DOWNLOAD);
		pk_alpm_transaction_download_start (job, filename);
		break;

	case ALPM_DOWNLOAD_COMPLETED:
		pk_backend_job_set_percentage (job, 100);
		transaction_dcomplete += completed->total;
		break;

	case ALPM_DOWNLOAD_PROGRESS:
		if (transaction_dtotal > 0) {
			transaction_dcomplete += progress->downloaded;
			percentage = ((transaction_dcomplete + progress->downloaded) * 100) / transaction_dtotal;
			pk_backend_job_set_percentage (job, percentage);
		} else if (transaction_dtotal < 0) {
			static off_t previous_total = 0;
			static guint current_database = 0;
			guint total_databases = -transaction_dtotal;

			if (progress->total != previous_total) {
				current_database++;
				previous_total = progress->total;
			}

			percentage = ((current_database - 1) * 100) / total_databases;
			percentage += sub_percentage / total_databases;

			pk_backend_job_set_percentage (job, percentage);
		}
		break;
	default:
		syslog (LOG_DAEMON | LOG_WARNING, "unhandled download callback case, most likely libalpm change or error");
	}
}

static void
pk_alpm_transaction_progress_cb (void *ctx, alpm_progress_t type, const gchar *target,
					gint percent, gsize targets, gsize current)
{
	static gint recent = 101;
	gsize overall = percent + (current - 1) * 100;

	PkBackendJob* job;
	g_assert (pkalpm_current_job);
	job = pkalpm_current_job;

	if (g_strcmp0(target, "") == 0) {
		switch (type) {
			case ALPM_PROGRESS_KEYRING_START:
				pk_backend_job_set_status(job, PK_STATUS_ENUM_SIG_CHECK);
				pk_backend_job_set_percentage(job, percent);
				break;
			case ALPM_PROGRESS_INTEGRITY_START:
				pk_backend_job_set_status(job, PK_STATUS_ENUM_SIG_CHECK);
				pk_backend_job_set_percentage(job, percent);
				break;
			case ALPM_PROGRESS_LOAD_START:
				pk_backend_job_set_status(job, PK_STATUS_ENUM_LOADING_CACHE);
				pk_backend_job_set_percentage(job, percent);
				break;
			case ALPM_PROGRESS_DISKSPACE_START:
				pk_backend_job_set_status(job, PK_STATUS_ENUM_TEST_COMMIT);
				pk_backend_job_set_percentage(job, percent);
				break;
			case ALPM_PROGRESS_CONFLICTS_START:
				pk_backend_job_set_status(job, PK_STATUS_ENUM_TEST_COMMIT);
				pk_backend_job_set_percentage(job, percent);
				break;
			default:
				syslog (LOG_DAEMON | LOG_WARNING, "unhandled progress type for transaction %d", type);
				break;
		}
	}

	/* TODO: remove block if/when this is made consistent upstream */
	if (type == ALPM_PROGRESS_CONFLICTS_START ||
	    type == ALPM_PROGRESS_DISKSPACE_START ||
	    type == ALPM_PROGRESS_INTEGRITY_START ||
	    type == ALPM_PROGRESS_LOAD_START ||
	    type == ALPM_PROGRESS_KEYRING_START) {
		if (current < targets) {
			++current;
			overall += 100;
		}
	}

	if (current < 1 || targets < current)
		syslog (LOG_DAEMON | LOG_WARNING, "TODO: CURRENT/TARGETS FAILED for %d", type);

	g_return_if_fail (target != NULL);
	g_return_if_fail (0 <= percent && percent <= 100);
	g_return_if_fail (1 <= current && current <= targets);

	/* update transaction progress */
	switch (type) {
	case ALPM_PROGRESS_ADD_START:
	case ALPM_PROGRESS_UPGRADE_START:
	case ALPM_PROGRESS_DOWNGRADE_START:
	case ALPM_PROGRESS_REINSTALL_START:
	case ALPM_PROGRESS_REMOVE_START:
		if (percent == recent)
			break;

		pk_backend_job_set_item_progress (job, target, PK_STATUS_ENUM_UNKNOWN, percent);
		pk_backend_job_set_percentage (job, overall / targets);
		recent = percent;

		syslog (LOG_DAEMON | LOG_WARNING, "%d%% of %s complete (%zu of %zu)", percent,
			 target, current, targets);
		break;

	default:
		syslog (LOG_DAEMON | LOG_WARNING, "unknown progress type %d", type);
		break;
	}
}

static void
pk_alpm_install_ignorepkg (PkBackendJob *job, alpm_question_install_ignorepkg_t *q)
{
	g_autofree gchar *output = NULL;

	g_return_if_fail (q != NULL);
	g_return_if_fail (q->pkg != NULL);

	switch (pk_backend_job_get_role (job)) {
	case PK_ROLE_ENUM_INSTALL_PACKAGES:
		output = g_strdup_printf ("%s: was not ignored\n",
					  alpm_pkg_get_name (q->pkg));
		pk_alpm_transaction_output (output);
#if  (!defined(__clang__)) && (__GNUC__ >= 7)
		__attribute__ ((fallthrough)); /* let's be explicit about falltrhough */
#endif
	case PK_ROLE_ENUM_DOWNLOAD_PACKAGES:
		q->install = 1;
		break;

	default:
		q->install = 0;
		break;
	}
}

static void
pk_alpm_select_provider (const alpm_list_t *providers,
			    alpm_depend_t *depend)
{
	g_autofree gchar *output = NULL;

	g_return_if_fail (depend != NULL);
	g_return_if_fail (providers != NULL);

	output = g_strdup_printf ("provider package was selected "
				  "(%s provides %s)\n",
				  alpm_pkg_get_name (providers->data),
				  depend->name);
	pk_alpm_transaction_output (output);
}

static void
pk_alpm_transaction_conv_cb (void *ctx, alpm_question_t *question)
{
	PkBackendJob* job;
	g_assert (pkalpm_current_job);
	job = pkalpm_current_job;

	g_return_if_fail (question != NULL);

	switch (question->type) {
	case ALPM_QUESTION_INSTALL_IGNOREPKG:
		{
			alpm_question_install_ignorepkg_t *q = &question->install_ignorepkg;
			pk_alpm_install_ignorepkg (job, q);
		}
		break;
	case ALPM_QUESTION_REPLACE_PKG:
		{
			alpm_question_replace_t *q = &question->replace;
			g_debug ("safe question %d", question->type);
			q->replace = 1;
		}
		break;
	case ALPM_QUESTION_CONFLICT_PKG:
	case ALPM_QUESTION_CORRUPTED_PKG:
		{
			alpm_question_conflict_t *q = &question->conflict;
			g_debug ("safe question %d", question->type);
			q->remove = 1;
		}
		break;
//	case ALPM_QUESTION_LOCAL_NEWER:
	case ALPM_QUESTION_REMOVE_PKGS:
		{
			alpm_question_remove_pkgs_t *q = &question->remove_pkgs;
			g_debug ("unsafe question %d", question->type);
			q->skip = 0;
		}
		break;
	/* TODO: handle keys better */
	case ALPM_QUESTION_IMPORT_KEY:
		{
			alpm_question_import_key_t *q = &question->import_key;
			g_debug ("unsafe question %d", question->type);
			q->import = 0;
		}
		break;
	case ALPM_QUESTION_SELECT_PROVIDER:
		{
			alpm_question_select_provider_t *q = &question->select_provider;
			pk_alpm_select_provider (q->providers, q->depend);
			q->use_index = 0;
		}
		break;

	default:
		syslog (LOG_DAEMON | LOG_WARNING, "unknown question %d", question->type);
		break;
	}
}

static void
pk_alpm_transaction_output_end ()
{
	tpkg = NULL;

	if (toutput != NULL) {
		pk_alpm_transaction_output (toutput->str);
		g_string_free (toutput, TRUE);
		toutput = NULL;
	}
}

static void
pk_alpm_transaction_output_start (alpm_pkg_t *pkg)
{
	g_return_if_fail (pkg != NULL);

	if (tpkg != NULL)
		pk_alpm_transaction_output_end ();

	tpkg = pkg;
}

void
pk_alpm_transaction_output (const gchar *output)
{
	g_return_if_fail (output != NULL);

	if (tpkg != NULL) {
		if (toutput == NULL) {
			toutput = g_string_new ("<b>");
			g_string_append (toutput, alpm_pkg_get_name (tpkg));
			g_string_append (toutput, "</b>\n");
		}
		g_string_append (toutput, output);
	}
}

static void
pk_alpm_transaction_dep_resolve (PkBackendJob *job)
{
	pk_backend_job_set_status (job, PK_STATUS_ENUM_DEP_RESOLVE);
}

static void
pk_alpm_transaction_hook (PkBackendJob *job)
{
	pk_backend_job_set_status (job, PK_STATUS_ENUM_RUN_HOOK);
	pk_backend_job_set_percentage (job, 0);
}

static void
pk_alpm_transaction_hook_run (PkBackendJob *job, alpm_event_hook_run_t * event)
{
	/* Every hook runs a single command, so there is no progress.
	   Instead calculate the progress from total and finished hooks. */
	pk_backend_job_set_percentage (job, 100 * event->position / event->total);
	syslog (LOG_DAEMON | LOG_WARNING, "Hook %s (%s) complete (%zu of %zu)",
		event->name, event->desc, event->position, event->total);
}

static void
pk_alpm_transaction_test_commit (PkBackendJob *job)
{
	pk_backend_job_set_status (job, PK_STATUS_ENUM_TEST_COMMIT);
}

static void
pk_alpm_transaction_add_start (PkBackendJob *job, alpm_pkg_t *pkg)
{
	g_return_if_fail (pkg != NULL);

	pk_backend_job_set_status (job, PK_STATUS_ENUM_INSTALL);
	pk_alpm_pkg_emit (job, pkg, PK_INFO_ENUM_INSTALLING);
	pk_alpm_transaction_output_start (pkg);
}

static void
pk_alpm_transaction_add_done (PkBackendJob *job, alpm_pkg_t *pkg)
{
	PkBackend *backend = pk_backend_job_get_backend (job);
	PkBackendAlpmPrivate *priv = pk_backend_get_user_data (backend);
	const gchar *name, *version;
	const alpm_list_t *i, *optdepends;

	g_return_if_fail (pkg != NULL);

	name = alpm_pkg_get_name (pkg);
	version = alpm_pkg_get_version (pkg);

	alpm_logaction (priv->alpm, PK_LOG_PREFIX, "installed %s (%s)\n", name,
			version);
	pk_alpm_pkg_emit (job, pkg, PK_INFO_ENUM_FINISHED);

	optdepends = alpm_pkg_get_optdepends (pkg);
	if (optdepends != NULL) {
		pk_alpm_transaction_output ("Optional dependencies:\n");

		for (i = optdepends; i != NULL; i = i->next) {
			char *depend = alpm_dep_compute_string (i->data);
			g_autofree gchar *output = g_strdup_printf ("%s\n", depend);
			free (depend);
			pk_alpm_transaction_output (output);
		}
	}
	pk_alpm_transaction_output_end ();
}

static void
pk_alpm_transaction_remove_start (PkBackendJob *job, alpm_pkg_t *pkg)
{
	g_return_if_fail (pkg != NULL);

	pk_backend_job_set_status (job, PK_STATUS_ENUM_REMOVE);
	pk_alpm_pkg_emit (job, pkg, PK_INFO_ENUM_REMOVING);
	pk_alpm_transaction_output_start (pkg);
}

static void
pk_alpm_transaction_remove_done (PkBackendJob *job, alpm_pkg_t *pkg)
{
	PkBackend *backend = pk_backend_job_get_backend (job);
	PkBackendAlpmPrivate *priv = pk_backend_get_user_data (backend);
	const gchar *name, *version;

	g_return_if_fail (pkg != NULL);

	name = alpm_pkg_get_name (pkg);
	version = alpm_pkg_get_version (pkg);

	alpm_logaction (priv->alpm, PK_LOG_PREFIX, "removed %s (%s)\n", name, version);
	pk_alpm_pkg_emit (job, pkg, PK_INFO_ENUM_FINISHED);
	pk_alpm_transaction_output_end ();
}

static void
pk_alpm_transaction_upgrade_start (PkBackendJob *job, alpm_pkg_t *pkg,
				      alpm_pkg_t *old)
{
	PkRoleEnum role;
	PkStatusEnum state;
	PkInfoEnum info;

	g_return_if_fail (pkg != NULL);

	role = pk_backend_job_get_role (job);
	if (role == PK_ROLE_ENUM_INSTALL_FILES) {
		state = PK_STATUS_ENUM_INSTALL;
		info = PK_INFO_ENUM_INSTALLING;
	} else {
		state = PK_STATUS_ENUM_UPDATE;
		info = PK_INFO_ENUM_UPDATING;
	}

	pk_backend_job_set_status (job, state);
	pk_alpm_pkg_emit (job, pkg, info);
	pk_alpm_transaction_output_start (pkg);
}

static gint
pk_alpm_depend_compare (gconstpointer a, gconstpointer b)
{
	const alpm_depend_t *first = a;
	const alpm_depend_t *second = b;
	gint result;

	g_return_val_if_fail (first != NULL, 0);
	g_return_val_if_fail (second != NULL, 0);

	result = g_strcmp0 (first->name, second->name);
	if (result == 0) {
		result = first->mod - second->mod;
		if (result == 0) {
			result = g_strcmp0 (first->version, second->version);
			if (result == 0)
				result = g_strcmp0 (first->desc, second->desc);
		}
	}

	return result;
}

static void
pk_alpm_transaction_process_new_optdepends (alpm_pkg_t *pkg, alpm_pkg_t *old)
{
	alpm_list_t *optdepends;
	const alpm_list_t *i;

	g_return_if_fail (pkg != NULL);
	g_return_if_fail (old != NULL);

	optdepends = alpm_list_diff (alpm_pkg_get_optdepends (pkg),
				     alpm_pkg_get_optdepends (old),
				     pk_alpm_depend_compare);
	if (optdepends == NULL)
		return;

	pk_alpm_transaction_output ("New optional dependencies:\n");

	for (i = optdepends; i != NULL; i = i->next) {
		char *depend = alpm_dep_compute_string (i->data);
		g_autofree gchar *output = g_strdup_printf ("%s\n", depend);
		free (depend);
		pk_alpm_transaction_output (output);
	}

	alpm_list_free (optdepends);
}

static void
pk_alpm_transaction_upgrade_done (PkBackendJob *job, alpm_pkg_t *pkg,
				     alpm_pkg_t *old, gint direction)
{
	PkBackend *backend = pk_backend_job_get_backend (job);
	PkBackendAlpmPrivate *priv = pk_backend_get_user_data (backend);
	const gchar *name, *pre, *post;

	g_return_if_fail (pkg != NULL);
	g_return_if_fail (old != NULL || direction == ALPM_PACKAGE_REINSTALL);

	name = alpm_pkg_get_name (pkg);
	if (direction != ALPM_PACKAGE_REINSTALL)
		pre = alpm_pkg_get_version (old);
	post = alpm_pkg_get_version (pkg);

	if (direction == ALPM_PACKAGE_UPGRADE) {
		alpm_logaction (priv->alpm, PK_LOG_PREFIX, "upgraded %s (%s -> %s)\n",
				name, pre, post);
	} else if (direction == ALPM_PACKAGE_DOWNGRADE) {
		alpm_logaction (priv->alpm, PK_LOG_PREFIX,
				"downgraded %s (%s -> %s)\n", name, pre, post);
	} else {
		alpm_logaction (priv->alpm, PK_LOG_PREFIX, "reinstalled %s (%s)\n",
				name, post);
	}
	pk_alpm_pkg_emit (job, pkg, PK_INFO_ENUM_FINISHED);

	if (direction != ALPM_PACKAGE_REINSTALL)
		pk_alpm_transaction_process_new_optdepends (pkg, old);
	pk_alpm_transaction_output_end ();
}

static void
pk_alpm_transaction_sig_check (PkBackendJob *job)
{
	pk_backend_job_set_status (job, PK_STATUS_ENUM_SIG_CHECK);
}

static void
pk_alpm_transaction_setup (PkBackendJob *job)
{
	pk_backend_job_set_status (job, PK_STATUS_ENUM_SETUP);
}

static void
pk_alpm_transaction_download (PkBackendJob *job)
{
	pk_backend_job_set_status (job, PK_STATUS_ENUM_DOWNLOAD);
}

static void
pk_alpm_transaction_optdepend_removal (PkBackendJob *job, alpm_pkg_t *pkg,
					   alpm_depend_t *optdepend)
{
	char *depend = NULL;
	g_autofree gchar *output = NULL;

	g_return_if_fail (pkg != NULL);
	g_return_if_fail (optdepend != NULL);

	depend = alpm_dep_compute_string (optdepend);
	output = g_strdup_printf ("optionally requires %s\n", depend);
	free (depend);

// 	pk_backend_job_message (job, pkg, output);
	pk_backend_job_error_code (job, PK_ERROR_ENUM_DEP_RESOLUTION_FAILED,
				   "%s\n%s", alpm_pkg_get_name (pkg), output);
}

static void
pk_alpm_transaction_event_cb (void *ctx, alpm_event_t *event)
{
	PkBackendJob* job;
	job = pkalpm_current_job;
	g_assert (job);

	/* figure out backend status and process package changes */
	switch (event->type) {
	case ALPM_EVENT_CHECKDEPS_START:
	case ALPM_EVENT_RESOLVEDEPS_START:
		pk_alpm_transaction_dep_resolve (job);
		break;
	case ALPM_EVENT_DISKSPACE_START:
	case ALPM_EVENT_FILECONFLICTS_START:
	case ALPM_EVENT_INTERCONFLICTS_START:
		pk_alpm_transaction_test_commit (job);
		break;
	case ALPM_EVENT_PACKAGE_OPERATION_START:
		{
			alpm_event_package_operation_t *e = (alpm_event_package_operation_t *) event;
			switch(e->operation) {
				case ALPM_PACKAGE_INSTALL:
					pk_alpm_transaction_add_start (job, e->newpkg);
					break;
				case ALPM_PACKAGE_REMOVE:
					pk_alpm_transaction_remove_start (job, e->oldpkg);
					break;
				case ALPM_PACKAGE_UPGRADE:
				case ALPM_PACKAGE_DOWNGRADE:
				case ALPM_PACKAGE_REINSTALL:
					pk_alpm_transaction_upgrade_start (job, e->newpkg, e->oldpkg);
					break;
			}
		}
		break;
	case ALPM_EVENT_PACKAGE_OPERATION_DONE:
		{
			alpm_event_package_operation_t *e = (alpm_event_package_operation_t *) event;
			switch(e->operation) {
				case ALPM_PACKAGE_INSTALL:
					pk_alpm_transaction_add_done (job, e->newpkg);
					break;
				case ALPM_PACKAGE_REMOVE:
					pk_alpm_transaction_remove_done (job, e->oldpkg);
					break;
				case ALPM_PACKAGE_UPGRADE:
					pk_alpm_transaction_upgrade_done (job, e->newpkg, e->oldpkg, ALPM_PACKAGE_UPGRADE);
					break;
				case ALPM_PACKAGE_DOWNGRADE:
					pk_alpm_transaction_upgrade_done (job, e->newpkg, e->oldpkg, ALPM_PACKAGE_DOWNGRADE);
					break;
				case ALPM_PACKAGE_REINSTALL:
					pk_alpm_transaction_upgrade_done (job, e->newpkg, e->oldpkg, ALPM_PACKAGE_REINSTALL);
					break;
			}
		}
		break;
	case ALPM_EVENT_INTEGRITY_START:
	case ALPM_EVENT_KEYRING_START:
		pk_alpm_transaction_sig_check (job);
		break;
	case ALPM_EVENT_LOAD_START:
		pk_alpm_transaction_setup (job);
		break;
	case ALPM_EVENT_SCRIPTLET_INFO:
		pk_alpm_transaction_output (((alpm_event_scriptlet_info_t *) event)->line);
		break;
	case ALPM_EVENT_KEY_DOWNLOAD_START:
	case ALPM_EVENT_DB_RETRIEVE_START:
		pk_alpm_transaction_download (job);
		break;
	case ALPM_EVENT_OPTDEP_REMOVAL:
		/* TODO: remove if this results in notification spam */
		{
			alpm_event_optdep_removal_t *e = (alpm_event_optdep_removal_t *) event;
			pk_alpm_transaction_optdepend_removal (job, e->pkg, e->optdep);
		}
		break;
	case ALPM_EVENT_HOOK_START:
		pk_alpm_transaction_hook (job);
		break;
	case ALPM_EVENT_HOOK_RUN_DONE:
		pk_alpm_transaction_hook_run (job, (alpm_event_hook_run_t *)event);
		break;
	case ALPM_EVENT_CHECKDEPS_DONE:
	case ALPM_EVENT_DATABASE_MISSING:
	case ALPM_EVENT_DISKSPACE_DONE:
	case ALPM_EVENT_FILECONFLICTS_DONE:
	case ALPM_EVENT_HOOK_DONE:
	case ALPM_EVENT_HOOK_RUN_START:
	case ALPM_EVENT_INTEGRITY_DONE:
	case ALPM_EVENT_INTERCONFLICTS_DONE:
	case ALPM_EVENT_KEY_DOWNLOAD_DONE:
	case ALPM_EVENT_KEYRING_DONE:
	case ALPM_EVENT_LOAD_DONE:
	case ALPM_EVENT_PACNEW_CREATED:
	case ALPM_EVENT_PACSAVE_CREATED:
	case ALPM_EVENT_PKG_RETRIEVE_DONE:
	case ALPM_EVENT_PKG_RETRIEVE_FAILED:
	case ALPM_EVENT_PKG_RETRIEVE_START:
	case ALPM_EVENT_RESOLVEDEPS_DONE:
	case ALPM_EVENT_DB_RETRIEVE_DONE:
	case ALPM_EVENT_DB_RETRIEVE_FAILED:
	case ALPM_EVENT_TRANSACTION_DONE:
	case ALPM_EVENT_TRANSACTION_START:
		/* ignored */
		break;

	default:
		syslog (LOG_DAEMON | LOG_WARNING, "unhandled event %d", event->type);
		break;
	}
}

static void
pk_alpm_transaction_cancelled_cb (GCancellable *object, gpointer data)
{
	PkBackend *backend = pk_backend_job_get_backend (PK_BACKEND_JOB (data));
	PkBackendAlpmPrivate *priv = pk_backend_get_user_data (backend);
	alpm_trans_interrupt (priv->alpm);
}

gboolean
pk_alpm_transaction_initialize (PkBackendJob* job, alpm_transflag_t flags, const gchar* dirname, GError** error)
{
	PkBackend *backend = pk_backend_job_get_backend (job);
	PkBackendAlpmPrivate *priv = pk_backend_get_user_data (backend);

	if (alpm_trans_init (priv->alpm, flags) < 0) {
		alpm_errno_t alpm_err = alpm_errno (priv->alpm);
		g_set_error_literal (error, PK_ALPM_ERROR, alpm_err,
				     alpm_strerror (alpm_err));
		return FALSE;
	}

	g_assert (pkalpm_current_job == NULL);
	pkalpm_current_job = job;
	pkalpm_dirname = dirname;

	alpm_option_set_eventcb (priv->alpm, pk_alpm_transaction_event_cb, NULL);
	alpm_option_set_questioncb (priv->alpm, pk_alpm_transaction_conv_cb, NULL);
	alpm_option_set_progresscb (priv->alpm, pk_alpm_transaction_progress_cb, NULL);

	alpm_option_set_dlcb (priv->alpm, pk_alpm_transaction_dlcb, NULL);

	g_cancellable_connect (pk_backend_job_get_cancellable (job),
			       G_CALLBACK (pk_alpm_transaction_cancelled_cb),
			       job, NULL);

	return TRUE;
}

static gchar *
pk_alpm_pkg_build_list (const alpm_list_t *i)
{
	GString *list;

	if (i == NULL)
		return NULL;
	list = g_string_new ("");
	for (; i != NULL; i = i->next) {
		if (i->data == NULL)
			continue;
		g_string_append_printf (list, "%s, ",
					alpm_pkg_get_name (i->data));
	}

	if (list->len > 2)
		g_string_truncate (list, list->len - 2);
	return g_string_free (list, FALSE);
}

static gchar *
pk_alpm_miss_build_list (const alpm_list_t *i)
{
	GString *list;

	if (i == NULL)
		return NULL;
	list = g_string_new ("");
	for (; i != NULL; i = i->next) {
		alpm_depmissing_t *miss = (alpm_depmissing_t *) i->data;
		char *depend = alpm_dep_compute_string (miss->depend);

		g_string_append_printf (list, "%s <- %s, ", depend,
					miss->target);
		free (depend);
	}

	g_string_truncate (list, list->len - 2);
	return g_string_free (list, FALSE);
}

static void
pk_alpm_depend_free (alpm_depend_t *depend)
{
	free (depend->name);
	free (depend->version);
	free (depend->desc);
	free (depend);
}

static void
pk_alpm_depmissing_free (gpointer miss)
{
	alpm_depmissing_t *self = (alpm_depmissing_t *) miss;

	free (self->target);
	pk_alpm_depend_free (self->depend);
	free (self->causingpkg);
	free (miss);
}

static gchar *
pk_alpm_conflict_build_list (const alpm_list_t *i)
{
	GString *list;

	if (i == NULL)
		return NULL;
	list = g_string_new ("");
	for (; i != NULL; i = i->next) {
		alpm_conflict_t *conflict = (alpm_conflict_t *) i->data;
		alpm_depend_t *depend = conflict->reason;

		if (g_strcmp0 (conflict->package1, depend->name) == 0 ||
		    g_strcmp0 (conflict->package2, depend->name) == 0) {
			g_string_append_printf (list, "%s <-> %s, ",
						conflict->package1,
						conflict->package2);
		} else {
			char *reason = alpm_dep_compute_string (depend);
			g_string_append_printf (list, "%s <-> %s (%s), ",
						conflict->package1,
						conflict->package2, reason);
			free (reason);
		}
	}

	g_string_truncate (list, list->len - 2);
	return g_string_free (list, FALSE);
}

static void
pk_alpm_conflict_free (gpointer conflict)
{
	alpm_conflict_t *self = (alpm_conflict_t *) conflict;

	free (self->package1);
	free (self->package2);
	free (conflict);
}

static gchar *
pk_alpm_fileconflict_build_list (const alpm_list_t *i)
{
	GString *list;

	if (i == NULL)
		return NULL;
	list = g_string_new ("");
	for (; i != NULL; i = i->next) {
		alpm_fileconflict_t *conflict = (alpm_fileconflict_t *) i->data;

		if (*conflict->ctarget != '\0') {
			g_string_append_printf (list, "%s <-> %s (%s), ",
						conflict->target,
						conflict->ctarget,
						conflict->file);
		} else {
			g_string_append_printf (list, "%s (%s), ",
						conflict->target,
						conflict->file);
		}
	}

	g_string_truncate (list, list->len - 2);
	return g_string_free (list, FALSE);
}

static void
pk_alpm_fileconflict_free (gpointer conflict)
{
	alpm_fileconflict_t *self = (alpm_fileconflict_t *) conflict;

	free (self->target);
	free (self->file);
	free (self->ctarget);
	free (conflict);
}

gboolean
pk_alpm_transaction_simulate (PkBackendJob *job, GError **error)
{
	PkBackend *backend = pk_backend_job_get_backend (job);
	PkBackendAlpmPrivate *priv = pk_backend_get_user_data (backend);
	alpm_list_t *data = NULL;
	g_autofree gchar *prefix = NULL;

	if (alpm_trans_prepare (priv->alpm, &data) >= 0)
		return TRUE;

	switch (alpm_errno (priv->alpm)) {
	case ALPM_ERR_PKG_INVALID_ARCH:
		prefix = pk_alpm_pkg_build_list (data);
		alpm_list_free (data);
		break;
	case ALPM_ERR_UNSATISFIED_DEPS:
		prefix = pk_alpm_miss_build_list (data);
		alpm_list_free_inner (data, pk_alpm_depmissing_free);
		alpm_list_free (data);
		break;
	case ALPM_ERR_CONFLICTING_DEPS:
		prefix = pk_alpm_conflict_build_list (data);
		alpm_list_free_inner (data, pk_alpm_conflict_free);
		alpm_list_free (data);
		break;
	case ALPM_ERR_FILE_CONFLICTS:
		prefix = pk_alpm_fileconflict_build_list (data);
		alpm_list_free_inner (data, pk_alpm_fileconflict_free);
		alpm_list_free (data);
		break;
	default:
		if (data != NULL)
			syslog (LOG_DAEMON | LOG_WARNING, "unhandled error %d", alpm_errno (priv->alpm));
		break;
	}

	if (prefix != NULL) {
		alpm_errno_t alpm_err = alpm_errno (priv->alpm);
		g_set_error (error, PK_ALPM_ERROR, alpm_err, "%s: %s", prefix,
			     alpm_strerror (alpm_err));
	} else {
		alpm_errno_t alpm_err = alpm_errno (priv->alpm);
		g_set_error_literal (error, PK_ALPM_ERROR, alpm_err,
				     alpm_strerror (alpm_err));
	}

	return FALSE;
}

void
pk_alpm_transaction_packages (PkBackendJob *job)
{
	PkBackend *backend = pk_backend_job_get_backend (job);
	PkBackendAlpmPrivate *priv = pk_backend_get_user_data (backend);
	const alpm_list_t *i;
	PkInfoEnum info;

	/* emit packages that would have been installed */
	for (i = alpm_trans_get_add (priv->alpm); i != NULL; i = i->next) {
		const gchar *name;
		if (pk_backend_job_is_cancelled (job))
			break;

		name = alpm_pkg_get_name (i->data);

		if (alpm_db_get_pkg (priv->localdb, name) != NULL) {
			info = PK_INFO_ENUM_UPDATING;
		} else {
			info = PK_INFO_ENUM_INSTALLING;
		}

		pk_alpm_pkg_emit (job, i->data, info);
	}

	switch (pk_backend_job_get_role (job)) {
	case PK_ROLE_ENUM_UPDATE_PACKAGES:
		info = PK_INFO_ENUM_OBSOLETING;
		break;

	default:
		info = PK_INFO_ENUM_REMOVING;
		break;
	}

	/* emit packages that would have been removed */
	for (i = alpm_trans_get_remove (priv->alpm); i != NULL; i = i->next) {
		if (pk_backend_job_is_cancelled (job))
			break;
		pk_alpm_pkg_emit (job, i->data, info);
	}
}

static gchar *
pk_alpm_string_build_list (const alpm_list_t *i)
{
	GString *list;

	if (i == NULL)
		return NULL;
	list = g_string_new ("");
	for (; i != NULL; i = i->next)
		g_string_append_printf (list, "%s, ", (const gchar *) i->data);

	g_string_truncate (list, list->len - 2);
	return g_string_free (list, FALSE);
}

gboolean
pk_alpm_transaction_commit (PkBackendJob *job, GError **error)
{
	PkBackend *backend = pk_backend_job_get_backend (job);
	PkBackendAlpmPrivate *priv = pk_backend_get_user_data (backend);
	alpm_list_t *data = NULL;
	g_autofree gchar *prefix = NULL;
	gint commit_result;

	if (pk_backend_job_is_cancelled (job))
		return TRUE;

	pk_backend_job_set_allow_cancel (job, FALSE);
	pk_backend_job_set_status (job, PK_STATUS_ENUM_RUNNING);

	pk_backend_transaction_inhibit_start (backend);
	commit_result = alpm_trans_commit (priv->alpm, &data);
	pk_backend_transaction_inhibit_end (backend);
	if (commit_result >= 0)
		return TRUE;

	switch (alpm_errno (priv->alpm)) {
	case ALPM_ERR_FILE_CONFLICTS:
		prefix = pk_alpm_fileconflict_build_list (data);
		alpm_list_free_inner (data, pk_alpm_fileconflict_free);
		alpm_list_free (data);
		break;
	case ALPM_ERR_PKG_INVALID:
		prefix = pk_alpm_string_build_list (data);
		alpm_list_free (data);
		break;
	default:
		if (data != NULL) {
			syslog (LOG_DAEMON | LOG_WARNING, "unhandled error %d",
				   alpm_errno (priv->alpm));
		}
		break;
	}

	if (prefix != NULL) {
		alpm_errno_t alpm_err = alpm_errno (priv->alpm);
		g_set_error (error, PK_ALPM_ERROR, alpm_err, "%s: %s", prefix,
			     alpm_strerror (alpm_err));
	} else {
		alpm_errno_t alpm_err = alpm_errno (priv->alpm);
		g_set_error_literal (error, PK_ALPM_ERROR, alpm_err,
				     alpm_strerror (alpm_err));
	}

	return FALSE;
}

gboolean
pk_alpm_transaction_end (PkBackendJob *job, GError **error)
{
	PkBackend *backend = pk_backend_job_get_backend (job);
	PkBackendAlpmPrivate *priv = pk_backend_get_user_data (backend);

	alpm_option_set_eventcb (priv->alpm, NULL, NULL);
	alpm_option_set_questioncb (priv->alpm, NULL, NULL);
	alpm_option_set_progresscb (priv->alpm, NULL, NULL);

	alpm_option_set_dlcb (priv->alpm, NULL, NULL);
//	alpm_option_set_totaldlcb (priv->alpm, NULLa;

	if (dpkg != NULL)
		pk_alpm_transaction_download_end (job);
	if (tpkg != NULL)
		pk_alpm_transaction_output_end ();

	g_assert (pkalpm_current_job);
	pkalpm_current_job = NULL;

	if (alpm_trans_release (priv->alpm) < 0) {
		alpm_errno_t alpm_err = alpm_errno (priv->alpm);
		g_set_error_literal (error, PK_ALPM_ERROR, alpm_err,
				     alpm_strerror (alpm_err));
		return FALSE;
	}

	return TRUE;
}

gboolean
pk_alpm_transaction_finish (PkBackendJob *job, GError *error)
{

	pk_alpm_transaction_end (job, (error == NULL) ? &error : NULL);

	return pk_alpm_finish (job, error);
}
