/*
 *   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>

#ifdef WITH_NCURSES
#   include <ncurses/curses.h>
#   include <ncurses/term.h>
#else
#   include <curses.h>
#endif

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

extern struct pkg *packages;		/* goodies to install */
extern int NRpkg;			/* number of goodies to install */

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

struct install_status {
    unsigned long total;
    unsigned long progress;
    struct pkg *pkg;
} ;
#define STATUS(x)	((struct install_status*)mData(x))

/*
 * install_a_package installs a single package, running a pretty progress
 * gauge as it goes
 */
int
install_a_package(void *p, int fd)
{
    struct install_status *pk = STATUS(p);
    int pi[2];
    pid_t pid;
    unsigned long offset;
    int perc, last_perc;
    FILE *f;
    int rc;
    char line[200];
    char *extractarg;

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

    if (pk->pkg->flags & PKG_INSTALLED)
	return 0;
    pk->pkg->flags |= PKG_INSTALLED;

    extractarg = (getpid() == 1) ? "-xzvRf" : "-tzvRf";

    fprintf(stderr, "Installing %s\n", pk->pkg->desc);
    fflush(stderr);

    last_perc = -1;
    if (pipe(pi) != -1) {
	if ((pid = fork()) == -1) {
	    close(pi[0]);
	    close(pi[1]);
	}
	if (pid == 0) {
	    dup2(fd,0);
	    dup2(pi[1], 1);
	    close(pi[0]);
	    execl(PATH_TAR, "tar", extractarg, "-", 0);
	    exit(255);
	}
	else {
	    close(fd);
	    close(pi[1]);
	    f = fdopen(pi[0], "r");

	    while (fgets(line, sizeof line, f)) {
		if (sscanf(line, "%ld", &offset) == 1) {
		    perc = PERC(pk->total, offset + pk->progress);
		    if ((perc > last_perc) && (perc < 100) && (perc >= 0)) {
			progress_message(perc, "Installing %s", pk->pkg->desc);
			last_perc = perc;
			sync();
		    }
		}
	    }
	    fclose(f);
	    close(pi[0]);

	    pk->progress += pk->pkg->size;
	    perc = PERC(pk->total, pk->progress);
	    progress_message(perc, "Installing %s", pk->pkg->desc);
	    sync();
	    waitpid(pid, &rc, 0);

	    if (WIFEXITED(rc) && WEXITSTATUS(rc) == 0)
		return 0;
	    return 0x100;
	}
    }
    return 0x100 | errno;
} /* install_a_package */


/*
 * install_one() installs a single package, doing retries if the install
 *               fails.
 */
static void
install_one(void *m, struct pkg *p)
{
    int rc;
    char msg[400];
    char *no;

    while ( (rc = mInstall(m, p->name)) != 0 ) {
	if (p->flags & PKG_REQUIRED) {
	    snprintf(msg, sizeof msg, "Error (%s) when installing the %s\n"
				      "package. The Mastodon install requires\n"
				      "this package; Do you want to try to\n"
				      "install again or cancel this install\n"
				      "and reboot the computer?",
				      strerror(errno), p->name);
	    no  = "2:Cancel";
	}
	else {
	    snprintf(msg, sizeof msg, "Error (%s) when installing the %s\n"
				      "package.  Do you want to try to\n"
				      "install it again or skip it?\n",
				      strerror(errno), p->name);
	    no = "2:Skip";
	}
	while ((rc = ndialog_yesno(INSTALLER, msg, -1, -1, "1:Retry", no)) < 0)
	    ;
	if (rc != 0) {
	    if (p->flags & PKG_REQUIRED)
		falloverdead();
	    break;
	}
    }
} /* install_one */


/*
 * installpackages() loads the base installation onto the target disk(s)
 */
int
installpackages()
{
    int x;
    char *old_help;
    struct install_status info;

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

    info.total = 0;
    info.progress = 0;
    setmData(mdesc,&info);

    dialog_clear();
    progress_message(0, "Installing Mastodon");

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

    before_required();

    /* then install all REQUIRED packages */
    for (info.progress=0, x=0; x<NRpkg; x++)
	if (packages[x].flags & PKG_REQUIRED)
	    install_one(mdesc, info.pkg = &packages[x]);

    after_required();

    before_selected();

    /* then install all selected packages */
    for (x=0; x<NRpkg; x++)
	if (packages[x].flags & PKG_SELECTED)
	    install_one(mdesc, info.pkg = &packages[x]);

    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++) {
	if ((packages[x].flags & PKG_OPTIONAL) == 0) {
	    wanted += packages[x].size;
	    required += packages[x].size;
	}
	if (packages[x].flags & PKG_REQUIRED) {
	    needed += packages[x].size;
	    continue;
	}
	menu = realloc(menu, (1+nrpkg) * sizeof menu[0]);
	menu[nrpkg].id = (char*)x;
	menu[nrpkg].item = packages[x].desc;
	menu[nrpkg].selected = !(packages[x].flags & PKG_OPTIONAL);
	menu[nrpkg].help = 0;
	nrpkg++;

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

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

    chain = newList(-1,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(4+width-sizeof("Space required:|mb"),12,
		    4, 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, FORMSZ, 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 non-optional package it can find.
 */
int
takeallpackages(long *wanted, long *required)
{
    int x;
    long size = 0L;

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

    *wanted = size;
    *required = size;

    return 0;
} /* takeallpackages */
