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

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

#define FILENAME_TAG 1000000

static int selectPackagesByGroup(struct pkgSet * psp);
static int selectPackagesWindow(struct pkgSet * psp, char * group,
				unsigned int * kSelected);
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, unsigned int * kSelected);
static int queryIndividual(int * result);

char * skipList[] = { "XFree86-8514", "XFree86-AGX", "XFree86-Mach32",
		      "XFree86-Mach64", "XFree86-Mach8", "XFree86-Mono",
		      "XFree86-P9000", "XFree86-S3", "XFree86-S3",
		      "XFree86-SVGA", "XFree86-VGA16", "XFree86-W32",
		      NULL };

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

    return strcmp(*one, *two);
}

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 strcmp((*a)->name, (*b)->name);
};

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

    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;
    }

    chdir(dirname);

    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'))))) {
	    fd = open(ent->d_name, O_RDONLY);

	    if (fd < 0) {
		logMessage("failed to open %s: %s", ent->d_name, 
			   strerror(errno));
	    } else {
		rc = pkgReadHeader(fd, &h, &isSource, NULL, NULL);

		close(fd);
		if (rc) {
		    logMessage("failed to pkgReadHeaer %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]->data = strdup(ent->d_name);

		    getEntry(h, RPMTAG_NAME, &type, (void **) &name, &count);
		    if (getEntry(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 (!getEntry(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;
	}
    } 

    *psp = ps;

    qsort(ps.packages, ps.numPackages, sizeof(*ps.packages), 
	  (void *) pkgCompare);
    
    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 preskelNum = 0;
    int baseNum = 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")) {
	messageWindow("Error", "Comps file is not version 0 as expected");
	fclose(f);
	return INST_ERROR;
    }

    compsAlloced = 5;
    cs.numComponents = 0;
    cs.comps = malloc(sizeof(*cs.comps) * compsAlloced);
    cs.preskel = NULL;
    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') {
		messageWindow("Error", "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 (!*start) {
		messageWindow("comps Error", 
				"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, "*beforeskel*"))
		    preskelNum = cs.numComponents;
		else 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) {
		    messageWindow("comps Error", 
				    "package %s at line %d dne", start, line);
		    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.preskel = cs.comps + preskelNum;

    cs.base->inmenu = 0;
    cs.preskel->inmenu = 0;
    cs.base->selected = 1;
    cs.preskel->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;
    }

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

    *csp = cs;

    return 0;
}

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

static void sizeCallback(newtComponent co, struct packageCheckbox * cbi) {
    char sizeBuf[20];

    if (cbi->state != cbi->lastState) {
	if (cbi->state == ' ') 
	    *cbi->kSelected -= cbi->size / 1024;
	else
	    *cbi->kSelected += cbi->size / 1024;
	cbi->lastState = cbi->state;

	sprintf(sizeBuf, "%dM", *cbi->kSelected / 1024);
	newtLabelSetText(cbi->sizeLabel, sizeBuf);
	newtRefresh();
    }
}

static int selectPackagesWindow(struct pkgSet * psp, char * group,
				unsigned int * kSelected) {
    newtComponent okay, form, checkList, sb, cancel, current, size;
    struct packageCheckbox * cbs;
    int i, row, groupLength, rc;
    char val;
    struct newtExitStruct answer;
    int done = 0;
    char sizeBuf[20];

    if (!group) {
	group = "";
	groupLength = 0;
    } else
	groupLength = strlen(group);

    cbs = alloca(sizeof(*cbs) * psp->numPackages);

    newtOpenWindow(22, 4, 40, 16, "Select Packages");
    newtPushHelpLine("<F1> will show you a size and description of a package");

    form = newtForm(NULL, NULL, 0);
    okay = newtButton(6, 12, "Ok");
    cancel = newtButton(21, 12, "Cancel");

    newtFormAddComponent(form, newtLabel(1, 1, 
			"What packages should be installed?"));
    newtFormAddComponent(form, newtLabel(1, 10, 
			"Size of all selected packages:"));
    sprintf(sizeBuf, "%dM", *kSelected / 1024);
    size = newtLabel(32, 10, sizeBuf);
    newtFormAddComponent(form, size);

    sb = newtVerticalScrollbar(32, 3, 6, 9, 10);
    checkList = newtForm(sb, NULL, 0);
    newtFormSetBackground(checkList, NEWT_COLORSET_CHECKBOX);

    for (i = 0, row = 0; i < psp->numPackages; i++) {
	if (psp->packages[i]->inmenu &&
	    strlen(psp->packages[i]->group) >= groupLength &&
	    !strncmp(psp->packages[i]->group, group, groupLength)) {
	    if (psp->packages[i]->selected)
		val = '*';
	    else
		val = ' ';

	    cbs[i].cb = newtCheckbox(6, 3 + row++, psp->packages[i]->name, val, 
				     NULL, &cbs[i].state);
	    newtComponentAddCallback(cbs[i].cb, (void *) sizeCallback, cbs + i);
	    cbs[i].sizeLabel = size;
	    cbs[i].lastState = cbs[i].state = val;
	    cbs[i].size = psp->packages[i]->size;
	    cbs[i].kSelected = kSelected;
	    newtFormAddComponent(checkList, cbs[i].cb);
	} else {
	    cbs[i].state = ' ';
	    cbs[i].cb = NULL;
	}
    }

    if (row > 6) {
	newtFormSetHeight(checkList, 6);
	newtFormAddComponent(checkList, sb);
    } else
	newtFormSetWidth(checkList, 27);

    newtFormAddComponents(form, checkList, okay, cancel, NULL);

    newtFormAddHotKey(form, NEWT_KEY_F1);

    rc = 0;
    while (!done) {
	newtFormRun(form, &answer);

	if (answer.reason == NEWT_EXIT_HOTKEY) {
	    if (answer.u.key == NEWT_KEY_F12) 
		done = 1;
	    else if (answer.u.key == NEWT_KEY_F1) {
		current = newtFormGetCurrent(checkList);
		for (i = 0; i < psp->numPackages; i++) {
		    if (cbs[i].cb == current) {
			showPackageInfo(psp->packages[i]->h, kSelected);
			break;
		    }
		}
	    }
	} else if (answer.reason == NEWT_EXIT_COMPONENT) {
	    done = 1;
	    if (answer.u.co == cancel)
		rc = INST_CANCEL;
	}
    }

    newtFormDestroy(form);
    newtPopWindow();
    newtPopHelpLine();

    if (rc) return rc;

    for (i = 0; i < psp->numPackages; i++) 
	if (cbs[i].cb)
	    if (cbs[i].state != ' ')
	       psp->packages[i]->selected = 1;
	    else
	       psp->packages[i]->selected = 0;

    return 0;
}

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

int psSelectPackages(struct pkgSet * psp, struct componentSet * csp,
			 int goForward, int isUpgrade) {
    int rc;
    int stage;
    static int doIndividual = 0;

    if (!goForward && doIndividual)
	stage = SELECT_PACKAGES;
    else
	stage = SELECT_COMPONENTS;

    while (stage != SELECT_DONE) {
	switch (stage) {
	  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:
	    rc = selectPackagesByGroup(psp);
	    if (rc == INST_CANCEL) 
		stage = SELECT_COMPONENTS;
	    else if (rc) 
		return rc;
	    else
		stage = SELECT_VERIFY;
	    break;

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

    return 0;
}

int psVerifyDependencies(struct pkgSet * psp, int fixup) {
    rpmdb db = NULL;
    rpmDependencies rpmdeps;
    int i;
    struct rpmDependencyConflict * conflicts;
    struct packageInfo * package;
    int numConflicts;
    newtComponent okay, form, textbox, info, cancel, answer;
    char * text, buf[80];
    char selectPackages;

    if (!access("/mnt/var/lib/rpm/packages.rpm", R_OK))
	if (rpmdbOpen("/mnt", &db, O_RDWR | O_CREAT, 0644))
	    db = NULL;

    rpmdeps = rpmdepDependencies(db);

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

    rpmdepCheck(rpmdeps, &conflicts, &numConflicts);

    rpmdepDone(rpmdeps);
    if (db) rpmdbClose(db);

    if (!numConflicts) {
	return 0;
    }

    if (fixup) {
	for (i = 0; i < numConflicts; i++) {
	    package = 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++) {
	package = conflicts[i].suggestedPackage;
	if (package)
	    sprintf(buf, "%-20s %-20s", conflicts[i].byName, 
		    package->name);
	else
	    sprintf(buf, "%-20s (no suggestion)", conflicts[i].byName);

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

    newtOpenWindow(15, 2, 50, 19, "Unresolved Dependencies");
    
    form = newtForm(NULL, NULL, 0);

    info = newtTextbox(1, 1, 45, 4, NEWT_TEXTBOX_WRAP);
    newtTextboxSetText(info, 
	"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.");

    sprintf(buf, "%-20s %-20s", "Package", "Requirement");
    okay = newtButton(10, 15, "Ok");
    cancel = newtButton(30, 15, "Cancel");
    newtFormAddComponent(form, newtLabel(3, 6, buf));
    textbox = newtTextbox(3, 7, 45, 5, NEWT_TEXTBOX_SCROLL);
    newtTextboxSetText(textbox, text); 
    newtFormAddComponent(form, 
	newtCheckbox(3, 13, "Install packages to satisfy dependencies", '*',
		     NULL, &selectPackages));
    newtFormAddComponents(form, info, textbox, okay, cancel, NULL);
    newtFormSetCurrent(form, okay);

    answer = newtRunForm(form);

    newtFormDestroy(form);
    newtPopWindow();

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

    if (selectPackages != ' ') {
	for (i = 0; i < numConflicts; i++) {
	    package = 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;
    char val, individualPackages, everything;
    char * states;
    int row;

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

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

    newtOpenWindow(15, 2, 50, 19, "Components to Install");

    form = newtForm(NULL, NULL, 0);

    newtFormAddComponent(form, newtLabel(1, 1, 
				"Choose components to install:"));
    /*newtFormAddComponent(form, newtLabel(36, 1, "Size:"));
    size = newtLabel(1, 42,*/

    sb = newtVerticalScrollbar(47, 3, 9, 9, 10);
    checklist = newtForm(sb, NULL, 0);
    newtFormSetHeight(checklist, 9);
    newtFormSetBackground(checklist, NEWT_COLORSET_CHECKBOX);
    newtFormAddComponent(checklist, sb);

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

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

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

    checkbox = newtCheckbox(10, 13, "Select individual packages", 
				individualPackages, NULL, &individualPackages);

    okay = newtButton(10, 15, "Ok");
    cancel = newtButton(30, 15, "Cancel");

    newtFormAddComponents(form, checklist, checkbox, okay, cancel, NULL);

    answer = newtRunForm(form);

    newtFormDestroy(form);
    newtPopWindow();

    if (answer == cancel) return INST_CANCEL;

    *doIndividual = (individualPackages != ' ');

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

    for (i = 0; i < csp->numComponents; i++) {
	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;
	}
    }

    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 psFromHeaderList(char * file, struct pkgSet * psp) {
    struct pkgSet ps;
    int fd, end, type, count;
    unsigned int * sizeptr;
    Header h;
    int packagesAlloced;
    char * name, * group;

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

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

    end = lseek(fd, 0, SEEK_END);
    lseek(fd, 0, SEEK_SET);

    while (end > lseek(fd, 0, SEEK_CUR)) {
	h = readHeader(fd, 1);
	if (!h) {
	    messageWindow("Error", "error reading header at %d\n", 
		    (int) lseek(fd, 0, SEEK_CUR));
	    free(ps.packages);
	    return INST_ERROR;
	}

	getEntry(h, RPMTAG_NAME, &type, (void **) &name, &count);

	if (!getEntry(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;

	if (getEntry(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;
	getEntry(h, FILENAME_TAG, &type,
		 (void **) &ps.packages[ps.numPackages]->data, 
		 &count);

	ps.numPackages++;
    }

    close(fd);

    *psp = ps;

    qsort(ps.packages, ps.numPackages, sizeof(*ps.packages), 
	  (void *) pkgCompare);
    
    return 0;
}

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

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

    return 0;
}

static int selectPackagesByGroup(struct pkgSet * psp) {
    newtComponent okay, form, listbox, cancel, answer;
    newtComponent sizeLabel;
    int i, row, numGroups;
    unsigned int kSelected = 0;
    int rc = 0;
    hashTable ht;
    htIterator iter;
    char buf[200], * chptr;
    char * group;
    char ** groupList;

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

	htAddToTable(ht, group);

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

    numGroups = htNumEntries(ht);
    groupList = alloca(sizeof(*groupList) * numGroups);
    htIterStart(&iter);
    i = 0;
    while (htIterGetNext(ht, &iter, &group)) {
	groupList[i] = alloca(strlen(group) + 1);
	strcpy(groupList[i], group);
	i++;
    }

    qsort(groupList, numGroups, sizeof(char *), strptrCmp);
    htFreeHashTable(ht);

    newtOpenWindow(4, 2, 45, 14, "Select Group");

    form = newtForm(NULL, NULL, 0);
    okay = newtButton(7, 10, "Ok");
    cancel = newtButton(23, 10, "Cancel");

    newtFormAddComponent(form, newtLabel(1, 1, 
			"Choose a group to examine:"));
    newtFormAddComponent(form, newtLabel(33, 1,
			"Size:"));
    sprintf(buf, "%dM", kSelected / 1024);
    sizeLabel = newtLabel(39, 1, buf);
    newtFormAddComponent(form, sizeLabel);

    listbox = newtListbox(3, 3, 6, NEWT_LISTBOX_RETURNEXIT);

    for (i = 0; i < numGroups; i++) {
	newtListboxAddEntry(listbox, groupList[i], groupList[i]);
    }

    newtFormAddComponents(form, listbox, okay, cancel, NULL);

    do {
	answer = newtRunForm(form);
	if (answer == listbox) {
	    group = newtListboxGetCurrent(listbox);
	    rc = selectPackagesWindow(psp, group, &kSelected);

	    sprintf(buf, "%dM", kSelected / 1024);
	    newtLabelSetText(sizeLabel, buf);
	}
    } while (answer == listbox && rc != INST_ERROR);

    newtFormDestroy(form);
    newtPopWindow();

    if (rc == INST_ERROR) return rc;

    if (answer == cancel) return INST_CANCEL;

    return 0;
}

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

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

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

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

    newtOpenWindow(31, 6, 45, 15, name);

    form = newtForm(NULL, NULL, 0);
    newtFormAddComponent(form, newtLabel(1, 1, "Package:"));
    newtFormAddComponent(form, newtLabel(1, 2, "Size   :"));

    sprintf(infostr, "%s-%s-%s", name, version, release);
    newtFormAddComponent(form, newtLabel(10, 1, infostr));
    sprintf(infostr, "%d", *size);
    newtFormAddComponent(form, newtLabel(10, 2, infostr));

    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, 4, 43, 6, NEWT_TEXTBOX_WRAP | 
				NEWT_TEXTBOX_SCROLL);
    newtTextboxSetText(textbox, buf);

    okay = newtButton(20, 11, "Ok");
    newtFormAddComponents(form, textbox, okay, NULL);

    newtRunForm(form);

    newtFormDestroy(form);
    newtPopWindow();
}

static int queryIndividual(int * result) {
    newtComponent text, yes, no, cancel, form;
    struct newtExitStruct answer;
    int rc = 0;

    *result = 0;

    newtOpenWindow(17, 4, 45, 15, "Upgrade Packages");
    text = newtTextbox(1, 1, 40, 10, NEWT_TEXTBOX_WRAP);
    newtTextboxSetText(text,
	"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?");

    yes = newtButton(4, 10, "Yes");
    no = newtButton(18, 10, "No");
    cancel = newtButton(32, 10, "Cancel");

    form = newtForm(NULL, NULL, 0);
    newtFormAddComponents(form, text, yes, no, cancel, NULL);
    
    newtFormRun(form, &answer);

    newtFormDestroy(form);
    newtPopWindow();

    if (answer.reason == NEWT_EXIT_HOTKEY && answer.u.key == NEWT_KEY_F12) {
	*result = 1;
    } else if (answer.reason == NEWT_EXIT_COMPONENT) {
	if (answer.u.co == cancel) 
	    rc = 1;
	else if (answer.u.co == yes)
	    *result = 1;
    }

    return rc;
}
