/*
 * PlowJogs.c --
 *
 * Jog cleanup.
 *
 *     *********************************************************************
 *     * 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/plow/PlowJogs.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 "utils/undo.h"
#include "plow/plow.h"
#include "plow/plowInt.h"
#include "utils/malloc.h"
#include "debug/debug.h"

/* Passed to filter functions */
Edge *jogEdge;			/* Points to RHS edge of jog */
Rect *jogArea;			/* Area in which jogs are eliminated, in
				 * plowYankDef coordinates.
				 */
int jogTopDir, jogBotDir;	/* Direction of top and bottom of the outline
				 * at the top and bottom of the jog candidate.
				 */
Point jogTopPoint, jogBotPoint;	/* Location of top and bot of above outline */

/* Used when applying plowing rules */
Rect *plowJogLHS;		/* If non-NULL, it's also OK for any edge
				 * along this rectangle's LHS to move as
				 * a result of applying the plowing rules.
				 */
bool plowJogMoved;		/* TRUE if any other edges moved */
LinkedRect *plowJogEraseList;	/* List of areas to erase */
Rect plowJogChangedArea;	/* Area changed */

/*
 * Codes for jogTopDir, jogBotDir above.
 * For jogBotDir, we use the same codes, except "up" really means "down".
 */
#define	J_N	0	/* Left via top of clip area */
#define	J_NE	1	/* Left via RHS of clip area */
#define	J_NW	2	/* Went up then turned left */
#define	J_NES	3	/* Went up, turned right, then turned down */
#define	J_NEN	4	/* Went up, turned right, then turned up */

/* Imports from PlowMain.c */
extern int plowInitialPaint();

/* Forward declarations */
int plowJogPropagateLeft();
int plowProcessJogFunc();
int plowJogMoveFunc();
int plowJogDragLHS();
int plowJogTopProc();
int plowJogBotProc();

extern void plowProcessJog();


/*
 * ----------------------------------------------------------------------------
 *
 * plowCleanupJogs --
 *
 * Clean up all jogs in the interior of the Rect 'area', in the CellDef
 * plowYankDef, by scanning from right to left and eliminating jogs if
 * we can do so without moving any new edges.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Modifies the geometry of plowYankDef in the area 'area'.
 *	Updates changedArea via GeoIncludes to reflect the area
 *	modified by jog reduction.
 *
 * ----------------------------------------------------------------------------
 */

void
plowCleanupJogs(area, changedArea)
    Rect *area;
    Rect *changedArea;
{
    Edge edge;

    /*
     * We use our own procedure for propagating the effects of
     * moving an edge.  Instead of adding more edges to a queue,
     * our procedure simply keeps track of whether any edges moved
     * at all, since we won't want to eliminate a jog if it causes
     * other stuff to move.
     */
    plowPropagateProcPtr = plowJogMoveFunc;

    /* Initialize the queue of edges to move */
    plowQueueInit(area, area->r_xtop - area->r_xbot);

    /* We store the area changed in a global for easy access */
    plowJogChangedArea = *changedArea;

    /*
     * Process as though the RHS of the area were an edge.
     * This means searching leftward for edges with material
     * on their LHS and space on their RHS.
     */
    edge.e_x = edge.e_newx = area->r_xtop;
    edge.e_ybot = area->r_ybot;
    edge.e_ytop = area->r_ytop;
    edge.e_use = (CellUse *) NULL;
    edge.e_flags = 0;
    for (edge.e_pNum = PL_TECHDEPBASE; edge.e_pNum < DBNumPlanes; edge.e_pNum++)
	plowProcessJog(&edge, area);

    /* While edges remain, process them, and propagate leftward */
    while (plowQueueRightmost(&edge))
	plowProcessJog(&edge, area);

    /* Clean up */
    plowQueueDone();
    *changedArea = plowJogChangedArea;
}

/*
 * ----------------------------------------------------------------------------
 *
 * plowProcessJog --
 *
 * Process an edge between space and material for jog elimination.
 * The idea is to search to the left of this edge for jogs that
 * can be straightened without forcing other material to move.
 * We repeat this, searching for a jog and eliminating it, until
 * we can move no more jogs.
 *
 * When finally done, we search leftward for any edges between
 * space and material, and add these to the queue to be processed.
 *
 * The argument 'area' bounds the area in which jog elimination
 * will take place.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Updates the geometry of plowYankDef.
 *
 * ----------------------------------------------------------------------------
 */

void
plowProcessJog(edge, area)
    Edge *edge;
    Rect *area;
{
    Rect r;

    if (DebugIsSet(plowDebugID, plowDebJogs))
	plowDebugEdge(edge, (RuleTableEntry *) NULL, "plowProcessJog");

    /* Scan left from this edge to the LHS of the jog reduction area */
    r.r_xbot = area->r_xbot;
    r.r_xtop = edge->e_x;
    r.r_ybot = edge->e_ybot;
    r.r_ytop = edge->e_ytop;

    /*
     * Scan leftward for edges between material and space.  Such edges
     * are potentially jogs that can be removed.  If plowProcessJogFunc
     * does in fact remove a jog containing one of the edges it finds,
     * it returns 1 and aborts.  We must therefore iterate until no more
     * jogs are eliminated.
     */
    while (plowSrShadowBack(edge->e_pNum, &r, DBSpaceBits,
		plowProcessJogFunc, (ClientData) area))
	/* Nothing */;

    /* Scan to next edge between space and material */
    (void) plowSrShadowBack(edge->e_pNum, &r, DBAllButSpaceBits,
		plowJogPropagateLeft, (ClientData) NULL);
}

/*
 * ----------------------------------------------------------------------------
 *
 * plowJogPropagateLeft --
 *
 * Called by plowSrShadowBack(), we add edges between space and material
 * to the queue for further processing.
 *
 * Results:
 *	Always returns 0.
 *
 * Side effects:
 *	Adds the edge to the queue of edges to move via plowQueueAdd()
 *	if e_ltype == TT_SPACE and e_rtype != TT_SPACE.
 *	Leaves edge->e_newx == edge->e_x.
 *
 * ----------------------------------------------------------------------------
 */

int
plowJogPropagateLeft(edge)
    Edge *edge;
{
    if (DebugIsSet(plowDebugID, plowDebJogs))
	plowDebugEdge(edge, (RuleTableEntry *) NULL, "plowJogPropagateLeft");

    edge->e_newx = edge->e_x;
    if (edge->e_ltype == TT_SPACE && edge->e_rtype != TT_SPACE)
	(void) plowQueueAdd(edge);
    return (0);
}

/*
 * ----------------------------------------------------------------------------
 *
 * plowProcessJogFunc --
 *
 * Do the real work of eliminating a jog.
 * This procedure is called via plowSrShadowBack() for each non-space
 * edge in the shadow of a space edge.  Each edge we find will have
 * e_ltype != TT_SPACE and e_rtype == TT_SPACE.
 *
 * We start following the outline of this edge; if it turns out that we
 * can eliminate a jog, we do so and return 1 to stop the search; otherwise,
 * we return 0 to force the search to continue.
 *
 * Results:
 *	Returns 1 if we eliminated a jog, 0 otherwise.
 *
 * Side effects:
 *	May update the geometry of plowYankDef.
 *
 * ----------------------------------------------------------------------------
 */

int
plowProcessJogFunc(edge, area)
    Edge *edge;		/* Edge found by shadow search */
    Rect *area;		/* Area in which jogs are being eliminated */
{
    LinkedRect *lr;
    Rect r, lhs;
    TileTypeBitMask mask;
    Point startPoint;
    int width, ret;
    Edge newedge;
    Plane *plane;

    if (DebugIsSet(plowDebugID, plowDebJogs))
	plowDebugEdge(edge, (RuleTableEntry *) NULL, "plowProcessJogFunc");

    TTMaskSetOnlyType(&mask, edge->e_ltype);
    startPoint.p_x = edge->e_x;
    jogEdge = edge;
    jogArea = area;

    /* Walk up the outline of which this edge is a part */
    startPoint.p_y = edge->e_ytop;
    jogTopPoint = startPoint;
    jogTopDir = J_N;
    plowSrOutline(edge->e_pNum, &startPoint, mask, GEO_NORTH,
		GMASK_NORTH|GMASK_WEST|GMASK_EAST,
		plowJogTopProc, (ClientData) NULL);

    /* Walk down the outline of which this edge is a part */
    TTMaskCom(&mask);
    startPoint.p_y = edge->e_ybot;
    jogBotPoint = startPoint;
    jogBotDir = J_N;
    plowSrOutline(edge->e_pNum, &startPoint, mask, GEO_SOUTH,
		GMASK_SOUTH|GMASK_WEST|GMASK_EAST,
		plowJogBotProc, (ClientData) NULL);

    /* Try to reject this jog based solely on the geometry */
    if (jogTopDir == J_N || jogBotDir == J_N) return (0);
    if (jogTopDir != J_NEN && jogBotDir != J_NEN)
	return (0);

    /*
     * More geometry-based rejection.
     * Reject if we found either of the following configurations:
     *
     *		+-----+				|
     *		|     |				|
     *		|				|
     *		+-------+		+-------+
     *			|		|
     *			|		|     |
     *			|		+-----+
     */
    if (jogTopDir == J_NES && jogTopPoint.p_x <= jogBotPoint.p_x)
	return (0);
    if (jogBotDir == J_NES && jogBotPoint.p_x <= jogTopPoint.p_x)
	return (0);

    /*
     * Extend the edge to the full height of the jog.
     * The jog will move as far as the leftmost of the two endpoints
     * (jogTopPoint.p_x, jogBotPoint.p_x) if it is a C-shaped jog, or the
     * farther of the two if it is a Z-shaped jog.
     */
    newedge = *edge;
    newedge.e_ybot = jogBotPoint.p_y;
    newedge.e_ytop = jogTopPoint.p_y;
    newedge.e_newx = (jogTopDir == J_NW || jogBotDir == J_NW)
		? MAX(jogTopPoint.p_x, jogBotPoint.p_x)
		: MIN(jogTopPoint.p_x, jogBotPoint.p_x);
    jogEdge = &newedge;
    if (DebugIsSet(plowDebugID, plowDebJogs))
	plowDebugEdge(&newedge, (RuleTableEntry *) NULL, "jog extended edge");

    /* Reject if this jog extends outside of the area */
    if (!GEO_SURROUND(area, &newedge.e_rect))
	return (0);

    /*
     * Apply the plowing rules.
     * Don't do anything if moving the RHS edge will cause
     * other edges to move.
     */
    plowJogMoved = FALSE;
    plowJogLHS = (Rect *) NULL;
    plowApplySearchRules(&newedge);
    if (plowJogMoved)
	return (0);

    /*
     * Now handle the LHS.
     * Find the width of this wire (looking leftward) and
     * search for edges 'width' to the left of the RHS.
     * Extend the search area by 'width' to the top or bottom
     * depending on the shape of this jog, to be sure to catch
     * all of the LHS of the jog.
     */
    TTMaskSetOnlyType(&mask, edge->e_ltype);
    width = plowFindWidthBack(&newedge, mask, area, (Rect *) NULL);
    r.r_xtop = newedge.e_x;
    r.r_xbot = newedge.e_x - width - 1;
    r.r_ytop = newedge.e_ytop;
    r.r_ybot = newedge.e_ybot;
    if (jogTopDir != J_NW) r.r_ytop += width;	/* Extend */
    if (jogBotDir != J_NW) r.r_ybot -= width;	/* Extend */

    /* Reject if the LHS extends outside of area */
    if (!GEO_SURROUND(area, &r))
	return (0);

    /* Also OK to move parts of the LHS (e.g, sliver elimination) */
    lhs = r;
    lhs.r_xbot++;
    plowJogLHS = &lhs;

    ret = 0;
    plowJogEraseList = (LinkedRect *) NULL;
    if (plowSrShadowBack(newedge.e_pNum, &r, mask,
		plowJogDragLHS, (ClientData) newedge.e_newx - width) == 0)
    {
	/* Success: first paint to extend the RHS of the jog */
	plane = plowYankDef->cd_planes[newedge.e_pNum];
	DBPaintPlane(plane, &newedge.e_rect,
		    DBWriteResultTbl[newedge.e_ltype], (PaintUndoInfo *) NULL);
	(void) GeoInclude(&newedge.e_rect, &plowJogChangedArea);

	/* Now erase to extend the pieces of the LHS of the jog */
	for (lr = plowJogEraseList; lr; lr = lr->r_next)
	{
	    DBPaintPlane(plane, &lr->r_r,
		    DBWriteResultTbl[TT_SPACE], (PaintUndoInfo *) NULL);
	    (void) GeoInclude(&lr->r_r, &plowJogChangedArea);
	}
	ret = 1;
    }

    /* Free the erase list we built in plowJogDragLHS */
    for (lr = plowJogEraseList; lr; lr = lr->r_next)
	freeMagic((char *) lr);
    plowJogEraseList = (LinkedRect *) NULL;
    return (ret);
}

/*
 * ----------------------------------------------------------------------------
 *
 * plowJogTopProc --
 * plowJogBotProc --
 *
 * Procedures called by plowSrOutline() to trace the outline of
 * a potential jog.
 *
 * Results:
 *	Both procedures return 1 to stop the search, 0 to continue it.
 *
 * Side effects:
 *	plowJogTopProc fills in jogTopDir, jogTopPoint.
 *	plowJogBotProc fills in jogBotDir, jogBotPoint.
 *
 * ----------------------------------------------------------------------------
 */

int
plowJogTopProc(outline)
    Outline *outline;
{
    /* Stop if we're no longer adjacent to space */
    if (TiGetTypeExact(outline->o_outside) != TT_SPACE)
	return (1);

    switch (outline->o_currentDir)
    {
	case GEO_WEST:
	    jogTopDir = J_NW;
	    return (1);
	case GEO_NORTH:
	    jogTopPoint = outline->o_rect.r_ur;
	    jogTopDir = J_N;
	    if (outline->o_rect.r_ytop > jogArea->r_ytop)
	    {
		jogTopPoint.p_y = jogArea->r_ytop;
		jogTopDir = J_N;
		return (1);
	    }
	    break;
	case GEO_EAST:
	    jogTopPoint = outline->o_rect.r_ur;
	    jogTopDir = J_NE;
	    if (outline->o_rect.r_xtop >= jogArea->r_xtop)
	    {
		jogTopPoint.p_x = jogArea->r_xtop;
		jogTopDir = J_NE;
		return (1);
	    }
	    switch (outline->o_nextDir)
	    {
		case GEO_NORTH:
		    jogTopDir = J_NEN;
		    return (1);
		case GEO_SOUTH:
		    jogTopDir = J_NES;
		    return (1);
	    }
	    break;
    }

    return (0);
}

int
plowJogBotProc(outline)
    Outline *outline;
{
    /* Stop if we're no longer adjacent to space */
    if (TiGetTypeExact(outline->o_inside) != TT_SPACE)
	return (1);

    switch (outline->o_currentDir)
    {
	case GEO_WEST:
	    jogBotDir = J_NW;
	    return (1);
	case GEO_SOUTH:
	    jogBotPoint = outline->o_rect.r_ll;
	    jogBotDir = J_N;
	    if (outline->o_rect.r_ybot < jogArea->r_ybot)
	    {
		jogBotPoint.p_y = jogArea->r_ybot;
		jogBotDir = J_N;
		return (1);
	    }
	    break;
	case GEO_EAST:
	    jogBotPoint = outline->o_rect.r_ur;
	    jogBotDir = J_NE;
	    if (outline->o_rect.r_xtop >= jogArea->r_xtop)
	    {
		jogBotPoint.p_x = jogArea->r_xtop;
		jogBotDir = J_NE;
		return (1);
	    }
	    /* Note that these directions are reversed from plowJogTopProc */
	    switch (outline->o_nextDir)
	    {
		case GEO_NORTH:
		    jogBotDir = J_NES;
		    return (1);
		case GEO_SOUTH:
		    jogBotDir = J_NEN;
		    return (1);
	    }
	    break;
    }

    return (0);
}

/*
 * ----------------------------------------------------------------------------
 *
 * plowJogDragLHS --
 *
 * Procedure called via plowSrShadowBack() from the RHS of a potential
 * jog, to see if dragging each of the pieces of the LHS will cause
 * any other edges to move.  We ignore 'edge' if it doesn't have space
 * to its left.
 *
 * Results:
 *	Returns 1 if we detect that something else must move, 0 otherwise.
 *
 * Side effects:
 *	Prepends a LinkedRect to plowJogEraseList if we return 0.
 *	This LinkedRect has a rectangle equal to the Edge 'edge'
 *	with e_newx == newx.
 *
 * ----------------------------------------------------------------------------
 */

int
plowJogDragLHS(edge, newx)
    Edge *edge;		/* Edge potentially on the LHS of the jog */
    int newx;		/* Move the edge to this position */
{
    LinkedRect *lr;

    /* Ignore edges that are not to space */
    if (edge->e_ltype != TT_SPACE) return (0);

    /* See what will happen if we move this edge right to newx */
    edge->e_newx = newx;
    plowJogMoved = FALSE;
    plowApplySearchRules(edge);
    if (plowJogMoved)
	return (1);

    /*
     * Success: nothing else (except perhaps the original RHS edge) moved.
     * Append this edge to the list of linked rectangles.
     */
    lr = (LinkedRect *) mallocMagic((unsigned) (sizeof (LinkedRect)));
    lr->r_r = edge->e_rect;
    lr->r_next = plowJogEraseList;
    plowJogEraseList = lr;

    /* Keep going */
    return (0);
}

/*
 * ----------------------------------------------------------------------------
 *
 * plowJogMoveFunc --
 *
 * Procedure called via (*plowPropagateProcPtr)() if we find that
 * eliminating a jog might cause other edges to move.  If our argument
 * edge is not a sub-piece of the RHS of the jog (jogEdge),
 * then we set plowJogMoved to TRUE.
 *
 * Results:
 *	Returns 0 always.
 *
 * Side effects:
 *	Sets plowJogMoved as described above.
 *
 * ----------------------------------------------------------------------------
 */

int
plowJogMoveFunc(edge)
    Edge *edge;
{
    Edge *origEdge = jogEdge;

    if (DebugIsSet(plowDebugID, plowDebJogs))
	plowDebugEdge(edge, (RuleTableEntry *) NULL, "plowJogMoveFunc");

    if (origEdge->e_pNum == edge->e_pNum)
    {
	/* It's OK to move the original edge */
	if (origEdge->e_x == edge->e_x
		&& origEdge->e_ytop >= edge->e_ytop
		&& origEdge->e_ybot <= edge->e_ybot)
	{
	    return (0);
	}

	/* It's OK to move other parts of the LHS, e.g, slivers */
	if (plowJogLHS
		&& edge->e_x == plowJogLHS->r_xbot
		&& edge->e_ybot >= plowJogLHS->r_ybot
		&& edge->e_ytop <= plowJogLHS->r_ytop
		&& edge->e_ltype == TT_SPACE
		&& edge->e_rtype == origEdge->e_ltype)
	{
	    return (0);
	}
    }

    plowJogMoved = TRUE;
    return (0);
}
