/*
 * DBtiles.c --
 *
 * Low-level tile primitives for the database.
 * This includes area searching and all other primitives that
 * need to know what lives in a tile body.
 *
 *     *********************************************************************
 *     * 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/database/DBtiles.c,v 1.2 2010/06/24 12:37:15 tim Exp $";
#endif  /* not lint */

#include <stdio.h>

#include "utils/magic.h"
#include "utils/geometry.h"
#include "tiles/tile.h"
#include "utils/signals.h"
#include "utils/hash.h"
#include "utils/undo.h"
#include "database/database.h"
#include "database/databaseInt.h"
#include "utils/malloc.h"

/* Used by DBCheckMaxHStrips() and DBCheckMaxVStrips() */
struct dbCheck
{
    int		(*dbc_proc)();
    Rect	  dbc_area;
    ClientData    dbc_cdata;
};

int dbCheckMaxHFunc(), dbCheckMaxVFunc();

/*
 * --------------------------------------------------------------------
 *
 * dbEvalCorner --
 *
 *	Used by DBTestNMInteract() to determine whether two non-
 *	Manhattan areas have crossing diagonals by evaluating the
 *	corner points of the area of intersection between the two
 *	tiles.  This routine finds the distance from a point in
 *	the second triangle to the diagonal of the first, in both
 *	x and y.  If the point is below or to the left, the
 *	distance is negative;  otherwise the distance is positive.
 *
 * Results:
 *	1 for a positive result, -1 for a negative result, and 0
 *	for the same result (point touches the diagonal).
 *
 * Side effects:
 *	None.
 *
 * --------------------------------------------------------------------
 */

int
dbEvalCorner(Point *p,	// Point to evaluate
    Rect *rect,		// Triangular area bounding rectangle
    TileType di)	// Diagonal information for split rect
{
    dlong D;

    /* D is the distance from a point to the diagonal of the rectangle.
     * The magnitude is not important.  It only matters what the sign
     * is, so return 1 for positive, -1 for negative, or 0.
     */

    if (di & TT_DIRECTION)
	D = (p->p_y - rect->r_ybot) * (rect->r_xtop - rect->r_xbot) -
		(rect->r_xtop - p->p_x) * (rect->r_ytop - rect->r_ybot);
    else
	D = (p->p_y - rect->r_ybot) * (rect->r_xtop - rect->r_xbot) -
		(p->p_x - rect->r_xbot) * (rect->r_ytop - rect->r_ybot);

    if (D > 0) return 1;
    if (D < 0) return -1;
    return 0;
}

/*
 * --------------------------------------------------------------------
 *
 * DBTestNMInteract --
 *
 *	Determine if a tile (t2) interacts with (touches or overlaps)
 *	a triangular area (rect1, with diagonal split information in
 *	di1).  Tile t2 may or may not be a split tile.  If t2 is
 *	split, then diagonal split information is in di2.
 *
 *	There are two distinct cases:  DBSrPaintNMArea() looks for
 *	tiles that overlap the area of rect1, but extTestNMInteract()
 *	looks for tiles that both overlap or touch (indicating
 *	electrical connectivity between the two).  "overlap_only"
 *	distinguishes between the two use cases.  Set "overlap_only"
 *	to TRUE for overlap searches, and FALSE for interaction
 *	searches.
 *
 * Results:
 *	
 *	If overlap_only is TRUE:
 *	    Return TRUE if the indicated areas overlap, FALSE if not.
 *	If overlap_only is FALSE:
 *	    Return TRUE if the indicated areas touch or overlap, FALSE if not.
 *
 * Side effects:
 *	None.
 *
 * --------------------------------------------------------------------
 */

bool
DBTestNMInteract(Rect *rect1,
    TileType tt1,
    Tile *t2,
    TileType di2,
    bool overlap_only)
{
    Rect rect2, r;
    Point p;
    int rheight, rwidth, rmax;
    dlong f1, f2, f3, f4;
    TileType tt2;
    int pos, neg, touch, sign;

    TiToRect(t2, &rect2);

    /* Assuming that rect1 is a split area, then check if any part of t2
     * overlaps the split side of interest in rect1, regardless of whether
     * t2 is split or not.  If there is no overlap, then return FALSE.
     */

    rheight = rect1->r_ytop - rect1->r_ybot;
    rwidth = rect1->r_xtop - rect1->r_xbot;
    rmax = MAX(rheight, rwidth);

    f1 = (rect2.r_ybot > MINFINITY + 2) ?
                ((dlong)(rect1->r_ytop - rect2.r_ybot) * rwidth) : DLONG_MAX;
    f2 = (rect2.r_ytop < INFINITY - 2) ?
                ((dlong)(rect2.r_ytop - rect1->r_ybot) * rwidth) : DLONG_MAX;

    if (tt1 & TT_SIDE)
    {
        /* Outside-of-triangle check---ignore sub-integer slivers */
        if (rect2.r_xtop < INFINITY - 2)
        {
            f3 = (dlong)(rect1->r_xtop - rect2.r_xtop) * rheight;
            f3 += rmax;
        }
        else
            f3 = DLONG_MIN;
        if ((tt1 & TT_DIRECTION) ? (f2 < f3) : (f1 < f3))
            return FALSE;
    }
    else
    {
        /* Outside-of-triangle check---ignore sub-integer slivers */
        if (rect2.r_xbot > MINFINITY + 2)
        {
            f4 = (dlong)(rect2.r_xbot - rect1->r_xbot) * rheight;
            f4 += rmax;
        }
        else
            f4 = DLONG_MIN;
        if ((tt1 & TT_DIRECTION) ? (f1 < f4) : (f2 < f4))
            return FALSE;
    }

    /* If t2 is not split, or its diagonal is the opposite of t1,
     * or its side is the same as that of t1, then they overlap.
     */
    if (!IsSplit(t2)) return TRUE;

    tt2 = TiGetTypeExact(t2) | di2;

    if ((tt1 & TT_DIRECTION) != (tt2 & TT_DIRECTION)) return TRUE;
    // if ((tt1 & TT_SIDE) == (tt2 & TT_SIDE)) return TRUE;

    /* Hard case:  Same diagonal direction, opposite sides.  To determine
     * overlap, count which of the three points of triangle t2 land on
     * one side or the other of the rect1 split diagonal.  From those
     * counts, determine if the triangles are overlapping, touching,
     * or disjoint.
     */

    /* Evaluate the three corners of the rect2 triangle */
    
    pos = neg = touch = 0;
    if (!(tt2 & TT_DIRECTION) || !(tt2 & TT_SIDE))
    {
	/* Evaluate the lower left corner */
	sign = dbEvalCorner(&rect2.r_ll, rect1, tt1);
	if (sign == 1)
	    pos++;
	else if (sign == -1)
	    neg++;
	else
	    touch++;
    }
    
    if (!(tt2 & TT_DIRECTION) || (tt2 & TT_SIDE))
    {
	/* Evaluate the upper right corner */
	sign = dbEvalCorner(&rect2.r_ur, rect1, tt1);
	if (sign == 1)
	    pos++;
	else if (sign == -1)
	    neg++;
	else
	    touch++;
    }

    if ((tt2 & TT_DIRECTION) || !(tt2 & TT_SIDE))
    {
	/* Evaluate the upper left corner */
	p.p_x = rect2.r_xbot;
	p.p_y = rect2.r_ytop;
	sign = dbEvalCorner(&p, rect1, tt1);
	if (sign == 1)
	    pos++;
	else if (sign == -1)
	    neg++;
	else
	    touch++;
    }

    if ((tt2 & TT_DIRECTION) || (tt2 & TT_SIDE))
    {
	/* Evaluate the lower right corner */
	p.p_x = rect2.r_xtop;
	p.p_y = rect2.r_ybot;
	sign = dbEvalCorner(&p, rect1, tt1);
	if (sign == 1)
	    pos++;
	else if (sign == -1)
	    neg++;
	else
	    touch++;
    }

    /* If side and direction match, then pos and neg need to be swapped */
    if (((tt1 & TT_SIDE) && (tt1 & TT_DIRECTION)) ||
		(!(tt1 & TT_SIDE) && !(tt1 & TT_DIRECTION)))
    {
	int temp = neg;
	neg = pos;
	pos = temp;
    }

    /* Return TRUE or FALSE depending on the values of pos, neg, and
     * touch, and depending on whether overlap_only is set or not.
     */
    if (pos == 3)
	return FALSE;		/* Fully disjoint */
    else if (neg == 3)
    {
	if ((tt1 & TT_SIDE) != (tt2 & TT_SIDE))
	    return TRUE;		/* Fully enclosed */
	else
	{
	    /* This is a trickier situation.  Both triangles have
	     * the same TT_SIDE bit, but the triangular area of t2
	     * could still be outside of rect1.  Need to check where
	     * the inside corner of rect1 lands with respect to the
	     * t2 diagonal.
	     */
	    if (((tt1 & TT_SIDE) == 0) && ((tt1 & TT_DIRECTION) != 0))
	    {
		sign = dbEvalCorner(&rect1->r_ll, &rect2, tt2);
	    }
	    else if (((tt1 & TT_SIDE) == 0) && ((tt1 & TT_DIRECTION) == 0))
	    {
		p.p_x = rect1->r_ll.p_x;
		p.p_y = rect1->r_ur.p_y;
		sign = dbEvalCorner(&p, &rect2, tt2);
	    }
	    else if (((tt1 & TT_SIDE) != 0) && ((tt1 & TT_DIRECTION) == 0))
	    {
		p.p_x = rect1->r_ur.p_x;
		p.p_y = rect1->r_ll.p_y;
		sign = dbEvalCorner(&p, &rect2, tt2);
	    }
	    else /* if (((tt1 & TT_SIDE) != 0) && ((tt1 & TT_DIRECTION) != 0)) */
	    {
		sign = dbEvalCorner(&rect1->r_ur, &rect2, tt2);
	    }

	    /* Again, if side and direction match, then sign is backwards
	     */
	    if (((tt2 & TT_SIDE) && (tt2 & TT_DIRECTION)) ||
			(!(tt2 & TT_SIDE) && !(tt2 & TT_DIRECTION)))
		sign = -sign;

	    if (sign == 1)
	 	return FALSE;		/* Fully disjoint */
	    else if (sign == -1)
	 	return TRUE;		/* Fully overlapping */
	    else if (overlap_only)
		return FALSE;		/* Touching but not overlapping */
	    else
		return TRUE;		/* Touching but not overlapping */
	}
    }
    else if (overlap_only)
    {
	if ((touch > 0) && (neg + touch == 3))
	    return TRUE;	/* Enclosed and touching */
	else if ((touch > 0) && (pos + touch == 3))
	    return FALSE;	/* Unenclosed but touching */
	else
	    return TRUE;	/* Partially overlapping */
    }
    else	/* overlap_only == FALSE */
    {
	if ((touch > 0) && (neg + touch == 3))
	    return TRUE;	/* Enclosed and touching */
	else if ((touch > 0) && (pos + touch == 3))
	    return TRUE;	/* Unenclosed but touching */
	else
	    return TRUE;	/* Partially overlapping */
    }
}

/*
 * --------------------------------------------------------------------
 *
 * DBSrPaintNMArea --
 *
 * Find all tiles overlapping a given triangular area whose types are
 * contained in the mask supplied.  Apply the given procedure to each
 * such tile.  The procedure should be of the following form:
 *
 *	int
 *	func(tile, cdata)
 *	    Tile *tile;
 *	    TileType dinfo;
 *	    ClientData cdata;
 *	{
 *	}
 *
 * This is equivalent to DBSrPaintArea, but is used when only one
 * diagonal half (nonmanhattan extension) of the area should be searched.
 * The bitmask for the diagonal are passed in "ttype" (diagonal, side and
 * direction only are used, as in DBNMPaintPlane).
 *
 * Results:
 *	0 is returned if the search completed normally.  1 is returned
 *	if it aborted.
 *
 * Side effects:
 *	Whatever side effects result from application of the
 *	supplied procedure.
 *
 * Notes:
 *	Do not call this routine on an infinite search area (e.g., area
 *	== &TiPlaneRect), even if "ttype" is set appropriately.
 * --------------------------------------------------------------------
 */

#define IGNORE_LEFT 1
#define IGNORE_RIGHT 2

int
DBSrPaintNMArea(
    Tile *hintTile,		/* Tile at which to begin search, if not NULL.
				 * If this is NULL, use the hint tile supplied
				 * with plane.
				 */
    Plane *plane,		/* Plane in which tiles lie.  This is used to
				 * provide a hint tile in case hintTile == NULL.
				 * The hint tile in the plane is updated to be
				 * the last tile visited in the area
				 * enumeration, if plane is non-NULL.
				 */
    TileType ttype,		/* Information about the non-manhattan area to
			 	 * search; zero if area is manhattan.
			 	 */
    Rect *rect,			/* Area to search.  This area should not be
				 * degenerate.  Tiles must OVERLAP the area.
				 */
    TileTypeBitMask *mask,	/* Mask of those paint tiles to be passed to
				 * func.
				 */
    int (*func)(),		/* Function to apply at each tile */
    ClientData arg)		/* Additional argument to pass to (*func)() */
{
    Point start;
    Tile *tp, *tpnew;
    TileType tpt;
    int rheight, rwidth, rmax;
    dlong f1, f2, f3, f4;
    int ignore_sides;

    /* If the search area is not diagonal, return the result of the	*/
    /* standard (manhattan) search function.				*/

    if (!(ttype & TT_DIAGONAL))
	return DBSrPaintArea(hintTile, plane, rect, mask, func, arg);

    start.p_x = rect->r_xbot;
    start.p_y = rect->r_ytop - 1;
    tp = hintTile ? hintTile : PlaneGetHint(plane);
    GOTOPOINT(tp, &start);

    /* Each iteration visits another tile on the LHS of the search area */
    while (TOP(tp) > rect->r_ybot)
    {
	/* Each iteration enumerates another tile */
nm_enum:
	if (plane != (Plane *)NULL) PlaneSetHint(plane, tp);
	if (SigInterruptPending)
	    return (1);

	/* Check if the tile is in the (nonmanhattan) area, and continue */
	/* the tile enumeration if it is not.				 */
	/* Watch for calculations involving (M)INFINITY in tile (tp)!	 */

	if (IsSplit(tp))
	{
	    TileType tpdi = TiGetTypeExact(tp);

	    if (TTMaskHasType(mask, SplitLeftType(tp)))
		if (DBTestNMInteract(rect, ttype, tp, tpdi, TRUE))
		    if ((*func)(tp, (TileType)TT_DIAGONAL, arg))
			return 1;
	    if (TTMaskHasType(mask, SplitRightType(tp)))
		if (DBTestNMInteract(rect, ttype, tp, tpdi | TT_SIDE, TRUE))
		    if ((*func)(tp, (TileType)TT_DIAGONAL | TT_SIDE, arg))
			return 1;
	}
	else
	{
	    if (TTMaskHasType(mask, TiGetType(tp)))
		if (DBTestNMInteract(rect, ttype, tp, (TileType)0, TRUE))
		    if ((*func)(tp, (TileType)0, arg))
			return 1;
	}

enum_next:
	tpnew = TR(tp);
	if (LEFT(tpnew) < rect->r_xtop)
	{
	    while (BOTTOM(tpnew) >= rect->r_ytop) tpnew = LB(tpnew);
	    if (BOTTOM(tpnew) >= BOTTOM(tp) || BOTTOM(tp) <= rect->r_ybot)
	    {
		tp = tpnew;
		goto nm_enum;
	    }
	}

	/* Each iteration returns one tile further to the left */
	while (LEFT(tp) > rect->r_xbot)
	{
	    if (BOTTOM(tp) <= rect->r_ybot)
		return (0);
	    tpnew = LB(tp);
	    tp = BL(tp);
	    if (BOTTOM(tpnew) >= BOTTOM(tp) || BOTTOM(tp) <= rect->r_ybot)
	    {
		tp = tpnew;
		goto nm_enum;
	    }
	}

	/* At left edge -- walk down to next tile along the left edge */
	for (tp = LB(tp); RIGHT(tp) <= rect->r_xbot; tp = TR(tp))
	    /* Nothing */;
    }
    return (0);
}


/*
 * --------------------------------------------------------------------
 *
 * DBSrPaintArea --
 *
 * Find all tiles overlapping a given area whose types are contained
 * in the mask supplied.  Apply the given procedure to each such tile.
 * The procedure should be of the following form:
 *
 *	int
 *	func(tile, cdata)
 *	    Tile *tile;
 *	    TileType dinfo;
 *	    ClientData cdata;
 *	{
 *	}
 *
 * Func normally should return 0.  If it returns 1 then the search
 * will be aborted.  WARNING: THE CALLED PROCEDURE MAY NOT MODIFY
 * THE PLANE BEING SEARCHED!!!
 *
 *			NOTE:
 *
 * Results:
 *	0 is returned if the search completed normally.  1 is returned
 *	if it aborted.
 *
 * Side effects:
 *	Whatever side effects result from application of the
 *	supplied procedure.
 *
 * --------------------------------------------------------------------
 */

int
DBSrPaintArea(
    Tile *hintTile,		/* Tile at which to begin search, if not NULL.
				 * If this is NULL, use the hint tile supplied
				 * with plane.
				 */
    Plane *plane,	/* Plane in which tiles lie.  This is used to
				 * provide a hint tile in case hintTile == NULL.
				 * The hint tile in the plane is updated to be
				 * the last tile visited in the area
				 * enumeration.
				 */
    Rect *rect,	/* Area to search.  This area should not be
				 * degenerate.  Tiles must OVERLAP the area.
				 */
    TileTypeBitMask *mask,	/* Mask of those paint tiles to be passed to
				 * func.
				 */
    int (*func)(),		/* Function to apply at each tile */
    ClientData arg)		/* Additional argument to pass to (*func)() */
{
    Point start;
    Tile *tp, *tpnew;

    start.p_x = rect->r_xbot;
    start.p_y = rect->r_ytop - 1;
    tp = hintTile ? hintTile : PlaneGetHint(plane);
    GOTOPOINT(tp, &start);

    /* Each iteration visits another tile on the LHS of the search area */
    while (TOP(tp) > rect->r_ybot)
    {
	/* Each iteration enumerates another tile */
enumerate:
	PlaneSetHint(plane, tp);
	if (SigInterruptPending)
	    return (1);

	/* Only perform func() on diagonal tiles if the mask includes the  */
	/* tile type for either the left or right sides of the tile (could */
	/* also use top and bottom)---by definition, opposite sides must   */
	/* be the two tile types defined by the diagonal split.            */
	if (IsSplit(tp))
	{
	    /* May call function twice to paint both sides of   */
	    /* the split tile, if necessary.                    */

	    /* f1 to f4 are used to find if the search box rect */
	    /* is completely outside the triangle.  If so, do   */
	    /* not call the function func().                    */

	    /* Watch for calculations involving (M)INFINITY     */

	    int theight, twidth;
	    dlong f1, f2, f3, f4;

	    theight = TOP(tp) - BOTTOM(tp);
	    twidth = RIGHT(tp) - LEFT(tp);
	    f1 = (rect->r_ybot > MINFINITY + 2) ?
		(dlong)(TOP(tp) - rect->r_ybot) * twidth : DLONG_MAX;
	    f2 = (rect->r_ytop < INFINITY - 2) ?
		(dlong)(rect->r_ytop - BOTTOM(tp)) * twidth : DLONG_MAX;

	    if (TTMaskHasType(mask, SplitLeftType(tp)))
	    {
		/* !Outside-of-triangle check */
		f4 = (rect->r_xbot > MINFINITY + 2) ?
			(dlong)(rect->r_xbot - LEFT(tp)) * theight : DLONG_MIN;
		if (SplitDirection(tp) ? (f1 > f4) : (f2 > f4))
		{
		    if ((*func)(tp, (TileType)TT_DIAGONAL, arg)) return (1);
		}
	    }

	    if (TTMaskHasType(mask, SplitRightType(tp)))
	    {
		/* !Outside-of-triangle check */
		f3 = (rect->r_xtop < INFINITY - 2) ?
			(dlong)(RIGHT(tp) - rect->r_xtop) * theight : DLONG_MIN;
		if (SplitDirection(tp) ? (f2 > f3) : (f1 > f3))
		{
		    if ((*func)(tp, (TileType)TT_DIAGONAL | TT_SIDE, arg)) return (1);
		}
	    }
	}
	else
	    if (TTMaskHasType(mask, TiGetType(tp)) && (*func)(tp, (TileType)0, arg))
		return (1);

	tpnew = TR(tp);
	if (LEFT(tpnew) < rect->r_xtop)
	{
	    while (BOTTOM(tpnew) >= rect->r_ytop) tpnew = LB(tpnew);
	    if (BOTTOM(tpnew) >= BOTTOM(tp) || BOTTOM(tp) <= rect->r_ybot)
	    {
		tp = tpnew;
		goto enumerate;
	    }
	}

	/* Each iteration returns one tile further to the left */
	while (LEFT(tp) > rect->r_xbot)
	{
	    if (BOTTOM(tp) <= rect->r_ybot)
		return (0);
	    tpnew = LB(tp);
	    tp = BL(tp);
	    if (BOTTOM(tpnew) >= BOTTOM(tp) || BOTTOM(tp) <= rect->r_ybot)
	    {
		tp = tpnew;
		goto enumerate;
	    }
	}

	/* At left edge -- walk down to next tile along the left edge */
	for (tp = LB(tp); RIGHT(tp) <= rect->r_xbot; tp = TR(tp))
	    /* Nothing */;
    }
    return (0);
}


/*
 * --------------------------------------------------------------------
 *
 * DBSrPaintClient --
 *
 * Find all tiles overlapping a given area whose types are contained
 * in the mask supplied, and whose ti_client field matches 'client'.
 * Apply the given procedure to each such tile.  The procedure should
 * be of the following form:
 *
 *	int
 *	func(tile, cdata)
 *	    Tile *tile;
 *	    TileType dinfo;
 *	    ClientData cdata;
 *	{
 *	}
 *
 * Func normally should return 0.  If it returns 1 then the search
 * will be aborted.
 *
 * Results:
 *	0 is returned if the search completed normally.  1 is returned
 *	if it aborted.
 *
 * Side effects:
 *	Whatever side effects result from application of the
 *	supplied procedure.
 *
 * --------------------------------------------------------------------
 */

int
DBSrPaintClient(
    Tile *hintTile,		/* Tile at which to begin search, if not NULL.
				 * If this is NULL, use the hint tile supplied
				 * with plane.
				 */
    Plane *plane,	/* Plane in which tiles lie.  This is used to
				 * provide a hint tile in case hintTile == NULL.
				 * The hint tile in the plane is updated to be
				 * the last tile visited in the area
				 * enumeration.
				 */
    Rect *rect,	/* Area to search.  This area should not be
				 * degenerate.  Tiles must OVERLAP the area.
				 */
    TileTypeBitMask *mask,	/* Mask of those paint tiles to be passed to
				 * func.
				 */
    ClientData client,		/* The ti_client field of each tile must
				 * match this.
				 */
    int (*func)(),		/* Function to apply at each tile */
    ClientData arg)		/* Additional argument to pass to (*func)() */
{
    Point start;
    Tile *tp, *tpnew;

    start.p_x = rect->r_xbot;
    start.p_y = rect->r_ytop - 1;
    tp = hintTile ? hintTile : PlaneGetHint(plane);
    GOTOPOINT(tp, &start);

    /* Each iteration visits another tile on the LHS of the search area */
    while (TOP(tp) > rect->r_ybot)
    {
	/* Each iteration enumerates another tile */
enumerate:
	PlaneSetHint(plane, tp);
	if (SigInterruptPending)
	    return (1);

	/* Only perform func() on diagonal tiles if the mask includes the  */
	/* tile type for either the left or right sides of the tile (could */
	/* also use top and bottom)---by definition, opposite sides must   */
	/* be the two tile types defined by the diagonal split.            */
	if (IsSplit(tp))
	{
	    /* May call function twice to paint both sides of   */
	    /* the split tile, if necessary.                    */

	    /* f1 to f4 are used to find if the search box rect */
	    /* is completely outside the triangle.  If so, do   */
	    /* not call the function func().                    */

	    /* Watch for calculations involving (M)INFINITY     */

	    int theight, twidth;
	    dlong f1, f2, f3, f4;

	    theight = TOP(tp) - BOTTOM(tp);
	    twidth = RIGHT(tp) - LEFT(tp);
	    f1 = (rect->r_ybot > MINFINITY + 2) ?
		(dlong)(TOP(tp) - rect->r_ybot) * (dlong)twidth : DLONG_MAX;
	    f2 = (rect->r_ytop < INFINITY - 2) ?
		(dlong)(rect->r_ytop - BOTTOM(tp)) * (dlong)twidth : DLONG_MAX;

	    if (TTMaskHasType(mask, SplitLeftType(tp)))
	    {
		/* !Outside-of-triangle check */
		f4 = (rect->r_xbot > MINFINITY + 2) ?
			(dlong)(rect->r_xbot - LEFT(tp)) * (dlong)theight : DLONG_MIN;
		if (SplitDirection(tp) ? (f1 > f4) : (f2 > f4))
		{
		    if ((tp->ti_client == client) && (*func)(tp, (TileType)TT_DIAGONAL, arg))
			return (1);
		}
	    }

	    if (TTMaskHasType(mask, SplitRightType(tp)))
	    {
		/* !Outside-of-triangle check */
		f3 = (rect->r_xtop < INFINITY - 2) ?
			(dlong)(RIGHT(tp) - rect->r_xtop) * (dlong)theight : DLONG_MIN;
		if (SplitDirection(tp) ? (f2 > f3) : (f1 > f3))
		{
		    if ((tp->ti_client == client) && (*func)(tp, (TileType)TT_DIAGONAL
				| TT_SIDE, arg))
			return (1);
		}
	    }
	}
	else
	    if (TTMaskHasType(mask, TiGetType(tp)) && tp->ti_client == client
				&& (*func)(tp, (TileType)0, arg))
		return (1);

	tpnew = TR(tp);
	if (LEFT(tpnew) < rect->r_xtop)
	{
	    while (BOTTOM(tpnew) >= rect->r_ytop) tpnew = LB(tpnew);
	    if (BOTTOM(tpnew) >= BOTTOM(tp) || BOTTOM(tp) <= rect->r_ybot)
	    {
		tp = tpnew;
		goto enumerate;
	    }
	}

	/* Each iteration returns one tile further to the left */
	while (LEFT(tp) > rect->r_xbot)
	{
	    if (BOTTOM(tp) <= rect->r_ybot)
		return (0);
	    tpnew = LB(tp);
	    tp = BL(tp);
	    if (BOTTOM(tpnew) >= BOTTOM(tp) || BOTTOM(tp) <= rect->r_ybot)
	    {
		tp = tpnew;
		goto enumerate;
	    }
	}

	/* At left edge -- walk down to next tile along the left edge */
	for (tp = LB(tp); RIGHT(tp) <= rect->r_xbot; tp = TR(tp))
	    /* Nothing */;
    }
    return (0);
}

/*
 * --------------------------------------------------------------------
 *
 * DBResetTilePlane --
 *
 * Reset the ti_client fields of all tiles in a paint tile plane to
 * the value 'cdata'.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Resets the ti_client fields of all tiles.
 *
 * --------------------------------------------------------------------
 */

void
DBResetTilePlane(
    Plane *plane,	/* Plane whose tiles are to be reset */
    ClientData cdata)
{
    Tile *tp, *tpnew;
    const Rect *rect = &TiPlaneRect;

    /* Start with the leftmost non-infinity tile in the plane */
    tp = TR(plane->pl_left);

    /* Each iteration visits another tile on the LHS of the search area */
    while (TOP(tp) > rect->r_ybot)
    {
	/* Each iteration resets another tile */
enumerate:
	tp->ti_client = cdata;

	/* Move along to the next tile */
	tpnew = TR(tp);
	if (LEFT(tpnew) < rect->r_xtop)
	{
	    while (BOTTOM(tpnew) >= rect->r_ytop) tpnew = LB(tpnew);
	    if (BOTTOM(tpnew) >= BOTTOM(tp) || BOTTOM(tp) <= rect->r_ybot)
	    {
		tp = tpnew;
		goto enumerate;
	    }
	}

	/* Each iteration returns one tile further to the left */
	while (LEFT(tp) > rect->r_xbot)
	{
	    if (BOTTOM(tp) <= rect->r_ybot)
		return;
	    tpnew = LB(tp);
	    tp = BL(tp);
	    if (BOTTOM(tpnew) >= BOTTOM(tp) || BOTTOM(tp) <= rect->r_ybot)
	    {
		tp = tpnew;
		goto enumerate;
	    }
	}

	/* At left edge -- walk down to next tile along the left edge */
	for (tp = LB(tp); RIGHT(tp) <= rect->r_xbot; tp = TR(tp))
	    /* Nothing */;
    }
}

/*
 * --------------------------------------------------------------------
 *
 * DBResetTilePlaneSpecial --
 *
 * This routine works like DBResetTilePlane(), but is designed
 * specifically to be run after extFindNodes() or ExtFindRegions()
 * to check for split tiles that have an allocated ExtSplitRegion
 * structure in the ClientData;  this needs to be freed before
 * resetting the ClientData value to "cdata".  It is not necessary
 * to know anything about the ExtSplitRegion structure other than
 * the condition under which it can be expected to be present,
 * which is a split tile with neither side having type TT_SPACE.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Resets the ti_client fields of all tiles.
 *
 * --------------------------------------------------------------------
 */

void
DBResetTilePlaneSpecial(
    Plane *plane,	/* Plane whose tiles are to be reset */
    ClientData cdata)
{
    Tile *tp, *tpnew;
    const Rect *rect = &TiPlaneRect;

    /* Start with the leftmost non-infinity tile in the plane */
    tp = TR(plane->pl_left);

    /* Each iteration visits another tile on the LHS of the search area */
    while (TOP(tp) > rect->r_ybot)
    {
	/* Each iteration resets another tile */
enumerate:
	if (IsSplit(tp))
	    if ((TiGetLeftType(tp) != TT_SPACE) && (TiGetRightType(tp) != TT_SPACE))
		if (tp->ti_client != cdata)
		{
		    ASSERT(TiGetBody((Tile *)tp->ti_client) == CLIENTDEFAULT,
				"DBResetTilePlaneSpecial");
		    freeMagic(tp->ti_client);
		}

	tp->ti_client = cdata;

	/* Move along to the next tile */
	tpnew = TR(tp);
	if (LEFT(tpnew) < rect->r_xtop)
	{
	    while (BOTTOM(tpnew) >= rect->r_ytop) tpnew = LB(tpnew);
	    if (BOTTOM(tpnew) >= BOTTOM(tp) || BOTTOM(tp) <= rect->r_ybot)
	    {
		tp = tpnew;
		goto enumerate;
	    }
	}

	/* Each iteration returns one tile further to the left */
	while (LEFT(tp) > rect->r_xbot)
	{
	    if (BOTTOM(tp) <= rect->r_ybot)
		return;
	    tpnew = LB(tp);
	    tp = BL(tp);
	    if (BOTTOM(tpnew) >= BOTTOM(tp) || BOTTOM(tp) <= rect->r_ybot)
	    {
		tp = tpnew;
		goto enumerate;
	    }
	}

	/* At left edge -- walk down to next tile along the left edge */
	for (tp = LB(tp); RIGHT(tp) <= rect->r_xbot; tp = TR(tp))
	    /* Nothing */;
    }
}

/*
 * --------------------------------------------------------------------
 *
 * DBFreePaintPlane --
 *
 * Deallocate all tiles in a paint tile plane of a given CellDef.
 * Don't deallocate the four boundary tiles, or the plane itself.
 *
 * This is a procedure internal to the database.  The only reason
 * it lives in DBtiles.c rather than DBcellsubr.c is that it requires
 * intimate knowledge of the contents of paint tiles and tile planes.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Deallocates a lot of memory.
 *
 *			*** WARNING ***
 *
 * This procedure uses a carfully constructed non-recursive area
 * enumeration algorithm.  Care is taken to not access a tile that has
 * been deallocated.
 *
 * --------------------------------------------------------------------
 */

void
DBFreePaintPlane(
    Plane *plane)	/* Plane whose storage is to be freed */
{
    Tile *tp, *tpnew;
    const Rect *rect = &TiPlaneRect;

    /* Start with the bottom-right non-infinity tile in the plane */
    tp = BL(plane->pl_right);

    Tile *delayed = NULL;
    /* Each iteration visits another tile on the RHS of the search area */
    while (BOTTOM(tp) < rect->r_ytop)
    {
enumerate:

#define	CLIP_TOP(t)	(MIN(TOP(t),rect->r_ytop))

	/* Move along to the next tile to the left */
	if (LEFT(tp) > rect->r_xbot)
	{
	    tpnew = BL(tp);
	    while (TOP(tpnew) <= rect->r_ybot) tpnew = RT(tpnew);
	    if (CLIP_TOP(tpnew) <= CLIP_TOP(tp))
	    {
		tp = tpnew;
		goto enumerate;
	    }
	}

	/* Each iteration returns one tile further to the right */
	while (RIGHT(tp) < rect->r_xtop)
	{
	    TiFree1(&delayed, tp);
	    tpnew = RT(tp); /* deref of delayed */
	    tp = TR(tp); /* deref of delayed */
	    if (CLIP_TOP(tpnew) <= CLIP_TOP(tp) && BOTTOM(tpnew) < rect->r_ytop)
	    {
		tp = tpnew;
		goto enumerate;
	    }
	}

	TiFree1(&delayed, tp);
	/* At right edge -- walk up to next tile along the right edge */
	tp = RT(tp); /* deref of delayed */
	if (BOTTOM(tp) < rect->r_ytop) {
	    while(LEFT(tp) >= rect->r_xtop) tp = BL(tp);
	}
    }
    TiFreeIf(delayed);
}

/*
 * --------------------------------------------------------------------
 *
 * DBClearCellPlane --
 *
 * Removes all CellUses from a def's cell plane.  Does not remove the
 * cell plane itself.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Deallocates a lot of memory.
 *
 * --------------------------------------------------------------------
 */

void
DBClearCellPlane(
    CellDef *def)
{
    int dbDeleteCellUse();	/* Forward reference */

    /* Don't let this search be interrupted. */
    SigDisableInterrupts();

    /* Remove everything from the BPlane */
    /* Do not use BPDelete() inside a BPEnum loop.  Use DBSrCellUses */
    /* to get a linked list of cell instances, then remove each one. */

    DBSrCellUses(def, dbDeleteCellUse, (ClientData)def);

    SigEnableInterrupts();
}

/*
 * --------------------------------------------------------------------
 *
 * dbDeleteCellUse ---
 *
 * Callback function from DBSrCellUses, calls BPDelete to remove a
 * cell use from the cell plane
 *
 * --------------------------------------------------------------------
 */

int dbDeleteCellUse(CellUse *use, ClientData arg)
{
    CellDef *def = (CellDef *)arg;
    CellUse *defuses, *lastuse;

    dbInstanceUnplace(use);

    if (UndoIsEnabled())
	DBUndoCellUse(use, UNDO_CELL_DELETE);

    /* Remove use from cd_parents of the use's def */
    lastuse = (CellUse *)NULL;
    for (defuses = use->cu_def->cd_parents; defuses ; defuses = defuses->cu_nextuse)
    {
	if (defuses == use)
	{
	    if (lastuse)
		lastuse->cu_nextuse = defuses->cu_nextuse;
	    else
		use->cu_def->cd_parents = defuses->cu_nextuse;
	    defuses->cu_nextuse = (CellUse *)NULL;
	    break;
	}
	lastuse = defuses;
    }
    if (use->cu_id) freeMagic(use->cu_id);
    freeMagic(use);

    return 0;
}


/*
 * --------------------------------------------------------------------
 *
 * DBCheckMaxHStrips --
 *
 * Check the maximal horizontal strip property for the
 * tile plane 'plane' over the area 'area'.
 *
 * Results:
 *	Normally returns 0; returns 1 if the procedure
 *	(*proc)() returned 1 or if the search were
 *	aborted with an interrupt.
 *
 * Side effects:
 *	Calls the procedure (*proc)() for each offending tile.
 *	This procedure should have the following form:
 *
 *	int
 *	proc(tile, side, cdata)
 *	    Tile *tile;
 *	    int side;
 *	    ClientData cdata;
 *	{
 *	}
 *
 *	The client data is the argument 'cdata' passed to us.
 *	The argument 'side' is one of GEO_NORTH, GEO_SOUTH,
 *	GEO_EAST, or GEO_WEST, and indicates which side of
 *	the tile the strip property was violated on.
 *	If (*proc)() returns 1, we abort and return 1
 *	to our caller.
 *
 * --------------------------------------------------------------------
 */

int
DBCheckMaxHStrips(
    Plane *plane,	/* Search this plane */
    Rect *area,		/* Process all tiles in this area */
    int (*proc)(),	/* Filter procedure: see above */
    ClientData cdata)	/* Passed to (*proc)() */
{
    struct dbCheck dbc;

    dbc.dbc_proc = proc;
    dbc.dbc_area = *area;
    dbc.dbc_cdata = cdata;
    return (DBSrPaintArea((Tile *) NULL, plane, area,
		&DBAllTypeBits, dbCheckMaxHFunc, (ClientData) &dbc));
}

/*
 * dbCheckMaxHFunc --
 *
 * Filter function for above.
 * See the description at the top.
 */

int
dbCheckMaxHFunc(
    Tile *tile,
    TileType dinfo,		/* (unused) */
    struct dbCheck *dbc)
{
    Tile *tp;

    /*
     * Property 1:
     * No tile to the left or to the right should have the same
     * type as 'tile'.
     */
    if (RIGHT(tile) < dbc->dbc_area.r_xtop)
	for (tp = TR(tile); TOP(tp) > BOTTOM(tile); tp = LB(tp))
	    if (TiGetType(tp) == TiGetType(tile))
		if ((*dbc->dbc_proc)(tile, GEO_EAST, dbc->dbc_cdata))
		    return (1);
    if (LEFT(tile) > dbc->dbc_area.r_xbot)
	for (tp = BL(tile); BOTTOM(tp) < TOP(tile); tp = RT(tp))
	    if (TiGetType(tp) == TiGetType(tile))
		if ((*dbc->dbc_proc)(tile, GEO_WEST, dbc->dbc_cdata))
		    return (1);

    /*
     * Property 2:
     * No tile to the top or bottom should be of the same type and
     * have the same width.
     */
    if (TOP(tile) < dbc->dbc_area.r_ytop)
    {
	tp = RT(tile);
	if (TiGetType(tp) == TiGetType(tile)
		&& LEFT(tp) == LEFT(tile)
		&& RIGHT(tp) == RIGHT(tile))
	    if ((*dbc->dbc_proc)(tile, GEO_NORTH, dbc->dbc_cdata))
		return (1);
    }
    if (BOTTOM(tile) > dbc->dbc_area.r_ybot)
    {
	tp = LB(tile);
	if (TiGetType(tp) == TiGetType(tile)
		&& LEFT(tp) == LEFT(tile)
		&& RIGHT(tp) == RIGHT(tile))
	    if ((*dbc->dbc_proc)(tile, GEO_SOUTH, dbc->dbc_cdata))
		return (1);
    }

    return (0);
}

/*
 * --------------------------------------------------------------------
 *
 * DBCheckMaxVStrips --
 *
 * Check the maximal vertical strip property for the
 * tile plane 'plane' over the area 'area'.
 *
 * Results:
 *	Normally returns 0; returns 1 if the procedure
 *	(*proc)() returned 1 or if the search were
 *	aborted with an interrupt.
 *
 * Side effects:
 *	See DBCheckMaxHStrips() above.
 *
 * --------------------------------------------------------------------
 */

int
DBCheckMaxVStrips(
    Plane *plane,	/* Search this plane */
    Rect *area,		/* Process all tiles in this area */
    int (*proc)(),	/* Filter procedure: see above */
    ClientData cdata)	/* Passed to (*proc)() */
{
    struct dbCheck dbc;

    dbc.dbc_proc = proc;
    dbc.dbc_area = *area;
    dbc.dbc_cdata = cdata;
    return (DBSrPaintArea((Tile *) NULL, plane, area,
		&DBAllTypeBits, dbCheckMaxVFunc, (ClientData) &dbc));
}

/*
 * dbCheckMaxVFunc --
 *
 * Filter function for above.
 * See the description at the top.
 */

int
dbCheckMaxVFunc(
    Tile *tile,
    TileType dinfo,		/* (unused) */
    struct dbCheck *dbc)
{
    Tile *tp;

    /*
     * Property 1:
     * No tile to the top or to the bottom should have the same
     * type as 'tile'.
     */
    if (TOP(tile) < dbc->dbc_area.r_ytop)
	for (tp = RT(tile); RIGHT(tp) > LEFT(tile); tp = BL(tp))
	    if (TiGetType(tp) == TiGetType(tile))
		if ((*dbc->dbc_proc)(tile, GEO_NORTH, dbc->dbc_cdata))
		    return (1);
    if (BOTTOM(tile) > dbc->dbc_area.r_ybot)
	for (tp = LB(tile); LEFT(tp) < RIGHT(tile); tp = TR(tp))
	    if (TiGetType(tp) == TiGetType(tile))
		if ((*dbc->dbc_proc)(tile, GEO_SOUTH, dbc->dbc_cdata))
		    return (1);

    /*
     * Property 2:
     * No tile to the left or right should be of the same type and
     * have the same height.
     */
    if (RIGHT(tile) < dbc->dbc_area.r_xtop)
    {
	tp = TR(tile);
	if (TiGetType(tp) == TiGetType(tile)
		&& BOTTOM(tp) == BOTTOM(tile)
		&& TOP(tp) == TOP(tile))
	    if ((*dbc->dbc_proc)(tile, GEO_EAST, dbc->dbc_cdata))
		return (1);
    }
    if (LEFT(tile) > dbc->dbc_area.r_xbot)
    {
	tp = BL(tile);
	if (TiGetType(tp) == TiGetType(tile)
		&& BOTTOM(tp) == BOTTOM(tile)
		&& TOP(tp) == TOP(tile))
	    if ((*dbc->dbc_proc)(tile, GEO_WEST, dbc->dbc_cdata))
		return (1);
    }

    return (0);
}

