#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <rpm/rpmlib.h>
#include <rpm/header.h>
#include <string.h>

#include "install.h"
#include "log.h"
#include "hash.h"
#include "pkgs.h"

#define MAXPKGS 1024

#if 0
static void printMemStats(char *mess)
{
    char buf[1024];
    printf("%s\n", mess);
    sprintf(buf, "cat /proc/%d/status | grep VmSize", getpid());
    system(buf);
}
#endif

static void compareFileList(int availFileCount, char **availFiles,
			    int installedFileCount, char **installedFiles,
			    struct hash_table *ht)
{
    int installedX, availX, rc;
    
    availX = 0;
    installedX = 0;
    while (installedX < installedFileCount) {
	if (availX == availFileCount) {
	    /* All the rest have moved */
	    /* printf("=> %s\n", installedFiles[installedX]); */
	    if (strncmp(installedFiles[installedX], "/etc/rc.d/", 10))
		htAddToTable(ht, installedFiles[installedX]);
	    installedX++;
	} else {
	    rc = strcmp(availFiles[availX], installedFiles[installedX]);
	    if (rc > 0) {
		/* Avail > Installed -- file has moved */
		/* printf("=> %s\n", installedFiles[installedX]); */
		if (strncmp(installedFiles[installedX], "/etc/rc.d/", 10))
		    htAddToTable(ht, installedFiles[installedX]);
		installedX++;
	    } else if (rc < 0) {
		/* Avail < Installed -- avail has some new files */
		availX++;
	    } else {
		/* Files are equal -- file not moved */
		availX++;
		installedX++;
	    }
	}
    }
}

static void addLostFiles( rpmdb db, struct pkgSet *psp, struct hash_table *ht)
{
    int num;
    Header h;
    char *name;
    struct packageInfo **pack;
    struct packageInfo key;
    struct packageInfo *keyaddr = &key;
    char **installedFiles;
    int installedFileCount;

    num = rpmdbFirstRecNum(db);
    while (num) {
	h = rpmdbGetRecord(db, num);
	headerGetEntry(h, RPMTAG_NAME, NULL, (void **) &name, NULL);
	key.name = name;
	
	pack = bsearch(&keyaddr, psp->packages, psp->numPackages,
		       sizeof(*psp->packages), (void *)pkgCompare);
	if (!pack) {
	    if (headerGetEntry(h, RPMTAG_FILENAMES, NULL,
			  (void **) &installedFiles, &installedFileCount)) {
		compareFileList(0, NULL, installedFileCount,
				installedFiles, ht);
		free(installedFiles);
	    }
	}
	
	headerFree(h);
	num = rpmdbNextRecNum(db, num);
    }
}

static int findPackagesWithObsoletes(rpmdb db, struct pkgSet *psp)
{
    dbiIndexSet matches;
    int rc, count, obsoletesCount;
    struct packageInfo **pip;
    char **obsoletes;

    count = psp->numPackages;
    pip = psp->packages;
    while (count--) {
	if ((*pip)->selected) {
	    pip++;
	    continue;
	}

	if (headerGetEntry((*pip)->h, RPMTAG_OBSOLETES, NULL,
		       (void **) &obsoletes, &obsoletesCount)) {
	    while (obsoletesCount--) {
		rc = rpmdbFindPackage(db, obsoletes[obsoletesCount], &matches);
		if (!rc) {
		    if (matches.count) {
			(*pip)->selected = 1;
			dbiFreeIndexRecord(matches);
			break;
		    }

		    dbiFreeIndexRecord(matches);
		}
	    }

	    free(obsoletes);
	}

	pip++;
    }

    return 0;
}

static void errorFunction(void)
{
}

static int findUpgradePackages(rpmdb db, struct pkgSet *psp,
			       struct hash_table *ht)
{
    int skipThis;
    Header h, installedHeader;
    char *name, *version, *release;
    dbiIndexSet matches;
    int rc, i, count;
    char **installedFiles, **availFiles;
    int installedFileCount, availFileCount;
    struct packageInfo **pip;

    count = psp->numPackages;
    pip = psp->packages;
    while (count--) {
	h = (*pip)->h;
	name = version = release = NULL;
	headerGetEntry(h, RPMTAG_NAME, NULL, (void **) &name, NULL);
	headerGetEntry(h, RPMTAG_VERSION, NULL, (void **) &version, NULL);
	headerGetEntry(h, RPMTAG_RELEASE, NULL, (void **) &release, NULL);
	if (! (name && version && release)) {
	    /* bum header */
	    logMessage("Failed with bad header");
	    return(INST_ERROR);
	}

	/* XXX - Need to do serial number stuff someday */
	/*printf("Avail: %s-%s-%s\n", name, version, release);*/
	rc = rpmdbFindPackage(db, name, &matches);

	if (rc == 0) {
	    skipThis = 0;
	    rpmErrorSetCallback(errorFunction);
	    for (i = 0; i < matches.count; i++) {
		installedHeader =
		    rpmdbGetRecord(db, matches.recs[i].recOffset);
		if (rpmVersionCompare(installedHeader, h) >= 0) {
		    /* already have a newer version installed */
		    /*printf("Already have newer version\n");*/
		    skipThis = 1;
		    break;
		}
	    }
	    rpmErrorSetCallback(NULL);
	    if (! skipThis) {
		/*printf("No newer version installed\n");*/
	    }
	} else {
	    skipThis = 1;
	    /*printf("Not installed\n");*/
	}
	
	if (skipThis) {
	    /*printf("DO NOT INSTALL\n");*/
	} else {
	    /*printf("UPGRADE\n");*/
	    (*pip)->selected = 1;

	    if (!headerGetEntry(h, RPMTAG_FILENAMES, NULL,
			  (void **) &availFiles, &availFileCount)) {
		availFiles = NULL;
		availFileCount = 0;
	    }

	    for (i = 0; i < matches.count; i++) {
		/* Compare the file lists */
		installedHeader =
		    rpmdbGetRecord(db, matches.recs[i].recOffset);
		if (!headerGetEntry(installedHeader, RPMTAG_FILENAMES, NULL,
			      (void **) &installedFiles,
			      &installedFileCount)) {
		    installedFiles = NULL;
		    installedFileCount = 0;
		}

		compareFileList(availFileCount, availFiles,
				installedFileCount, installedFiles, ht);

		if (installedFiles) {
		    free(installedFiles);
		}
		headerFree(installedHeader);
	    }

	    if (availFiles) {
		free(availFiles);
	    }
	}

	if (rc == 0) {
	    dbiFreeIndexRecord(matches);
	}

	/*printf("\n\n");*/

	pip++;
    }

    return 0;
}

static int removeMovedFilesAlreadyHandled(struct pkgSet *psp,
					  struct hash_table *ht)
{
    char *name;
    int i, count;
    Header h;
    char **availFiles;
    int availFileCount;
    char *file;
    struct packageInfo **pip;

    count = psp->numPackages;
    pip = psp->packages;
    while (count--) {
	h = (*pip)->h;
	if ((*pip)->selected) {
	    name = NULL;
	    headerGetEntry(h, RPMTAG_NAME, NULL, (void **) &name, NULL);

	    if (!headerGetEntry(h, RPMTAG_FILENAMES, NULL,
			  (void **) &availFiles, &availFileCount)) {
		availFiles = NULL;
		availFileCount = 0;
	    }

	    for (i = 0; i < availFileCount; i++) {
		if ((file = htInTable(ht, availFiles[i]))) {
		    *file = '\0';
		    /*printf("File already in %s: %s\n", name, availFiles[i]);*/
		    break;
		}
	    }
	    if (availFiles) {
		free(availFiles);
	    }
	}

	pip++;
    }

    return 0;
}

static int findPackagesWithRelocatedFiles(struct pkgSet *psp,
					  struct hash_table *ht)
{
    char *name;
    int i, count;
    Header h;
    char **availFiles;
    int availFileCount;
    char *file;
    struct packageInfo **pip;

    count = psp->numPackages;
    pip = psp->packages;
    while (count--) {
	h = (*pip)->h;
	if (! (*pip)->selected) {
	    name = NULL;
	    headerGetEntry(h, RPMTAG_NAME, NULL, (void **) &name, NULL);

	    availFiles = NULL;
	    availFileCount = 0;
	    if (headerGetEntry(h, RPMTAG_FILENAMES, NULL,
			 (void **) &availFiles, &availFileCount)) {
		for (i = 0; i < availFileCount; i++) {
		    if ((file = htInTable(ht, availFiles[i]))) {
			*file = '\0';
			/*printf("Found file in %s: %s\n", name,
			  availFiles[i]);*/
			(*pip)->selected = 1;
			break;
		    }
		}
		free(availFiles);
	    }
	}

	pip++;
    }

    return 0;
}

static void printCount(struct pkgSet *psp)
{
    int i, upgradeCount;
    struct packageInfo **pip;
    
    upgradeCount = 0;
    pip = psp->packages;
    i = psp->numPackages;
    while (i--) {
	if ((*pip)->selected) {
	    upgradeCount++;
	}
	pip++;
    }
    logMessage("marked %d packages for upgrade", upgradeCount);
}

static int unmarkPackagesAlreadyInstalled(rpmdb db, struct pkgSet *psp)
{
    dbiIndexSet matches;
    Header h, installedHeader;
    char *name, *version, *release;
    struct packageInfo **pip;
    int count, rc, i;

    count = psp->numPackages;
    pip = psp->packages;
    while (count--) {
	if ((*pip)->selected) {
	    h = (*pip)->h;
	    /* If this package is already installed, don't bother */
	    name = version = release = NULL;
	    headerGetEntry(h, RPMTAG_NAME, NULL, (void **) &name, NULL);
	    headerGetEntry(h, RPMTAG_VERSION, NULL, (void **) &version, NULL);
	    headerGetEntry(h, RPMTAG_RELEASE, NULL, (void **) &release, NULL);
	    if (! (name && version && release)) {
		/* bum header */
		logMessage("Failed with bad header");
		return(INST_ERROR);
	    }
	    rc = rpmdbFindPackage(db, name, &matches);
	    if (rc == 0) {
		rpmErrorSetCallback(errorFunction);
		for (i = 0; i < matches.count; i++) {
		    installedHeader =
			rpmdbGetRecord(db, matches.recs[i].recOffset);
		    if (rpmVersionCompare(installedHeader, h) >= 0) {
			/* already have a newer version installed */
			/*printf("Already have newer version\n");*/
			(*pip)->selected = 0;
			break;
		    }
		}
		rpmErrorSetCallback(NULL);
		dbiFreeIndexRecord(matches);
	    }
	}

	pip++;
    }

    return 0;
}
	    

int ugFindUpgradePackages(struct pkgSet *psp, char *installRoot)
{
    rpmdb db;
    struct hash_table *hashTable;

    logDebugMessage(("ugFindUpgradePackages() ..."));

    rpmReadConfigFiles(NULL, NULL, NULL, 0);

    if (rpmdbOpen(installRoot, &db, O_CREAT | O_RDWR, 0644)) {
	logMessage("failed opening %s/var/lib/rpm/packages.rpm",
		     installRoot);
	return(INST_ERROR);
    }

    hashTable = htNewTable(1103);

    /* For all packages that are installed, if there is no package       */
    /* available by that name, add the package's files to the hash table */
    addLostFiles(db, psp, hashTable);
    logDebugMessage(("added lost files"));
    printCount(psp);
    
    /* Find packges that are new, and mark them in installThisPackage,  */
    /* updating availPkgs with the count.  Also add files to the hash   */
    /* table that do not exist in the new package - they may have moved */
    if (findUpgradePackages(db, psp, hashTable)) {
	rpmdbClose(db);
	return(INST_ERROR);
    }
    logDebugMessage(("found basic packages to upgrade"));
    printCount(psp);
    /*hash_stats(hashTable);*/

    /* Remove any files that were added to the hash table that are in */
    /* some other package marked for upgrade.                         */
    removeMovedFilesAlreadyHandled(psp, hashTable);
    logDebugMessage(("removed extra files which have moved"));
    printCount(psp);

    findPackagesWithRelocatedFiles(psp, hashTable);
    logDebugMessage(("found packages with relocated files"));
    printCount(psp);

    findPackagesWithObsoletes(db, psp);
    logDebugMessage(("found packages that obsolete installed packages"));
    printCount(psp);
    
    unmarkPackagesAlreadyInstalled(db, psp);
    logDebugMessage(("unmarked packages already installed"));
    printCount(psp);
    
    htFreeHashTable(hashTable);
    
    /*printMemStats("Done");*/

    rpmdbClose(db);

    return 0;
}
