#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <rpm/rpmlib.h>
#include <rpm/header.h>
#include <rpm/rpmerr.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(struct pkgSet *psp, struct hash_table *ht, rpmdb db)
{
    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);
	getEntry(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 (getEntry(h, RPMTAG_FILENAMES, NULL,
			  (void **) &installedFiles, &installedFileCount)) {
		compareFileList(0, NULL, installedFileCount,
				installedFiles, ht);
		free(installedFiles);
	    }
	}
	
	freeHeader(h);
	num = rpmdbNextRecNum(db, num);
    }
}

static void errorFunction(void)
{
}

static int findUpgradePackages(rpmdb db, int ugdbfd, struct hash_table *ht,
			       char **resultArray, int *availPkgs)
{
    int pkgNum, skipThis;
    Header h, installedHeader;
    char *name, *version, *release;
    dbIndexSet matches;
    int rc, i;
    char **installedFiles, **availFiles;
    int installedFileCount, availFileCount;

    lseek(ugdbfd, 0, SEEK_SET);

    pkgNum = 0;

    while ((h = readHeader(ugdbfd, HEADER_MAGIC))) {
	name = version = release = NULL;
	getEntry(h, RPMTAG_NAME, NULL, (void **) &name, NULL);
	getEntry(h, RPMTAG_VERSION, NULL, (void **) &version, NULL);
	getEntry(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;
	    errSetCallback(errorFunction);
	    for (i = 0; i < matches.count; i++) {
		if (rpmEnsureOlder(db, name, version, release,
				   matches.recs[i].recOffset)) {
		    /* already have a newer version installed */
		    /*printf("Already have newer version\n");*/
		    skipThis = 1;
		    break;
		}
	    }
	    errSetCallback(NULL);
	    if (! skipThis) {
		/* no newer version installed */
		/*printf("No newer version installed\n");*/
		skipThis = 0;
	    }
	} else {
	    skipThis = 1;
	    /*printf("Not installed\n");*/
	}
	
	if (skipThis) {
	    /*printf("DO NOT INSTALL\n");*/
	} else {
	    /*printf("UPGRADE\n");*/
	    if (!resultArray[pkgNum]) {
		resultArray[pkgNum] = strdup(name);
	    }

	    if (!getEntry(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 (!getEntry(installedHeader, RPMTAG_FILENAMES, NULL,
			      (void **) &installedFiles,
			      &installedFileCount)) {
		    installedFiles = NULL;
		    installedFileCount = 0;
		}

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

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

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

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

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


	freeHeader(h);
	pkgNum++;
    }

    *availPkgs = pkgNum;
    return 0;
}

static int removeMovedFilesAlreadyHandled(int ugdbfd, struct hash_table *ht,
					  char **resultArray)
{
    int pkgNum;
    char *name;
    int i;
    Header h;
    char **availFiles;
    int availFileCount;
    char *file;

    lseek(ugdbfd, 0, SEEK_SET);

    pkgNum = 0;
    while ((h = readHeader(ugdbfd, HEADER_MAGIC))) {
	if (resultArray[pkgNum]) {
	    name = NULL;
	    getEntry(h, RPMTAG_NAME, NULL, (void **) &name, NULL);

	    if (!getEntry(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]);*/
		    if (!resultArray[pkgNum]) {
			resultArray[pkgNum] = strdup(name);
		    }
		    break;
		}
	    }
	    if (availFiles) {
		free(availFiles);
	    }
	}

	pkgNum++;
	freeHeader(h);
    }

    return 0;
}

static int findPackagesWithRelocatedFiles(int ugdbfd, struct hash_table *ht,
					  char **resultArray)
{
    int pkgNum;
    char *name;
    int i;
    Header h;
    char **availFiles;
    int availFileCount;
    char *file;

    lseek(ugdbfd, 0, SEEK_SET);

    pkgNum = 0;
    while ((h = readHeader(ugdbfd, HEADER_MAGIC))) {
	if (! resultArray[pkgNum]) {
	    name = NULL;
	    getEntry(h, RPMTAG_NAME, NULL, (void **) &name, NULL);

	    availFiles = NULL;
	    availFileCount = 0;
	    if (getEntry(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]);*/
			if (!resultArray[pkgNum]) {
			    resultArray[pkgNum] = strdup(name);
			}
			break;
		    }
		}
		free(availFiles);
	    }
	}

	pkgNum++;
	freeHeader(h);
    }

    return 0;
}

int ugFindUpgradePackages(struct pkgSet *psp, char *installRoot,
			  char *ugdbName)
{
    int fd;
    rpmdb db;
    struct hash_table *hashTable;
    char *installThisPackage[MAXPKGS];
    int availPkgs, i, upgradeCount;
    struct packageInfo **pack;
    struct packageInfo key;
    struct packageInfo *keyaddr = &key;

    if ((fd = open(ugdbName, O_RDONLY)) < 0) {
	logMessage("failed opeing %s", ugdbName);
	return(INST_ERROR);
    }

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

    for (i = 0; i < MAXPKGS; i++) {
	installThisPackage[i] = NULL;
    }

    hashTable = htNewTable(1103);

    addLostFiles(psp, hashTable, db);
    
    if (findUpgradePackages(db, fd, hashTable, \
			    installThisPackage, &availPkgs)) {
	close(fd);
	rpmdbClose(db);
	return(INST_ERROR);
    }
    /*hash_stats(hashTable);*/

    removeMovedFilesAlreadyHandled(fd, hashTable, installThisPackage);

    findPackagesWithRelocatedFiles(fd, hashTable, installThisPackage);

    htFreeHashTable(hashTable);
    
    /*printMemStats("Done");*/

    rpmdbClose(db);
    close(fd);

    upgradeCount = 0;
    for (i = 0; i < MAXPKGS; i++) {
	if (installThisPackage[i]) {
	    key.name = installThisPackage[i];
	    pack = bsearch(&keyaddr, psp->packages, psp->numPackages,
			   sizeof(*psp->packages), (void *)pkgCompare);
	    (*pack)->selected = 1;
	    free(installThisPackage[i]);
	    upgradeCount++;
	}
    }

    logMessage("ugFindUpgradePackages() marked %d packages", upgradeCount);

    return 0;
}
