#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 "hash.h"

#define MAXPKGS 1024

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]); */
	    add_to_table(ht, installedFiles[installedX]);
	    installedX++;
	} else {
	    rc = strcmp(availFiles[availX], installedFiles[installedX]);
	    if (rc > 0) {
		/* Avail > Installed -- file has moved */
		/* printf("=> %s\n", installedFiles[installedX]); */
		add_to_table(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++;
	    }
	}
    }
		
}

void errorFunction(void)
{
}

int findUpgradePackages(rpmdb db, int ugdbfd, struct hash_table *ht,
			int *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 */
	    printf("Failed with bad header\n");
	    exit(1);
	}

	/* XXX - Need to do serial number stuff someday */
	/*printf("Avail: %s-%s-%s\n", name, version, release);*/
	rc = rpmdbFindPackage(db, name, &matches);
	/*printf("Find returned: %d\n", rc);*/
	    
	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");*/
	    resultArray[pkgNum] = 1;

	    getEntry(h, RPMTAG_FILENAMES, NULL,
		     (void **) &availFiles, &availFileCount);

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

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

		free(installedFiles);
		freeHeader(installedHeader);
	    }

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

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

	freeHeader(h);
	pkgNum++;
    }

    *availPkgs = pkgNum;
    return 0;
}

int removeMovedFilesAlreadyHandled(int ugdbfd, struct hash_table *ht,
				   int *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);

	    getEntry(h, RPMTAG_FILENAMES, NULL,
		     (void **) &availFiles, &availFileCount);

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

	pkgNum++;
	freeHeader(h);
    }

    return 0;
}

int findPackagesWithRelocatedFiles(int ugdbfd, struct hash_table *ht,
				   int *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);

	    getEntry(h, RPMTAG_FILENAMES, NULL,
		     (void **) &availFiles, &availFileCount);

	    for (i = 0; i < availFileCount; i++) {
		if ((file = in_table(ht, availFiles[i]))) {
		    *file = '\0';
		    printf("Found file in %s: %s\n", name, availFiles[i]);
		    resultArray[pkgNum] = 1;
		    break;
		}
	    }
	    
	    free(availFiles);
	}

	pkgNum++;
	freeHeader(h);
    }

    return 0;
}

int main(int argc, char **argv)
{
    int fd;
    rpmdb db;
    struct hash_table *hashTable;
    int installThisPackage[MAXPKGS];
    int availPkgs, i;

    if ((fd = open(argv[1], O_RDONLY)) < 0) {
	perror("open");
	exit(1);
    }

    if (rpmdbOpen("/", &db, O_RDONLY, 0644)) {
	fprintf(stderr, "error: cannot open /var/lib/rpm/packages.rpm\n");
	exit(1);
    }

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

    hashTable = new_table(1103);

    findUpgradePackages(db, fd, hashTable, installThisPackage, &availPkgs);
    rpmdbClose(db);
    hash_stats(hashTable);
    removeMovedFilesAlreadyHandled(fd, hashTable, installThisPackage);
    findPackagesWithRelocatedFiles(fd, hashTable, installThisPackage);

    close(fd);

    return 0;
}
