/*
 * CmdE.c --
 *
 * Commands with names beginning with the letter E.
 *
 *     *********************************************************************
 *     * 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 const char rcsid[] __attribute__ ((unused)) = "$Header: /usr/cvsroot/magic-8.0/commands/CmdE.c,v 1.4 2010/06/17 14:38:33 tim Exp $";
#endif  /* not lint */

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

#include "tcltk/tclmagic.h"
#include "utils/magic.h"
#include "utils/styles.h"
#include "utils/geometry.h"
#include "utils/utils.h"
#include "tiles/tile.h"
#include "utils/hash.h"
#include "database/database.h"
#include "windows/windows.h"
#include "graphics/graphics.h"
#include "dbwind/dbwind.h"
#include "utils/main.h"
#include "commands/commands.h"
#include "textio/textio.h"
#include "utils/macros.h"
#include "drc/drc.h"
#include "textio/txcommands.h"
#include "extract/extract.h"
#include "extract/extractInt.h"
#include "select/select.h"

/* C99 compat */
#include "dbwind/dbwtech.h"


/*
 * ----------------------------------------------------------------------------
 *
 * CmdEdit --
 *
 * Implement the "edit" command.
 * Use the cell that is currently selected as the edit cell.  If more than
 * one cell is selected, use the point to choose between them.  If the
 * optional argument "<instname>" is provided, then edit the specified
 * instance (if it exists in the current layout window).
 *
 * Usage:
 *	edit [<instname>]
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Sets EditCellUse.
 *
 * ----------------------------------------------------------------------------
 */

/* The following variable is set by cmdEditEnumFunc to signal that there
 * was at least one cell in the selection.
 */

static bool cmdFoundNewEdit;

void
CmdEdit(
    MagWindow *w,
    TxCommand *cmd)
{
    Rect area, pointArea;
    CellUse *usave, *use = NULL;
    CellDef *csave;
    int cmdEditRedisplayFunc(MagWindow *w, Rect *area);		/* Forward declaration. */
    int cmdEditEnumFunc(CellUse *selUse, CellUse *use, Transform *transform, Rect *area);		/* Forward declaration. */
    bool noCurrentUse = FALSE;

    if ((w != NULL) && (cmd->tx_argc == 2))
    {
	CellUse *rootUse;
	SearchContext scx;

	rootUse = (CellUse *)w->w_surfaceID;
	bzero(&scx, sizeof(SearchContext));
	DBTreeFindUse(cmd->tx_argv[1], rootUse, &scx);
	use = scx.scx_use;
    }

    if ((use == NULL) && (cmd->tx_argc > 1))
    {
	TxError("Usage: edit\nMaybe you want the \"load\" command\n");
	return;
    }
    usave = EditCellUse;
    if (usave != (CellUse *)NULL)
    {

        /* Record the current edit cell's area for redisplay (now that it's
         * not the edit cell, it will be displayed differently).  Do this
         * only in windows where the edit cell is displayed differently from
         * other cells.
         */

        GeoTransRect(&EditToRootTransform, &(usave->cu_def->cd_bbox), &area);
        (void) WindSearch(DBWclientID, (ClientData) NULL,
	    	(Rect *) NULL, cmdEditRedisplayFunc, (ClientData) &area);

	DBWUndoOldEdit(EditCellUse, EditRootDef, &EditToRootTransform,
			&RootToEditTransform);
    }

    /* Use the position of the point to select one of the currently-selected
     * cells (if there are more than one).  If worst comes to worst, just
     * select any selected cell.
     */

    (void) ToolGetPoint((Point *) NULL, &pointArea);

    cmdFoundNewEdit = FALSE;
    csave = EditRootDef;
    usave = EditCellUse;

    if (use == NULL)
    {
	EditCellUse = NULL;
	SelEnumCells(FALSE, (bool *) NULL, (SearchContext *) NULL,
		cmdEditEnumFunc, (ClientData) &pointArea);
    }
    else
    {
	EditCellUse = use;
	cmdFoundNewEdit = TRUE;
    }

    if (EditCellUse == (CellUse *)NULL)
    {
	TxError("No cell selected to edit.\n");
	EditCellUse = usave;
	EditRootDef = csave;
	return;
    }
    else if (!(EditCellUse->cu_def->cd_flags & CDAVAILABLE))
	DBCellRead(EditCellUse->cu_def, TRUE, TRUE, NULL);

    if (EditCellUse->cu_def->cd_flags & CDNOEDIT)
    {
	TxError("File %s is not writeable.  Edit not set.\n",
		EditCellUse->cu_def->cd_file);
	cmdFoundNewEdit = FALSE;
	EditCellUse = usave;
	EditRootDef = csave;
	return;
    }

    if (!cmdFoundNewEdit)
	TxError("You haven't selected a new cell to edit.\n");

    CmdSetWindCaption(EditCellUse, EditRootDef);
    DBWUndoNewEdit(EditCellUse, EditRootDef,
		&EditToRootTransform, &RootToEditTransform);

    /* Now record the new edit cell's area for redisplay. */

    GeoTransRect(&EditToRootTransform, &(EditCellUse->cu_def->cd_bbox), &area);
    (void) WindSearch(DBWclientID, (ClientData) NULL,
	    (Rect *) NULL, cmdEditRedisplayFunc, (ClientData) &area);

    if ((cmd->tx_argc == 1) && cmdFoundNewEdit)
    {
	/* Recast the command with the instance name for logging */
	sprintf(cmd->tx_argstring, "edit %s", EditCellUse->cu_id);
	TxRebuildCommand(cmd);
    }

}

/* Search function to handle redisplays for CmdEdit:  it checks to
 * be sure that this is a window on the edit cell, then if edit cells
 * are displayed differently from other cells in the window, the area
 * of the edit cell is redisplayed.  Also, if the grid is on in this
 * window, the origin area is redisplayed.
 */

int
cmdEditRedisplayFunc(
    MagWindow *w,			/* Window containing edit cell. */
    Rect *area)			/* Area to be redisplayed. */
{
    static const Rect origin = {{-1, -1}, {1, 1}};
    Rect tmp;
    DBWclientRec *crec = (DBWclientRec *) w->w_clientData;

    if (((CellUse *) w->w_surfaceID)->cu_def != EditRootDef) return 0;
    if (!(crec->dbw_flags & DBW_ALLSAME))
	DBWAreaChanged(EditRootDef, area, crec->dbw_bitmask,
	    &DBAllButSpaceBits);
    if (crec->dbw_flags & DBW_GRID)
    {
	GeoTransRect(&EditToRootTransform, &origin, &tmp);
	DBWAreaChanged(EditRootDef, &tmp, crec->dbw_bitmask,
	    &DBAllButSpaceBits);
    }
    return 0;
}

/* Search function to find the new edit cell:  look for a cell use
 * that contains the rectangle passed as argument.  If we find such
 * a use, return 1 to abort the search.  Otherwise, save information
 * about this use anyway:  it'll become the edit cell if nothing
 * better is found.
 */

int
cmdEditEnumFunc(
    CellUse *selUse,		/* Use from selection (not used). */
    CellUse *use,		/* Use from layout that corresponds to
				 * selUse (could be an array!).
				 */
    Transform *transform,	/* Transform from use->cu_def to root coords. */
    Rect *area)			/* We're looking for a use containing this
				 * area, in root coords.
				 */
{
    Rect defArea, useArea;
    int xhi, xlo, yhi, ylo;

    /* Save this use as the default next edit cell, regardless of whether
     * or not it overlaps the area we're interested in.
     */

    EditToRootTransform = *transform;
    GeoInvertTrans(transform, &RootToEditTransform);
    EditCellUse = use;
    EditRootDef = SelectRootDef;
    cmdFoundNewEdit = TRUE;

    /* See if the bounding box of this use overlaps the area we're
     * interested in.
     */

    GeoTransRect(&RootToEditTransform, area, &defArea);
    GeoTransRect(&use->cu_transform, &defArea, &useArea);
    if (!GEO_OVERLAP(&useArea, &use->cu_bbox)) return 0;

    /* It overlaps.  Now find out which array element it points to,
     * and adjust the transforms accordingly.
     */

    DBArrayOverlap(use, &useArea, &xlo, &xhi, &ylo, &yhi);
    GeoTransTrans(DBGetArrayTransform(use, xlo, ylo), transform,
	    &EditToRootTransform);
    GeoInvertTrans(&EditToRootTransform, &RootToEditTransform);
    return 1;
}

/*
 * ----------------------------------------------------------------------------
 *
 * CmdElement --
 *
 * Implement the "element" command.
 * Handle general-purpose elements which are drawn on top of the layout.
 *
 * Usage:
 *	element name <add|delete|configure|cget> <line|rectangle|text> ...
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	May modify the hash table of elements.
 *
 * ----------------------------------------------------------------------------
 */

#define ELEMENT_ADD	0
#define ELEMENT_DELETE	1
#define ELEMENT_CONFIG	2
#define ELEMENT_NAMES	3
#define ELEMENT_INBOX	4
#define ELEMENT_HELP	5

#define ELEMENT_LINE	0
#define ELEMENT_RECT	1
#define ELEMENT_TEXT	2

#define OPTION_TEXT	0
#define OPTION_STYLE	1
#define OPTION_POSITION	2
#define OPTION_FLAGS	3

void
CmdElement(
    MagWindow *w,
    TxCommand *cmd)
{

    int option, type;
    const char * const *msg;
    Rect area;
    int style;
    CellDef *def;
    CellUse *use;
    bool getopt;

    static const char * const cmdElementOption[] = {
	"add			create a new element",
	"delete		delete an existing element",
	"configure		configure or query an existing element",
	"names		print names of all elements",
	"inbox		print name of element nearest the box",
	"help		print help information",
	NULL
    };

    static const char * const cmdElementType[] = {
	"line		name style x1 y1 x2 y2",
	"rectangle		name style llx lly urx ury",
	"text		name style cx cy label",
	NULL
    };

    static const char * const cmdConfigureType[] = {
	"text		get (or) replace <string>",
	"style		get (or) add <style> (or) remove <style>",
	"position		get (or) <point> (or) <rect>",
	"flags		set element flags",
	NULL
    };

    option = ELEMENT_HELP;
    windCheckOnlyWindow(&w, DBWclientID);
    if (w == (MagWindow *) NULL) return;

    use = (CellUse *)w->w_surfaceID;
    if (use == NULL) return;
    def = use->cu_def;
    if (def == NULL) return;

    if (cmd->tx_argc > 1)
    {
	option = Lookup(cmd->tx_argv[1], cmdElementOption);
	if (option < 0) option = ELEMENT_HELP;
    }

    switch (option)
    {
	case ELEMENT_ADD:
	    if (cmd->tx_argc < 3)
	    {
		TxError("Usage:  element add <type> <name> <values...>\n");
		return;
	    }
	    type = Lookup(cmd->tx_argv[2], cmdElementType);
	    if (type < 0)
	    {
		TxError("Usage:  element add <type>..., where type is one of:\n");
		for (msg = &(cmdElementType[0]); *msg != NULL; msg++)
		    TxError("    %s\n", *msg);

		return;
	    }
	    else if (cmd->tx_argc >= 7)
	    {
		if (StrIsInt(cmd->tx_argv[4]))
		    style = atoi(cmd->tx_argv[4]);
		else
		{
		    style = GrGetStyleFromName(cmd->tx_argv[4]);
		    if (style < 0)
		    {
			TxError("Unknown style \"%s\".", cmd->tx_argv[4]);
			TxError("Use a number or one of the long names in the"
				" .dstyle file\n");
			return;
		    }
		}
		area.r_xbot = cmdParseCoord(w, cmd->tx_argv[5], FALSE, TRUE);
		area.r_ybot = cmdParseCoord(w, cmd->tx_argv[6], FALSE, FALSE);

		switch (type)
		{
		    case ELEMENT_LINE:
			if (cmd->tx_argc != 9)
			{
			    TxError("Usage:  element add %s\n",
					cmdElementType[ELEMENT_LINE]);
			    return;
			}
			area.r_xtop = cmdParseCoord(w, cmd->tx_argv[7], FALSE, TRUE);
			area.r_ytop = cmdParseCoord(w, cmd->tx_argv[8], FALSE, FALSE);
			DBWElementAddLine(w, cmd->tx_argv[3], &area, def, style);
			break;
		    case ELEMENT_RECT:
			if (cmd->tx_argc != 9)
			{
			    TxError("Usage:  element add %s\n",
					cmdElementType[ELEMENT_RECT]);
			    return;
			}
			area.r_xtop = cmdParseCoord(w, cmd->tx_argv[7], FALSE, TRUE);
			area.r_ytop = cmdParseCoord(w, cmd->tx_argv[8], FALSE, FALSE);
			DBWElementAddRect(w, cmd->tx_argv[3], &area, def, style);
			break;
		    case ELEMENT_TEXT:
			if (cmd->tx_argc != 8)
			{
			    TxError("Usage:  element add %s\n",
					cmdElementType[ELEMENT_TEXT]);
			    return;
			}
			DBWElementAddText(w, cmd->tx_argv[3], area.r_xbot,
				area.r_ybot, cmd->tx_argv[7], def, style);
			break;
		}
	    }
	    else
	    {
		TxError("Usage:  element add %s\n", cmdElementType[type]);
		return;
	    }
	    break;
	case ELEMENT_DELETE:
	    if (cmd->tx_argc != 3)
	    {
		TxPrintf("Usage:  element delete <name>\n");
		return;
	    }
	    DBWElementDelete(w, cmd->tx_argv[2]);
	    break;
	case ELEMENT_CONFIG:
	    if (cmd->tx_argc < 4)
	    {
		TxError("Usage:  element configure <name> <option...>\n");
		return;
	    }
	    type = Lookup(cmd->tx_argv[3], cmdConfigureType);
	    if ((type < 0) || (cmd->tx_argc < 4))
	    {
		TxError("Usage:  element configure <name> <option...>, "
				"where option is one of:\n");
		for (msg = &(cmdConfigureType[0]); *msg != NULL; msg++)
		{
		    TxError("    %s\n", *msg);
		}
		return;
	    }

	    if (cmd->tx_argc == 4)
		getopt = TRUE;
	    else
		getopt = !strncmp(cmd->tx_argv[4], "get", 3);

	    switch (type)
	    {
		case OPTION_TEXT:
		    if (getopt)
			DBWElementText(w, cmd->tx_argv[2], NULL);
		    else
		    {
			if (cmd->tx_argc == 5)
			    DBWElementText(w, cmd->tx_argv[2], cmd->tx_argv[4]);
			else if (cmd->tx_argc != 6)
			{
			    TxError("Usage:  element configure <name> text "
					"replace <string>\n");
			    return;
			}
			else
			{
			    DBWElementText(w, cmd->tx_argv[2], cmd->tx_argv[4]);
			}
		    }
		    break;
		case OPTION_STYLE:
		    if (getopt)
			DBWElementStyle(w, cmd->tx_argv[2], -1, 0);
		    else if (cmd->tx_argc != 6)
		    {
			TxError("Usage: element configure <name> style add|remove "
					" <style>\n");
			return;
		    }
		    else
		    {
			if (StrIsInt(cmd->tx_argv[5]))
			    style = atoi(cmd->tx_argv[5]);
			else
			{
			    style = GrGetStyleFromName(cmd->tx_argv[5]);
		    	    if (style < 0)
		    	    {
				TxError("Unknown style \"%s\".", cmd->tx_argv[5]);
				TxError("Use a number or one of the long names "
						"in the .dstyle file\n");
				return;
			    }
			}
			if (!strncmp(cmd->tx_argv[4], "add", 3))
			    DBWElementStyle(w, cmd->tx_argv[2], style, TRUE);
			else
			    DBWElementStyle(w, cmd->tx_argv[2], style, FALSE);
		    }
		    break;
		case OPTION_POSITION:
		    if (getopt)
		    {
			DBWElementPos(w, cmd->tx_argv[2], NULL);
		    }
		    else
		    {
			Rect crect;

			if (cmd->tx_argc >= 6)
			{
			    crect.r_xbot = cmdParseCoord(w, cmd->tx_argv[4],
					FALSE, TRUE);
			    crect.r_ybot = cmdParseCoord(w, cmd->tx_argv[5],
					FALSE, FALSE);
			    crect.r_xtop = crect.r_xbot; /* placeholder */
			    crect.r_ytop = crect.r_ybot; /* placeholder */
			}
			if (cmd->tx_argc == 8)
			{
			    crect.r_xtop = cmdParseCoord(w, cmd->tx_argv[6],
					FALSE, TRUE);
			    crect.r_ytop = cmdParseCoord(w, cmd->tx_argv[7],
					FALSE, FALSE);
			}

			if (cmd->tx_argc == 6 || cmd->tx_argc == 8)
			    DBWElementPos(w, cmd->tx_argv[2], &crect);
			else
			{
badrect:
			    TxError("Usage: element configure <name> position "
					"<x> <y> [<x2> <y2>]\n");
			    return;
			}
		    }
		    break;
		case OPTION_FLAGS:
		    if (getopt)
		    {
			TxError("(unimplemented function)\n");
		    }
		    else
		    {
			if (cmd->tx_argc >= 5)
			    DBWElementParseFlags(w, cmd->tx_argv[2], cmd->tx_argv[4]);
			else
			{
			    TxError("Usage: element configure <name> flags "
					"(flag)\n");
			    return;
			}
		    }
		    break;
	    }
	    break;
	case ELEMENT_NAMES:
	    DBWElementNames();
	    break;
	case ELEMENT_INBOX:
	    if (!ToolGetBox((CellDef **)NULL, &area))
	    {
		TxError("Box tool must be present\n");
		return;
	    }
	    DBWElementInbox(&area);
	    break;
	case ELEMENT_HELP:
badusage:
            for (msg = &(cmdElementOption[0]); *msg != NULL; msg++)
            {
                TxPrintf("    %s\n", *msg);
            }
	    break;
    }
}



/*
 * ----------------------------------------------------------------------------
 *
 * CmdErase --
 *
 * Implement the "erase" command.
 * Erase paint in the specified layers from underneath the box in
 * EditCellUse->cu_def.
 *
 * Usage:
 *	erase [layers | cursor | pick x y]
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Modified EditCellUse->cu_def.
 *
 * ----------------------------------------------------------------------------
 */

/* The following information is used to keep track of cells to be
 * erased.  It is needed because we can't delete cells while searching
 * for cells, or the database gets screwed up.
 */

#define MAXCELLS 100
static CellUse *cmdEraseCells[MAXCELLS];
static int cmdEraseCount;

void
CmdErase(
    MagWindow *w,
    TxCommand *cmd)
{
    Rect editRect, areaReturn;
    TileTypeBitMask mask, errorLayersForErasure, activeLayersForErasure;
    extern int cmdEraseCellsFunc(SearchContext *scx, ClientData cdarg);

    windCheckOnlyWindow(&w, DBWclientID);
    if (w == (MagWindow *) NULL) return;

    if ((cmd->tx_argc == 4) && !strcmp(cmd->tx_argv[1], "pick"))
    {
	Point editPoint, rootPoint;
	editPoint.p_x = cmdParseCoord(w, cmd->tx_argv[2], FALSE, TRUE);
	editPoint.p_y = cmdParseCoord(w, cmd->tx_argv[3], FALSE, FALSE);
	GeoTransPoint(&EditToRootTransform, &editPoint, &rootPoint);
	CmdPaintEraseButton(w, &rootPoint, FALSE, FALSE);
	return;
    }

    if (cmd->tx_argc > 2)
    {
	TxError("Usage: %s [<layers> | cursor | pick x y]\n", cmd->tx_argv[0]);
	return;
    }

    if (!ToolGetEditBox(&editRect)) return;
    if (EditCellUse == NULL)
    {
        TxError("No cell def being edited!\n");
	return;
    }

    /*
     * Erase with no arguments is the same as erasing
     * everything underneath the box tool (ie, painting space)
     * Labels are automatically affected; subcells are not.
     */

    if (cmd->tx_argc == 1)
	(void) CmdParseLayers("*,label", &mask);
    else if (!strncmp(cmd->tx_argv[1], "cursor", 6))
    {
	Point editPoint, rootPoint;

	CmdPaintEraseButton(w, &cmd->tx_p, FALSE, TRUE);

	/* Recast the command as "erase pick x y" for logging purposes */
	CmdGetRootPoint(&rootPoint, (Rect *)NULL);
	GeoTransPoint(&RootToEditTransform, &rootPoint, &editPoint);
	sprintf(cmd->tx_argstring, "erase pick %di %di", editPoint.p_x,
			editPoint.p_y);
	TxRebuildCommand(cmd);
	return;
    }
    else if (!CmdParseLayers(cmd->tx_argv[1], &mask))
	return;

    if (TTMaskEqual(&mask, &DBSpaceBits))
	(void) CmdParseLayers("*,label", &mask);
    TTMaskClearType(&mask, TT_SPACE);
    if (TTMaskIsZero(&mask))
	return;

    /* Split the mask into active and error layers.  This restores	*/
    /* functionality lost since magic version 7.1.  Modified by BIM	*/
    /* (Iain McNally) 8/18/2018						*/

    TTMaskAndMask3(&activeLayersForErasure, &mask, &DBActiveLayerBits);
    TTMaskClearMask3(&errorLayersForErasure, &mask, &DBAllButSpaceAndDRCBits);

    /* Erase paint. */
    DBEraseValid(EditCellUse->cu_def, &editRect, &activeLayersForErasure, 0);

    /* Erase error layers if selected. */
    if (!TTMaskIsZero(&errorLayersForErasure))
    {
	DBEraseMask(EditCellUse->cu_def, &editRect, &errorLayersForErasure);
    }

    /* Erase labels. */
    areaReturn = editRect;
    DBEraseLabel(EditCellUse->cu_def, &editRect, &mask, &areaReturn);

    /* Erase subcells. */
    if (TTMaskHasType(&mask, L_CELL))
    {
	SearchContext scx;
	int i;

	/* To erase cells, we make a series of passes.  In each
	 * pass, collect a whole bunch of cells that are in the
	 * area of interest, then erase all those cells.  Continue
	 * this until all cells have been erased.
	 */

	scx.scx_use = EditCellUse;
	scx.scx_x = scx.scx_y = 0;
	scx.scx_area = editRect;
	scx.scx_trans = GeoIdentityTransform;
	while (TRUE)
	{
	    cmdEraseCount = 0;
	    (void) DBCellSrArea(&scx, cmdEraseCellsFunc, (ClientData) NULL);
	    for (i=0; i<cmdEraseCount; i++)
	    {
		DRCCheckThis(EditCellUse->cu_def, TT_CHECKSUBCELL,
		    &(cmdEraseCells[i]->cu_bbox));
		DBWAreaChanged(EditCellUse->cu_def,
		    &(cmdEraseCells[i]->cu_bbox), DBW_ALLWINDOWS,
		    (TileTypeBitMask *) NULL);
		DBUnLinkCell(cmdEraseCells[i], EditCellUse->cu_def);
		DBDeleteCell(cmdEraseCells[i]);
		(void) DBCellDeleteUse(cmdEraseCells[i]);
	    }
	    if (cmdEraseCount < MAXCELLS) break;
	}
    }
    DBAdjustLabels(EditCellUse->cu_def, &editRect);

    /* Don't run expensive DRC when only labels were erased */
    TTMaskClearType(&mask, L_LABEL);
    if (!TTMaskIsZero(&mask))
	DRCCheckThis (EditCellUse->cu_def, TT_CHECKPAINT, &editRect);
    TTMaskClearType(&mask, L_CELL);
    SelectClear();
    DBWAreaChanged(EditCellUse->cu_def, &areaReturn, DBW_ALLWINDOWS, &mask);
    DBReComputeBbox(EditCellUse->cu_def);
}

int
cmdEraseCellsFunc(
    SearchContext *scx,		/* Indicates cell found. */
    ClientData cdarg)		/* Not used. */
{
    /* All this procedure does is to remember cells that are
     * found, up to MAXCELLS of them.
     */

    if (cmdEraseCount >= MAXCELLS) return 1;
    cmdEraseCells[cmdEraseCount] = scx->scx_use;
    cmdEraseCount += 1;
    return 2;
}

/*
 * ----------------------------------------------------------------------------
 *
 * CmdExpand --
 *
 * Implement the "expand" command.
 *
 * Usage:
 *	expand [selection|surround|overlap|all] [toggle]
 *
 *	"selection" expands cells in the selection.  All other options
 *	expand cells in the layout.  "all" expands all cells in the
 *	layout.  "surround" expands cells which the cursor box
 *	surrounds completely, and "overlap" expands cells which the
 *	cursor box overlaps.
 *
 *	If "toggle" is specified, flips the expanded/unexpanded status.
 *	Cells which were expanded are unexpanded, and cells which were
 *	unexpanded are expanded.
 *
 *	For backwards compatibility:
 *	"expand" alone implements "expand overlap".
 *	"expand toggle" implements "expand selection toggle".
 *
 *	Also see:  CmdUnexpand
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Expansion state of cells is changed.  May read cells in from
 *	disk, and update bounding boxes that have changed.
 *
 * ----------------------------------------------------------------------------
 */

#define EXPAND_SELECTION      0
#define EXPAND_SURROUND       1
#define EXPAND_OVERLAP        2
#define EXPAND_ALL            3
#define EXPAND_HELP           4

void
CmdExpand(
    MagWindow *w,
    TxCommand *cmd)
{
    int windowMask, boxMask, d, option;
    bool doToggle = FALSE;
    const char * const *msg;
    Rect rootRect;
    CellUse *rootBoxUse;
    CellDef *rootBoxDef;

    int cmdExpandFunc(CellUse *use, int windowMask);		/* Forward reference. */

    static const char * const cmdExpandOption[] = {
	"selection	expand cell instances in the selection",
	"surround	expand cell instances which the cursor box surrounds",
	"overlap	expand cell instances which the cursor box overlaps",
	"all		expand all cell instances",
	NULL
    };

    if (cmd->tx_argc > 1)
    {
	if (!strncmp(cmd->tx_argv[cmd->tx_argc - 1], "toggle",
			strlen(cmd->tx_argv[cmd->tx_argc - 1])))
	{
	    doToggle = TRUE;
	    cmd->tx_argc--;
	}
    }

    if (cmd->tx_argc > 1)
    {
	option = Lookup(cmd->tx_argv[1], cmdExpandOption);
	if (option < 0) option = EXPAND_HELP;
    }
    else
	option = EXPAND_OVERLAP;

    if (option == EXPAND_HELP) goto badusage;

    windCheckOnlyWindow(&w, DBWclientID);
    if (w == (MagWindow *) NULL)
    {
	TxError("Point to a window first.\n");
	return;
    }
    windowMask = ((DBWclientRec *) w->w_clientData)->dbw_bitmask;
    d = DBLambda[1];
    rootBoxUse = (CellUse *)w->w_surfaceID;
    rootBoxDef = rootBoxUse->cu_def;

    /* Check for possible rescaling during the expansion, and adjust    */
    /* the cursor box and window to restore the original view.          */

    d = DBLambda[1];
    do
    {
	if (d != DBLambda[1])
	{
            d = DBLambda[1] / d;
            DBScalePoint(&rootRect.r_ll, d, 1);
            DBScalePoint(&rootRect.r_ur, d, 1);
            ToolMoveBox(TOOL_BL, &rootRect.r_ll, FALSE, rootBoxDef);
            ToolMoveCorner(TOOL_TR, &rootRect.r_ur, FALSE, rootBoxDef);

            /* Adjust all window viewing scales and positions and redraw */

            WindScale(d, 1);
	    TxPrintf("expand: rescaled by %d\n", d);
	    d = DBLambda[1];
	    if (doToggle) break;	/* Don't toggle twice */
	}
	(void) ToolGetBoxWindow(&rootRect, &boxMask);

	if (option != EXPAND_SELECTION)
	{
	    if ((boxMask & windowMask) != windowMask)
	    {
		TxError("The box isn't in the same window as the cursor.\n");
		return;
	    }
	}

	switch (option)
	{
	    case EXPAND_SELECTION:
		SelectExpand(windowMask,
			(doToggle) ? DB_EXPAND_TOGGLE : DB_EXPAND,
			(Rect *)NULL);
		break;
	    case EXPAND_OVERLAP:
		if (doToggle)
		{
		    DBExpandAll(rootBoxUse, &rootRect, windowMask,
				DB_EXPAND_TOGGLE | DB_EXPAND_OVERLAP,
				cmdExpandFunc, (ClientData)(pointertype)windowMask);
		    SelectExpand(windowMask,
			DB_EXPAND_TOGGLE | DB_EXPAND_OVERLAP,
			&rootRect);
		}
		else
		{
		    DBExpandAll(rootBoxUse, &rootRect, windowMask,
				DB_EXPAND | DB_EXPAND_OVERLAP,
				cmdExpandFunc, (ClientData)(pointertype)windowMask);
		    SelectExpand(windowMask,
			DB_EXPAND | DB_EXPAND_OVERLAP,
			&rootRect);
		}
		break;
	    case EXPAND_SURROUND:
		if (doToggle)
		{
		    DBExpandAll(rootBoxUse, &rootRect, windowMask,
				DB_EXPAND_TOGGLE | DB_EXPAND_SURROUND,
				cmdExpandFunc, (ClientData)(pointertype)windowMask);
		    SelectExpand(windowMask,
			DB_EXPAND_TOGGLE | DB_EXPAND_SURROUND,
			&rootRect);
		}
		else
		{
		    DBExpandAll(rootBoxUse, &rootRect, windowMask,
				DB_EXPAND | DB_EXPAND_SURROUND,
				cmdExpandFunc, (ClientData)(pointertype)windowMask);
		    SelectExpand(windowMask,
			DB_EXPAND | DB_EXPAND_SURROUND,
			&rootRect);
		}
		break;
	    case EXPAND_ALL:
		if (doToggle)
		{
		    DBExpandAll(rootBoxUse, &TiPlaneRect, windowMask,
				DB_EXPAND | DB_EXPAND_OVERLAP,
				cmdExpandFunc, (ClientData)(pointertype)windowMask);
		    SelectExpand(windowMask,
			DB_EXPAND | DB_EXPAND_OVERLAP,
			(Rect *)NULL);
		}
		else
		{
		    DBExpandAll(rootBoxUse, &TiPlaneRect, windowMask,
				DB_EXPAND | DB_EXPAND_OVERLAP,
				cmdExpandFunc, (ClientData)(pointertype)windowMask);
		    SelectExpand(windowMask,
			DB_EXPAND | DB_EXPAND_OVERLAP,
			(Rect *)NULL);
		}
		break;
	}
    } while (d != DBLambda[1]);

    return;

badusage:
    for (msg = &(cmdExpandOption[0]); *msg != NULL; msg++)
	TxPrintf("    %s\n", *msg);
    TxPrintf("    toggle	Toggle the visibility of cell instances.\n");
}

/* This function is called for each cell whose expansion status changed.
 * It forces the cells area to be redisplayed, then returns 0 to keep
 * looking for more cells to expand.
 */

int
cmdExpandFunc(
    CellUse *use,		/* Use that was just expanded. */
    int windowMask)		/* Window where it was expanded. */
{
    if (use->cu_parent == NULL) return 0;
    DBWAreaChanged(use->cu_parent, &use->cu_bbox, windowMask,
	    &DBAllButSpaceBits);
    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * CmdExtract --
 *
 * Implement the "extract" command.
 *
 * Usage:
 *	extract option args
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	There are no side effects on the circuit.  Various options
 *	may produce .ext files or change extraction parameters.
 *
 * ----------------------------------------------------------------------------
 */
#ifndef NO_EXT

#define EXTINCREMENTAL -1
#define	EXTALL		0
#define EXTCELL		1
#define	EXTDO		2
#define EXTHALO		3
#define EXTHELP		4
#define	EXTLENGTH	5
#define	EXTNO		6
#define	EXTPARENTS	7
#define EXTPATH		8
#define	EXTSHOWPARENTS	9
#define EXTSTEPSIZE	10
#define	EXTSTYLE	11
#define	EXTUNIQUE	12
#define	EXTWARN		13

#define	WARNALL		0
#define WARNDUP		1
#define	WARNFETS	2
#define WARNLABELS	3

#define	DOADJUST	0
#define	DOALL		1
#define	DOCAPACITANCE	2
#define	DOCORNERS	3
#define	DOCOUPLING	4
#define	DOEXTRESIST	5
#define	DOLENGTH	6
#define	DOLOCAL		7
#define	DORESISTANCE	8
#define	DOLABELCHECK	9
#define	DOALIASES      10
#define	DOUNIQUE       11
#define	DOEXTRESIST2   12

#define	LENCLEAR	0
#define	LENDRIVER	1
#define	LENRECEIVER	2

#define UNIQALL		0
#define UNIQTAGGED	1
#define UNIQNOPORTS	2
#define UNIQNOTOPPORTS	3

void
CmdExtract(
    MagWindow *w,
    TxCommand *cmd)
{
    const char * const *msg;
    char *namep, *arg;
    int option, warn, len, n, all;
    int dist;
    bool no;
    CellUse *selectedUse;
    CellDef	*selectedDef;
    bool dolist = FALSE;
    bool doforall = FALSE;
    bool doLocal = FALSE;
    int argc = cmd->tx_argc;
    char **argv = cmd->tx_argv;

    static const char * const cmdExtWarn[] =
    {
	"all			enable all warnings",
	"dup			warn when different nodes have the same name",
	"fets			warn about badly constructed fets",
	"labels		warn when subcell nodes are unlabelled",
	NULL
    };
    static const char * const cmdExtOption[] =
    {
	"adjust			compensate R and C hierarchically",
	"all			all options",
	"capacitance		extract substrate capacitance",
	"corners [value]	extract corner fringe capacitance,\n"
	"			optionally setting the model scale factor",
	"coupling		extract coupling capacitance",
	"extresist		extract resistance",
	"length			compute driver-receiver pathlengths",
	"local			put all generated files in the current directory",
	"lumped			estimate lumped resistance",
	"labelcheck		check for connections through sticky labels",
	"aliases		output all net name aliases",
	"unique	 [notopports]	ensure unique node names during extraction",
	"resistance		extract resistance (same as \"do extresist\")",
	NULL
    };
    static const char * const cmdExtLength[] =
    {
	"clear			clear the driver and receiver tables",
	"driver termName(s)	identify a driving (output) terminal",
	"receiver termName(s)	identify a receiving (input) terminal",
	NULL
    };

    /* These must match definitions EXT_UNIQ_* in extract/extract.h: */

    static const char * const cmdExtUniq[] =
    {
	"all			extract matching labels as unique nodes",
	"#			extract tagged labels as unique nodes",
	"noports		ignore ports when making labels unique",
	"notopports		ignore top-level ports when making labels unique",
	NULL
    };
    static const char * const cmdExtCmd[] =
    {
	"all			extract root cell and all its children",
	"cell name		extract selected cell into file \"name\"",
	"do [option]		enable extractor option",
	"halo [value]		print or set the sidewall halo distance",
	"help			print this help information",
	"length [option]	control pathlength extraction information",
	"no [option]		disable extractor option",
	"parents		extract selected cell and all its parents",
	"path [path]		if set, extract into the indicated path",
	"showparents		show all parents of selected cell",
	"stepsize [value]	print or set the extraction step size",
	"style [stylename]	set current extraction parameter style",
	"unique [option]	generate unique names when different nodes\n\
			have the same name",
	"warn [ [no] option]	enable/disable reporting of non-serious errors",
	NULL
    };

    if (argc > 1)
    {
	if (!strncmp(argv[1], "list", 4))
	{
	    dolist = TRUE;
	    if (!strncmp(argv[1], "listall", 7))
	    {
		doforall = TRUE;
	    }
	    argv++;
	    argc--;
	}
	option = Lookup(argv[1], cmdExtCmd);
	if (option < 0)
	{
	    TxError("\"%s\" isn't a valid extract option.", argv[1]);
	    TxError("  Type \"extract help\" for a list of valid options.\n");
	    return;
        }
    }
    else option = EXTINCREMENTAL;

    /* Only check for a window on options requiring one */

    if ((option != EXTSTYLE) && (option != EXTHELP))
    {
	windCheckOnlyWindow(&w, DBWclientID);
	if (w == (MagWindow *) NULL)
	{
	    if (ToolGetBox(&selectedDef,NULL) == FALSE)
	    {
		TxError("Point to a window first\n");
		return;
	    }
	    selectedUse = selectedDef->cd_parents;
	}
	else
	{
    	    selectedUse = (CellUse *)w->w_surfaceID;
	}

	if (argc == 1)
	{
	    if (!strcmp(selectedUse->cu_def->cd_name, UNNAMED))
		TxError("Please name the cell before extracting.\n");
	    else
		ExtIncremental(selectedUse);
	    return;
	}
    }

    switch (option)
    {
	case EXTUNIQUE:
	    if (argc < 3)
		all = UNIQALL;
	    else
	    {
		arg = argv[2];
		all = Lookup(arg, cmdExtUniq);
	    }
	    if (all < 0)
	    {
		TxError("Usage: extract unique [option]\n");
		TxPrintf("where option is one of:\n");
		for (msg = &(cmdExtUniq[0]); *msg != NULL; msg++)
		{
		    if (**msg == '*') continue;
		    TxPrintf("  %s\n", *msg);
		}
		TxPrintf("No option is equivalent to option \"all\"\n");
		return;
	    }
	    ExtUnique(selectedUse, all);
	    break;

	case EXTALL:
	    if (!strcmp(selectedUse->cu_def->cd_name, UNNAMED))
		TxError("Please name the cell before extracting.\n");
	    else
		ExtAll(selectedUse);
	    return;

	case EXTCELL:
	    if (argc != 3) goto wrongNumArgs;
	    namep = argv[2];
	    selectedUse = CmdGetSelectedCell((Transform *) NULL);
	    if (selectedUse == NULL)
	    {
		TxError("No cell selected\n");
		return;
	    }
	    ExtractOneCell(selectedUse->cu_def, namep, FALSE);
	    return;

	case EXTHALO:
	    if (ExtCurStyle == NULL)
	    {
		TxError("No extraction style set.\n");
		return;
	    }
	    else if (argc == 2)
	    {
		char *halodisp;
		halodisp = DBWPrintValue(ExtCurStyle->exts_sideCoupleHalo,
			w, TRUE);
#ifdef MAGIC_WRAPPER
		Tcl_SetObjResult(magicinterp, Tcl_NewStringObj(halodisp, -1));
#else
		TxPrintf("Side overlap halo is %s\n", halodisp);
#endif
		return;
	    }
	    else if (argc != 3) goto wrongNumArgs;

	    /* argv[2] is a halo distance */
	    dist = cmdParseCoord(w, argv[2], TRUE, TRUE);
	    if (dist < 0)
	    {
		TxError("Bad halo distance.  Halo must be non-negative.");
		return;
	    }
	    else
		ExtCurStyle->exts_sideCoupleHalo = dist;
	    break;

	case EXTSTEPSIZE:
	    if (ExtCurStyle == NULL)
	    {
		TxError("No extraction style set.\n");
		return;
	    }
	    else if (argc == 2)
	    {
		char *stepdisp;
		stepdisp = DBWPrintValue(ExtCurStyle->exts_stepSize, w, TRUE);
#ifdef MAGIC_WRAPPER
		Tcl_SetObjResult(magicinterp, Tcl_NewStringObj(stepdisp, -1));
#else
		TxPrintf("Extraction step size is %s\n", stepdisp);
#endif
		return;
	    }
	    else if (argc != 3) goto wrongNumArgs;

	    /* argv[2] is a step size */
	    dist = cmdParseCoord(w, argv[2], TRUE, TRUE);
	    if (dist <= 0)
	    {
		TxError("Bad step size.  Step size must be strictly positive.");
		return;
	    }
	    else
		ExtCurStyle->exts_stepSize = dist;
	    break;

	case EXTPARENTS:
	    selectedUse = CmdGetSelectedCell((Transform *) NULL);
	    if (selectedUse == NULL)
	    {
		TxError("No cell selected\n");
		return;
	    }
	    if (!strcmp(selectedUse->cu_def->cd_name, UNNAMED))
		TxError("Please name the cell before extracting.\n");
	    else
		ExtParents(selectedUse);
	    return;

	case EXTSHOWPARENTS:
	    selectedUse = CmdGetSelectedCell((Transform *) NULL);
	    if (selectedUse == NULL)
	    {
		TxError("No cell selected\n");
		return;
	    }
	    ExtShowParents(selectedUse);
	    return;

	case EXTPATH:
	    if (argc == 2)
		ExtPrintPath(dolist);
	    else
		ExtSetPath(argv[2]);
	    return;

	case EXTSTYLE:
	    if (argc == 2)
		ExtPrintStyle(dolist, doforall, !doforall);
	    else
		ExtSetStyle(argv[2]);
	    return;

	case EXTHELP:
	    if (argc != 2)
	    {
		wrongNumArgs:
		TxError("Wrong number of arguments in \"extract\" command.");
		TxError("  Try \"extract help\" for help.\n");
		return;
	    }
	    TxPrintf("Extract commands have the form \"extract option\",\n");
	    TxPrintf("where option is one of:\n");
	    for (msg = &(cmdExtCmd[0]); *msg != NULL; msg++)
	    {
		if (**msg == '*') continue;
		TxPrintf("  %s\n", *msg);
	    }
	    TxPrintf("If no option is given, the root cell and all its\n");
	    TxPrintf("children are extracted incrementally.\n");
	    return;

#define	WARNSET(f)	((ExtDoWarn & (f)) ? "do" : "no")
	case EXTWARN:
	    if (argc < 3)
	    {
		TxPrintf("The following extractor warnings are enabled:\n");
		TxPrintf("    %s dup\n", WARNSET(EXTWARN_DUP));
		TxPrintf("    %s fets\n", WARNSET(EXTWARN_FETS));
		TxPrintf("    %s labels\n", WARNSET(EXTWARN_LABELS));
		return;
	    }
#undef	WARNSET

	    no = FALSE;
	    arg = argv[2];
	    if (argc > 3 && strcmp(arg, "no") == 0)
		no = TRUE, arg = argv[3];

	    option = Lookup(arg, cmdExtWarn);
	    if (option < 0)
	    {
		TxError("Usage: extract warnings [no] option\n");
		TxPrintf("where option is one of:\n");
		for (msg = &(cmdExtWarn[0]); *msg != NULL; msg++)
		{
		    if (**msg == '*') continue;
		    TxPrintf("  %s\n", *msg);
		}
		return;
	    }
	    switch (option)
	    {
		case WARNALL:		warn = EXTWARN_ALL; break;
		case WARNDUP:		warn = EXTWARN_DUP; break;
		case WARNFETS:		warn = EXTWARN_FETS; break;
		case WARNLABELS:	warn = EXTWARN_LABELS; break;
	    }
	    if (no) ExtDoWarn &= ~warn;
	    else ExtDoWarn |= warn;
	    return;
	case EXTDO:
	case EXTNO:
#define	OPTSET(f)	((ExtOptions & (f)) ? "do" : "no")
	    if (argc < 3)
	    {
		TxPrintf("The following are the extractor option settings:\n");
		TxPrintf("%s adjust\n", OPTSET(EXT_DOADJUST));
		TxPrintf("%s capacitance\n", OPTSET(EXT_DOCAPACITANCE));
		TxPrintf("%s corners (scale factor %g)\n", OPTSET(EXT_DOCORNERS),
			ExtCornerFactor);
		TxPrintf("%s coupling\n", OPTSET(EXT_DOCOUPLING));
		TxPrintf("%s length\n", OPTSET(EXT_DOLENGTH));
		TxPrintf("%s lumped R\n", OPTSET(EXT_DORESISTANCE));
		TxPrintf("%s label check\n", OPTSET(EXT_DOLABELCHECK));
		TxPrintf("%s aliases\n", OPTSET(EXT_DOALIASES));
		TxPrintf("%s unique\n", OPTSET(EXT_DOUNIQUE));
		TxPrintf("%s unique notopports\n", OPTSET(EXT_DOUNIQNOTOPPORTS));
		TxPrintf("%s resistance (extresist)\n", OPTSET(EXT_DOEXTRESIST));
		return;
#undef	OPTSET
	    }

	    no = (option == EXTNO);
	    arg = argv[2];
	    option = Lookup(arg, cmdExtOption);
	    if (option < 0)
	    {
		TxError("Usage: extract do option\n");
		TxError("   or  extract no option\n");
		TxPrintf("where option is one of:\n");
		for (msg = &(cmdExtOption[0]); *msg != NULL; msg++)
		{
		    if (**msg == '*') continue;
		    TxPrintf("  %s\n", *msg);
		}
		return;
	    }
	    switch (option)
	    {
		case DOADJUST:		option = EXT_DOADJUST; break;
		case DOALL:		option = EXT_DOALL; break;
		case DOCAPACITANCE:	option = EXT_DOCAPACITANCE; break;
		case DOCORNERS:
		    if (argc == 4)
		    {
			double kval;
			if (sscanf(argv[3], "%lf", &kval) == 1)
			    ExtCornerFactor = kval;
			else
			{
			    TxError("Usage:  extract do corners [value]\n");
			    return;
			}
		    }
		    option = EXT_DOCORNERS;
		    break;
		case DOCOUPLING:	option = EXT_DOCOUPLING; break;
		case DOLENGTH:		option = EXT_DOLENGTH; break;
		case DORESISTANCE:	option = EXT_DORESISTANCE; break;
		case DOLABELCHECK:	option = EXT_DOLABELCHECK; break;
		case DOALIASES:		option = EXT_DOALIASES; break;
		case DOEXTRESIST:
		case DOEXTRESIST2:	option = EXT_DOEXTRESIST; break;
		case DOUNIQUE:
		    if (argc == 4)
		    {
			if (!strncmp(argv[3], "notop", 5))
			    option = EXT_DOUNIQNOTOPPORTS | EXT_DOUNIQUE;
			else
			    TxError("Usage:  extract do unique [notopports]\n");
		    }
		    else
			option = EXT_DOUNIQUE;
		    break;
		case DOLOCAL:
		    /* "extract do local" and "extract no local" are kept for
		     * backwards compatibility, but now effectively implement
		     * "extract path ." and "extract path none", respectively.
		     */
		    if (no)
			StrDup(&ExtLocalPath, NULL);
		    else
			StrDup(&ExtLocalPath, ".");
		    return;
	    }
	    if (no) ExtOptions &= ~option;
	    else ExtOptions |= option;
	    return;
	case EXTLENGTH:
	    if (argc < 3)
		goto lenUsage;
	    arg = argv[2];
	    len = Lookup(arg, cmdExtLength);
	    if (len < 0)
	    {
lenUsage:
		TxError("Usage: extract length option [args]\n");
		TxPrintf("where option is one of:\n");
		for (msg = &(cmdExtLength[0]); *msg != NULL; msg++)
		{
		    if (**msg == '*') continue;
		    TxPrintf("  %s\n", *msg);
		}
		return;
	    }
	    switch (len)
	    {
		case LENCLEAR:
		    ExtLengthClear();
		    break;
		case LENDRIVER:
		    if (argc < 4)
		    {
driverUsage:
			TxError("Must specify one or more terminal names\n");
			return;
		    }
		    for (n = 3; n < argc; n++)
			ExtSetDriver(argv[n]);
		    break;
		case LENRECEIVER:
		    if (argc < 4)
			goto driverUsage;
		    for (n = 3; n < argc; n++)
			ExtSetReceiver(argv[n]);
		    break;
	    }
	    return;
    }

    /*NOTREACHED*/
}
#endif
