/*
 * updatedpackagesdialog.cc
 *
 * Copyright (c) 2002, 2003 Frerich Raabe <raabe@kde.org>
 *
 * 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. For licensing and distribution details, check the
 * accompanying file 'COPYING'.
 */
#include "portlistview.h"
#include "portmgr.h"
#include "updatedpackagesdialog.h"

#include <klocale.h>

#include <ctype.h>
#include <err.h>
#include <stdlib.h>

// The following two C functions for comparing version numbers have been
// taken from a FreeBSD 5.0-p7 system's
// /usr/src/usr.sbin/pkg_install/lib/version.c file.
namespace {
/*
 * version_of(pkgname, epoch, revision) returns a pointer to the version
 * portion of a package name and the two special components.
 *
 * Jeremy D. Lea.
 */
const char *
version_of(const char *pkgname, int *epoch, int *revision)
{
    char *ch;

    if (pkgname == NULL)
	errx(2, "%s: Passed NULL pkgname.", __func__);
    if (epoch != NULL) {
	if ((ch = strrchr(pkgname, ',')) == NULL)
	    *epoch = 0;
	else
	    *epoch = atoi(&ch[1]);
    }
    if (revision != NULL) {
	if ((ch = strrchr(pkgname, '_')) == NULL)
	    *revision = 0;
	else
	    *revision = atoi(&ch[1]);
    }
    /* Cheat if we are just passed a version, not a valid package name */
    if ((ch = strrchr(pkgname, '-')) == NULL)
	return pkgname;
    else
	return &ch[1];
}

/*
 * version_cmp(pkg1, pkg2) returns -1, 0 or 1 depending on if the version
 * components of pkg1 is less than, equal to or greater than pkg2. No
 * comparison of the basenames is done.
 *
 * The port version is defined by:
 * ${PORTVERSION}[_${PORTREVISION}][,${PORTEPOCH}]
 * ${PORTEPOCH} supersedes ${PORTVERSION} supersedes ${PORTREVISION}.
 * See the commit log for revision 1.349 of ports/Mk/bsd.port.mk
 * for more information.
 *
 * The epoch and revision are defined to be a single number, while the rest
 * of the version should conform to the porting guidelines. It can contain
 * multiple components, separated by a period, including letters.
 *
 * The tests below allow for significantly more latitude in the version
 * numbers than is allowed in the guidelines. No point in wasting user's
 * time enforcing them here. That's what flamewars are for.
 *
 * Jeremy D. Lea.
 */
int
version_cmp(const char *pkg1, const char *pkg2)
{
    const char *c1, *c2, *v1, *v2;
    char *t1, *t2;
    int e1, e2, r1, r2, n1, n2;

    v1 = version_of(pkg1, &e1, &r1);
    v2 = version_of(pkg2, &e2, &r2);
    /* Minor optimisation. */
    if (strcmp(v1, v2) == 0)
	return 0;
    /* First compare epoch. */
    if (e1 != e2)
	return (e1 < e2 ? -1 : 1);
    else {
	/*
	 * We walk down the versions, trying to convert to numbers.
	 * We terminate when we reach an underscore, a comma or the
	 * string terminator, thanks to a nasty trick with strchr().
	 * strtol() conveniently gobbles up the chars it converts.
	 */
	c1 = strchr("_,", v1[0]);
	c2 = strchr("_,", v2[0]);
	while (c1 == NULL && c2 == NULL) {
	    n1 = strtol(v1, &t1, 10);
	    n2 = strtol(v2, &t2, 10);
	    if (n1 != n2)
		return (n1 < n2 ? -1 : 1);
	    /*
	     * The numbers are equal, check for letters. Assume they're
	     * letters purely because strtol() didn't chomp them.
	     */
	    c1 = strchr("_,.", t1[0]);
	    c2 = strchr("_,.", t2[0]);
	    if (c1 == NULL && c2 == NULL) {
		/* Both have letters. Compare them. */
		if (t1[0] != t2[0])
		    return (t1[0] < t2[0] ? -1 : 1);
		/* Boring. The letters are equal. Carry on. */
		v1 = &t1[1], v2 = &t2[1];
	    } else if (c1 == NULL) {
		/*
		 * Letters are strange. After a number, a letter counts
		 * as greater, but after a period it's less.
		 */
		return (isdigit(v1[0]) ? 1 : -1);
	    } else if (c2 == NULL) {
		return (isdigit(v2[0]) ? -1 : 1);
	    } else {
		/* Neither were letters.  Advance over the period. */
		v1 = (t1[0] == '.' ? &t1[1] : t1);
		v2 = (t2[0] == '.' ? &t2[1] : t2);
	    }
	    c1 = strchr("_,", v1[0]);
	    c2 = strchr("_,", v2[0]);
	}
	/* If we got here, check if one version has something left. */
	if (c1 == NULL)
	    return (isdigit(v1[0]) ? 1 : -1);
	if (c2 == NULL)
	    return (isdigit(v2[0]) ? -1 : 1);
	/* We've run out of version. Try the revision... */
	if (r1 != r2)
	    return (r1 < r2 ? -1 : 1);
	else
	    return 0;
    }
}
} // Anonymous namespace

using namespace Barry;

UpdatedPackagesProgressDialog::UpdatedPackagesProgressDialog(
	QWidget *parent, const char *name )
	: ActionProgressDialog( parent, name )
{
	addColumn( i18n( "Name" ) );
	addColumn( i18n( "Current" ) );
	addColumn( i18n( "Installed" ) );

	search( PortMgr::self().installedPorts() );
}

void UpdatedPackagesProgressDialog::processPort( const Port &port )
{
	const QString pkgScheme = port.name() + QString::fromLatin1( "-" );
	const QString pkg1 = pkgScheme + port.installedVersion();
	const QString pkg2 = pkgScheme + port.version();
	if ( ::version_cmp( pkg1.latin1(), pkg2.latin1() ) == -1 )
		enlistPort( port );
}

void UpdatedPackagesProgressDialog::setupItem( PortItem *item )
{
	item->setText( 2, item->port().installedVersion() );
}

#include "updatedpackagesdialog.moc"
// vim:ts=4:sw=4:noet:list
