/* SimSelect.c -
 *
 *	This file provides routines to make selections for Rsim by copying
 *	things into a special cell named "__SELECT__".  It is based
 *	on code in the select module.
 *
 *     *********************************************************************
 *     * 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.    *
 *     *********************************************************************
 * of California
 */

#ifndef lint
static const char sccsid[] = "@(#)SimSelect.c	4.14 MAGIC (Berkeley) 10/3/85";
#endif  /* not lint */

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

#include "tcltk/tclmagic.h"
#include "utils/magic.h"
#include "utils/geometry.h"
#include "tiles/tile.h"
#include "utils/hash.h"
#include "database/database.h"
#include "windows/windows.h"
#include "dbwind/dbwind.h"
#include "utils/undo.h"
#include "commands/commands.h"
#include "select/selInt.h"
#include "utils/main.h"
#include "utils/malloc.h"
#include "utils/signals.h"
#include "sim/sim.h"

/* C99 compat */
#include "textio/textio.h"
#include "extract/extract.h"

/* Two cells worth of information are kept around by the selection
 * module.  SelectDef and SelectUse are for the cells whose contents
 * are the current selection.  Select2Def and Select2Use provide a
 * temporary working space for procedures that manipulate the selection.
 * for example, Select2Def is used to hold nets or regions while they
 * are being extracted by SelectRegion or SelectNet.  Once completely
 * extracted, information is copied to SelectDef.  Changes to
 * SelectDef are undo-able and redo-able (so that the undo package
 * can deal with selection changes), but changes to Select2Def are
 * not undo-able (undoing is always disabled when the cell is modified).
 */

extern CellDef *SelectDef, *Select2Def;
extern CellUse *SelectUse, *Select2Use;

typedef struct def_list_elt
{
    CellDef  	*dl_def;
    struct 	def_list_elt *dl_next;
    bool	dl_isMarked;
} SimDefListElt;

static SimDefListElt *SimCellLabList = (SimDefListElt *) NULL;
				/* list of all the cell defs we have
				 * put RSIM labels in
				 */

/* Data structure for node names extracted from the current selection.  For
 * each node, save the node name, a tile which lies in the node, and
 * the text for the label corresponding to the node's rsim value.
 */

typedef struct TLE {
    const char *tl_nodeName;
    Tile *tl_nodeTile;
    TileType tl_dinfo;
    const char *tl_simLabel;
    struct TLE *tl_next;
} TileListElt;


TileListElt *NodeList = (TileListElt *) NULL;
				/* list of all nodes in the selected area */
HashTable SimNodeNameTbl;	/* node names found in the selected area */
HashTable SimGetnodeTbl;	/* node names to abort name search on */
HashTable SimGNAliasTbl;	/* node name aliases found during search */
HashTable SimAbortSeenTbl;	/* aborted node names found during search */

bool 	  SimRecomputeSel = TRUE;	/* selection has changed */
bool	  SimInitGetnode = TRUE;	/* Getnode called for the 1st time */
bool	  SimGetnodeAlias = FALSE;	/* if node aliases are to be printed */
bool	  SimSawAbortString;	/* if saw string to abort name search */
bool	  SimIsGetnode;		/* true if command was issued from Getnode */
bool	  SimUseCoords;		/* true if we should use trans. position */
bool	  SimIgnoreGlobals = TRUE;	/* If FALSE, node names ending in "!"	*/
					/* are treated as global node names.	*/

#ifndef RSIM_MODULE
bool	  SimRsimRunning = FALSE;	/* Always false if there's no rsim module */
#endif

/*
 * ----------------------------------------------------------------------------
 *
 *   SimSelectNode --
 *
 * 	This procedure selects an entire node.  It is similar to SelectNet.
 *
 * Results:
 *	Returns the node name.
 *
 * Side effects:
 *	Starting from material of type "type" under scx, this procedure
 *	finds all material in all expanded cells that are electrically-
 *	connected to the starting material through a chain of expanded
 *	cells.
 *
 * ----------------------------------------------------------------------------
 */

char *
SimSelectNode(
    SearchContext *scx,		/* Area to tree-search for material.  The
				 * transform must map to EditRoot coordinates.
				 */
    TileType type,		/* The type of material to be considered. */
    int xMask,			/* Indicates window (or windows) where cells
				 * must be expanded for their contents to be
				 * considered.  0 means treat everything as
				 * expanded.
				 */
    char *buffer)		/* buffer to hold node name */
{
    TileTypeBitMask mask;
    char *strptr;

    TTMaskZero(&mask);
    TTMaskSetType(&mask, type);

    /* Clear out the temporary selection cell and yank all of the
     * connected paint into it.
     */

    UndoDisable();
    DBCellClearDef(Select2Def);
    SimTreeCopyConnect(scx, &mask, xMask, DBConnectTbl,
	    &TiPlaneRect, Select2Use, buffer);
    UndoEnable();

    /* Strip out path if name is global (ends with a "!")	*/
    /* and flag SimIgnoreGlobals is not set.			*/

    if (!SimIgnoreGlobals)
    {
	strptr = buffer + strlen(buffer) - 1;
	if (*strptr == '!') {
	    *strptr = '\0';
	    while (strptr != buffer) {
		if (*strptr == '/') {
		    strptr++;
		    break;
		}
		strptr--;
	    }
	}
	else {
	    strptr = buffer;
	}
    }
    else
	strptr = buffer;

    return(strptr);
}

int
NullFunc(void)
{
    return(0);
}

/*
 * ----------------------------------------------------------------------------
 * SimFreeNodeList
 *
 *	This procedure frees all space allocated for the node list
 *	data structure.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	After the list has been deallocated, the head pointer is set to
 *	a NULL value.
 * ----------------------------------------------------------------------------
 */

void
SimFreeNodeList(
    TileListElt **List)
{
    TileListElt *current;
    TileListElt *temp;

    temp = *List;
    while (temp != NULL) {
	current = temp;
	temp = temp->tl_next;
	freeMagic((char*)current->tl_nodeName);
	freeMagic(current);
    }
    *List = (TileListElt *) NULL;
}

TileListElt *
simFreeNodeEntry(
    TileListElt *list,
    TileListElt *entry)
{
    TileListElt *prev, *curr;

    prev = list;
    for( curr = prev->tl_next; curr != NULL; prev = curr,curr = curr->tl_next )
	if( curr == entry )
	{
	    prev->tl_next = curr->tl_next;
	    freeMagic( (char*)entry->tl_nodeName );
	    freeMagic( entry );
	    return( prev );		/* next element in list */
	}

    /* should never get here */
    return(entry);
}

/*
 * ----------------------------------------------------------------------------
 * SimSelectArea
 *
 * 	This procedure checks the current selected paint in the circuit and
 *	extracts the names of all the nodes in the selected area.
 *
 * Results:
 *	Returns a list of node name data structures.
 *
 * Side Effects:
 *	The tiles of the selection cell definition are first marked in the
 *	search algorithm.  After finishing the search, these marks are
 *	erased.
 *
 * ----------------------------------------------------------------------------
 */

TileListElt *
SimSelectArea(
    const Rect * __unused__(rect))
{
    int plane;
    int SimSelectFunc(Tile *tile, TileType dinfo, ClientData cdata); /* cb_database_srpaintarea_t (TileListElt **pHead) */

    /* only need to extract node names if the selection has changed or
     * if node aliases are to be printed.
     */

    if (SimRecomputeSel || (SimGetnodeAlias && SimIsGetnode)) {
	SimFreeNodeList(&NodeList);
	HashInit(&SimAbortSeenTbl, 20, HT_STRINGKEYS);

	/* find all nodes in the current selection */

	for (plane = PL_TECHDEPBASE; plane < DBNumPlanes; plane++)
	{
	    (void) DBSrPaintArea((Tile *) NULL, SelectDef->cd_planes[plane],
			&TiPlaneRect, &DBAllButSpaceAndDRCBits,
			SimSelectFunc, PTR2CD(&NodeList));
	}

	HashKill(&SimAbortSeenTbl);
	ExtResetTiles(SelectDef, (ClientData) CLIENTDEFAULT);
	SimGetNodeCleanUp();
	SimRecomputeSel = FALSE;
    }
    if (SigInterruptPending) {

	/* if caught an interrupt, be sure to recompute the selection
	 * next time around.
	 */

	SimRecomputeSel = TRUE;
    }
    return(NodeList);
}


/*
 * ----------------------------------------------------------------------------
 * SimSelectFunc
 *
 * 	This procedure is called for each tile in the current selection.
 *	It first checks to see if the node the tile belongs to has not
 *	yet been visited.  If it has not been visited, then the node name
 *	is extracted and all other tiles in the selection which belong to
 *	this node are marked.
 *
 * Results:
 *	Return 0 to keep the search going.
 *
 * Side effects:
 *	The tiles in the selection cell definition are left marked.
 *	It is the responsibility of the calling function to erase these
 *	tile marks when finished.
 * ----------------------------------------------------------------------------
 */

/** @typedef cb_database_srpaintarea_t */
int
SimSelectFunc(
    Tile *tile,		/* Tile in SelectDef. */
    TileType dinfo,	/* Split tile information */
    ClientData cdata)	/* list of node names found (TileListElt **pHead) */
{
    TileListElt **pHead = (TileListElt **)CD2PTR(cdata);
    TileTypeBitMask 	mask;
    SearchContext 	scx;
    DBWclientRec 	*crec;
    MagWindow 		*window;
    char 		nameBuff[256], *nodeName;
    TileListElt 	*newNodeTile;
    TileType 		type;
    bool		coord;

    window = CmdGetRootPoint((Point *) NULL, &scx.scx_area);
    if (window == NULL) return 1;

    /* check to see if the node has already been extracted */

    if (TiGetClientINT(tile) == 1) {
	return(0);
    }

    if (IsSplit(tile))
    {
	type = (dinfo & TT_SIDE) ? SplitRightType(tile):
			SplitLeftType(tile);
    }
    else
	type = TiGetTypeExact(tile);

    /* get the tile's area, and initialize the tile's search context. */

    TITORECT(tile, &scx.scx_area);

    /* make sure that search context isn't placed on an empty	*/
    /* corner of a split tile.					*/
    if (IsSplit(tile))
    {
	if (dinfo & TT_SIDE)
	    scx.scx_area.r_xbot = scx.scx_area.r_xtop - 1;
	if (!(SplitDirection(tile) ^ ((dinfo & TT_SIDE) ? 1 : 0)))
	    scx.scx_area.r_ybot = scx.scx_area.r_ytop - 1;
    }

    scx.scx_area.r_xtop = scx.scx_area.r_xbot + 1;
    scx.scx_area.r_ytop = scx.scx_area.r_ybot + 1;

    scx.scx_use = (CellUse *) window->w_surfaceID;
    scx.scx_trans = GeoIdentityTransform;
    crec = (DBWclientRec *) window->w_clientData;

    TTMaskZero(&mask);
    TTMaskSetType(&mask, type);
    TTMaskAndMask(&mask, &crec->dbw_visibleLayers);
    TTMaskAndMask(&mask, &DBAllButSpaceAndDRCBits);

    /* Check if the area is above a layer which is not visible. */

    if (TTMaskIsZero(&mask)) {
	return(0);
    }

    /* mark all other tiles in the selection that are part of this node */

    SimSrConnect(SelectDef, &scx.scx_area, &DBAllButSpaceAndDRCBits,
            DBConnectTbl, &TiPlaneRect, NullFunc, (ClientData) NULL);

    /* Pick a tile type to use for selection. */

    for (type = TT_TECHDEPBASE; type < DBNumTypes; type += 1)
    {
	if (TTMaskHasType(&mask, type)) break;
    }

    nameBuff[0] = '\0';
    nodeName = SimSelectNode(&scx, type, CU_DESCEND_ALL, nameBuff);

    /* add the node name to the list only if it has not been seen yet */

    coord = (nodeName[0] == '@' && nodeName[1] == '=') ? TRUE : FALSE;

    if (coord || HashLookOnly(&SimNodeNameTbl, nodeName) == (HashEntry *) NULL)
    {
	if (! coord)
	    HashFind(&SimNodeNameTbl, nodeName);
	newNodeTile = (TileListElt *) mallocMagic((unsigned) (sizeof (TileListElt)));
	char *tmp = (char *) mallocMagic((unsigned) (strlen(nodeName) + 1));
	strcpy(tmp, nodeName);
	newNodeTile->tl_nodeName = tmp;  /* assign to const */
	newNodeTile->tl_nodeTile = tile;
	newNodeTile->tl_dinfo = dinfo;
	newNodeTile->tl_next = *pHead;
	*pHead = newNodeTile;
    }
    return(0);
}

#ifdef RSIM_MODULE

/*
 * ----------------------------------------------------------------------------
 * SimSelection
 *
 *	This procedure applies the specified rsim command to the list of
 *	nodes in the current selection and also attaches the rsim node
 *	value string to each node.
 *
 * Results:
 *	returns FALSE if no nodes are in the selection.
 *
 * Side effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */

bool
SimSelection(
    const char *cmd)		/* rsim command to apply to the selection */
{
    static const char Hstring[] = "RSIM=1";
    static const char Lstring[] = "RSIM=0";
    static const char Xstring[] = "RSIM=X";
    static const char QUESTstring[] = "?";

    char timeString[256];
    char questString[2];
    TileListElt	*current, *node_list;
    char 		*replyLine;
    char		*strPtr;
    bool		goodReply;

    extern void		RsimErrorMsg(void);

    timeString[0] = 0;
    strcpy(questString, QUESTstring); /* writable form */

    /* check to see if Rsim has been started yet */

    if (!SimRsimRunning) {
	RsimErrorMsg();
	return(FALSE);
    }

    /* get the list of all nodes in the selection */

    SimIsGetnode = FALSE;
    SimUseCoords = SimHasCoords;

    HashInit(&SimNodeNameTbl, 60, HT_STRINGKEYS);
    node_list = SimSelectArea((Rect *)NULL);

    if (node_list == (TileListElt *) NULL) {
	TxPrintf("You must select paint (rather than a cell) to use Rsim on \
the selection.\n");
	goto bad;
    }

    /* Walk the list of node names, apply the rsim command to each node,
     * and then process the results.
     */

    for (current = node_list; current != NULL; current = current->tl_next)
    {
	current->tl_simLabel = QUESTstring;
	SimRsimIt(cmd, current->tl_nodeName);

	if (!SimGetReplyLine(&replyLine)) {
	    goto bad;
	}

	if (!replyLine) {
	    /* Rsim's reponse to the command was just a prompt.  We are done
	     * with the current node, so process the next node in the
	     * selection.
	     */
	    continue;
	}

	/* check for node names not recognized by Rsim */

	if (strncmp(replyLine, "time = ", 7) == 0)   {
	    if (timeString[0] == 0) {
		strcpy(timeString, replyLine);
	    }
	    TxPrintf("%s not recognized in sim file\n", current->tl_nodeName);
	    while(replyLine) {
		/* swallow Rsim reply until no more is left */
		if (!SimGetReplyLine(&replyLine)) {
		    goto bad;
		}
	    }
	    continue;
	}

	/* update the node value label strings  */

	if (*cmd == 'd')
	{
	    const char  *name = current->tl_nodeName;
	    bool  coord = (name[0] == '@' && name[1] == '=') ? TRUE : FALSE;

	    strPtr = strrchr( replyLine, '=' );
	    if( strPtr == NULL )
		strPtr = questString;
	    else if( coord )
	    {
		*strPtr = '\0';
		name = replyLine;
		if( HashLookOnly(&SimNodeNameTbl, name) == (HashEntry *) NULL)
		{
		    freeMagic((char*)current->tl_nodeName);
		    char *tmp = (char *) mallocMagic((unsigned) (strlen(name) + 1));
		    strcpy(tmp, name);
		    current->tl_nodeName = tmp; /* assign to const */
		    HashFind(&SimNodeNameTbl, current->tl_nodeName);
		    *strPtr++ = '=';
		}
		else
		{
		    current = simFreeNodeEntry( node_list, current );
		    *strPtr = '=';
		}
	    }
	    else
		strPtr++;

	    switch (*strPtr) {
		case '1' :
		    current->tl_simLabel = Hstring;
		    break;
		case '0' :
		    current->tl_simLabel = Lstring;
		    break;
		case 'X' :
		    current->tl_simLabel = Xstring;
		    break;
		case '=' :
		    break;
		default :
		    current->tl_simLabel = QUESTstring;
		    break;
	    }
	}

	/* read all lines of the Rsim reply */

	goodReply = TRUE;
	for (;replyLine; goodReply = SimGetReplyLine(&replyLine)) {
	    if (!goodReply) {
		goto bad;
	    }
	    if (!strncmp(replyLine, "time = ", 7)) {
		if (!(timeString[0])) {
		    strcpy(timeString, replyLine);
		}
		continue;
	    } else if( *strPtr != '=' ) {
		TxPrintf("%s\n", replyLine);
	    }
	}
    }
    if (timeString[0] != 0) {
	TxPrintf("%s\n", timeString);
    }

    HashKill(&SimNodeNameTbl);
    return(TRUE);

  bad:
    HashKill(&SimNodeNameTbl);
    return(FALSE);
}


#endif

/*
 * ----------------------------------------------------------------------------
 * SimAddLabels
 *
 *	This procedure adds the node value labels to the Magic database
 *	so they will be displayed in the layout.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The cell modified flags are deliberately not set when these labels
 *	are added to the database.
 *
 * ----------------------------------------------------------------------------
 */

void
SimAddLabels(
    TileListElt *SelectNodeList,
    CellDef *rootuse)		/* the root cell def for the window */
{

    TileListElt *current;
    Rect selectBox;
    int pos;

    /* walk the list of selected nodes, add the node value label to the
     * database.
     */

    for (current = SelectNodeList; current != (TileListElt *) NULL;
	current = current->tl_next) {
	if (*(current->tl_simLabel) == '?') {
	    continue;
	}
	TiToRect(current->tl_nodeTile, &selectBox);
	pos = SimPutLabel(rootuse, &selectBox, GEO_CENTER,
		current->tl_simLabel, TT_SPACE);
	DBReComputeBbox(rootuse);
	DBWAreaChanged(rootuse, &selectBox, DBW_ALLWINDOWS, &DBAllButSpaceBits);
    }
}

#ifdef RSIM_MODULE

/*
 * ----------------------------------------------------------------------------
 * SimRsimMouse
 *
 *	This procedure erases the old rsim node labels and display the
 *	node labels of the current selection.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */

void
SimRsimMouse(
    MagWindow *w)
{

    CellUse *cu;
    bool sawcell;
    SimDefListElt *dummy;

    if ((w == (MagWindow *) NULL) || (w->w_client != DBWclientID))
    {
	TxError("Put the cursor in a layout window\n");
	return;
    }

    cu = (CellUse *)w->w_surfaceID;	/* get root cell use for wind */

    /* check to see if the cell def is already in our list */
    sawcell = FALSE;
    for (dummy = SimCellLabList; dummy; dummy = dummy->dl_next) {
	if (dummy->dl_def == cu->cu_def) {
	    sawcell = TRUE;
	    break;
	}
    }

    if (!sawcell) {
	/* add the cell def to the list */
	if (SimCellLabList == (SimDefListElt *) NULL) {
	    SimCellLabList = (SimDefListElt *) mallocMagic((unsigned) (sizeof(SimDefListElt)));
	    SimCellLabList->dl_isMarked = FALSE;
	    SimCellLabList->dl_def = cu->cu_def;
	    SimCellLabList->dl_next = (SimDefListElt *) NULL;
	    dummy = SimCellLabList;
	}
	else {
	    dummy = (SimDefListElt *) mallocMagic((unsigned) (sizeof(SimDefListElt)));
	    dummy->dl_isMarked = FALSE;
	    dummy->dl_next = SimCellLabList;
	    dummy->dl_def = cu->cu_def;
	    SimCellLabList= dummy;
	}
    }

    SimEraseLabels();
    if (SimSelection("d")) {
	dummy->dl_isMarked = TRUE;
	SimAddLabels(NodeList, dummy->dl_def);
    }
}

#endif

/*
 * ----------------------------------------------------------------------------
 * SimGetnode
 *
 *	This procedure prints the node names of all selected nodes.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */

void
SimGetnode(void)
{
    TileListElt *current;

    /* get the list of node names */

    SimIsGetnode = TRUE;
    SimUseCoords = FALSE;

    HashInit(&SimNodeNameTbl, 60, HT_STRINGKEYS);
    current = SimSelectArea((Rect *)NULL);
    HashKill(&SimNodeNameTbl);

    if (current == (TileListElt *) NULL) {
	TxPrintf("You must select paint (not a cell) to use getnode.\n");
	return;
    }

    for (; current != (TileListElt *) NULL; current = current->tl_next)
    {
#ifdef MAGIC_WRAPPER
	/* Return the node name as the result of the command */
	Tcl_AppendElement(magicinterp, current->tl_nodeName);
#else
	TxPrintf("node name : %s\n", current->tl_nodeName);
#endif
    }
}

/*
 * ----------------------------------------------------------------------------
 * SimGetsnode
 *
 *	This procedure prints the short node names of all selected nodes.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */

void
SimGetsnode(void)
{
    TileListElt *current;

    /* get the list of node names */

    SimIsGetnode = TRUE;
    SimUseCoords = TRUE;

    HashInit(&SimNodeNameTbl, 60, HT_STRINGKEYS);
    current = SimSelectArea((Rect *)NULL);
    HashKill(&SimNodeNameTbl);

    if (current == (TileListElt *) NULL) {
	TxPrintf("You must select paint (not a cell) to use getnode.\n");
	return;
    }

    for (; current != (TileListElt *) NULL; current = current->tl_next)
    {
#ifdef MAGIC_WRAPPER
	/* Return the node short name as the result of the command */
	Tcl_AppendElement(magicinterp, current->tl_nodeName);
#else
	TxPrintf("short node name : %s\n", current->tl_nodeName);
#endif
    }
}



/*
 * ----------------------------------------------------------------------------
 * SimEraseLabels
 *
 *	This procedure erases the RSIM labels from any cell defs they
 *	may have been added to.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Removes the RSIM labels from "marked" cell defs.
 *
 * ----------------------------------------------------------------------------
 */

void
SimEraseLabels(void)
{
    SimDefListElt *p;

    for (p = SimCellLabList; p; p = p->dl_next) {
	if (p->dl_isMarked) {
	    p->dl_isMarked = FALSE;
	    DBEraseLabelsByContent(p->dl_def, (Rect *)NULL, -1, "RSIM=X");
	    DBEraseLabelsByContent(p->dl_def, (Rect *)NULL, -1, "RSIM=1");
	    DBEraseLabelsByContent(p->dl_def, (Rect *)NULL, -1, "RSIM=0");
	}
    }
}
