/*
 *   Copyright (c) 1999 David Parsons. All rights reserved.
 *   
 *   Redistribution and use in source and binary forms, with or without
 *   modification, are permitted provided that the following conditions
 *   are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in
 *     the documentation and/or other materials provided with the
 *     distribution.
 *  3. All advertising materials mentioning features or use of this
 *     software must display the following acknowledgement:
 *     
 *   This product includes software developed by David Parsons
 *   (orc@pell.chi.il.us)
 *
 *  4. My name may not be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *     
 *  THIS SOFTWARE IS PROVIDED BY DAVID PARSONS ``AS IS'' AND ANY
 *  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 *  PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DAVID
 *  PARSONS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 *  TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 *  IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 *  THE POSSIBILITY OF SUCH DAMAGE.
 */
/*
 * packages:  code that deals with installation packages
 */
#include "pathnames.h"

#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>

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

#include <dialog.h>
#include <ndialog.h>
#include <basis/rpopen.h>

#include "packages.h"
#include "util.h"
#include "media.h"

static struct pkg *packages;		/* goodies to install */
static int NRpkg = 0;			/* number of goodies to install */

extern void before_required(),
            after_required(),
	    before_selected(),
	    after_selected();

/*
 * readpackagelist() reads the PACKAGES file and fills the packages[] array
 */
int
readpackagelist(char *toc)
{
    FILE *tf;
    int cnt;
    char line[200], name[80], desc[80], flags[80], size[20];
    char **dep;
    int required;

    /* free old package list, if any
     */
    for (cnt=0; cnt<NRpkg; cnt++) {
	free(packages[cnt].name);
	free(packages[cnt].desc);
    }
    NRpkg = 0;
    if (packages)
	free(packages);

    packages = malloc(0);
    dep = malloc(0);

    fprintf(stderr, "reading package TOC from %s\n", toc);
    if ((tf = fopen(toc, "r")) != NULL) {
	while (fgets(line, sizeof line, tf)) {
	    if (line[0] == '#')
		continue;
	    memset(flags, 0, sizeof flags);
	    if (sscanf(line, "%[^:]:%[^:]:%[^:]:%[^\n]",
				    name,flags,size,desc) == 4) {

		required = (strcasecmp(flags, "required-package") == 0);

		snprintf(line, sizeof line, SOURCE "/%s", name);
		if (getpid() == 1 && access(line, R_OK) != 0)
		    if (required) {
			die("The install device is corrupted, and doesn't\n"
			    "have the required installation package %s\n"
			    "(%s) on it.\n", name, desc);
		    }
		    else {
			fprintf(stderr, "Can't access package %s\n", name);
			continue;
		    }
		packages = realloc(packages, (2+NRpkg)*sizeof packages[0]);
		dep = realloc(dep, (2+NRpkg) * sizeof dep[0]);
		dep[NRpkg] = 0;
		packages[NRpkg].flags = 0;
		packages[NRpkg].name = strdup(name);
		packages[NRpkg].size = atoi(size);
		packages[NRpkg].desc = strdup(desc);

		if (required)
		    packages[NRpkg].flags |= PKG_REQUIRED;
		else if (strcasecmp(flags, "none") != 0)
		    dep[NRpkg] = strdup(flags);
		fprintf(stderr, "%s: depends=[%s], %ld bytes, `%s'\n",
				name, flags, packages[NRpkg].size, desc);
		NRpkg++;
	    }
	}
	fclose(tf);

	/* walk the dependency list to build dependencies for each package
	 */
	for (cnt=0; cnt < NRpkg; cnt++) {
	    char *p;

	    FD_ZERO(&packages[cnt].depends);

	    if (dep[cnt] == 0)
		continue;

	    for (p = strtok(dep[cnt], ","); p; p = strtok(0, ",")) {
		int i;

		for (i=0; i < NRpkg; i++)
		    if (i != cnt && strcmp(p, packages[i].name) == 0) {
			FD_SET(i, &packages[cnt].depends);
			break;
		    }
	    }
	    free(dep[cnt]);
	}
	free(dep);

	return 0;
    }
    dialog_notify("can't read TOC file");
    return 1;
} /* readpackagelist */


/*
 * install_a_package installs a single package, running a pretty progress
 * gauge as it goes
 */
static int
install_a_package(int pkg, unsigned long total, unsigned long *progress)
{
#if ACTUAL
    pid_t zombie;
    unsigned long offset;
    int perc, last_perc;
    FILE *f;
    int rc;
    char line[200];

#define PERC(tot,acc)	((int)((100.0 * (acc))/(float)(tot)))

    /* arguments passed to tar to pick apart the base installation */
    static char *extractArgs[] = {"tar", "-xzRpvf", (char*)0, (char*)0 };


    if (packages[pkg].flags & PKG_INSTALLED)
	return 0;
    packages[pkg].flags |= PKG_INSTALLED;

    fprintf(stderr, "Installing %s\n", packages[pkg].desc);

    snprintf(line, sizeof line, "%s%s", PATH_PKGS, packages[pkg].name);
    extractArgs[2] = line;
    last_perc = -1;
    if ((f = rpopen(PATH_TAR, extractArgs, "r")) != (FILE*)0) {
	while (fgets(line, sizeof line, f)) {
	    offset = 0;
	    if (sscanf(line, "%ld", &offset) < 1)
		continue;
	    offset *= 1024;
	    perc = PERC(total, offset + (*progress));
	    if ((perc > last_perc) && (perc < 100) && (perc >= 0)) {
		progress_message(perc, "Installing %s", packages[pkg].desc);
		last_perc = perc;
		sync();
	    }
	}
	*progress += packages[pkg].size;
	perc = PERC(total, (*progress));
	progress_message(perc, "Installing %s", packages[pkg].desc);
	sync();
	rc = rpclose(f);
	if (rc == EOF || WEXITSTATUS(rc) == 255 || !WIFEXITED(rc))
	    die("Could not install %s: internal error!", packages[pkg].desc);

	while ( (zombie = waitpid(-1, 0, WNOHANG)) > 0)
	    ;
    }
    else
	die("Installation error on %s", packages[pkg].desc);
#endif
    return 1;
} /* install_a_package */


/*
 * installpackages() loads the base installation onto the target disk(s)
 */
int
installpackages()
{
    int x;
    unsigned long total, installed;
    char *old_help;

    old_help = get_helpline();
    use_helpline("");

    progress_message(0, "Installing Mastodon");

    /* first, sum up the total size of the installation */
    for (total=x=0; x<NRpkg; x++)
	if (packages[x].flags & (PKG_SELECTED|PKG_REQUIRED)) {
	    total += packages[x].size;
	    packages[x].flags &= ~PKG_INSTALLED;
	}

    before_required();

    /* then install all REQUIRED packages */
    for (installed=0.0, x=0; x<NRpkg; x++)
	if (packages[x].flags & PKG_REQUIRED)
	    install_a_package(x, total, &installed);

    after_required();

    before_selected();

    /* then install all selected packages */
    for (x=0; x<NRpkg; x++)
	if (packages[x].flags & PKG_SELECTED)
	    install_a_package(x, total, &installed);

    after_selected();

    /*use_helpline(old_help);*/
    return 0;
} /* installpackages */


/* 
 * pkselect() recursively select all the prerequisites for a given
 * package
 */
static int
pkselect(int pkgno, ListItem* list, int nrlist)
{
    int x, touched = 0;
    struct pkg *curr = &packages[pkgno];

    for (x = 0; x < nrlist; x++)
	if ( x != pkgno && FD_ISSET((int)(list[x].id), &(curr->depends)) )
	    if (list[x].selected == 0) {
		touched = list[x].selected = 1;
		pkselect((int)(list[x].id), list, nrlist);
	    }
    return touched;
} /* pkselect */


/*
 * pkdeselect() recursively deselect all dependents for a given package
 */
static int
pkdeselect(int pkgno, ListItem* list, int nrlist)
{
    int x, touched = 0;

    for (x = 0; x < nrlist; x++)
	if ( FD_ISSET(pkgno, &(packages[(int)(list[x].id)].depends)) )
	    if (list[x].selected != 0) {
		touched = 1;
		list[x].selected = 0;
		pkdeselect((int)(list[x].id), list, nrlist);
	    }
    return touched;
} /* pkdeselect */

static void *sum;

/*
 * packagechecklist() is a local callback that selects prerequisites when
 * an item is selected, and deselects dependents when an item is deselected.
 */
static int
packagechecklist(void *obj, void *display)
{
    ListItem *list = getObjList(obj);
    int nrlist = getObjListSize(obj);
    int current = getObjCursor(obj);

    int touched = 0;
    int x;
    long required;

    if (list == 0)
	return 0;

    if (list[current].selected) {
	/* select prerequisites */
	touched = pkselect((int)(list[current].id), list, nrlist);
    }
    else {
	/* deselect dependents */
	touched = pkdeselect((int)(list[current].id), list, nrlist);
    }
    if (touched)
	touchObj(obj);

    /* after tweaking dependencies, update the space required
     * message on the menu
     */
    for (required = x = 0; x < NRpkg; x++)
	if (packages[x].flags & PKG_REQUIRED)
	    required += packages[x].size;

    for (x = 0; x < nrlist; x++)
	if (list[x].selected)
	    required += packages[(int)(list[x].id)].size;

    sprintf(objDataPtr(sum), "%ld", (required+ONE_MB-1)/ONE_MB);
    drawObj(sum, display);

    return 1;
} /* packagechecklist */

/*
 * choosepackages() pops up a menu and lets you interactively choose
 * packages for installation
 */
int
choosepackages(long *space_wanted, long *space_required)
{
    int rc, x;
    int nrpkg = 0;
    int width = 0;
    void* chain;
    ListItem *menu = malloc(1);
    long int needed=0, wanted=0;
    long int required = 0;
    char sizebfr[40];

    *space_required = *space_wanted = 0L;

    for (x=0; x<NRpkg; x++) {
	wanted += packages[x].size;
	required += packages[x].size;
	if ((packages[x].flags & PKG_REQUIRED) == 0) {
	    menu = realloc(menu, (1+nrpkg) * sizeof menu[0]);
	    menu[nrpkg].id = (char*)x;
	    menu[nrpkg].item = packages[x].desc;
	    menu[nrpkg].selected = 1;
	    menu[nrpkg].help = 0;
	    nrpkg++;

	    if ((rc = strlen(packages[x].desc)) > width)
		width = rc;
	}
	else
	    needed += packages[x].size;
    }
    fprintf(stderr, "%d packages to select\n", nrpkg);

    for (x=0; x<NRpkg; x++)
	packages[x].flags &= ~PKG_SELECTED;

    chain = newList(0,0, -1, (nrpkg > 10) ? 10 : nrpkg, nrpkg, menu, 0, "",
		NO_HOTKEYS|ALWAYS_HIGHLIT, packagechecklist, "packages.html");

    sprintf(sizebfr, "%ld", (required+ONE_MB-1)/ONE_MB);
    sum = newString(3+width-sizeof("Space required:|mb"),12,
		    3, sizeof sizebfr, sizebfr, 0, "Space required:|mb", 0, 0);
    setReadonly(sum);
    chain = ObjChain(chain, sum);

    chain = ObjChain(chain, newOKButton(1, "OK", 0, "packages.html"));

    rc = MENU(chain, -1, -1, INSTALLER,
		"Which optional packages do you want to install?", 0);

    deleteObjChain(chain);

    switch (rc) {
    case MENU_OK:
	for (x = 0; x < nrpkg; x++) {
	    if (menu[x].selected) {
		packages[(int)(menu[x].id)].flags |= PKG_SELECTED;
		needed += packages[(int)(menu[x].id)].size;
	    }
	    else
		packages[(int)(menu[x].id)].flags &= ~PKG_SELECTED;
	}

	needed = (needed+ONE_MB-1)/ONE_MB;
	*space_wanted = (wanted+ONE_MB-1)/ONE_MB;
	*space_required = (needed*3)/2;
	break;

    case MENU_ERROR:
	die("?Internal installer error:  %s", strerror(errno));
	/* we are dead now, but if that fails.... */

    case MENU_CANCEL:
    case MENU_ESCAPE:
	rc = EOF;	/* remap the error code to back us out to the */
			/* previous menu */
	break;
    }
    free(menu);

    return rc;
} /* choosepackages */


/*
 * takeallpackages() selects every package it can find, then returns 0.
 */
int
takeallpackages(long *wanted, long *required)
{
    int x;
    long size = 0L;

    for (size = 0, x=0; x<NRpkg; x++) {
	packages[x].flags |= PKG_SELECTED;
	size += packages[x].size;
    }
    size = (size * 3) / 2;

    *wanted = size;
    *required = size;

    return 0;
} /* takeallpackages */
