#include "config.h"

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#if HAVE_ALLOCA_H
# include <alloca.h>
#endif

#include "miscfn.h"
#include "install.h"
#include "intl.h"
#include "lib/rpmlib.h"
#include "messages.h"
#include "query.h"
#include "url.h"

static int hashesPrinted = 0;

static void printHash(const unsigned long amount, const unsigned long total);
static void printPercent(const unsigned long amount, const unsigned long total);
static void printDepProblems(FILE * f, struct rpmDependencyConflict * conflicts,
			     int numConflicts);

static void printHash(const unsigned long amount, const unsigned long total) {
    int hashesNeeded;

    if (hashesPrinted != 50) {
	hashesNeeded = 50 * (total ? (((float) amount) / total) : 1);
	while (hashesNeeded > hashesPrinted) {
	    printf("#");
	    hashesPrinted++;
	}
	fflush(stdout);
	hashesPrinted = hashesNeeded;

	if (hashesPrinted == 50)
	    printf("\n");
    }
}

static void printPercent(const unsigned long amount, const unsigned long total) 
{
    printf("%%%% %f\n", (total
                        ? ((float) ((((float) amount) / total) * 100))
                        : 100.0));
    fflush(stdout);
}

static int installPackages(char * rootdir, char ** packages, char * location,
			    int numPackages, int installFlags, 
			    int interfaceFlags, rpmdb db) {
    int i, fd;
    int numFailed = 0;
    char ** filename;
    char * printFormat = NULL;
    char * chptr;
    int rc;
    rpmNotifyFunction fn;
    char * netsharedPath = NULL;

    if (interfaceFlags & INSTALL_PERCENT)
	fn = printPercent;
    else if (interfaceFlags & INSTALL_HASH)
	fn = printHash;
    else
	fn = NULL;

    netsharedPath = rpmGetVar(RPMVAR_NETSHAREDPATH);

    for (i = 0, filename = packages; i < numPackages; i++, filename++) {
	if (!*filename) continue;

	hashesPrinted = 0;

	fd = open(*filename, O_RDONLY);
	if (fd < 0) {
	    fprintf(stderr, _("error: cannot open file %s\n"), *filename);
	    numFailed++;
	    *filename = NULL;
	    continue;
	} 

	if (interfaceFlags & INSTALL_PERCENT) 
	    printFormat = "%%f %s:%s:%s\n";
	else if (rpmIsVerbose() && (interfaceFlags & INSTALL_HASH)) {
	    chptr = strrchr(*filename, '/');
	    if (!chptr)
		chptr = *filename;
	    else
		chptr++;

	    printFormat = "%-28s";
	} else if (rpmIsVerbose())
	    printf(_("Installing %s\n"), *filename);

	if (db) {
	    rc = rpmInstallPackage(rootdir, db, fd, location, installFlags, fn, 
				   printFormat, netsharedPath);
	} else {
	    if (installFlags &= RPMINSTALL_TEST) {
		rpmMessage(RPMMESS_DEBUG, "stopping source install as we're "
			"just testing\n");
		rc = 0;
	    } else {
		rc = rpmInstallSourcePackage(rootdir, fd, NULL, fn,
					     printFormat);
	    }
	} 

	if (rc == 1) {
	    fprintf(stderr, 
		    _("error: %s does not appear to be a RPM package\n"), 
		    *filename);
	}
	    
	if (rc) {
	    fprintf(stderr, _("error: %s cannot be installed\n"), *filename);
	    numFailed++;
	}

	close(fd);
    }

    return numFailed;
}

int doInstall(char * rootdir, char ** argv, char * location, int installFlags, 
	      int interfaceFlags) {
    rpmdb db;
    int fd, i;
    int mode, rc;
    char ** packages, ** tmpPackages;
    char ** filename;
    int numPackages;
    int numTmpPackages = 0, numBinaryPackages = 0, numSourcePackages = 0;
    int numFailed = 0;
    Header * binaryHeaders;
    int isSource;
    int tmpnum = 0;
    rpmDependencies rpmdep;
    struct rpmDependencyConflict * conflicts;
    int numConflicts;
    int stopInstall = 0;

    if (installFlags & RPMINSTALL_TEST) 
	mode = O_RDONLY;
    else
	mode = O_RDWR | O_CREAT;

    rpmMessage(RPMMESS_DEBUG, "counting packages to install\n");
    for (filename = argv, numPackages = 0; *filename; filename++, numPackages++)
	;

    rpmMessage(RPMMESS_DEBUG, "found %d packages\n", numPackages);
    packages = alloca((numPackages + 1) * sizeof(char *));
    packages[numPackages] = NULL;
    tmpPackages = alloca((numPackages + 1) * sizeof(char *));
    binaryHeaders = alloca((numPackages + 1) * sizeof(Header));
	
    rpmMessage(RPMMESS_DEBUG, "looking for packages to download\n");
    for (filename = argv, i = 0; *filename; filename++) {
	if (urlIsURL(*filename)) {
	    if (rpmIsVerbose()) {
		printf(_("Retrieving %s\n"), *filename);
	    }
	    packages[i] = alloca(strlen(*filename) + 30 + strlen(rootdir));
	    sprintf(packages[i], "%s/var/tmp/rpm-ftp-%d-%d.tmp", rootdir, 
		    tmpnum++, (int) getpid());
	    rpmMessage(RPMMESS_DEBUG, 
			"getting %s as %s\n", *filename, packages[i]);
	    fd = urlGetFile(*filename, packages[i]);
	    if (fd < 0) {
		fprintf(stderr, 
			_("error: skipping %s - transfer failed - %s\n"), 
			*filename, ftpStrerror(fd));
		numFailed++;
	    } else {
		tmpPackages[numTmpPackages++] = packages[i];
		i++;
	    }
	} else {
	    packages[i++] = *filename;
	}
    }

    rpmMessage(RPMMESS_DEBUG, "retrieved %d packages\n", numTmpPackages);

    rpmMessage(RPMMESS_DEBUG, "finding source and binary packages\n");
    for (filename = packages; *filename; filename++) {
	fd = open(*filename, O_RDONLY);
	if (fd < 0) {
	    fprintf(stderr, _("error: cannot open file %s\n"), *filename);
	    numFailed++;
	    *filename = NULL;
	    continue;
	}

	rc = rpmReadPackageHeader(fd, &binaryHeaders[numBinaryPackages], 
					&isSource, NULL, NULL);

	close(fd);
	
	if (rc == 1) {
	    fprintf(stderr, 
			_("error: %s does not appear to be a RPM package\n"), 
			*filename);
	}
	    
	if (rc) {
	    fprintf(stderr, _("error: %s cannot be installed\n"), *filename);
	    numFailed++;
	    *filename = NULL;
	} else if (isSource) {
	    /* the header will be NULL if this is a v1 source package */
	    if (binaryHeaders[numBinaryPackages])
		headerFree(binaryHeaders[numBinaryPackages]);

	    numSourcePackages++;
	} else {
	    numBinaryPackages++;
	}
    }

    rpmMessage(RPMMESS_DEBUG, "found %d source and %d binary packages\n", 
		numSourcePackages, numBinaryPackages);

    if (numBinaryPackages) {
	rpmMessage(RPMMESS_DEBUG, "opening database mode: 0%o\n", mode);
	if (rpmdbOpen(rootdir, &db, mode, 0644)) {
	    fprintf(stderr, "error: cannot open %s%s/packages.rpm\n", 
			rpmGetVar(RPMVAR_DBPATH), rootdir);
	    exit(1);
	}

	rpmdep = rpmdepDependencies(db);
	for (i = 0; i < numBinaryPackages; i++)
	    if (installFlags & RPMINSTALL_UPGRADE)
		rpmdepUpgradePackage(rpmdep, binaryHeaders[i],
				     packages[i]);
	    else
		rpmdepAddPackage(rpmdep, binaryHeaders[i], 
				    packages[i]);

	if (!(interfaceFlags & INSTALL_NODEPS)) {
	    if (rpmdepCheck(rpmdep, &conflicts, &numConflicts)) {
		numFailed = numPackages;
		stopInstall = 1;
	    }

	    if (!stopInstall && conflicts) {
		fprintf(stderr, _("failed dependencies:\n"));
		printDepProblems(stderr, conflicts, numConflicts);
		rpmdepFreeConflicts(conflicts, numConflicts);
		numFailed = numPackages;
		stopInstall = 1;
	    }
	}

	if (!(interfaceFlags & INSTALL_NOORDER)) {
	    if (rpmdepOrder(rpmdep, (void ***) &packages)) {
		numFailed = numPackages;
		stopInstall = 1;
	    }
	}

	rpmdepDone(rpmdep);
    }
    else
	db = NULL;

    if (!stopInstall) {
	rpmMessage(RPMMESS_DEBUG, "installing binary packages\n");
	numFailed += installPackages(rootdir, packages, location, numPackages, 
				     installFlags, interfaceFlags, db);
    }

    for (i = 0; i < numTmpPackages; i++)
	unlink(tmpPackages[i]);

    for (i = 0; i < numBinaryPackages; i++) 
	headerFree(binaryHeaders[i]);

    if (db) rpmdbClose(db);

    return numFailed;
}

int doUninstall(char * rootdir, char ** argv, int uninstallFlags,
		 int interfaceFlags) {
    rpmdb db;
    dbiIndexSet matches;
    int i, j;
    int mode;
    int rc;
    int count;
    int numPackages, packageOffsetsAlloced;
    int * packageOffsets;
    char ** arg;
    int numFailed = 0;
    rpmDependencies rpmdep;
    struct rpmDependencyConflict * conflicts;
    int numConflicts;
    int stopUninstall = 0;

    rpmMessage(RPMMESS_DEBUG, "counting packages to uninstall\n");
    for (arg = argv, numPackages = 0; *arg; arg++, numPackages++)
	;

    packageOffsetsAlloced = numPackages;
    packageOffsets = malloc(sizeof(int *) * packageOffsetsAlloced);

    if (uninstallFlags & RPMUNINSTALL_TEST) 
	mode = O_RDONLY;
    else
	mode = O_RDWR | O_EXCL;
	
    if (rpmdbOpen(rootdir, &db, mode, 0644)) {
	fprintf(stderr, _("cannot open %s%s/packages.rpm\n"), 
		rpmGetVar(RPMVAR_DBPATH), rootdir);
	exit(1);
    }

    j = 0;
    numPackages = 0;
    for (arg = argv; *arg; arg++) {
	rc = rpmdbFindByLabel(db, *arg, &matches);
	if (rc == 1) {
	    fprintf(stderr, _("package %s is not installed\n"), *arg);
	    numFailed++;
	} else if (rc == 2) {
	    fprintf(stderr, _("error searching for package %s\n"), *arg);
	    numFailed++;
	} else {
	    count = 0;
	    for (i = 0; i < matches.count; i++)
		if (matches.recs[i].recOffset) count++;

	    if (count > 1 && !(interfaceFlags & UNINSTALL_ALLMATCHES)) {
		fprintf(stderr, _("\"%s\" specifies multiple packages\n"), 
			*arg);
		numFailed++;
	    }
	    else { 
		numPackages += count;
		if (numPackages > packageOffsetsAlloced) {
		    packageOffsetsAlloced = numPackages + 5;
		    packageOffsets = realloc(packageOffsets, 
				sizeof(int *) * packageOffsetsAlloced);
		}
		for (i = 0; i < matches.count; i++) {
		    if (matches.recs[i].recOffset) {
			packageOffsets[j++] = matches.recs[i].recOffset;
		    }
		}
	    }

	    dbiFreeIndexRecord(matches);
	}
    }

    rpmMessage(RPMMESS_DEBUG, "found %d packages to uninstall\n", numPackages);

    if (!(interfaceFlags & UNINSTALL_NODEPS)) {
	rpmdep = rpmdepDependencies(db);
	for (i = 0; i < numPackages; i++)
	    rpmdepRemovePackage(rpmdep, packageOffsets[i]);

	if (rpmdepCheck(rpmdep, &conflicts, &numConflicts)) {
	    numFailed = numPackages;
	    stopUninstall = 1;
	}

	rpmdepDone(rpmdep);

	if (!stopUninstall && conflicts) {
	    fprintf(stderr, _("removing these packages would break "
			      "dependencies:\n"));
	    printDepProblems(stderr, conflicts, numConflicts);
	    rpmdepFreeConflicts(conflicts, numConflicts);
	    numFailed += numPackages;
	    stopUninstall = 1;
	}
    }

    if (!stopUninstall) {
	for (i = 0; i < numPackages; i++) {
	    rpmMessage(RPMMESS_DEBUG, "uninstalling record number %d\n",
			packageOffsets[i]);
	    if (rpmRemovePackage(rootdir, db, packageOffsets[i], 
				 uninstallFlags))
		numFailed++;
	}
    }

    rpmdbClose(db);

    free(packageOffsets);

    return numFailed;
}

int doSourceInstall(char * rootdir, char * arg, char ** specFile) {
    int fd;
    int rc;

    fd = open(arg, O_RDONLY);
    if (fd < 0) {
	fprintf(stderr, _("error: cannot open %s\n"), arg);
	return 1;
    }

    if (rpmIsVerbose())
	printf("Installing %s\n", arg);

    rc = rpmInstallSourcePackage(rootdir, fd, specFile, NULL, NULL);
    if (rc == 1) {
	fprintf(stderr, _("error: %s cannot be installed\n"), arg);
    }

    close(fd);

    return rc;
}

void printDepFlags(FILE * f, char * version, int flags) {
    if (flags)
	fprintf(f, " ");

    if (flags & RPMSENSE_LESS) 
	fprintf(f, "<");
    if (flags & RPMSENSE_GREATER)
	fprintf(f, ">");
    if (flags & RPMSENSE_EQUAL)
	fprintf(f, "=");
    if (flags & RPMSENSE_SERIAL)
	fprintf(f, "S");

    if (flags)
	fprintf(f, " %s", version);
}

static void printDepProblems(FILE * f, struct rpmDependencyConflict * conflicts,
			     int numConflicts) {
    int i;

    for (i = 0; i < numConflicts; i++) {
	fprintf(f, "\t%s", conflicts[i].needsName);
	if (conflicts[i].needsFlags) {
	    printDepFlags(stderr, conflicts[i].needsVersion, 
			  conflicts[i].needsFlags);
	}

	if (conflicts[i].sense == RPMDEP_SENSE_REQUIRES) 
	    fprintf(f, _(" is needed by %s-%s-%s\n"), conflicts[i].byName, 
		    conflicts[i].byVersion, conflicts[i].byRelease);
	else
	    fprintf(f, _(" conflicts with %s-%s-%s\n"), conflicts[i].byName, 
		    conflicts[i].byVersion, conflicts[i].byRelease);
    }
}
