/* selUndo.c -
 *
 *	This file provides routines for undo-ing and redo-ing the
 *	the selection.  Most of the undo-ing is handled automatically
 *	by enabling undo-ing when the selection cell is modified.
 *	All this file does is record things to be redisplayed, since
 *	the normal undo package won't handle that.
 *
 *     *********************************************************************
 *     * 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/select/selUndo.c,v 1.1.1.1 2008/02/03 20:43:50 tim Exp $";
#endif  /* not lint */

#include <stdio.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 "graphics/graphics.h"
#include "dbwind/dbwind.h"
#include "utils/undo.h"
#include "commands/commands.h"
#include "textio/textio.h"
#include "select/select.h"
#include "select/selInt.h"

/* Each selection modification causes two records of the following
 * format to be added to the undo event list.  The first record
 * is added BEFORE the modification (sue_before is TRUE), and the
 * second is added afterwards.  The reason for doubling the events
 * is that we can't redisplay selection information until after the
 * selection has been modified.  This requires events to be in
 * different places depending on whether we're undo-ing or redo-ing.
 */

typedef struct
{
    CellDef *sue_def;		/* Definition in which selection must be
				 * redisplayed.
				 */
    Rect sue_area;		/* Area of sue_def in which selection info
				 * must be redisplayed.
				 */
    bool sue_before;		/* TRUE means this entry was made before
				 * the selection modifications.  FALSE
				 * means afterwards.
				 */
} SelUndoEvent;

/* Identifier for selection undo records: */

UndoType SelUndoClientID;

/* Network selection differs from other selection modes in two fundamental
 * ways: 1) it is almost always done for informational purposes (that is,
 * one does not usually copy, move, or stretch the entire network), and
 * 2) it usually involves a LOT of tiles.  Consequently, it makes sense
 * to define a separate "undo" operation for net selection, in which only
 * the information needed to reconstruct the event is retained.  This
 * eliminates the worst problem with selections (as previously handled
 * by the SelUndoClient, above), in which querying the power or ground
 * network of a large chip gobbles vast amount of memory as magic
 * remembers every single tile paint operation.  This memory is kept
 * "forever" (at least as far as the undo stack depth, which is pretty
 * large), and so continued queries on large networks quickly fills up
 * memory.  SelUndoNetClient is a much more efficient way to handle
 * the problem.  Although the "undo" command takes time to regenerate
 * the network from scratch, use of "undo" on network selections is
 * actually rather rare in practice, and the memory savings far outweighs
 * the difference in time to regenerate the net.  Eliminating the need
 * to malloc and fill giant chunks of memory reduces the time required to
 * select the network, anyway.
 *
 * SelUndoNetClient method added by Nishit, July 7-9, 2004
 */

typedef struct
{
    CellDef *sue_def;		/* Definition in which net selection
				 * must be redisplayed
				 */
    Point sue_startpoint;	/* One valid coordinate for the net, to
				 * use as the startpoint for net regeneration
				 */
    TileType sue_type;		/* Type of tile; in conjunction with the
				 * startpoint, uniquely identifies the net.
				 */
    bool sue_less;		/* value of "less" passed to SelectNet */
    bool sue_before;		/* TRUE means this entry was made before
				 * the net selection.  FALSE means it was
				 * made afterwards.
				 */
} SelUndoNetEvent;

/* Identifier for network selection undo records */

UndoType SelUndoNetClientID;


/*
 * ----------------------------------------------------------------------------
 *
 * SelUndoInit --
 *
 * 	Adds us as a client to the undo package.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Adds a new client to the undo package, and sets SelUndoClientID.
 *
 * ----------------------------------------------------------------------------
 */

void
SelUndoInit()
{
    extern void SelUndoForw(), SelUndoBack();
    extern void SelUndoNetForw(), SelUndoNetBack();

    SelUndoClientID = UndoAddClient((void (*)()) NULL, (void (*)()) NULL,
		(UndoEvent *(*)()) NULL, (int (*)()) NULL, SelUndoForw,
		SelUndoBack, "selection");

    if (SelUndoClientID < (UndoType) 0)
	TxError("Couldn't add selection as an undo client!\n");

    /* Special undo client for net selection (Nishit, July 8, 2004) */
    SelUndoNetClientID = UndoAddClient((void (*)()) NULL, (void (*)()) NULL,
		(UndoEvent *(*)()) NULL, (int (*)()) NULL, SelUndoNetForw,
		SelUndoNetBack, "net selection");

    if (SelUndoNetClientID < (UndoType) 0)
	TxError("Couldn't add net selection as an undo client!\n");
}

/*
 * ----------------------------------------------------------------------------
 *
 * SelRememberForUndo--
 *
 * 	This routine is called twice whenever the selection is modified.
 *	It must be called once before modifying the selection.  In this
 *	case before is TRUE and the other arguments are arbitrary.  It
 *	must also be called again after the selection is modified.  In
 *	this case before is FALSE and the other fields indicate exactly
 *	which area was modified.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Adds information to the undo list.
 *
 * ----------------------------------------------------------------------------
 */

void
SelRememberForUndo(before, def, area)
    bool before;		/* TRUE means caller is about to modify
				 * the given area of the selection.  FALSE
				 * means the caller has just modified
				 * the area.
				 */
    CellDef *def;		/* Root definition on top of whom selection
				 * information was just modified.
				 */
    Rect *area;			/* The area of def where selection info
				 * changed.  This pointer may be NULL, even
				 * on the second call, if there's no need
				 * to do redisplay during undo's.  This is
				 * the case if layout information is being
				 * modified over the area of the selection:
				 * when layout is redisplayed, selection info
				 * will automatically be redisplayed too.
				 */
{
    SelUndoEvent *sue;
    static SelUndoEvent *beforeEvent = NULL;
    static Rect nullRect = {{0, 0}, {-1, -1}};

    sue = (SelUndoEvent *) UndoNewEvent(SelUndoClientID, sizeof(SelUndoEvent));
    if (sue == NULL) return;

    /* We don't have complete information when the "before" event is
     * created, so save around its address and fill in the event when
     * the "after" event is created.
     */

    if (before)
    {
	sue->sue_before = TRUE;
	sue->sue_def = NULL;

	beforeEvent = sue;
    }
    else
    {
	if (area == NULL) area = &nullRect;
	sue->sue_def = def;
	sue->sue_area = *area;
	sue->sue_before = before;

	beforeEvent->sue_def = def;
	beforeEvent->sue_area = *area;
	beforeEvent = NULL;
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * SelUndoForw --
 * SelUndoBack --
 *
 * 	Called to process undo redisplay events.  The two procedures
 *	are identical except that each one looks at different events.
 *	The idea is to do the selection redisplay only AFTER the selection
 *	has actually been modified.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Highlights (including the selection) are redisplayed.
 *
 * ----------------------------------------------------------------------------
 */

void
SelUndoForw(sue)
    SelUndoEvent *sue;		/* Event to be redone. */
{
    if (sue->sue_before) return;
    if (sue->sue_def == NULL) return;
    SelSetDisplay(SelectUse, sue->sue_def);
    SelectRootDef = sue->sue_def;
    DBReComputeBbox(SelectDef);
    if (sue->sue_area.r_xbot <= sue->sue_area.r_xtop)
	DBWHLRedraw(sue->sue_def, &sue->sue_area, TRUE);
    DBWAreaChanged(SelectDef, &sue->sue_area, DBW_ALLWINDOWS,
	(TileTypeBitMask *) NULL);
}

void
SelUndoBack(sue)
    SelUndoEvent *sue;		/* Event to be undone. */
{
    if (!sue->sue_before) return;
    if (sue->sue_def == NULL) return;
    SelSetDisplay(SelectUse, sue->sue_def);
    SelectRootDef = sue->sue_def;
    DBReComputeBbox(SelectDef);
    if (sue->sue_area.r_xbot <= sue->sue_area.r_xtop)
	DBWHLRedraw(sue->sue_def, &sue->sue_area, TRUE);
    DBWAreaChanged(SelectDef, &sue->sue_area, DBW_ALLWINDOWS,
	(TileTypeBitMask *) NULL);
}


/* BY NP */
/* The following methods are created for Net selection undo/redo-ing. */

/*
 * ----------------------------------------------------------------------------
 *
 * SelNetRememberForUndo--
 *
 * 	This routine is called once whenever a net selection is created.
 *	It is called once before the modifications (with "before" TRUE)
 *	and once afterward ("before" FALSE).  In the second case,
 *	arguments (def, startpoint, type, and less) are ignored.
 *
 *	This routine is called from within a routine (SelectNet) that
 *	is invoked by the undo mechanism.  To avoid corrupting the undo
 *	stack records, return immediately if selections have been disabled.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Adds information to the undo list.
 *
 * ----------------------------------------------------------------------------
 */
void
SelNetRememberForUndo(def, startpoint, type, less, before)
    CellDef *def;		/* Root definition on top of which selection
				 * information was just modified.
				 */
    Point *startpoint;		/* Starting point, from where we start
				 * selecting the network.
    				 */
    TileType type;		/* Tile type that uniquely identifies the
				 * network node at the above startpoint.
				 */
    bool less;			/* Value of "less" passed to SelectNet */
    bool before;		/* Does this mark the beginning or end
				 * of the net selection.
				 */
{
    static SelUndoNetEvent *beforeEvent = NULL;
    SelUndoNetEvent *sue;

    if (!UndoIsEnabled()) return;

    sue = (SelUndoNetEvent *) UndoNewEvent(SelUndoNetClientID,
		sizeof(SelUndoNetEvent));
    if (sue == NULL) return;

    if (before)
    {
	sue->sue_before = TRUE;
	sue->sue_def = def;
	sue->sue_startpoint = *startpoint;
	sue->sue_less = less;
	sue->sue_type = type;

	ASSERT(beforeEvent == NULL, "Forgot to call SelRememberForUndo after");
	beforeEvent = sue;
    }
    else
    {
	sue->sue_before = FALSE;
	ASSERT(beforeEvent != NULL, "Forgot to call SelRememberForUndo before");

	sue->sue_def = beforeEvent->sue_def;
	sue->sue_startpoint = beforeEvent->sue_startpoint;
	sue->sue_less = beforeEvent->sue_less;
	sue->sue_type = beforeEvent->sue_type;

	/* Don't set to NULL;  it is possible to run through	*/
	/* this code twice in a row if we "undo" back to, but	*/
	/* not over, the SelectNet record, and then start	*/
	/* again.  beforeEvent will still be pointing to the	*/
	/* correct event.					*/
	/* beforeEvent = NULL; */
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * SelUndoCreateNet --
 *
 * ----------------------------------------------------------------------------
 */

void
SelUndoCreateNet(sue)
    SelUndoNetEvent *sue;		/* Event description */
{
    SearchContext scx;
    DBWclientRec *crec;
    MagWindow *window;

    scx.scx_area.r_xbot = sue->sue_startpoint.p_x;
    scx.scx_area.r_ybot = sue->sue_startpoint.p_y;
    scx.scx_area.r_xtop = scx.scx_area.r_xbot + 1;
    scx.scx_area.r_ytop = scx.scx_area.r_ybot + 1;

    // window = CmdGetRootPoint((Point *) NULL, &scx.scx_area);

    window = CmdGetRootPoint((Point *) NULL, (Rect *) NULL);
    if (window == NULL) return;
    scx.scx_use = (CellUse *) window->w_surfaceID;
    scx.scx_trans = GeoIdentityTransform;
    crec = (DBWclientRec *) window->w_clientData;

    UndoDisable();
    SelectClear();	// Selection should already be clear at this point.
    SelectNet(&scx, sue->sue_type, crec->dbw_bitmask, (Rect *) NULL,
		sue->sue_less);
    UndoEnable();
}

/*
 * ----------------------------------------------------------------------------
 *
 * SelUndoDestroyNet --
 *
 * ----------------------------------------------------------------------------
 */

void
SelUndoDestroyNet()
{
    UndoDisable();
    SelectClear();
    UndoEnable();
}

/*
 * ----------------------------------------------------------------------------
 *
 * SelUndoNetForw --
 * SelUndoNetBack --
 *
 * 	Called to process redo(undo) redisplay events.  These routines
 *	are symmetric:  SelUndoNetForw creates the net on a "before"
 *	event and erases it on an "after" event.  SelUndoNetBack erases
 *	the net on a "before" event and creates it on an "after" event.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Highlights (including the selection) are redisplayed.
 *
 * ----------------------------------------------------------------------------
 */

void
SelUndoNetForw(sue)
    SelUndoNetEvent *sue;		/* Event to be redone. */
{
    if (sue->sue_def == NULL) return;

    if (sue->sue_before)
	SelUndoCreateNet(sue);
    else
	SelUndoDestroyNet();
}

void
SelUndoNetBack(sue)
    SelUndoNetEvent *sue;		/* Event to be redone. */
{
    if (sue->sue_def == NULL) return;

    if (sue->sue_before)
	SelUndoDestroyNet();
    else
	SelUndoCreateNet(sue);
}
