/* NMnetlist.c -
 *
 *	This file manages netlists for the Magic netlist menu
 *	package.  It reads and writes netlists, and provides
 *	routines to modify the nets.
 *
 *     *********************************************************************
 *     * Copyright (C) 1985, 1990 Regents of the University of California. *
 *     * Permission to use, copy, modify, and distribute this              *
 *     * software and its documentation for any purpose and without        *
 *     * fee is hereby granted, provided that the above copyright          *
 *     * notice appear in all copies.  The University of California        *
 *     * makes no representations about the suitability of this            *
 *     * software for any purpose.  It is provided "as is" without         *
 *     * express or implied warranty.  Export of this software outside     *
 *     * of the United States of America may require an export license.    *
 *     *********************************************************************
 */

#ifndef lint
static char rcsid[] __attribute__ ((unused)) = "$Header: /usr/cvsroot/magic-8.0/netmenu/NMnetlist.c,v 1.2 2010/09/12 23:36:13 tim Exp $";
#endif  /* not lint */

#include <stdio.h>
#include <string.h>
#include <strings.h>

#include "utils/magic.h"
#include "utils/utils.h"
#include "utils/geometry.h"
#include "tiles/tile.h"
#include "utils/hash.h"
#include "database/database.h"
#include "windows/windows.h"
#include "utils/main.h"
#include "textio/textio.h"
#include "netmenu/nmInt.h"
#include "utils/undo.h"
#include "utils/malloc.h"
#include "netmenu/netmenu.h"

/* The data structure below is used to describe each of the
 * netlist files currently known to Magic.  At any given time,
 * only one is "current".  A netlist is not much more than a
 * big hash table of terminal names.  Some of the entries in
 * the hash table have null values (these correspond to terminals
 * that have been deleted).  Where a hash table entry has a
 * non-zero value, it points to a NetEntry, which links together
 * all of the terminals in a net.
 */

/* Netlist structure:  one of these per netlist known to Magic. */

typedef struct xxx_nl_1
{
    char *nl_name;		/* Name of the netlist file, before
				 * path expansion.
				 */
    char *nl_fileName;		/* Actual path-expanded file name
				 * (place to write back the netlist.
				 */
    HashTable nl_table;		/* Hash table holding nets. */
    int nl_flags;		/* Various flag bits;  see below. */
    struct xxx_nl_1 *nl_next;	/* All netlists are linked together
				 * in one big long list.
				 */
} Netlist;

/* Flag bits for Netlist:
 *
 * NL_MODIFIED:		1 means this netlist has been modified since
 *			the last time it was written to disk.
 */

#define NL_MODIFIED 1

/* NetEntry structure:  one of these for each terminal in each
 * net.  The hash table entry for the terminal points to this
 * structure.  Double links are used to tie together all entries
 * for one net into a circular structure.
 */

typedef struct xxx_ne_1
{
    char *ne_name;		/* Pointer to name of terminal (this
				 * points to the text string in the
				 * hash table entry.
				 */
    int ne_flags;		/* Various flags, see below. */
    struct xxx_ne_1 *ne_next;	/* Next entry for this net. */
    struct xxx_ne_1 *ne_prev;	/* Previous entry for this net. */
} NetEntry;

/* The flags currently used in NetEntry's are:
 *
 * NETENTRY_SEEN:  Used to keep this entry from being processed twice
 *		   when enumerating nets.
 */

#define NETENTRY_SEEN 1

Netlist *nmCurrentNetlist = NULL;	/* The netlist all procedures operate
					 * on for now.
					 */
Netlist *nmListHead = NULL;		/* The first netlist in the linked
					 * list of all netlists.
					 */

/* Used in asking the user for confirmation: */

static const char * const yesno[] = {"no", "yes", NULL};


/*
 * ----------------------------------------------------------------------------
 *
 * NMAddTerm --
 *
 * 	This adds a terminal to the same net as another given terminal.
 *
 * Results:
 *	A pointer is returned to the name of the terminal that has
 *	been added.  This is a pointer to the hash table entry, so
 *	it's not going to go away until the hash table is explictly
 *	deleted.  This is a convenience to provide callers with a
 *	handle they can use later to refer to this net.  If no
 *	terminal was added, either because other is NULL or because
 *	there isn't a current netlist, NULL is returned.
 *
 * Side effects:
 *	If new is already on a net, it is removed from that net.
 *	If other doesn't exist in the table, an entry is created
 *	for it.  Then new is added to the net containing other.
 *	If new and other are the same, a new net is created with
 *	just one terminal.
 *
 * ----------------------------------------------------------------------------
 */

char *
NMAddTerm(new, other)
    char *new;			/* Name of new terminal to be added. */
    char *other;		/* Name of terminal whose net new is to join.*/
{
    HashEntry *hNew, *hOther;
    NetEntry *newEntry, *otherEntry;

    /* Lookup new, and remove it from its current net, if there is one. */

    if ((nmCurrentNetlist == NULL) || (new == NULL) || (other == NULL))
	return NULL;

    nmCurrentNetlist->nl_flags |= NL_MODIFIED;
    hNew = HashFind(&nmCurrentNetlist->nl_table, new);
    newEntry = (NetEntry *) HashGetValue(hNew);
    if (newEntry != 0)
    {
	NMUndo(newEntry->ne_name, newEntry->ne_prev->ne_name, NMUE_REMOVE);
	newEntry->ne_prev->ne_next = newEntry->ne_next;
	newEntry->ne_next->ne_prev = newEntry->ne_prev;
    }
    else
    {
	/* Create a new entry for this terminal. */

	newEntry = (NetEntry *) mallocMagic(sizeof(NetEntry));
	newEntry->ne_name = hNew->h_key.h_name;
	newEntry->ne_flags = 0;
	HashSetValue(hNew, newEntry);
    }
    newEntry->ne_prev = newEntry;
    newEntry->ne_next = newEntry;

    /* Now lookup the (supposedly pre-existing) terminal.  If it
     * doesn't have an entry in the hash table, make a new one.
     */

    hOther = HashFind(&nmCurrentNetlist->nl_table, other);
    otherEntry = (NetEntry *) HashGetValue(hOther);
    if (otherEntry == 0)
    {
	otherEntry = (NetEntry *) mallocMagic(sizeof(NetEntry));
	otherEntry->ne_name = hOther->h_key.h_name;
	otherEntry->ne_flags = 0;
	otherEntry->ne_prev = otherEntry;
	otherEntry->ne_next = otherEntry;
	HashSetValue(hOther, otherEntry);
    }

    /* Tie the new terminal onto other's list. */

    if (otherEntry != newEntry)
    {
	newEntry->ne_prev = otherEntry->ne_prev;
	newEntry->ne_next = otherEntry;
	newEntry->ne_prev->ne_next = newEntry;
	otherEntry->ne_prev = newEntry;
    }
    NMUndo(new, other, NMUE_ADD);
    return otherEntry->ne_name;
}

/*
 * ----------------------------------------------------------------------------
 *
 * NMDeleteTerm --
 *
 * 	This procedure removes a terminal from its net.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The named terminal is removed from its net, if it is currently
 *	in a net.
 *
 * ----------------------------------------------------------------------------
 */

void
NMDeleteTerm(name)
    char *name;			/* Name of a terminal. */
{
    HashEntry *h;
    NetEntry *entry;

    if ((name == 0) || (nmCurrentNetlist == NULL)) return;

    h = HashLookOnly(&nmCurrentNetlist->nl_table, name);
    if (h == NULL) return;
    entry = (NetEntry *) HashGetValue(h);
    if (entry == 0) return;
    nmCurrentNetlist->nl_flags |= NL_MODIFIED;
    HashSetValue(h, 0);
    NMUndo(entry->ne_name, entry->ne_next->ne_name, NMUE_REMOVE);
    entry->ne_next->ne_prev = entry->ne_prev;
    entry->ne_prev->ne_next = entry->ne_next;
    freeMagic((char *) entry);
}

/*
 * ----------------------------------------------------------------------------
 *
 * NMJoinNets --
 *
 * 	This procedure joins two nets together.  It is similar to
 *	NMAddTerm, except that it applies to every terminal in
 *	the first net rather than just a single terminal.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	All of the terminals in the nets associated with termA
 *	and termB are joined together into a single net.
 *
 * ----------------------------------------------------------------------------
 */

void
NMJoinNets(termA, termB)
    char *termA;		/* Name of a terminal in first net. */
    char *termB;		/* Name of a terminal in second net. */
{
    HashEntry *ha, *hb;
    NetEntry *netA, *netB, *tmp;

    if ((termA == NULL) || (termB == NULL)) return;
    if (nmCurrentNetlist == NULL) return;

    /* Lookup the two nets, and make sure that they both exist
     * and aren't already the same.
     */

    ha = HashFind(&nmCurrentNetlist->nl_table, termA);
    netA = (NetEntry *) HashGetValue(ha);
    hb = HashFind(&nmCurrentNetlist->nl_table, termB);
    netB = (NetEntry *) HashGetValue(hb);
    if ((netA == 0) || (netB == 0)) return;
    nmCurrentNetlist->nl_flags |= NL_MODIFIED;
    tmp = netA;
    while (TRUE)
    {
	if (tmp == netB) return;
	tmp = tmp->ne_next;
	if (tmp == netA) break;
    }

    /* Record the changes for undo purposes.  This code is a bit
     * tricky:  since termB is used as the reference network for
     * deleting all other terminals from it, termB itself must
     * be the last terminal to be deleted.  Otherwise undo won't
     * work.
     */

    tmp = netB->ne_next;
    while (TRUE)
    {
	NMUndo(tmp->ne_name, termB, NMUE_REMOVE);
	NMUndo(tmp->ne_name, termA, NMUE_ADD);
	if (tmp == netB) break;
	tmp = tmp->ne_next;
    }

    /* Join the two nets. */

    tmp = netA->ne_prev;
    netB->ne_prev->ne_next = netA;
    netA->ne_prev = netB->ne_prev;
    tmp->ne_next = netB;
    netB->ne_prev = tmp;
}

/*
 * ----------------------------------------------------------------------------
 *
 * NMDeleteNet --
 *
 * 	This procedure deletes a net by removing all of the terminals
 *	from it.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	All the terminals in the given net are deleted from the net.
 *
 * ----------------------------------------------------------------------------
 */

void
NMDeleteNet(net)
    char *net;			/* Name of one of the terminals in the net
				 * to be deleted.
				 */
{
    HashEntry *h;
    NetEntry *ne, *next;

    if ((net == NULL) || (nmCurrentNetlist == NULL)) return;
    h = HashLookOnly(&nmCurrentNetlist->nl_table, net);
    if (h == NULL) return;
    ne = (NetEntry *) HashGetValue(h);
    if (ne == 0) return;
    nmCurrentNetlist->nl_flags |= NL_MODIFIED;

    /* The order of processing is a bit tricky.  Since we use net for
     * the "current net" in undo-ing, it must be the last terminal
     * to be deleted.
     */

    next = ne->ne_next;
    free_magic1_t mm1 = freeMagic1_init();
    while (TRUE)
    {
	NMUndo(next->ne_name, net, NMUE_REMOVE);
	HashSetValue(HashFind(&nmCurrentNetlist->nl_table, next->ne_name), 0);
	freeMagic1(&mm1, (char *) next);
	if (next == ne) break;
	next = next->ne_next;
    }
    freeMagic1_end(&mm1);
}

/*
 * ----------------------------------------------------------------------------
 *
 * NMNewNetlist --
 *
 * 	This procedure sets everything up to use a new netlist from
 *	now on.  If the netlist isn't already loaded into memory,
 *	it is read from disk.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	A new netlist may be read from disk.
 *
 * ----------------------------------------------------------------------------
 */

void
NMNewNetlist(name)
    char *name;			/* Name of the netlist file.  If NULL,
				 * then the netlist file association
				 * is eliminated.
				 */
{
    Netlist *new;
    FILE *file;
#define MAXLINESIZE 256
    char line[MAXLINESIZE], *fullName, *currentTerm, *p;

    /* Save undo information, and re-adjust things for the rest
     * of this module.
     */

    NMUndo(name, NMNetListButton.nmb_text, NMUE_NETLIST);
    (void) StrDup(&NMNetListButton.nmb_text, name);
    if (NMWindow != NULL)
        (void) NMredisplay(NMWindow, &NMNetListButton.nmb_area, (Rect *) NULL);
    NMSelectNet((char *) NULL);

    if ((name == NULL) || (name[0] == 0))
    {
	nmCurrentNetlist = NULL;
	return;
    }

    /* First of all, see if this netlist is already loaded. */

    for (new = nmListHead; new != NULL; new = new->nl_next)
    {
	if (strcmp(name, new->nl_name) == 0)
	{
	    nmCurrentNetlist = new;
	    return;
	}
    }

    /* Create and initialize a new netlist. */

    new = (Netlist *) mallocMagic(sizeof(Netlist));
    new->nl_name = NULL;
    new->nl_fileName = NULL;
    HashInit(&new->nl_table, 32, 0);
    new->nl_flags = 0;
    new->nl_next = nmListHead;
    nmListHead = new;
    nmCurrentNetlist = new;
    (void) StrDup(&new->nl_name, name);

    /* Open a file for reading the netlist.  If it doesn't exist,
     * or doesn't have a proper header line, issue a warning message,
     * then just start a new list.
     */

    file = PaOpen(name, "r", ".net", Path, CellLibPath, &fullName);
    if (file == NULL)
    {
	TxError("Netlist file %s.net couldn't be found.\n", name);
	TxError("Creating new netlist.\n");
	new->nl_fileName = mallocMagic((unsigned) (5 + strlen(name)));
	(void) sprintf(new->nl_fileName, "%s.net", name);
	return;
    }
    (void) StrDup(&new->nl_fileName, fullName);
    if ((fgets(line, MAXLINESIZE, file) == NULL)
	|| ((strcasecmp(line, " Net List File\n") != 0) /* Backward compatibility*/
	&& (strcasecmp(line, " Netlist File\n") != 0)))
    {
	TxError("%s isn't a legal netlist file.\n", new->nl_fileName);
	TxError("Creating new netlist.\n");
	(void) fclose(file);
	return;
    }

    /* Read nets from the file one at a time.  Each net consists of
     * a bunch of terminal names, one per line.  Nets are separated
     * by lines that are either empty or have a space as the first
     * character.  Lines starting with "#" are treated as comments.
     * None of this gets recorded for undo-ing.
     */

    UndoDisable();
    currentTerm = NULL;
    while (fgets(line, MAXLINESIZE, file) != NULL)
    {
	/* Strip the newline character from the end of the line. */

	for (p = line; *p != 0; p++)
	{
	    if (*p == '\n')
	    {
		*p = 0;
		break;
	    }
	}
	if ((line[0] == 0) || (line[0] == ' '))
	{
	    currentTerm = NULL;
	    continue;
	}
	if (line[0] == '#') continue;
	if (NMTermInList(line) != NULL)
	{
	    TxError("Warning: terminal \"%s\" appears in more than one net.\n",
		    line);
	    TxError("    Only the last appearance will be used.\n");
	}
	if (currentTerm == NULL)
	    currentTerm = NMAddTerm(line, line);
	else (void) NMAddTerm(line, currentTerm);
    }
    UndoEnable();

    nmCurrentNetlist->nl_flags &= ~NL_MODIFIED;
    (void) fclose(file);
}

/*
 * ----------------------------------------------------------------------------
 *
 * NMNetlistName --
 *
 * 	Return the name of the current net list.  Do it as a function to make
 *	it clear that the exported value is read-only.
 *
 * Results:
 *	The name of the current netlist.
 *
 * Side effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */
char *
NMNetlistName()
{
    if(nmCurrentNetlist!=NULL)
	return(nmCurrentNetlist->nl_name);
    else
	return ((char *) NULL);
}

/*
 * ----------------------------------------------------------------------------
 *
 * NMEnumNets --
 *
 * 	This procedure calls a client function for each terminal in
 *	each net.  The supplied function should be of the form:
 *
 *	int
 *	func(name, firstInNet, clientData)
 *	    char *name;
 *	    bool firstInNet;
 *	    ClientData;
 *	{
 *	}
 *
 *	In the above, name is the name of a terminal.  FirstInNet
 *	is TRUE if this is the first terminal in the net, FALSE
 *	for all other terminals in the net.  All the terminals in
 *	a given net are enumerated consecutively.  ClientData is
 *	an arbitrary parameter value passed in by our caller.  Func
 *	should normally return 0.  If it returns a non-zero value,
 *	the enumeration will be aborted immediately.
 *
 * Results:
 *	If the search terminates normally, 0 is returned.  If the
 *	client function returns a non-zero value, then 1 is returned.
 *
 * Side effects:
 *	Whatever the client function does.
 *
 * ----------------------------------------------------------------------------
 */

int NMEnumNets(func, clientData)
    int (*func)();		/* Function to call for each terminal. */
    ClientData clientData;	/* Parameter to pass to function. */
{
    HashSearch hs;
    HashEntry *h;
    NetEntry *entry, *entry2;
    int result;

    if (nmCurrentNetlist == NULL) return 0;

    /* The search runs in two passes.  During the first pass, set flags
     * to avoid enumerating the same net or terminal twice.  During
     * the second pass, clear the flags.
     */

    HashStartSearch(&hs);
    result = 0;
    while (TRUE)
    {
	h = HashNext(&nmCurrentNetlist->nl_table, &hs);
	if (h == NULL) break;
	entry = (NetEntry *) HashGetValue(h);
	if (entry == 0) continue;
	if (entry->ne_flags & NETENTRY_SEEN) continue;
	entry->ne_flags |= NETENTRY_SEEN;

	/* Enumerate this entire net. */

	if ((*func)(entry->ne_name, TRUE, clientData) != 0)
	{
	    result = 1;
	    goto cleanup;
	}
	for (entry2 = entry->ne_next; entry2 != entry;
	    entry2 = entry2->ne_next)
	{
	    entry2->ne_flags |= NETENTRY_SEEN;
	    if ((*func)(entry2->ne_name, FALSE, clientData) != 0)
	    {
		result = 1;
		goto cleanup;
	    }
	}
    }

    cleanup: HashStartSearch(&hs);
    while (TRUE)
    {
	h = HashNext(&nmCurrentNetlist->nl_table, &hs);
	if (h == NULL) break;
	entry = (NetEntry *) HashGetValue(h);
	if (entry != 0) entry->ne_flags &= ~NETENTRY_SEEN;
    }
    return result;
}

/*
 * ----------------------------------------------------------------------------
 *
 * NMEnumTerms --
 *
 * 	This procedure calls a client function for each terminal in
 *	a given net.  The supplied function should be of the form:
 *
 *	int
 *	func(name, clientData)
 *	    char *name;
 *	    ClientData;
 *	{
 *	}
 *
 *	In the above, name is the name of a terminal.  The terminals
 *	in a given net are enumerated consecutively.  ClientData is
 *	an arbitrary parameter value passed in by our caller.  The
 *	client function should return 0 under normal conditions.  If
 *	it wishes to abort the search, it should return 1.
 *
 * Results:
 *	If the search terminates normally, 0 is returned.  If the
 *	client function returns a non-zero value, then 1 is returned.
 *
 * Side effects:
 *	Whatever the client function does.
 *
 * ----------------------------------------------------------------------------
 */

int
NMEnumTerms(name, func, clientData)
    char *name;			/* Name of terminal in net to be enumerated. */
    int (*func)();		/* Function to call for each terminal. */
    ClientData clientData;	/* Parameter to pass to function. */
{
    HashEntry *h;
    NetEntry *entry, *entry2;

    if (nmCurrentNetlist == NULL) return 0;
    h = HashLookOnly(&nmCurrentNetlist->nl_table, name);
    if (h == NULL) return 0;
    entry = (NetEntry *) HashGetValue(h);
    if (entry == NULL) return 0;
    entry2 = entry;
    while (TRUE)
    {
	if ((*func)(entry2->ne_name, clientData) != 0) return 1;
	entry2 = entry2->ne_next;
	if (entry2 == entry) break;
    }
    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * NMHasList --
 *
 * 	This procedure checks to see if a netlist is selected.  It is used
 *	to let the outside world know.
 *
 * Results:
 *	TRUE if the netlist is selected.  Otherwise FALSE.
 *
 * Side effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */

bool
NMHasList()
{
    return(nmCurrentNetlist != NULL);
}

/*
 * ----------------------------------------------------------------------------
 *
 * NMTermInList --
 *
 * 	Tells whether or not the given terminal name is in the current
 *	netlist.
 *
 * Results:
 *	If the terminal isn't part of any net, NULL is returned.
 *	If it is part of some net, the terminal's name from the
 *	hash table is returned.  This is a token that won't go
 *	away, which the caller can use to remember the net name.
 *
 * Side effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */

char *
NMTermInList(name)
    char *name;			/* Name of terminal. */
{
    HashEntry *h;
    NetEntry *entry;

    if (nmCurrentNetlist == NULL) return NULL;
    h = HashLookOnly(&nmCurrentNetlist->nl_table, name);
    if (h == NULL) return NULL;
    entry = (NetEntry *) HashGetValue(h);
    if (entry == NULL) return NULL;
    return entry->ne_name;
}

/*
 * ----------------------------------------------------------------------------
 *
 * NMWriteNetlist --
 *
 * 	This procedure writes out a netlist to a file.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The file on disk is overwritten.
 *
 * ----------------------------------------------------------------------------
 */

void
NMWriteNetlist(fileName)
    char *fileName;		/* If non-NULL, gives name of file in
				 * which to write current netlist.  If NULL,
				 * the netlist gets written to the place
				 * from which it was read.
				 */
{
    FILE *file;
    int nmWriteNetsFunc();
    char *realName, line[50];
    char *alloc = NULL;

    if (nmCurrentNetlist == NULL)
    {
	TxError("There isn't a current net list to write.\n");
	return;
    }

    /* Decide what file to use to write the file (if an explicit name
     * is given, we have to add on a ".net" extension, and we also
     * check to make sure the file doesn't exist).
     */

    if (fileName == NULL) realName = nmCurrentNetlist->nl_fileName;
    else
    {
	realName = alloc = mallocMagic((unsigned) (5 + strlen(fileName)));
	(void) sprintf(realName, "%s.net", fileName);
	file = PaOpen(realName, "r", (char *) NULL, ".",
	    (char *) NULL, (char **) NULL);
	if (file != NULL)
	{
	    (void) fclose(file);
	    TxPrintf("Net list file %s already exists.", realName);
	    TxPrintf("  Should I overwrite it? [no] ");
	    if (TxGetLine(line, sizeof(line)) == (char *) NULL)
	    {
	        freeMagic(alloc);
		return;
	    }
	    if ((strcmp(line, "y") != 0) && (strcmp(line, "yes") != 0))
	    {
	        freeMagic(alloc);
		return;
	    }
	}
    }

    file = PaOpen(realName, "w", (char *) NULL, ".",
	(char *) NULL, (char **) NULL);
    if (file == NULL)
    {
	TxError("Couldn't write file %s.\n", realName);
	if (alloc) freeMagic(alloc);
	return;
    }
    fprintf(file, " Netlist File\n");
    (void) NMEnumNets(nmWriteNetsFunc, (ClientData) file);
    if (strcmp(realName, nmCurrentNetlist->nl_fileName) == 0)
	nmCurrentNetlist->nl_flags &= ~NL_MODIFIED;
    (void) fclose(file);
    if (alloc) freeMagic(alloc);
}

int
nmWriteNetsFunc(name, firstInNet, file)
    char *name;			/* Name of terminal. */
    bool firstInNet;		/* TRUE means first terminal in net. */
    FILE *file;			/* File in which to write info. */
{
    if (firstInNet) fputs("\n", file);
    fprintf(file, "%s\n", name);
    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * NMCheckWritten --
 *
 * 	Checks to see if there are netlists that have been modified
 *	but not written back to disk.  If so, asks user whether he
 *	cares about them.
 *
 * Results:
 *	Returns TRUE if there are no modified netlists around, or
 *	if the user says he doesn't care about them.  Returns FALSE
 *	if the user says he cares.
 *
 * Side effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */

bool
NMCheckWritten()
{
    char *name;
    Netlist *nl;
    int count, indx;
    char answer[12];

    count = 0;
    for (nl = nmListHead; nl != NULL; nl = nl->nl_next)
    {
	if (nl->nl_flags & NL_MODIFIED)
	{
	    count += 1;
	    name = nl->nl_name;
	}
    }
    if (count == 0) return TRUE;

    do
    {
	if (count == 1)
	    TxPrintf("Net-list \"%s\" has been modified.", name);
	else
	    TxPrintf("%d netlists have been modified.", count);
	TxPrintf("  Do you want to lose the changes? [no] ");
	if ((TxGetLine(answer, sizeof answer) == NULL) || (answer[0] == 0))
	    return FALSE;
        indx = Lookup(answer, yesno);
    } while (indx < 0);
    return indx;
}

/*
 * ----------------------------------------------------------------------------
 *
 * NMWriteAll --
 *
 * 	Goes through all netlists that have been modified, asking
 *	the user whether to write out the netlist or not.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Net-lists may be written to disk.
 *
 * ----------------------------------------------------------------------------
 */

void
NMWriteAll()
{
    Netlist *nl, *saveCurrent;
    static const char * const options[] = {"write", "skip", "abort", NULL};
    char answer[10];
    int indx;

    saveCurrent = nmCurrentNetlist;

    for (nl = nmListHead; nl != NULL; nl = nl->nl_next)
    {
	if ((nl->nl_flags & NL_MODIFIED) == 0) continue;
	do
	{
	    TxPrintf("%s: write, skip, or abort command? [write] ",
		nl->nl_name);
	    if (TxGetLine(answer, sizeof answer) == NULL) continue;
	    if (answer[0] == 0) indx = 0;
	    else indx = Lookup(answer, options);
	} while (indx < 0);
        switch (indx)
	{
	    case 0:
		nmCurrentNetlist = nl;
		NMWriteNetlist((char *) NULL);
		break;
	    case 1:
		break;
	    case 2:
		return;
	}
    }

    nmCurrentNetlist = saveCurrent;
    return;
}

/*
 * ----------------------------------------------------------------------------
 *
 * NMFlushNetlist --
 *
 * 	This procedure flushes the contents of the named netlist
 *	from memory.  If the netlist was modified, the user is given
 *	a chance to abort the flush.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The contents of the netlist are changed.  If the netlist
 *	had been modified, all previous undo events are flushed.
 *
 * ----------------------------------------------------------------------------
 */

void
NMFlushNetlist(name)
    char *name;			/* Name of the netlist to be flushed. */
{
    Netlist *list, **prev;
    HashSearch hs;
    HashEntry *h;

    /* Find the netlist in question. */

    list = NULL;
    for (prev = &nmListHead; *prev != NULL; prev = &(*prev)->nl_next)
    {
	if (strcmp(name, (*prev)->nl_name) == 0)
	{
	    list = *prev;
	    break;
	}
    }
    if (list == NULL)
    {
	TxError("Netlist \"%s\" isn't currently loaded.\n", name);
	return;
    }

    /* If the netlist has been modified, give the user a chance to
     * skip this.
     */

    if (list->nl_flags & NL_MODIFIED)
    {
	char answer[10];
	int indx;

	while (TRUE)
	{
	    TxPrintf("Really throw away all changes made ");
	    TxPrintf("to netlist \"%s\"? [no] ", name);
	    if ((TxGetLine(answer, sizeof answer) == NULL) || (answer[0] == 0))
		return;
	    indx = Lookup(answer, yesno);
	    if (indx == 0) return;
	    if (indx == 1)
	    {
		UndoFlush();
		break;
	    }
	}
    }

    /* Unlink the netlist from the list of netlists, and free up
     * everything in it.
     */

    *prev = list->nl_next;
    HashStartSearch(&hs);
    while (TRUE)
    {
	h = HashNext(&list->nl_table, &hs);
	if (h == NULL) break;
	if (HashGetValue(h) != NULL)
	    freeMagic((char *) HashGetValue(h));
    }
    freeMagic((char *) list);

    /* If the netlist was the current netlist, read it in again from
     * disk.
     */

    if (list == nmCurrentNetlist)
        NMNewNetlist(name);
}
