/*
 *  sync.c
 * 
 *  Copyright (c) 2002-2005 by Judd Vinet <jvinet@zeroflux.org>
 * 
 *  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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 
 *  USA.
 */

#include "config.h"
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <libtar.h>
#include <zlib.h>
/* pacman */
#include "log.h"
#include "util.h"
#include "error.h"
#include "list.h"
#include "package.h"
#include "db.h"
#include "cache.h"
#include "deps.h"
#include "trans.h"
#include "sync.h"
#include "rpmvercmp.h"
#include "handle.h"

extern pmhandle_t *handle;

pmsync_t *sync_new(int type, pmpkg_t *lpkg, pmpkg_t *spkg)
{
	pmsync_t *sync;

	if((sync = (pmsync_t *)malloc(sizeof(pmsync_t))) == NULL) {
		return(NULL);
	}

	sync->type = type;
	sync->lpkg = lpkg;
	sync->spkg = spkg;
	
	return(sync);
}

/* It returns a PMList of packages extracted from the given archive
 * (the archive must have been generated by gensync)
 */
PMList *sync_load_archive(char *archive)
{
	PMList *lp = NULL;
	DIR *dir = NULL;
	TAR *tar = NULL;
	tartype_t gztype = {
		(openfunc_t)_alpm_gzopen_frontend,
		(closefunc_t)gzclose,
		(readfunc_t)gzread,
		(writefunc_t)gzwrite
	};

	if(tar_open(&tar, archive, &gztype, O_RDONLY, 0, TAR_GNU) == -1) {
		pm_errno = PM_ERR_NOT_A_FILE;
		goto error;
	}

	/* readdir tmp_dir */
	/* for each subdir, parse %s/desc and %s/depends */

	tar_close(tar);

	return(lp);

error:
	if(tar) {
		tar_close(tar);
	}
	if(dir) {
		closedir(dir);
	}
	return(NULL);
}

int sync_sysupgrade(PMList **data)
{
	PMList *i, *j, *k;
	PMList *targets = NULL;

	*data = NULL;

	/* check for "recommended" package replacements */
	for(i = handle->dbs_sync; i; i = i->next) {
		PMList *j;

		for(j = db_get_pkgcache(i->data); j; j = j->next) {
			pmpkg_t *spkg = j->data;

			for(k = spkg->replaces; k; k = k->next) {
				PMList *m;

				for(m = db_get_pkgcache(handle->db_local); m; m = m->next) {
					pmpkg_t *lpkg = m->data;

					if(!strcmp(k->data, lpkg->name)) {
						if(pm_list_is_strin(lpkg->name, handle->ignorepkg)) {
							_alpm_log(PM_LOG_WARNING, "%s-%s: ignoring package upgrade (to be replaced by %s-%s)",
								lpkg->name, lpkg->version, spkg->name, spkg->version);
						} else {
							pmsync_t *sync = sync_new(PM_SYSUPG_REPLACE, lpkg, spkg);
							if(sync == NULL) {
								pm_errno = PM_ERR_MEMORY;
								goto error;
							}
							_alpm_log(PM_LOG_DEBUG, "%s-%s elected for upgrade (to be replaced by %s-%s)",
							          lpkg->name, lpkg->version, spkg->name, spkg->version);
							targets = pm_list_add(targets, sync);
						}
					}
				}
			}
		}
	}

	/* match installed packages with the sync dbs and compare versions */
	for(i = db_get_pkgcache(handle->db_local); i; i = i->next) {
		int cmp;
		pmpkg_t *local = i->data;
		pmpkg_t *spkg = NULL;
		pmsync_t *sync;

		for(j = handle->dbs_sync; !spkg && j; j = j->next) {

			for(k = db_get_pkgcache(j->data); !spkg && k; k = k->next) {
				pmpkg_t *sp = k->data;
				if(!strcmp(local->name, sp->name)) {
					spkg = sp;
				}
			}
		}
		if(spkg == NULL) {
			/*fprintf(stderr, "%s: not found in sync db.  skipping.", local->name);*/
			continue;
		}

		/* compare versions and see if we need to upgrade */
		cmp = rpmvercmp(local->version, spkg->version);
		if(cmp > 0 && !spkg->force) {
			/* local version is newer */
			_alpm_log(PM_LOG_FLOW1, "%s-%s: local version is newer",
				local->name, local->version);
		} else if(cmp == 0) {
			/* versions are identical */
		} else if(pm_list_is_strin(i->data, handle->ignorepkg)) {
			/* package should be ignored (IgnorePkg) */
			_alpm_log(PM_LOG_FLOW1, "%s-%s: ignoring package upgrade (%s)",
				local->name, local->version, spkg->version);
		} else {
			sync = sync_new(PM_SYSUPG_UPGRADE, local, spkg);
			if(sync == NULL) {
				pm_errno = PM_ERR_MEMORY;
				goto error;
			}
			_alpm_log(PM_LOG_DEBUG, "%s-%s elected for upgrade (upgrade: %s => %s)",
				local->name, local->version, local->version, spkg->version);
			targets = pm_list_add(targets, sync);
		}
	}

	*data = targets;

	return(0);

error:
	FREELIST(targets);
	return(-1);
}

int sync_addtarget(pmdb_t *db, PMList *dbs_sync, pmtrans_t *trans, char *name)
{
	char targline[(PKG_NAME_LEN-1)+1+(DB_TREENAME_LEN-1)+1];
	char *targ, *treename;
	PMList *j;
	pmpkg_t *local;
	pmpkg_t *sync = NULL;
	int cmp;

	ASSERT(db != NULL, RET_ERR(PM_ERR_DB_NULL, -1));
	ASSERT(trans != NULL, RET_ERR(PM_ERR_TRANS_NULL, -1));
	ASSERT(name != NULL, RET_ERR(PM_ERR_WRONG_ARGS, -1));

	strncpy(targline, name, (PKG_NAME_LEN-1)+1+(DB_TREENAME_LEN-1)+1);
	targ = strchr(targline, '/');
	if(targ) {
		*targ = '\0';
		targ++;
		treename = targline;
		for(j = dbs_sync; j && !sync; j = j->next) {
			pmdb_t *dbs = j->data;
			if(strcmp(dbs->treename, targline) == 0) {
				sync = db_scan(dbs, targ, INFRQ_DESC|INFRQ_DEPENDS);
			}
		}
	} else {
		targ = targline;
		for(j = dbs_sync; j && !sync; j = j->next) {
			pmdb_t *dbs = j->data;
			sync = db_scan(dbs, targ, INFRQ_DESC|INFRQ_DEPENDS);
		}
	}
	if(sync == NULL) {
		RET_ERR(PM_ERR_PKG_NOT_FOUND, -1);
	}

	/* if not a sysupgrade, compare versions and determine if it is necessary */
	if(!trans->flags & PM_TRANS_FLAG_SYSUPG) {
		local = db_get_pkgfromcache(db, name);
		if(local) {
			cmp = alpm_pkg_vercmp(local->version, sync->version);
			if(cmp > 0) {
				/* local version is newer - get confirmation first */
				/* ORE
				if(!yesno(":: %s-%s: local version is newer.  Upgrade anyway? [Y/n] ", lpkgname, lpkgver)) {
				}*/
				_alpm_log(PM_LOG_WARNING, "%s-%s: local version is newer -- skipping");
				FREE(sync);
				return(0);
			} else if(cmp == 0) {
				/* versions are identical */
				/* ORE
				if(!yesno(":: %s-%s: is up to date.  Upgrade anyway? [Y/n] ", lpkgname, lpkgver)) {
				}*/
				_alpm_log(PM_LOG_WARNING, "%s-%s: is up to date -- skipping");
				FREE(sync);
				return(0);
			}
		}
	}

	/* add the package to the transaction */
	trans->packages = pm_list_add(trans->packages, sync);

	return(0);
}

int sync_prepare(pmdb_t *db, pmtrans_t *trans, PMList **data)
{
	PMList *list = NULL;
	PMList *trail = NULL;
	PMList *i;

	if(trans->packages == NULL) {
		return(0);
	}

	/* Resolve targets dependencies */
	if(!(trans->flags & PM_TRANS_FLAG_NODEPS)) {
		TRANS_CB(trans, PM_TRANS_EVT_RESOLVEDEPS_START, NULL, NULL);

		list = pm_list_new();
		trail = pm_list_new();

		for(i = trans->packages; i; i = i->next) {
			pmpkg_t *sync = i->data;
			_alpm_log(PM_LOG_FLOW1, "resolving dependencies for package %s", sync->name);
			if(resolvedeps(handle->db_local, handle->dbs_sync, sync, list, trail) == -1) {
				/* pm_errno is set by resolvedeps */
				goto error;
			}
			/* ORE
			if called from makepkg, reason should be set to REASON_DEPEND */
			sync->reason = PM_PKG_REASON_EXPLICIT;
		}
		FREELISTPTR(trail);

		for(i = list; i; i = i->next) {
			pmpkg_t *sync = i->data;
			if(sync == NULL) {
				continue;
			}
			if(!pkg_isin(sync, trans->packages)) {
				pmpkg_t *pkg = db_scan(sync->data, sync->name, INFRQ_DESC|INFRQ_DEPENDS);
				if(pkg == NULL) {
					_alpm_log(PM_LOG_ERROR, "could not find package \"%s\" in repository %s",
					          sync->name, ((pmdb_t *)sync->data)->treename);
					pm_errno = PM_ERR_PKG_NOT_FOUND;
					goto error;
				}
				pkg->reason = PM_PKG_REASON_DEPEND;
				trans->packages = pm_list_add(trans->packages, pkg);
			}
		}
		FREELISTPTR(list);

		TRANS_CB(trans, PM_TRANS_EVT_RESOLVEDEPS_DONE, NULL, NULL);
	}

	/* ORE
	check for inter-conflicts and whatnot */

	/* ORE
	any packages in rmtargs need to be removed from final.
	rather than ripping out nodes from final, we just copy over
	our "good" nodes to a new list and reassign. */

	/* ORE
	Check dependencies of packages in rmtargs and make sure
	we won't be breaking anything by removing them.
	If a broken dep is detected, make sure it's not from a
	package that's in our final (upgrade) list. */

	return(0);

error:
	FREELISTPTR(list);
	FREELISTPTR(trail);
	return(-1);
}

int sync_commit(pmdb_t *db, pmtrans_t *trans)
{
	PMList *i, *files = NULL;
	PMList *final = NULL;
	PMList *data;
	pmtrans_t *tr;

	/* remove any conflicting packages (WITHOUT dep checks) */

	/* remove to-be-replaced packages */

	/* install targets */
	tr = trans_new(PM_TRANS_TYPE_UPGRADE, 0);
	for(i = files; i; i = i->next) {
		trans_addtarget(tr, i->data);
	}
	trans_prepare(tr, &data);
	trans_commit(tr);
	trans_free(tr);

	/* propagate replaced packages' requiredby fields to their new owners */
	for(i = final; i; i = i->next) {
		/*syncpkg_t *sync = (syncpkg_t*)i->data;
		if(sync->replaces) {
			pkginfo_t *new = db_scan(db, sync->pkg->name, INFRQ_DEPENDS);
			for(j = sync->replaces; j; j = j->next) {
				pkginfo_t *old = (pkginfo_t*)j->data;
				// merge lists
				for(k = old->requiredby; k; k = k->next) {
					if(!is_in(k->data, new->requiredby)) {
						// replace old's name with new's name in the requiredby's dependency list
						PMList *m;
						pkginfo_t *depender = db_scan(db, k->data, INFRQ_DEPENDS);
						for(m = depender->depends; m; m = m->next) {
							if(!strcmp(m->data, old->name)) {
								FREE(m->data);
								m->data = strdup(new->name);
							}
						}
						db_write(db, depender, INFRQ_DEPENDS);

						// add the new requiredby
						new->requiredby = list_add(new->requiredby, strdup(k->data));
					}
				}
			}
			db_write(db, new, INFRQ_DEPENDS);
			FREEPKG(new);
		}*/
	}

	/* cache needs to be rebuilt */
	db_free_pkgcache(db);

	return(0);
}

/* vim: set ts=2 sw=2 noet: */