/*
 *   Copyright (c) 1998-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.
 */

static const char rcsid[] = "$Id: s.inventory.c 1.23 02/06/17 13:35:39-07:00 root@egypt.pell.portland.or.us $";

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

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

/*
 * You can specify the mouse by hand, instead of taking the mouse settings
 * that the installer discovered
 */
static void *mousedev_object;
static int newmousetype;
static char newmousedev[80];


/*
 * chk_mousedev() validates that the mouse device exists
 */
static int
chk_mousedev(void* obj, void* display)
{
    char *mymousedev = (char*)objDataPtr(obj);
    int fd;

    if (mymousedev == 0) {
	errno = 0;
	Error("Internal error in chk_mousedev!");
	return 0;
    }
    else if (strlen(mymousedev) < 1) {
	errno = 0;
	Error("You need to tell me the serial port that the mouse is\n"
	      "connected to.");
	return 0;
    }
    else if ((fd = open(mymousedev, 0)) >= 0) {
	close(fd);
	return 1;
    }
    else
	Error(mymousedev);
    return 0;
} /* chk_mousedev */


/*
 * chk_vermin() enables or disables the mousedev field in the pickamouse()
 * menu depending on whether or not we have a mouse with a variable mouse
 * device.
 */
static int
chk_vermin(void* obj, void* display)
{
    ListItem *list = getObjList(obj);
    int ix;

    for (ix = 0; ix < getObjListSize(obj); ix++)
	if (list[ix].selected) {
	    newmousetype = (int)(list[ix].id);
	    break;
	}
    if (ix >= getObjListSize(obj))
	return 0;

    if (newmousetype == NO_MOUSE || newmousetype == PS2_MOUSE) {
	setReadonly(mousedev_object);
	if (newmousetype == NO_MOUSE)
	    memset(newmousedev, 0, sizeof newmousedev);
	else
	    strcpy(newmousedev, "/dev/psaux");
    }
    else
	setWritable(mousedev_object);
    touchObj(mousedev_object);
    return 1;
} /* chk_vermin */


/*
 * chk_pickamouse() does one final check to make certain that you've
 * configured the mouse correctly
 */
int
chk_pickamouse(void* obj, void* display)
{
    if (newmousetype == NO_MOUSE || chk_mousedev(mousedev_object, display) != 0)
	return 1;
    return 0;
} /* chk_pickamouse */


/*
 * pickamouse() lets the user handpick a mouse from a list of vermin.
 */
static void
pickamouse()
{
    void *chain;

    /* List of mice we can select.  This MUST be in the same order
     * as the x_MOUSE #defines in util.h, or very funny things will
     * happen.  Note also that if you call newMenu with SHOW_KEY,
     * it will crash because we're subverting the id field to be
     * the mouse type.
     */
    static ListItem vermin[] = {
	{ (char*)NO_MOUSE,	"No mouse" },
	{ (char*)MS_MOUSE,	"Microsoft mouse" },
	{ (char*)LOGITECH_MOUSE,"Logitech mouse" },
	{ (char*)PS2_MOUSE,	"PS/2 mouse" },
    };
#define NR_VERMIN	(sizeof vermin/sizeof vermin[0])

    newmousetype = mousetype;
    if (mousedev)
	strcpy (newmousedev, mousedev);

    chain = newMenu(0,0,-1, NR_VERMIN, NR_VERMIN, vermin, 0, "",
				MENU_SELECTION, chk_vermin, "mouse.html");
    /* the menu will default to the current mouse */
    setObjCursor(chain, mousetype);

    mousedev_object = newString(20,0, 20, sizeof newmousedev, newmousedev,
				0, "Mouse port|", chk_mousedev, "mouse.html");
    chain = ObjChain(chain, mousedev_object);

    chain = ObjChain(chain, newOKButton(1, "OK", chk_pickamouse, "mouse.html"));
    chain = ObjChain(chain, newCancelButton(2, "CANCEL", 0, "mouse.html"));

    if (MENU(chain, -1, -1, "Configure your mouse", 0, 0) == MENU_OK) {
	if (strcmp(newmousedev,mousedev) != 0 || newmousetype != mousetype) {
	    static char mousebfr[80];
	    strcpy(mousebfr, newmousedev);
	    mousedev = mousebfr;
	    mousetype = newmousetype;
	    mouse_was_reset = 1;
	    mouse_reset();
	}
    }
    deleteObjChain(chain);
} /* pickamouse */


static ndObject o_mem;

/* 
 * chk_mem() is the callback for the string function in pick_memory.
 */
static int
chk_mem(ndObject obj, ndDisplay display)
{
    char *bfr = (char*)objDataPtr(o_mem);
    int size = atoi(bfr);

    if (bfr[0] && (size <= 0 || size > 4192)) {
	Error("Please enter a memory size > 0 and < 4192");
	return 0;
    }
    return 1;
} /* chk_mem */


/*
 * pickmemory() lets the user hand-enter the memory size
 */
static void
pickmemory(char *menusel)
{
    int rc;
    static char membfr[20];
    ndObject chain;

    o_mem = newString(0,0,20,sizeof membfr,membfr, 0,"Memory size (in MB) ", chk_mem, "inventory.html#memory");

    chain = ObjChain(o_mem, newOKButton(1, "  OK  ", chk_mem, "inventory.html#memory"));
    chain = ObjChain(chain, newCancelButton(2, "CANCEL", 0, "inventory.html#memory"));

    rc = MENU(chain, -1, -1, "Configure memory", 0, 0);

    deleteObjChain(chain);

    if (rc == MENU_OK)
	if (membfr[0]) {
	    total_memory = atoi(membfr);
	    strcpy(menusel, Size(total_memory*(ONE_MB/ONE_K)));
	}
	else {
	    total_memory = 0;
	    sprintf(menusel, "Auto (detected %s)", Size(memsize/ONE_K));
	}
} /* pickmemory */


/*
 * buildfromlist() builds the from[] array for pickinstall
 */
static int defaultfrom = -1;
static ListItem *from = 0;
static int nrfrom = 0;
static int last_from = 0;

static void
buildfromlist()
{
    char bfr[80];
    int x;

    if (from == 0) {
	nrfrom = NrMedia;
	from = malloc((NrMedia+2) * sizeof from[0]);

	for (x=0; x < NrMedia; x++) {
	    from[x].id = strdup(mName(InstallMedia[x]));
	    snprintf(bfr, sizeof bfr, "%s on %s",
			    from[x].id, mDev(InstallMedia[x]));
	    from[x].item = strdup(bfr);
	    from[x].help = "inventory.html#from";
	    from[x].selected = 0;
	}
	if (defaultfrom < 0)
	    defaultfrom = 0;
	from[defaultfrom].selected = 1;
    }
} /* buildfromlist */


/*
 * usemedium() sets up the install medium we want to use, and populates
 * a buffer that can be used as a menu selection
 */
static void*
usemedium(unsigned int which)
{
    return (which >= 0 && which < NrMedia) ? InstallMedia[which] : 0;
} /* usemedium */


/*
 * configuremedium(void *m) runs media-specific configuration (not in
 *                                  media.c because it does windowing)
 */
int
configuremedium(void *m)
{
    switch (mType(m)) {
    case M_NET:	return ethconfigure(m);
    default:    return 1;
    }
} /* configuremedium */


/*
 * pickinstall() lets the user select the install medium
 */
static void
pickinstall(char *menusel)
{
    void *obj;
    int rc;
    int x;

    obj = newRadioList(-1,0,-1,(nrfrom>8)?8:nrfrom, nrfrom, from,
		    0/*no prompt*/,
		    "" /*no prefix, but we want to box the menu */,
		    CR_LIST|MENU_SELECTION|NO_HOTKEYS,
		    okaydone, "inventory.html#from");
    for (x=0; x < nrfrom; x++)
	if (from[x].selected) {
	    setObjCursor(obj, x);
	    break;
	}

    obj = ObjChain(0, obj);

again:
    rc = MENU(obj, -1, -1, INSTALLER, "Choose your installation medium", 0);

    if (rc == MENU_OK) {
	for (x=0; x < nrfrom; x++)
	    if (from[x].selected) {
		if (!configuremedium(InstallMedia[x]))
		    goto again;
		strcpy(menusel, from[x].item);
		break;
	    }
    }
    deleteObjChain(obj);
} /* pickinstall */


/*
 * indexes into the list that give_inventory uses to display the system
 * configuration
 */
#define I_MEMORY	0
#define I_MOUSE		1
#define I_INSTALL	2
#define I_DISK		3


/*
 * inventory_callback() is the function that's called when someone presses
 * return over an inventory menu item.  If you press return over the MEMORY
 * field, you get a blurb saying you can hardcode the memory size, and if
 * you press return over the MOUSE field, you get a little menu of all the
 * available mousies in their most common configurations.
 */
int
inventory_callback(void* obj, void* display)
{
    ListItem *list = getObjList(obj);

    if (list[I_MOUSE].selected) {
	pickamouse();
	if (mousetype == NO_MOUSE)
	    strcpy(list[I_MOUSE].item, "None");
	else if (mousetype == PS2_MOUSE)
	    strcpy(list[I_MOUSE].item, mousenames[mousetype]);
	else
	    sprintf(list[I_MOUSE].item, "%s mouse on %s",
		    mousenames[mousetype], mousedev);
    }
    else if (list[I_MEMORY].selected)
	pickmemory(list[I_MEMORY].item);
    else if (list[I_INSTALL].selected) {
	if (nrfrom > 1)
	    pickinstall(list[I_INSTALL].item);
    }
#if 0
    if (list[getObjListSize(obj)-1].selected)
	return -1;
#endif

    return 0;
} /* inventory_callback */


/*
 * give_inventory() gives an inventory list
 */
void *
give_inventory()
{
    ListItem *items;		/* a list describing the system */
    int nritems;		/* how many items live in this list */
    char meminfo[80];		/* scratch strings to write memory, */
    char mouseinfo[80];		/* mouse, and install media descriptions */
    char installinfo[80];	/* into */

    int ix;			/* working index into items[] */
    int dx;			/* working index into Disk[] */
    int rc;			/* return code from MENU() */

    void *chain;		/* object chain for MENU() */
    void *li;			/* list object containing items[] */
    void *install_from = 0;	/* thing we want to install from */

    /* figure out how large the menu should be */
    /* the menu contains:  Memory size (settable), Mouse type (settable),
     * install media (settable), all detected disks, with the boot disk
     * being the first item.
     */

    /* number of disks */
    for (nritems=0, ix=0; ix < NrDisk; ix++)
	if ((Disk[ix].flags & ID_EXIST) && !(Disk[ix].flags & ID_CDROM))
	    nritems++;

    /* +1 for memory, +1 for mouse type, +1 for install media */
    nritems += 3;

    /* Now build the menu */

    items = (ListItem*)calloc( nritems+1, sizeof items[0] );

    if (items == 0)
	die("Out of memory!");

    /*  populate the menu.  Memory first */

    snprintf(meminfo, sizeof meminfo,
			"Auto (detected %s)", Size(memsize/ONE_K));
    items[0].id = "Memory";
    items[0].item = meminfo;
    items[0].help = "inventory.html#memory";

    /* then the mouse */

    if (mousetype == NO_MOUSE)
	strncpy(mouseinfo, "None", sizeof mouseinfo);
    else if (mousetype == PS2_MOUSE)
	strncpy(mouseinfo, mousenames[mousetype], sizeof mouseinfo);
    else
	snprintf(mouseinfo, sizeof mouseinfo, 
		"%s mouse on %s", mousenames[mousetype], mousedev);
    items[1].id = "Mouse";
    items[1].item = mouseinfo;
    items[1].help = "inventory.html#mouse";

    /* then the install media, if any */

    buildfromlist();
    if ((install_from = usemedium(last_from))) {
	items[2].id = "Install from";
	items[2].item = installinfo;
	strcpy(installinfo, from[last_from].item);
	items[2].help = "inventory.html#from";
    }

    /* finally all the disks, starting with the boot disk */

    for (ix = I_DISK, dx=0; dx < NrDisk; dx++)
	if ((Disk[dx].flags & ID_EXIST) && !(Disk[dx].flags & ID_CDROM)) {
	    items[ix].id = Disk[dx].device;
	    items[ix].item = strdup(describe_media(Disk+dx));
	    items[ix].help = "inventory.html#other";
	    ix++;
	}

#if 0
    items[ix].id = "I";
    items[ix++].item = "Install Mastodon";
    /* If there's a install cd, there should be a `run from cdrom'
       option.
     */
#endif


    /* now that we've allocated items[], build an object chain and
     * fire off MENU()
     */

    li = newMenu(-1,0,40,(nritems>8)?8:nritems, nritems, items,
		    0/*no prompt*/,
		    "" /*no prefix, but we want to box the menu */,
		    MENU_SELECTION|SHOW_IDS|NO_HOTKEYS,
		    inventory_callback, "inventory.html");

    chain = ObjChain(li, newOKButton(1, "Install Mastodon", 0, "inventory.html"));
#if 0
    chain = ObjChain(chain, newCancelButton(2, "Abort", 0, 0));
#endif

    while (1) {
redo:
	rc = MENU(chain, FORMSZ, INSTALLER, "Hardware inventory", AT_BUTTON);

	switch (rc) {
	default:    if (ndialog_yesno(INSTALLER,
			    "Do you want to exit the installation and\n"
			    "restart the computer?", -1, -1,
			    "2:EXIT", "1:NO") == MENU_OK)
			falloverdead();
	case MENU_OK:
		    if (nrfrom == 1 && !configuremedium(install_from))
			break;
		    for (ix = I_DISK; ix < nritems; ix++)
			free(items[ix].item);
		    free(items);

		    for (ix = 0; ix < nrfrom; ix++)
			if (from[ix].selected) {
			    last_from = ix;
			    return usemedium(ix);
			}
		    return install_from;
	}
    }
} /* give_inventory */
