#include <alloca.h>
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <newt.h>
#include <rpmlib.h>
#include <header.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

#include "hash.h"
#include "install.h"
#include "intl.h"
#include "kickstart.h"
#include "log.h"
#include "pkgs.h"
#include "windows.h"
#include "smp.h"

#define FILENAME_TAG 1000000

static int selectPackagesByGroup(struct pkgSet * psp);
static int skipPackage(char * name);
static int selectComponents(struct componentSet * csp, struct pkgSet * psp,
			    int * doIndividual);
static int strptrCmp(const void * a, const void * b);
static void showPackageInfo(Header h);
static int queryIndividual(int * result);

char * skipList[] = { "XFree86-8514", "XFree86-AGX", "XFree86-Mach32",
		      "XFree86-Mach64", "XFree86-Mach8", "XFree86-Mono",
		      "XFree86-P9000", "XFree86-S3", "XFree86-S3V",
		      "XFree86-SVGA", "XFree86-W32", "XFree86-I128",
		      "XFree86-Sun", "XFree86-SunMono", "XFree86-Sun24",
                      "XFree86-3DLabs", "kernel-boot",
		      "metroess", "metrotmpl", NULL };

static int strptrCmp(const void * a, const void * b) {
    const char * const * one = a;
    const char * const * two = b;

    return strcmp(*one, *two);
}

static int psGetArchScore(Header h) {
    void * pkgArch;
    int type, count;

    if (!headerGetEntry(h, RPMTAG_ARCH, &type, (void **) &pkgArch, &count) ||
        type == RPM_INT8_TYPE)
       return 150;
    else
        return rpmMachineScore(RPM_MACHTABLE_INSTARCH, pkgArch);
}

int pkgCompare(void * first, void * second) {
    struct packageInfo ** a = first;
    struct packageInfo ** b = second;

    /* put packages w/o names at the end */
    if (!(*a)->name) return 1;
    if (!(*b)->name) return -1;

    return strcasecmp((*a)->name, (*b)->name);
}

static int pkgCompareVer(void * first, void * second) {
    struct packageInfo ** a = first;
    struct packageInfo ** b = second;
    int ret, score1, score2;

    /* put packages w/o names at the end */
    if (!(*a)->name) return 1;
    if (!(*b)->name) return -1;

    ret = strcasecmp((*a)->name, (*b)->name);
    if (ret) return ret;
    score1 = psGetArchScore((*a)->h);
    if (!score1) return 1;
    score2 = psGetArchScore((*b)->h);
    if (!score2) return -1;
    if (score1 < score2) return -1;
    if (score1 > score2) return 1;
    return rpmVersionCompare((*b)->h, (*a)->h);
}

static void pkgSort(struct pkgSet * psp) {
    int i;
    char *name;

    qsort(psp->packages, psp->numPackages, sizeof(*psp->packages),
	 (void *) pkgCompareVer);

    name = psp->packages[0]->name;
    if (!name) {
       psp->numPackages = 0;
       return;
    }
    for (i = 1; i < psp->numPackages; i++) {
       if (!psp->packages[i]->name) break;
       if (!strcmp(psp->packages[i]->name, name))
	   psp->packages[i]->name = NULL;
       else
	   name = psp->packages[i]->name;
    }

    qsort(psp->packages, psp->numPackages, sizeof(*psp->packages),
	 (void *) pkgCompareVer);

    for (i = 0; i < psp->numPackages; i++)
       if (!psp->packages[i]->name) break;
    psp->numPackages = i;
}


int psUsingDirectory(char * dirname, struct pkgSet * psp) {
    DIR * dir;
    struct dirent * ent;
    FD_t fd;
    int rc, isSource;
    Header h;
    int packagesAlloced;
    struct pkgSet ps;
    int count, type;
    unsigned int * sizeptr;
    char * name, * group;
    char * filename;

    ps.numPackages = 0;
    packagesAlloced = 5;
    ps.packages = malloc(sizeof(*ps.packages) * packagesAlloced);

    logMessage("scanning %s for packages", dirname);
    dir = opendir(dirname);
    if (!dir) {
	errorWindow("error opening directory");
	return INST_ERROR;
    }

    errno = 0;
    ent = readdir(dir);
    if (errno) {
	free(ps.packages);
	errorWindow("error reading from directory");
	closedir(dir);
	return INST_ERROR;
    }

    filename = alloca(strlen(dirname) + 500);

    winStatus(33, 3, "Running", "Scanning available packages...");

    while (ent) {
	if (!(ent->d_name[0] == '.' && (ent->d_name[1] == '\0' || 
	    ((ent->d_name[1] == '.') && (ent->d_name[2] == '\0'))))) {
	    sprintf(filename, "%s/%s", dirname, ent->d_name);
	    fd = fdOpen(filename, O_RDONLY, 0666);

	    if (!fd) {
		logMessage("failed to open %s: %s", filename,
			   strerror(errno));
	    } else {
		rc = rpmReadPackageHeader(fd, &h, &isSource, NULL, NULL);

		fdClose(fd);
		if (rc) {
		    logMessage("failed to rpmReadPackageHeader %s:", 
				ent->d_name);
		} else {
		    if (ps.numPackages == packagesAlloced) {
			packagesAlloced += 5;
			ps.packages = realloc(ps.packages,
				sizeof(*ps.packages) * packagesAlloced);
		    }

		    ps.packages[ps.numPackages] = 
				malloc(sizeof(struct packageInfo));
		    ps.packages[ps.numPackages]->h = h;
		    ps.packages[ps.numPackages]->selected = 0;
		    ps.packages[ps.numPackages]->manuallySelected = 0;
		    ps.packages[ps.numPackages]->data = strdup(ent->d_name);

		    headerGetEntry(h, RPMTAG_NAME, &type, (void **) &name, 
					&count);
		    if (headerGetEntry(h, RPMTAG_SIZE, &type, (void **) 
				  &sizeptr, &count))
			ps.packages[ps.numPackages]->size = *sizeptr;
		    else
			ps.packages[ps.numPackages]->size = 0;

		    if (skipPackage(name)) 
			ps.packages[ps.numPackages]->inmenu = 0;
		    else
			ps.packages[ps.numPackages]->inmenu = 1;

		    if (!headerGetEntry(h, RPMTAG_GROUP, &type, 
					(void **) &group, &count)) {
			group = "(unknown group)";
		    } else if (!strlen(group)) {
			group = "(unknown group)";
		    }

		    ps.packages[ps.numPackages]->name = name;
		    ps.packages[ps.numPackages]->group = group;
	
		    ps.numPackages++;
		}
	    }
	}

	errno = 0;
	ent = readdir(dir);
	if (errno) {
	    newtPopWindow();
	    errorWindow("error reading from directory (2): %s");
	    free(ps.packages);
	    closedir(dir);
	    return INST_ERROR;
	}
    }
    
    pkgSort(&ps);

    *psp = ps;

    closedir(dir);

    newtPopWindow();

    return 0;
}

int psReadComponentsFile(char * filespec, struct pkgSet * psp, 
			 struct componentSet * csp) {
    FILE * f;
    char buf[255];
    int inComp;
    int line = 0;
    char * start;
    char * chptr;
    int compsAlloced;
    int packagesAlloced = 0;
    struct componentSet cs;
    struct component * currcomp = NULL;
    struct packageInfo packkey, ** pack;
    struct packageInfo * keyaddr = &packkey;
    int i;
    int baseNum = 0;
    int skipMissing = 0;

    f = fopen(filespec, "r");
    if (!f) {
	errorWindow(_("Cannot open components file: %s"));
	return INST_ERROR;
    }
  
    /* get the version number */
    line++;
    if (!fgets(buf, sizeof(buf), f)) {
	errorWindow(_("Cannot read components file: %s"));
	fclose(f);
	return INST_ERROR;
    }

    if (strcmp(buf, "0\n") && strcmp(buf, "0.1\n")) {
	newtWinMessage(_("Error"), _("Ok"), 
			_("Comps file is not version 0.1 as expected"));
	fclose(f);
	return INST_ERROR;
    }

    compsAlloced = 5;
    cs.numComponents = 0;
    cs.comps = malloc(sizeof(*cs.comps) * compsAlloced);
    cs.base = NULL;

    inComp = 0;
    while (fgets(buf, sizeof(buf), f)) {
	line++;

	/* remove any trailing '\n', leave chptr at the end of the string */
	chptr = buf + strlen(buf) - 1;
	if (*chptr == '\n') 
	    *chptr = '\0';
	else
	    chptr++;

	/* strip leading spaces */
	start = buf;
	while (*start && isspace(*start)) start++;

	/* empty string */
	if (!*start) continue;
	
	/* comment */
	if (*start == '#') continue;

	if (!inComp) {
	    /* first digit must be a zero or a one */
	    if (*start != '0' && *start != '1') {
		newtWinMessage(_("Error"), _("Ok"), 
				_("bad comps file at line %d"), line);
		continue;
	    }

	    if (compsAlloced == cs.numComponents) {
		compsAlloced += 5;
		cs.comps = realloc(cs.comps, sizeof(*cs.comps) * compsAlloced);
	    }

	    currcomp = cs.comps + cs.numComponents;
	    currcomp->selected = (*start == '1');
	    currcomp->inmenu = 1;

	    start++;
	    while (*start && isspace(*start)) start++;

	    if (!strncmp(start, "--hide ", 7)) {
		start += 7;
		while (*start && isspace(*start)) start++;
		currcomp->inmenu = 0;
	    }
	

	    if (!*start) {
		newtWinMessage(_("comps Error"), _("Ok"),  
				_("missing component name at line %d"), line);
		continue;
	    }

	    currcomp->name = strdup(start);

	    currcomp->ps.numPackages = 0;
	    packagesAlloced = 5;
	    currcomp->ps.packages = malloc(
		sizeof(struct packageInfo) * packagesAlloced);
	    inComp = 1;
	} else {
	    if (!strcmp(start, "end")) {
		inComp = 0;

		if (!strcasecmp(currcomp->name, "Base"))
		    baseNum = cs.numComponents;

		cs.numComponents++;
	    } else {
		packkey.name = start;
		pack = bsearch(&keyaddr, psp->packages, psp->numPackages,
				sizeof(*psp->packages), (void *) pkgCompare);
		if (!pack) {
		    if (!skipMissing &&
			newtWinChoice(_("comps Error"), _("Ok"),
			      _("Ignore all"),
			      _("package %s at line %d does not exist"), 
			      start, line) == 2)
			skipMissing = 1;
		    continue;
		}

		if (currcomp->ps.numPackages == packagesAlloced) {
		    packagesAlloced += 5;
		    currcomp->ps.packages = realloc(currcomp->ps.packages,
			sizeof(struct packageInfo) * packagesAlloced);
		}

		currcomp->ps.packages[currcomp->ps.numPackages] = *pack;
		currcomp->ps.numPackages++;
	    }
	}
    }

    fclose(f);

    cs.base = cs.comps + baseNum;

    cs.base->inmenu = 0;
    cs.base->selected = 1;

    for (i = 0; i < cs.base->ps.numPackages; i++) {
	cs.base->ps.packages[i]->inmenu = 0;
	cs.base->ps.packages[i]->selected = 1;
    }

    *csp = cs;

    return 0;
}

struct packageCheckbox {
    newtComponent cb, sizeLabel;
    unsigned int * kSelected;
    unsigned int size;
    char state, lastState;
};

#define SELECT_GNOME		1
#define SELECT_COMPONENTS	2
#define SELECT_PACKAGES		3
#define SELECT_VERIFY		4
#define SELECT_DONE		100

int psSelectPackages(struct pkgSet * psp, struct componentSet * csp,
			 char * justComponent, int goForward, int isUpgrade) {
    int rc;
    int stage;
    static int doIndividual = 0;
    struct ksPackage * ksList;
    int ksListLength;
    int i, j, k, dir = 1;
    struct packageInfo key;
    struct packageInfo ** pack, * keyaddr = &key;
    int gnomeComponent = -1;

    /* XXX This is a hack --- install a kernel-smp package if you have
       a smp motherboard */
    if (detectSMP() == 1) {
	key.name = "kernel-smp";
	pack = bsearch(&keyaddr, psp->packages, psp->numPackages,
		       sizeof(*psp->packages), (void *) pkgCompare);
	if (pack)
	    (*pack)->manuallySelected = 1;
    }

    if (justComponent) {
	/* first off, turn on the base packages */
	for (k = 0; k < csp->base->ps.numPackages; k++)
	    csp->base->ps.packages[k]->selected = 1;
	csp->base->selected = 1;

	for (j = 0; j < csp->numComponents; j++)
	    if (!strcasecmp(justComponent, csp->comps[j].name))
		break;

	if (j < csp->numComponents) {
	    for (k = 0; k < csp->comps[j].ps.numPackages; k++)
		csp->comps[j].ps.packages[k]->selected = 1;
	    return 0;
	}
    }

    if (kickstart) {
	ksGetPackageList(&ksList, &ksListLength);

	/* first off, turn on the base packages */
	for (k = 0; k < csp->base->ps.numPackages; k++)
	    csp->base->ps.packages[k]->selected = 1;
	csp->base->selected = 1;

	for (i = 0; i < ksListLength; i++) {
	    if (ksList[i].isComponent) {
		for (j = 0; j < csp->numComponents; j++)
		    if (!strcasecmp(ksList[i].name, csp->comps[j].name))
			break;
		if (j == csp->numComponents) {
		    newtWinMessage(_("Kickstart Error"), _("Ok"), 
				   _("Component %s does not exist.\n"),
				   ksList[i].name);
		} else {
		    for (k = 0; k < csp->comps[j].ps.numPackages; k++)
			csp->comps[j].ps.packages[k]->selected = 1;
		}
	    } else {
		key.name = ksList[i].name;
		pack = bsearch(&keyaddr, psp->packages, psp->numPackages,
				sizeof(*psp->packages), (void *) pkgCompare);
		if (!pack) {
		    newtWinMessage(_("Kickstart Error"), _("Ok"), 
				   _("Package %s does not exist.\n"),
				   key.name);
		} else {
		    (*pack)->selected = 1;
		}
	    }
	}

	rc = psVerifyDependencies(psp, 1);

	return 0;
    } else {
	if (!goForward && doIndividual)
	    stage = SELECT_PACKAGES;
	else
	    stage = SELECT_COMPONENTS;
    }

    if (isUpgrade) {
	/* Make sure X is around */
	for (i = 0; i < psp->numPackages; i++)
	    if (!strcmp(psp->packages[i]->name, "XFree86"))
		break;

	if (i < psp->numPackages && psp->packages[i]->selected) {
	    /* And that gnome-core isn't already installed */
	    for (i = 0; i < psp->numPackages; i++)
		if (!strcmp(psp->packages[i]->name, "gmc"))
		    break;

	    if (i == psp->numPackages || !psp->packages[i]->selected) {
		/* now find the gnome component */
		for (i = 0; i < csp->numComponents; i++)
		    if (strcasestr(csp->comps[i].name, "gnome"))
			break;
		if (i < csp->numComponents) {
		    gnomeComponent = i;
		    stage = SELECT_GNOME;
		}
	    }
	}
    }

    while (stage != SELECT_DONE) {
	switch (stage) {
	  case SELECT_GNOME:
	    rc = newtWinTernary(_("GNOME"), _("Yes"), _("No"), _("Back"),
		_("Would you like to have the GNOME desktop installed? "
		  "It provides an easy to use interface, including a "
		  "drag and drop capability and an integrated help system."));
	    if (rc == 3) return INST_CANCEL;
	    if (rc != 2) {
		csp->comps[gnomeComponent].selected = 1;
		for (i = 0; i < csp->comps[gnomeComponent].ps.numPackages; i++)
		    csp->comps[gnomeComponent].ps.packages[i]->selected = 1;
	
	    }
	    stage = SELECT_COMPONENTS;
	    break;

	  case SELECT_COMPONENTS:
	    if (isUpgrade)
		rc = queryIndividual(&doIndividual);
	    else
		rc = selectComponents(csp, psp, &doIndividual);
	    if (rc) return rc;
	    if (doIndividual)
		stage = SELECT_PACKAGES;
	    else
		stage = SELECT_VERIFY;
	    break;

	  case SELECT_PACKAGES:
	    if (doIndividual) {
	        rc = selectPackagesByGroup(psp);
		if (rc == INST_CANCEL) 
		    stage = SELECT_COMPONENTS;
		else if (rc) 
		    return rc;
		else
		    stage = SELECT_VERIFY;
	    } else {
		if (dir == -1)
		    stage = SELECT_COMPONENTS;
		else
		    stage = SELECT_VERIFY;
	    }
	    break;

	  case SELECT_VERIFY:
	    rc = psVerifyDependencies(psp, 0);
	    if (rc == INST_ERROR) 
		return rc;
	    else if (rc)
		stage = SELECT_PACKAGES, dir = -1;
	    else
		stage = SELECT_DONE;
	}
    } 

    return 0;
}

static void emptyErrorCallback(void) {
}

int psVerifyDependencies(struct pkgSet * psp, int fixup) {
    rpmdb db = NULL;
    rpmTransactionSet rpmdeps;
    int i;
    struct rpmDependencyConflict * conflicts;
    struct packageInfo * package;
    int numConflicts, numProblems;
    newtComponent okay, form, textbox, info, cancel, answer;
    newtGrid grid, buttons;
    char * text, buf[80];
    char selectPackages;
    char * reflowedText;
    int width, height;
    rpmErrorCallBackType old;
    
    if (!access("/mnt/var/lib/rpm/packages.rpm", R_OK)) {
	rpmSetVerbosity(RPMMESS_FATALERROR);
	old = rpmErrorSetCallback(emptyErrorCallback);
	if (rpmdbOpen("/mnt", &db, O_RDWR | O_CREAT, 0644))
	    db = NULL;
	rpmErrorSetCallback(old);
	rpmSetVerbosity(RPMMESS_NORMAL);
    }
    
    rpmdeps = rpmtransCreateSet(db, NULL);

    for (i = 0; i < psp->numPackages; i++) {
	if (psp->packages[i]->selected)
	    rpmtransAddPackage(rpmdeps, psp->packages[i]->h, NULL,
			       psp->packages[i], 0, NULL);
	else
	    rpmtransAvailablePackage(rpmdeps, psp->packages[i]->h,
				     psp->packages[i]);
    }

    rpmdepCheck(rpmdeps, &conflicts, &numConflicts);

    rpmtransFree(rpmdeps);
    if (db) rpmdbClose(db);

    /* XXX This is a hack. rpmlib seems to have problems in the conflicts 
       stuff */
    numProblems = 0;
    for (i = 0; i < numConflicts; i++) {
	if (conflicts[i].sense == RPMDEP_SENSE_REQUIRES) numProblems++;
    }

    if (!numProblems) {
	return 0;
    }

    if (fixup) {
	for (i = 0; i < numConflicts; i++) {
	    package = (void *) conflicts[i].suggestedPackage;
	    if (package) package->selected = 1;
	}

	rpmdepFreeConflicts(conflicts, numConflicts);

	return 0;
    }

    text = malloc(80 * numConflicts);
    *text = '\0';
    for (i = 0; i < numConflicts; i++) {
	/* HACK XXX */
	if (conflicts[i].sense == RPMDEP_SENSE_CONFLICTS) continue;

	package = (void *) conflicts[i].suggestedPackage;
	if (package)
	    sprintf(buf, "%-20s %-20s", conflicts[i].byName, 
		    package->name);
	else
	    sprintf(buf, "%-20s %s", conflicts[i].byName,
		    _("no suggestion"));

	if (i) strcat(text, "\n");
	strcat(text, buf);
    }

    if (!kickstart) {
	form = newtForm(NULL, NULL, 0);

	reflowedText = newtReflowText(
	    _("Some of the packages you have selected to install require "
	      "packages you have not selected. If you just select Ok "
	      "all of those required packages will be installed."),
	      45, 5, 5, &width, &height);
	info = newtTextbox(1, 1, width, height, NEWT_TEXTBOX_WRAP);
	newtTextboxSetText(info, reflowedText);
	free(reflowedText);

	sprintf(buf, "%-20s %-20s", _("Package"), _("Requirement"));
	buttons = newtButtonBar(_("Ok"), &okay, _("Back"), &cancel, NULL);

	textbox = newtTextbox(-1, -1, 45, 5, 
	    numProblems > 5 ? NEWT_FLAG_SCROLL : 0);
	newtTextboxSetText(textbox, text); 

	grid = newtCreateGrid(1, 5);

	newtGridSetField(grid, 0, 0, NEWT_GRID_COMPONENT, info,
			 0, 0, 0, 0, 0, 0);
	newtGridSetField(grid, 0, 1, NEWT_GRID_COMPONENT, 
			 newtLabel(-1, -1, buf), 0, 1, 0, 0, 
			 NEWT_ANCHOR_LEFT, 0);
	newtGridSetField(grid, 0, 2, NEWT_GRID_COMPONENT, textbox,
			 0, 0, 0, 0, 0, 0);
	newtGridSetField(grid, 0, 3, NEWT_GRID_COMPONENT, 
			 newtCheckbox(-1, -1, _("Install packages to "
			   "satisfy dependencies"), '*', NULL, &selectPackages),
			 0, 1, 0, 0, 0, 0);
	newtGridSetField(grid, 0, 4, NEWT_GRID_SUBGRID, buttons, 
			 0, 1, 0, 0, 0, NEWT_GRID_FLAG_GROWX);

	newtGridAddComponentsToForm(grid, form, 1);
	newtFormSetCurrent(form, okay);

	newtGridWrappedWindow(grid, _("Unresolved Dependencies"));
	newtGridFree(grid, 1);
	
	answer = newtRunForm(form);

	newtFormDestroy(form);
	newtPopWindow();

	if (answer == cancel) {
	    free(conflicts);
	    return INST_CANCEL;
	}
    }

    if (kickstart || selectPackages != ' ') {
	for (i = 0; i < numConflicts; i++) {
	    package = (void *) conflicts[i].suggestedPackage;
	    if (package) package->selected = 1;
	}
    }

    free(conflicts);
    
    return 0;
} ;

static int selectComponents(struct componentSet * csp, struct pkgSet * psp,
			    int * doIndividual) {
    int i, j;
    newtComponent okay, form, checklist, checkbox, sb, cancel, answer, blank;
    newtGrid buttons, checkgrid, grid;
    char val, individualPackages, everything;
    char * states;
    int row;
    int numCols, numRows;

    newtGetScreenSize(&numCols, &numRows);

    individualPackages = *doIndividual ? '*' : ' ';

    states = alloca(sizeof(*states) * csp->numComponents);

    form = newtForm(NULL, NULL, 0);

    sb = newtVerticalScrollbar(-1, -1, numRows - 18, 9, 10);
    checklist = newtForm(sb, NULL, 0);
    newtFormSetHeight(checklist, numRows - 18);
    newtFormSetBackground(checklist, NEWT_COLORSET_CHECKBOX);

    for (i = 0, row = 0; i < csp->numComponents; i++) {
	if (csp->comps[i].inmenu) {
	    if (csp->comps[i].selected)
		val = '*';
	    else
		val = ' ';

	    checkbox = newtCheckbox(0, row++, csp->comps[i].name, val, 
				    NULL, &states[i]);
	    newtFormAddComponent(checklist, checkbox);
	} else 
	    states[i] = ' ';
    }

    checkbox = newtCheckbox(0, row++, _("Everything"), ' ', 
			    NULL, &everything);
    newtFormAddComponent(checklist, checkbox);

    blank = newtForm(NULL, NULL, 0);
    newtFormSetHeight(blank, numRows - 18);
    newtFormSetWidth(blank, 2);
    newtFormSetBackground(blank, NEWT_COLORSET_CHECKBOX);
    checkgrid = newtGridHCloseStacked(NEWT_GRID_COMPONENT, checklist,
				      NEWT_GRID_COMPONENT, blank,
				      NEWT_GRID_COMPONENT, sb, NULL);

    checkbox = newtCheckbox(-1, -1, _("Select individual packages"), 
				individualPackages, NULL, &individualPackages);

    
    buttons = newtButtonBar(_("Ok"), &okay, _("Back"), &cancel, NULL);

    grid = newtGridBasicWindow(newtLabel(-1, -1, 
				    _("Choose components to install:")),
		       newtGridVStacked(NEWT_GRID_SUBGRID, checkgrid,
					NEWT_GRID_COMPONENT, checkbox, NULL),
		       buttons);

    newtGridAddComponentsToForm(grid, form, 1);
    newtGridWrappedWindow(grid, _("Components to Install"));
    newtGridFree(grid, 1);

    answer = newtRunForm(form);

    newtFormDestroy(form);
    newtPopWindow();

    if (answer == cancel) return INST_CANCEL;

    *doIndividual = (individualPackages != ' ');

    for (i = 0; i < psp->numPackages; i++) {
	psp->packages[i]->selected = 0;
    }

    if (everything != ' ') {
	for (i = 0; i < psp->numPackages; i++) {
	    for (j = 0; skipList[j]; j++) {
		if (!strcmp(psp->packages[i]->name, skipList[j])) break;
	    }
	    if (!skipList[j]) {
		psp->packages[i]->selected = 1;
	    }
	}
    }

    for (i = 0; i < csp->numComponents; i++) {
	if (!strcasecmp(csp->comps[i].name, "Base")) {
	    csp->comps[i].selected = 1;
	    for (j = 0; j < csp->comps[i].ps.numPackages; j++)
		csp->comps[i].ps.packages[j]->selected |= 
		    csp->comps[i].selected;
	}

	if (csp->comps[i].inmenu) {
	    if (states[i] != ' ')
		csp->comps[i].selected = 1;
	    else
		csp->comps[i].selected = 0;

	    for (j = 0; j < csp->comps[i].ps.numPackages; j++)
		csp->comps[i].ps.packages[j]->selected |= 
			csp->comps[i].selected;
	}
    }

    for (i = 0; i < psp->numPackages; i++) {
	if (psp->packages[i]->manuallySelected)
	    psp->packages[i]->selected = 1;
    }

    return 0;
}

void psFreeComponentSet(struct componentSet * csp) {
    int i;
    struct component * currcomp;

    currcomp = csp->comps;
    for (i = 0; i < csp->numComponents; i++, currcomp++) {
	free(currcomp->ps.packages);
    }

    free(csp->comps);
}

int psFromHeaderListDesc(FD_t fd, struct pkgSet * psp, int noSeek) {
    struct pkgSet ps;
    int end = 0, type, count;
    unsigned int * sizeptr;
    Header h;
    int packagesAlloced;
    char * name, * group;
    int done = 0;

    ps.numPackages = 0;
    packagesAlloced = 5;
    ps.packages = malloc(sizeof(*ps.packages) * packagesAlloced);

    if (!noSeek) {
	count = fdLseek(fd, 0, SEEK_CUR);
	end = fdLseek(fd, 0, SEEK_END); 
	fdLseek(fd, count, SEEK_SET); 
    }

    while (!done) {
	h = headerRead(fd, HEADER_MAGIC_YES);
	if (!h && noSeek) {
	    done = 1;
	} else if (!h) {
	    newtWinMessage(_("Error"), _("Ok"), "error reading header at %d\n", 
		    (int) fdLseek(fd, 0, SEEK_CUR));
	    free(ps.packages);
	    return INST_ERROR;
	} else {
	    headerGetEntry(h, RPMTAG_NAME, &type, (void **) &name, &count);

	    if (!headerGetEntry(h, RPMTAG_GROUP, &type, (void **) &group, 
				&count)) {
		group = "(unknown group)";
	    } else if (!strlen(group))
		group = "(unknown group)";

	    if (ps.numPackages == packagesAlloced) {
		packagesAlloced += 5;
		ps.packages = realloc(ps.packages,
			sizeof(*ps.packages) * packagesAlloced);
	    }

	    ps.packages[ps.numPackages] = 
			malloc(sizeof(struct packageInfo));
	    ps.packages[ps.numPackages]->h = h;
	    ps.packages[ps.numPackages]->selected = 0;
	    ps.packages[ps.numPackages]->manuallySelected = 0;

	    if (headerGetEntry(h, RPMTAG_SIZE, &type, (void **) &sizeptr, 
				&count))
		ps.packages[ps.numPackages]->size = *sizeptr;
	    else
		ps.packages[ps.numPackages]->size = 0;

	    if (skipPackage(name)) 
		ps.packages[ps.numPackages]->inmenu = 0;
	    else
		ps.packages[ps.numPackages]->inmenu = 1;

	    ps.packages[ps.numPackages]->name = name;
	    ps.packages[ps.numPackages]->group = group;
	    headerGetEntry(h, FILENAME_TAG, &type,
		     (void **) &ps.packages[ps.numPackages]->data, 
		     &count);

	    ps.numPackages++;
	}

	if (!noSeek) {
	    if (end <= fdLseek(fd, 0, SEEK_CUR)) 
		done = 1;
	}
    }

    logMessage("psFromHeaderListDesc read %d headers", ps.numPackages);
    
    pkgSort(&ps);

    *psp = ps;

    return 0;
}

int psFromHeaderListFile(char * file, struct pkgSet * psp) {
    int rc;
    FD_t fd;

    fd = fdOpen(file, O_RDONLY, 0644);
    if (!fd < 0) {
	errorWindow(_("error opening header file: %s"));
	return INST_ERROR;
    }

    rc = psFromHeaderListDesc(fd, psp, 0);
    fdClose(fd);

    return rc;
}

static int skipPackage(char * name) {
    char ** item;

    for (item = skipList; *item; item++) {
	if (!strcmp(*item, name)) return 1;
    }

    return 0;
}

struct grpInfo {
    char *group;
    int totalSize;
    int selectedSize;
    int totalPkgs;
    int selectedPkgs;
    int collapsed;
};
#define BYGROUP_GROUP	0x80000000
#define BYGROUP_PKG	0x40000000
#define BYGROUP_CHANGE	0x20000000
#define BYGROUP_MASK	0x0fffffff

static char *printSize(int size) {
    static char buf[8];
    
    if (!size)
    	*buf = '\0';
    else
        sprintf(buf, "%3d.%dM", size / 1024, (size * 10 / 1024) % 10);
    return buf;
}

static unsigned int printGroup(newtComponent listbox, struct grpInfo * groupInfo, char * buf, int len, unsigned int j)
{
    unsigned int k = j & BYGROUP_MASK;

    sprintf(buf, "%c [%c] %-*s %s", groupInfo[k].collapsed ? '+' : '-',
    		 groupInfo[k].selectedPkgs == groupInfo[k].totalPkgs ?
    		 '*' : groupInfo[k].selectedPkgs ? 'o' : ' ',
	    	 len - 15, groupInfo[k].group, 
		 printSize(groupInfo[k].selectedSize));
    /* Why is there no exported way to get num from key? We could use newtListboxSetEntry then. */
    if (newtListboxInsertEntry(listbox, buf, (void *)(j ^ BYGROUP_CHANGE), (void *)j)) {
	j ^= BYGROUP_CHANGE;
	newtListboxInsertEntry(listbox, buf, (void *)(j ^ BYGROUP_CHANGE), (void *)j);
    }
    newtListboxDeleteEntry(listbox, (void *)j);
    return j ^ BYGROUP_CHANGE;
}

static int printPkg(newtComponent listbox, struct pkgSet * psp, char * buf, int len, unsigned int j)
{
    unsigned int k = j & BYGROUP_MASK;

    sprintf(buf, "    [%c] %-*s %s", psp->packages[k]->selected ? '*' : ' ',
		 len - 17, psp->packages[k]->name,
		 printSize(psp->packages[k]->selected ? psp->packages[k]->size / 1024 : 0));
    if (newtListboxInsertEntry(listbox, buf, (void *)(j ^ BYGROUP_CHANGE), (void *)j)) {
	j ^= BYGROUP_CHANGE;
	newtListboxInsertEntry(listbox, buf, (void *)(j ^ BYGROUP_CHANGE), (void *)j);
    }
    newtListboxDeleteEntry(listbox, (void *)j);
    return j ^ BYGROUP_CHANGE;
}

static int selectPackagesByGroup(struct pkgSet * psp) {
    newtComponent done, form, listbox, back, tmp = NULL;
    struct newtExitStruct answer;
    newtComponent sizeLabel;
    int i, row, numGroups, width, height, len;
    unsigned int j, k;
    unsigned int kSelected = 0;
    hashTable ht;
    htIterator iter;
    char b[200], * chptr;
    char * buf;
    char * group = NULL;
    newtGrid grid, subgrid, sizegrid, buttons;
    struct grpInfo *groupInfo;

    newtGetScreenSize(&width, &height);
    
    ht = htNewTable(psp->numPackages);
    for (i = 0, row = 0; i < psp->numPackages; i++) {
	if (psp->packages[i]->inmenu) {
	    group = psp->packages[i]->group;
	    chptr = group;
	    while (*chptr && *chptr != '/') chptr++;
	    if (*chptr == '/') {
		chptr++;
		while (*chptr && *chptr != '/') chptr++;
		if (*chptr == '/') {
		    strncpy(b, group, chptr - group);
		    b[chptr - group] = '\0';
		    group = b;
		}
	    }
	}

	if (group)
	    htAddToTable(ht, group);

	if (psp->packages[i]->selected)
	    kSelected += (psp->packages[i]->size / 1024);
    }

    numGroups = htNumEntries(ht);
    groupInfo = alloca(sizeof(*groupInfo) * numGroups);
    memset(groupInfo, 0, sizeof(*groupInfo) * numGroups);
    htIterStart(&iter);
    len = 0;
    i = 0; j = 0;
    while (htIterGetNext(ht, &iter, &group)) {
    	i = strlen(group);
    	j += i + 1;
    	if (i > len) len = i;
    }
    chptr = alloca(j);
    htIterStart(&iter);
    i = 0;
    while (htIterGetNext(ht, &iter, &group)) {
	groupInfo[i].group = chptr;
	chptr += strlen(group) + 1;
	strcpy(groupInfo[i].group, group);
	groupInfo[i].collapsed = 1;
	for (j = 0; j < psp->numPackages; j++) {
	    if (psp->packages[j]->inmenu && !strcmp(psp->packages[j]->group, group)) {
	        if (strlen (psp->packages[j]->name) > len - 2)
	            len = strlen(psp->packages[j]->name) + 2;
		groupInfo[i].totalSize += (psp->packages[j]->size / 1024);
		groupInfo[i].totalPkgs++;
		if (psp->packages[j]->selected) {
		    groupInfo[i].selectedSize += (psp->packages[j]->size / 1024);
		    groupInfo[i].selectedPkgs++;
		}
	    }
	}
	i++;
    }

    len += 15;
    buf = alloca(len);

    qsort(groupInfo, numGroups, sizeof(*groupInfo), strptrCmp);
    htFreeHashTable(ht);

    form = newtForm(NULL, NULL, 0);

    buttons = newtButtonBar(_("Done"), &done, _("Back"), &back, NULL);

    sprintf(buf, "%dM", kSelected / 1024);
    while (strlen(buf) < 5)
	strcat(buf, " ");
    sizeLabel = newtLabel(-1, -1, buf);

    sizegrid = newtCreateGrid(2, 1);
    newtGridSetField(sizegrid, 0, 0, NEWT_GRID_COMPONENT,
		     newtLabel(-1, -1, _("Installed system size:")),
		     0, 0, 0, 0, NEWT_ANCHOR_LEFT, 0);
    newtGridSetField(sizegrid, 1, 0, NEWT_GRID_COMPONENT, sizeLabel,
		     1, 0, 0, 0, NEWT_ANCHOR_LEFT, 0);


    subgrid = newtCreateGrid(2, 2);
    newtGridSetField(subgrid, 0, 0, NEWT_GRID_COMPONENT,
		     newtLabel(-1, -1, _("Choose a group to examine")),
		     0, 0, 0, 0, NEWT_ANCHOR_LEFT, 0);
    newtGridSetField(subgrid, 0, 1, NEWT_GRID_COMPONENT,
		     newtLabel(-1, -1, _("Press F1 for a package description")),
		     0, 0, 0, 0, NEWT_ANCHOR_LEFT, 0);
    newtGridSetField(subgrid, 1, 0, NEWT_GRID_SUBGRID, sizegrid,
		     3, 0, 0, 0, NEWT_ANCHOR_LEFT, 0);

    listbox = newtListbox(3, 3, height - 16,
			  NEWT_FLAG_RETURNEXIT | NEWT_FLAG_SCROLL);

    for (i = 0; i < numGroups; i++) {
    	sprintf(buf, "+ [%c] %-*s %s", groupInfo[i].selectedPkgs == groupInfo[i].totalPkgs ?
    					'*' : groupInfo[i].selectedPkgs ? 'o' : ' ',
    					len - 15, groupInfo[i].group, printSize(groupInfo[i].selectedSize));
	newtListboxAddEntry(listbox, buf, (void *)(BYGROUP_GROUP|i));
    }

    grid = newtCreateGrid(1, 3);
    newtGridSetField(grid, 0, 0, NEWT_GRID_SUBGRID, subgrid,
		     0, 0, 0, 0, 0, 0);
    newtGridSetField(grid, 0, 1, NEWT_GRID_COMPONENT, listbox,
		     0, 1, 0, 0, 0, 0);
    newtGridSetField(grid, 0, 2, NEWT_GRID_SUBGRID, buttons,
		     0, 1, 0, 0, 0, NEWT_GRID_FLAG_GROWX);

    newtGridAddComponentsToForm(grid, form, 1);
    newtGridWrappedWindow(grid, _("Select Group"));
    newtGridFree(grid, 1);

    newtFormAddHotKey(form, ' ');
    newtFormAddHotKey(form, '*');
    newtFormAddHotKey(form, '+');
    newtFormAddHotKey(form, '-');
    newtFormAddHotKey(form, NEWT_KEY_F1);
    
    /* Main loop.  Handle enter/space bar on group entries
       (start with '+' or '-') and packages (everything else) */
    do {
	newtFormRun(form, &answer);
	j = (unsigned int)newtListboxGetCurrent(listbox);
	k = j & BYGROUP_MASK;
	
	if (answer.reason == NEWT_EXIT_HOTKEY) {
	    if (answer.u.key == NEWT_KEY_F1) {
		if (j & BYGROUP_PKG)
		    showPackageInfo(psp->packages[k]->h);
		continue;
	    }
	}
	
	if (answer.u.co == listbox ||
	    (answer.reason == NEWT_EXIT_HOTKEY && newtFormGetCurrent(form) == listbox)) {
	    if ((j & BYGROUP_GROUP)) {
		group = groupInfo[k].group;
	        switch (answer.u.key) {
	        case '*':
	        case '+':
		    /* Select the whole group */
		    if (groupInfo[k].selectedPkgs == groupInfo[k].totalPkgs)
			continue;
		    kSelected += groupInfo[k].totalSize - groupInfo[k].selectedSize;
		    sprintf(buf, "%dM", kSelected / 1024);
		    newtLabelSetText(sizeLabel, buf);
		    groupInfo[k].selectedPkgs = groupInfo[k].totalPkgs;
		    groupInfo[k].selectedSize = groupInfo[k].totalSize;
		    newtListboxSetCurrentByKey(listbox, (void *)printGroup(listbox, groupInfo, buf, len, j));
		    for (i = 0; i < psp->numPackages; i++) {
			if (psp->packages[i]->inmenu && !strcmp(psp->packages[i]->group, group)) {
			    psp->packages[i]->selected = 1;
			    psp->packages[i]->manuallySelected = 1;
			    if (!groupInfo[k].collapsed)
				printPkg(listbox, psp, buf, len, BYGROUP_PKG | i);
			}
		    }
		    continue;
	        case '-':
		    /* Deselect the whole group */
		    if (!groupInfo[k].selectedPkgs)
			continue;
		    kSelected -= groupInfo[k].selectedSize;
		    sprintf(buf, "%dM", kSelected / 1024);
		    newtLabelSetText(sizeLabel, buf);
		    groupInfo[k].selectedPkgs = 0;
		    groupInfo[k].selectedSize = 0;
		    newtListboxSetCurrentByKey(listbox, (void *)printGroup(listbox, groupInfo, buf, len, j));
		    for (i = 0; i < psp->numPackages; i++) {
			if (psp->packages[i]->inmenu && !strcmp(psp->packages[i]->group, group)) {
			    psp->packages[i]->selected = 0;
			    psp->packages[i]->manuallySelected = 0;
			    if (!groupInfo[k].collapsed)
				printPkg(listbox, psp, buf, len, BYGROUP_PKG | i);
			}
		    }
		    continue;
	        default:
		    break;
	        }
	    }
	    if ((j & BYGROUP_GROUP) && !groupInfo[k].collapsed) {
		/* collapse the tree */
		group = groupInfo[k].group;
		for (i = psp->numPackages - 1; i > 0; i--) {
		    if (psp->packages[i]->inmenu && !strcmp(psp->packages[i]->group, group)) {
			if (newtListboxDeleteEntry(listbox, (void *)(BYGROUP_PKG | i)) < 0)
			    newtListboxDeleteEntry(listbox, (void *)(BYGROUP_PKG | BYGROUP_CHANGE | i));
		    }
		}
		groupInfo[k].collapsed = 1;
		newtListboxSetCurrentByKey(listbox, (void *)printGroup(listbox, groupInfo, buf, len, j));

	    } else if ((j & BYGROUP_GROUP) && groupInfo[k].collapsed) {
		/* expand the tree */
		
		/* Go from the end to the beginning of the package list
		   so we insert in alphabetical order */
		group = groupInfo[k].group;
		for (i = psp->numPackages - 1; i > 0; i--) {
		    if (psp->packages[i]->inmenu && !strcmp(psp->packages[i]->group, group)) {
			sprintf(buf, "    [%c] %-*s %s", psp->packages[i]->selected ? '*' : ' ',
				 len - 17, psp->packages[i]->name, 
				 printSize(psp->packages[i]->selected ? psp->packages[i]->size / 1024 : 0));
			newtListboxInsertEntry(listbox, buf, (void *)(BYGROUP_PKG | i), (void *)j);
		    }
		}
		groupInfo[k].collapsed = 0;
		newtListboxSetCurrentByKey(listbox, (void *)printGroup(listbox, groupInfo, buf, len, j));
		
	    } else if (j & BYGROUP_PKG) {
		/* This is a package. */

		if ((answer.u.key == '+' || answer.u.key == '*') && psp->packages[k]->selected)
		    continue;
		if (answer.u.key == '-' && !psp->packages[k]->selected)
		    continue;
		for (i = 0; i < numGroups; i++)
		    if (!strcmp(groupInfo[i].group, psp->packages[k]->group))
			break;
		psp->packages[k]->selected = !psp->packages[k]->selected;
		psp->packages[k]->manuallySelected = psp->packages[k]->selected;
		if (psp->packages[k]->selected) {
		    kSelected += (psp->packages[k]->size / 1024);
		    groupInfo[i].selectedPkgs++;
		    groupInfo[i].selectedSize += (psp->packages[k]->size / 1024);
		} else {
		    kSelected -= (psp->packages[k]->size / 1024);
		    groupInfo[i].selectedPkgs--;
		    groupInfo[i].selectedSize -= (psp->packages[k]->size / 1024);
		}
		printGroup(listbox, groupInfo, buf, len, BYGROUP_GROUP | i);
		newtListboxSetCurrentByKey(listbox, (void *)printPkg(listbox, psp, buf, len, j));
	    }
	    sprintf(buf, "%dM", kSelected / 1024);
	    newtLabelSetText(sizeLabel, buf);
	}
	tmp = newtFormGetCurrent(form);
    } while ((tmp != done && tmp != back) &&
	     (answer.u.co == listbox ||
	     (answer.reason == NEWT_EXIT_HOTKEY &&
		(answer.u.key == ' ' || answer.u.key == NEWT_KEY_F1 ||
		 answer.u.key == '*' || answer.u.key == '+' ||
		 answer.u.key == '-'))));

    newtFormDestroy(form);
    newtPopWindow();

    if (tmp == back) return INST_CANCEL;

    return 0;
}

static void showPackageInfo(Header h) {
    newtComponent form, textbox, okay;
    newtGrid subgrid, grid;
    char * name, * version, * description, * release;
    uint_32 * size;
    int type, count;
    char infostr[255];
    char * buf, * from, * to;

    headerGetEntry(h, RPMTAG_NAME, &type, (void **) &name, &count);
    headerGetEntry(h, RPMTAG_VERSION, &type, (void **) &version, &count);
    headerGetEntry(h, RPMTAG_RELEASE, &type, (void **) &release, &count);

    if (!headerGetEntry(h, RPMTAG_SIZE, &type, (void **) &size, &count))
	size = 0;

    if (!headerGetEntry(h, RPMTAG_DESCRIPTION, &type, (void **) &description, 
		&count)) {
	if (!headerGetEntry(h, RPMTAG_SUMMARY, &type, (void **) &description, 
		&count)) {
	    description = _("(none available)");
	}
    }

    subgrid = newtCreateGrid(2, 2);

    newtGridSetField(subgrid, 0, 0, NEWT_GRID_COMPONENT,
		     newtLabel(-1, 1, _("Package:")),
		     0, 0, 0, 0, NEWT_ANCHOR_LEFT, 0);
    newtGridSetField(subgrid, 0, 1, NEWT_GRID_COMPONENT,
		     newtLabel(-1, -1, _("Size   :")),
		     0, 0, 0, 0, NEWT_ANCHOR_LEFT, 0);

    sprintf(infostr, "%s-%s-%s", name, version, release);
    newtGridSetField(subgrid, 1, 0, NEWT_GRID_COMPONENT,
		     newtLabel(-1, -1, infostr),
		     1, 0, 0, 0, NEWT_ANCHOR_LEFT, 0);

    sprintf(infostr, "%d", *size);
    newtGridSetField(subgrid, 1, 1, NEWT_GRID_COMPONENT,
		     newtLabel(-1, -1, infostr),
		     1, 0, 0, 0, NEWT_ANCHOR_LEFT, 0);


    to = buf = alloca(strlen(description) + 1);
    from = description; 
    /* Rip out all '\n' that don't start new paragraphs */
    while (*from) {
	if (*from == '\n') {
	    if ((*(from + 1) && isspace(*(from + 1))) ||
		(from > description && *(from - 1) == '\n'))
		*to++ = '\n';
	    else
		*to++ = ' ';
	} else
	    *to++ = *from;
	from++;
    }
    *to = '\0';

    textbox = newtTextbox(-1, -1, 43, 6, NEWT_TEXTBOX_WRAP | 
				NEWT_TEXTBOX_SCROLL);
    newtTextboxSetText(textbox, buf);

    okay = newtButton(20, 11, _("Ok"));

    grid = newtCreateGrid(1, 3);
    newtGridSetField(grid, 0, 0, NEWT_GRID_SUBGRID, subgrid,
		     0, 0, 0, 0, NEWT_ANCHOR_LEFT, 0);
    newtGridSetField(grid, 0, 1, NEWT_GRID_COMPONENT, textbox,
		     0, 1, 0, 0, NEWT_ANCHOR_LEFT, 0);
    newtGridSetField(grid, 0, 2, NEWT_GRID_COMPONENT, okay, 
		     0, 1, 0, 0, 0, 0);

    form = newtForm(NULL, NULL, 0);
    newtGridAddComponentsToForm(grid, form, 1);
    newtGridWrappedWindowAt(grid, name, 31, 6);
    newtGridFree(grid, 1);

    newtRunForm(form);

    newtFormDestroy(form);
    newtPopWindow();
}

static int queryIndividual(int * result) {
    int rc = 0;

    *result = 0;

    rc = newtWinTernary(_("Upgrade Packages"), _("Yes"), _("No"), _("Back"),
	_("The packages you have installed, and any other packages which are "
	  "needed to satisfy their dependencies, have been selected for "
	  "installation. Would you like to customize the set of packages that "
	  "will be upgraded?"));

    if (rc == 3)
	rc = INST_CANCEL;
    else if (rc == 2) {
	*result = 0;
	rc = 0;
    } else {
	*result = 1;
	rc = 0;
    }

    return rc;
}
