/*
 * grouteChan.c --
 *
 * Code to maintain a map of usable channel areas, using a
 * tile plane.  One tile is used for each "unobstructed"
 * channel area, namely one that contains no embedded regions
 * where the channel's density has been exceeded.
 *
 *     ********************************************************************* 
 *     * 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.    * 
 *     *********************************************************************
 */

#include <stdio.h>

#include "utils/magic.h"
#include "utils/geometry.h"
#include "utils/styles.h"
#include "utils/hash.h"
#include "utils/heap.h"
#include "utils/utils.h"
#include "tiles/tile.h"
#include "database/database.h"
#include "debug/debug.h"
#include "gcr/gcr.h"
#include "windows/windows.h"
#include "utils/main.h"
#include "dbwind/dbwind.h"
#include "utils/signals.h"
#include "router/router.h"
#include "grouter/grouter.h"
#include "utils/netlist.h"
#include "utils/styles.h"
#include "textio/textio.h"
#include "utils/malloc.h"

/* Plane to hold channel information */
Plane *glChanPlane;

/* Dummy celldef whose PL_DRC_CHECK plane is identical to glChanPlane above */
CellDef *glChanDef;
CellUse *glChanUse;

TileTypeBitMask glMaskRiver;	/* Mask of river channel types */
TileTypeBitMask glMaskNormal;	/* Mask of normal channel type */
TileTypeBitMask	glMaskChannel;	/* Mask of all channel types */

int glChanSplitRiver();
int glChanRiverBlock();
int glChanFloodVFunc(), glChanFloodHFunc();
bool glChanClip();
int glChanClipFunc();
int glChanMergeFunc();
int glChanFeedFunc();
int glChanPaintFunc();
int glChanSetClient();
void glChanFreeMap();
void glChanCheckCover();
void glChanBlockDens();
void glChanFlood();
void glChanShowTiles();
int glChanShowFunc();


typedef struct pa
{
    Rect	 pa_area;
    TileType	 pa_type;
    struct pa	*pa_next;
} PaintArea;

PaintArea *glChanPaintList;

/*
 * ----------------------------------------------------------------------------
 *
 * glChanBuildMap --
 *
 * Build a tile plane that represents all the channels in the routing
 * problem.  This tile plane will contain tiles of four types: CT_BLOCKED,
 * CT_NORMAL, CT_HRIVER, and CT_VRIVER, corresponding to unusable routing
 * area and the three types of channels respectively.
 *
 * Each channel tile's client field points back to the channel it covers.
 * A single channel can be covered by several tiles, however.  Initially,
 * each normal channel is covered by a single tile, but each river tile
 * is covered by several tiles.  For example, the tiles covering a HRIVER
 * channel are chosen such that there is never a vertical tile boundary
 * on either their RHS or LHS except at the top or bottom of the CT_HRIVER
 * tile.  Situations such as the following:
 *
 *		+-------+
 *		|	|
 *		|	| normal
 *		|	|
 *		|HRIVER	+------
 *		|	|
 *		|	| normal
 *		|	|
 *		+-------+
 *
 * aren't allowed to exist; the HRIVER tile gets split:
 *
 *		+-------+
 *		|	|
 *		|HRIVER	| normal
 *		|	|
 *		+-------+------
 *		|	|
 *		|HRIVER	| normal
 *		|	|
 *		+-------+
 *	
 * The reason for splitting CT_HRIVER tiles in this way (and CT_VRIVER tiles
 * vertically using similar criteria) is to ensure that there is a different
 * river tile for each channel on its opposite side.  This fact is crucial
 * to the working of the inner loop of the global router; see the code in
 * groutePair.c for details of the algorithm used.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Builds the tile plane glChanPlane as described above.
 *
 * ----------------------------------------------------------------------------
 */

void
glChanBuildMap(chanList)
    GCRChannel *chanList;	/* List of all channels in routing problem */
{
    GCRChannel *ch;
    bool workDone;

    if (glChanPlane == NULL)
    {
	DBNewYank("__CHANMAP__", &glChanUse, &glChanDef);
	glChanPlane = glChanDef->cd_planes[PL_DRC_CHECK];
	glChanFreeMap();
	TTMaskSetOnlyType(&glMaskRiver, CHAN_HRIVER);
	TTMaskSetType(&glMaskRiver, CHAN_VRIVER);
	TTMaskSetOnlyType(&glMaskNormal, CHAN_NORMAL);
	TTMaskSetMask3(&glMaskChannel, &glMaskNormal, &glMaskRiver);
    }

    /*
     * First pass: use painting to cover the areas of all channels
     * with the appropriate type of tile.
     */
    for (ch = chanList; ch; ch = ch->gcr_next)
	DBPaintPlane(glChanPlane, &ch->gcr_area, DBStdWriteTbl(ch->gcr_type),
			(PaintUndoInfo *) NULL);

    if (DebugIsSet(glDebugID, glDebTiles))
	glChanShowTiles("After painting all channels");

    /*
     * Second pass: use splits and merges to ensure that each channel
     * covers exactly one tile.  Leave this tile's ti_client field
     * pointing to 'ch'.
     */
    do
    {
	workDone = FALSE;
	for (ch = chanList; ch; ch = ch->gcr_next)
	    if (glChanClip(ch))
		workDone = TRUE;
    }
    while (workDone);
    if (DebugIsSet(glDebugID, glDebTiles))
	glChanShowTiles("After splits and merges");
    if (DebugIsSet(glDebugID, glDebChan))
	glChanCheckCover(chanList, &glMaskChannel);

    /*
     * Third pass: find regions of maximum density and "paint" them into
     * the tile map.  Regions where the density is exceeded in only one
     * of the two possible directions through a normal channel result in
     * painting a CHAN_HRIVER or CHAN_VRIVER tile as appropriate, while
     * regions that are totally blocked result in CHAN_BLOCKED tiles.
     * The client fields of the remaining nonblocked tiles are left
     * pointing to the channels those tiles overlap.
     */
    for (ch = chanList; ch; ch = ch->gcr_next)
	glChanBlockDens(ch);
    if (DebugIsSet(glDebugID, glDebTiles))
	glChanShowTiles("After density blockages");

    /*
     * Fourth pass: split river tiles as necessary to ensure
     * that no river tile has a perpendicular tile boundary
     * on either of the sides from which signals can enter.
     */
    while (DBSrPaintArea((Tile *) NULL, glChanPlane, &TiPlaneRect, &glMaskRiver,
		glChanSplitRiver, (ClientData) NULL))
	/* Nothing */;
    if (DebugIsSet(glDebugID, glDebTiles))
	glChanShowTiles("After splitting river tiles");

    /*
     * Final pass: turn any river tiles whose pins are completely
     * blocked on one side into blocked tiles.  This just involves
     * changing the type of the tile.
     */
    (void) DBSrPaintArea((Tile *) NULL, glChanPlane, &TiPlaneRect,
		&glMaskRiver, glChanRiverBlock, (ClientData) NULL);
    if (DebugIsSet(glDebugID, glDebTiles))
	glChanShowTiles("After blocking river tiles");
    if (DebugIsSet(glDebugID, glDebChan))
    {
	glChanCheckCover(chanList, &glMaskNormal);
	(void) DBSrPaintArea((Tile *) NULL, glChanPlane, &TiPlaneRect,
		&glMaskChannel, glChanFeedFunc, (ClientData) NULL);
    }
}

/*
 * glChanFeedFunc --
 *
 * Used for leaving feedback for each channel tile above.
 *
 * Results:
 *	Always returns 0.
 *
 * Side effects:
 *	Leaves feedback for each tile found.
 */

int
glChanFeedFunc(tile)
    Tile *tile;
{
    char *mesg;
    Rect r;

    switch (TiGetType(tile))
    {
	case CHAN_NORMAL: mesg = "normal channel"; break;
	case CHAN_HRIVER: mesg = "horizontal river channel"; break;
	case CHAN_VRIVER: mesg = "vertical river channel"; break;
    }
    TITORECT(tile, &r);
    DBWFeedbackAdd(&r, mesg, EditCellUse->cu_def, 1, STYLE_OUTLINEHIGHLIGHTS);
    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * glChanFreeMap --
 *
 * Free the tile plane built by glChanBuildMap().
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Frees memory.
 *
 * ----------------------------------------------------------------------------
 */

void
glChanFreeMap()
{
    Tile *newCenterTile;

    /* Eliminate all the tiles from this plane */
    DBFreePaintPlane(glChanPlane);

    /* Allocate a new central space tile */
    newCenterTile = TiAlloc();
    glChanPlane->pl_hint = newCenterTile;
    TiSetBody(newCenterTile, CHAN_BLOCKED);
    dbSetPlaneTile(glChanPlane, newCenterTile);
}

/*
 * ----------------------------------------------------------------------------
 *
 * glChanCheckCover --
 *
 * Debugging procedure used to check that each channel whose type is
 * contained in 'mask' is covered by exactly one tile.  Also check
 * all channels to make sure that the tiles they cover are the same
 * as the channel type (except for blockages).
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Leaves feedback where errors occur.
 *
 * ----------------------------------------------------------------------------
 */

int glChanCheckCount;

void
glChanCheckCover(chanList, mask)
    GCRChannel *chanList;
    TileTypeBitMask *mask;
{
    int glChanCheckFunc();
    GCRChannel *ch;
    char mesg[1024];

    for (ch = chanList; ch; ch = ch->gcr_next)
    {
	glChanCheckCount = 0;
	(void) DBSrPaintArea((Tile *) NULL, glChanPlane, &ch->gcr_area,
		&DBAllTypeBits, glChanCheckFunc,
		(ClientData) ch);
	if (TTMaskHasType(mask, ch->gcr_type) && glChanCheckCount != 1)
	{
	    (void) sprintf(mesg, "%d tiles over channel", glChanCheckCount);
	    DBWFeedbackAdd(&ch->gcr_area, mesg, EditCellUse->cu_def, 1,
			STYLE_PALEHIGHLIGHTS);
	}
    }
}

/*
 * glChanCheckFunc --
 *
 * Called by above for each tile overlapping a channel.
 *
 * Results:
 *	Always returns 0.
 *
 * Side effects:
 *	May leave feedback if a channel is overlapped by the
 *	wrong type of tile.
 */

int
glChanCheckFunc(tile, ch)
    Tile *tile;
    GCRChannel *ch;
{
    char mesg[1024];
    Rect r;

    glChanCheckCount++;
    if (NOTBLOCKED(tile))
    {
	if (TiGetType(tile) != ch->gcr_type)
	{
	    TITORECT(tile, &r);
	    (void) sprintf(mesg, "Different tile type %d for chan %d",
		    TiGetType(tile), ch->gcr_type);
	    DBWFeedbackAdd(&r, mesg, EditCellUse->cu_def,
		    1, STYLE_MEDIUMHIGHLIGHTS);
	}
	if (tile->ti_client != (ClientData) ch)
	{
	    TITORECT(tile, &r);
	    (void) sprintf(mesg, "Tile client %x doesn't match chan %x",
		    tile->ti_client, ch);
	    DBWFeedbackAdd(&r, mesg, EditCellUse->cu_def,
		    1, STYLE_MEDIUMHIGHLIGHTS);
	}
    }
    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * glChanClip --
 *
 * Leave a single tile overlapping the area of channel 'ch'.
 * Leave this tile pointing to 'ch'.
 *
 * Results:
 *	Returns TRUE if we had to do any splits or merges,
 *	FALSE if everything was as it should be.
 *
 * Side effects:
 *	May split or merge tiles.
 *
 * ----------------------------------------------------------------------------
 */

bool
glChanClip(ch)
    GCRChannel *ch;
{
    bool ret;

    ret = FALSE;

    /* First part: clip every tile overlapping this channel to its boundary */
    while (DBSrPaintArea((Tile *) NULL, glChanPlane, &ch->gcr_area,
	    &DBAllTypeBits, glChanClipFunc, (ClientData) &ch->gcr_area))
	ret = TRUE;

    /* Second part: set the client field of every tile to 'ch' */
    (void) DBSrPaintArea((Tile *) NULL, glChanPlane, &ch->gcr_area,
	    &DBAllTypeBits, glChanSetClient, (ClientData) ch);

    /*
     * Third part: merge every tile overlapping this channel into a
     * single one.  The procedure called for each tile extracts the
     * channel boundaries from the client field of the tile, and
     * then generates merges within the channel boundary.
     */
    while (DBSrPaintArea((Tile *) NULL, glChanPlane, &ch->gcr_area,
	    &DBAllTypeBits, glChanMergeFunc, (ClientData) NULL))
	ret = TRUE;

    if (DebugIsSet(glDebugID, glDebTiles))
    {
	char mesg[256];

	(void) sprintf(mesg, "After clipping chan %x", ch);
	glChanShowTiles(mesg);
    }

    return ret;
}

int
glChanSetClient(tile, cdata)
    Tile *tile;
    ClientData cdata;
{
    tile->ti_client = cdata;
    return 0;
}

void
glChanShowTiles(mesg)
    char *mesg;
{
    char answer[100], m[1024];

    DBWAreaChanged(glChanDef, &TiPlaneRect, DBW_ALLWINDOWS, 0);
    WindUpdate();
    (void) sprintf(m, "%s: --more-- (t for tiles): ", mesg);
    if (TxGetLinePrompt(answer, sizeof answer, m) == NULL || answer[0] != 't')
	return;

    (void) DBSrPaintArea((Tile *) NULL, glChanPlane, &TiPlaneRect,
	    &DBAllTypeBits, glChanShowFunc, (ClientData) NULL);
}

int
glChanShowFunc(tile)
    Tile *tile;
{
    GCRChannel *ch;
    char mesg[1024];
    Rect r;

    TITORECT(tile, &r);
    ShowRect(EditCellUse->cu_def, &r, STYLE_PALEHIGHLIGHTS);
    (void) sprintf(mesg, "tile ch=%x type=%d",
		tile->ti_client, TiGetType(tile));
    TxMore(mesg);
    ShowRect(EditCellUse->cu_def, &r, STYLE_ERASEHIGHLIGHTS);
    if (tile->ti_client == (ClientData) CLIENTDEFAULT)
	return 0;
    ch = (GCRChannel *) tile->ti_client;
    ShowRect(EditCellUse->cu_def, &ch->gcr_area, STYLE_MEDIUMHIGHLIGHTS);
    (void) sprintf(mesg, "chan %x type=%d", ch, ch->gcr_type);
    TxMore(mesg);
    ShowRect(EditCellUse->cu_def, &ch->gcr_area, STYLE_ERASEHIGHLIGHTS);
    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * glChanClipFunc --
 *
 * Called by DBSrPaintArea() on behalf of glChanClip() above for each
 * tile overlapping 'area': splits 'tile' as necessary to ensure that
 * it is completely contained within area.
 *
 * Results:
 *	Returns 1 if we did any splitting, 0 otherwise.
 *
 * Side effects:
 *	May split tiles.
 *	When a tile is split, the new tile's body and ti_client
 *	field are set to copies of the original tile's.
 *
 * ----------------------------------------------------------------------------
 */

int
glChanClipFunc(tile, area)
    Tile *tile;
    Rect *area;
{
    ClientData tileClient = tile->ti_client;
    TileType type = TiGetType(tile);
    Tile *newTile;
    int ret;

    ret = 0;
    if (LEFT(tile) < area->r_xbot)
    {
	tile = TiSplitX(tile, area->r_xbot);
	TiSetBody(tile, type);
	tile->ti_client = tileClient;
	ret = 1;
    }
    if (BOTTOM(tile) < area->r_ybot)
    {
	tile = TiSplitY(tile, area->r_ybot);
	TiSetBody(tile, type);
	tile->ti_client = tileClient;
	ret = 1;
    }
    if (RIGHT(tile) > area->r_xtop)
    {
	newTile = TiSplitX(tile, area->r_xtop);
	TiSetBody(newTile, type);
	newTile->ti_client = tileClient;
	ret = 1;
    }
    if (TOP(tile) > area->r_ytop)
    {
	newTile = TiSplitY(tile, area->r_ytop);
	TiSetBody(newTile, type);
	newTile->ti_client = tileClient;
	ret = 1;
    }

    return ret;
}

/*
 * ----------------------------------------------------------------------------
 *
 * glChanMergeFunc --
 *
 * Called by DBSrPaintArea() on behalf of glChanClip() above for each
 * tile contained within some area.  Merges this tile with any of
 * its neighbors within its channel's area, where the channel is
 * pointed to by the ti_client field.  We leave the resulting tile's
 * ti_client field pointing to this channel ('ch').
 *
 * IMPORTANT ASSUMPTION: no tile actually overlaps the boundary of
 * ch->gcr_area.  This is ensured by the caller's having called
 * glChanClipFunc() enough times to have split all the tiles
 * that originally might have crossed the channel's boundary.
 *
 * Results:
 *	Returns 1 if we did any merging, 0 otherwise.
 *
 * Side effects:
 *	May merge tiles.
 *	Leaves tile->ti_client set to 'ch'.
 *
 * ----------------------------------------------------------------------------
 */

int
glChanMergeFunc(tile)
    Tile *tile;
{
    GCRChannel *ch = (GCRChannel *) tile->ti_client;
    Tile *tp;
    int ret;

    ret = 0;
    if (TOP(tile) < ch->gcr_area.r_ytop)
    {
	tp = RT(tile);
	if (TiGetType(tp) == TiGetType(tile)
		&& LEFT(tp) == LEFT(tile)
		&& RIGHT(tp) == RIGHT(tile))
	{
	    TiJoinY(tile, tp, glChanPlane);
	    ret = 1;
	}
    }
    if (LEFT(tile) > ch->gcr_area.r_xbot)
    {
	tp = BL(tile);
	if (TiGetType(tp) == TiGetType(tile)
		&& TOP(tp) == TOP(tile)
		&& BOTTOM(tp) == BOTTOM(tile))
	{
	    TiJoinX(tile, tp, glChanPlane);
	    ret = 1;
	}
    }
    if (BOTTOM(tile) > ch->gcr_area.r_ybot)
    {
	tp = LB(tile);
	if (TiGetType(tp) == TiGetType(tile)
		&& LEFT(tp) == LEFT(tile)
		&& RIGHT(tp) == RIGHT(tile))
	{
	    TiJoinY(tile, tp, glChanPlane);
	    ret = 1;
	}
    }
    if (RIGHT(tile) < ch->gcr_area.r_xtop)
    {
	tp = TR(tile);
	if (TiGetType(tp) == TiGetType(tile)
		&& TOP(tp) == TOP(tile)
		&& BOTTOM(tp) == BOTTOM(tile))
	{
	    TiJoinX(tile, tp, glChanPlane);
	    ret = 1;
	}
    }

    return ret;
}

/*
 * ----------------------------------------------------------------------------
 *
 * glChanBlockDens --
 *
 * Find regions of maximum density in 'ch' (if 'ch' is a normal channel) and
 * "paint" them into the tile map.  Blockages of a single direction result
 * in CHAN_HRIVER or CHAN_VRIVER tiles being painted as appropriate; if
 * blockages exist in both directions, then we produce a CHAN_BLOCKED tile.
 *
 * We rely on the fact that regions of maximum density extend completely
 * across a channel, and hence completely across any tile (since tiles are
 * never bigger than channels).  The client fields of the any tiles in the
 * area of the channel that aren't totally blocked are left pointing to
 * the overlapping channel.
 *
 * If columns i through j inclusive have maximum density, then the blockage
 * we paint extends from midway between columns i-1 and i to midway between
 * column j and j+1, guaranteeing that tile boundaries will always fall
 * on half-grid lines.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	See above.
 *
 * ----------------------------------------------------------------------------
 */

void
glChanBlockDens(ch)
    GCRChannel *ch;
{
    GlobChan *gc = (GlobChan *) ch->gcr_client;
    int halfGrid, shiftedOrigin;
    DensMap *dRow, *dCol;
    PaintArea *pa;
    short *dens;
    int lo, hi;
    Rect area;

    if (ch->gcr_type != CHAN_NORMAL)
	return;

    dCol = &gc->gc_postDens[CZ_COL];
    dRow = &gc->gc_postDens[CZ_ROW];
    halfGrid = RtrGridSpacing / 2;
    glChanPaintList = (PaintArea *) NULL;
    if (dCol->dm_max >= dCol->dm_cap)
    {
	dens = dCol->dm_value;
	area.r_ybot = ch->gcr_area.r_ybot;
	area.r_ytop = ch->gcr_area.r_ytop;
	shiftedOrigin = ch->gcr_origin.p_x - halfGrid;
	for (lo = 1; lo < dCol->dm_size; lo++)
	{
	    if (dens[lo] >= dCol->dm_cap)
	    {
		hi = lo+1;
		while (dens[hi] >= dCol->dm_cap && hi < dCol->dm_size)
		    hi++;

		/* Remember area of blocked tile to paint */
		area.r_xbot = shiftedOrigin + lo * RtrGridSpacing;
		area.r_xtop = shiftedOrigin + hi * RtrGridSpacing;
		pa = (PaintArea *) mallocMagic((unsigned)(sizeof (PaintArea)));
		pa->pa_area = area;
		pa->pa_next = glChanPaintList;
		pa->pa_type = CHAN_VRIVER;
		glChanPaintList = pa;

		/* Will get incremented in loop header */
		lo = hi-1;
	    }
	}
    }

    if (dRow->dm_max >= dRow->dm_cap)
    {
	dens = dRow->dm_value;
	area.r_xbot = ch->gcr_area.r_xbot;
	area.r_xtop = ch->gcr_area.r_xtop;
	shiftedOrigin = ch->gcr_origin.p_y - halfGrid;
	for (lo = 1; lo < dRow->dm_size; lo++)
	{
	    if (dens[lo] >= dRow->dm_cap)
	    {
		hi = lo+1;
		while (dens[hi] >= dRow->dm_cap && hi < dRow->dm_size)
		    hi++;

		/* Remember area of blocked tile to paint */
		area.r_ybot = shiftedOrigin + lo * RtrGridSpacing;
		area.r_ytop = shiftedOrigin + hi * RtrGridSpacing;
		pa = (PaintArea *) mallocMagic((unsigned)(sizeof (PaintArea)));
		pa->pa_area = area;
		pa->pa_next = glChanPaintList;
		pa->pa_type = CHAN_HRIVER;
		glChanPaintList = pa;

		/* Will get incremented in loop header */
		lo = hi-1;
	    }
	}
    }

    /*
     * Now do all the work.
     * First paint all the blocked areas on glChanPaintList.
     * Then make a second pass through this list, searching the
     * area around each blocked area tile we painted for river
     * tiles of the appropriate type, and propagating this blockage
     * to those river tiles as well.  Do this until the wavefront
     * stops, which will happen as soon as we reach a normal channel.
     */
    do
    {
	for (pa = glChanPaintList; pa; pa = pa->pa_next)
	{
	    /* Clip tiles overlapped by pa->pa_area */
	    while (DBSrPaintArea((Tile *) NULL, glChanPlane, &pa->pa_area,
		    &DBAllTypeBits, glChanClipFunc, (ClientData) &pa->pa_area))
		/* Nothing */;

	    /* Change the type of all tiles within the area */
	    (void) DBSrPaintArea((Tile *) NULL, glChanPlane, &pa->pa_area,
		    &DBAllTypeBits, glChanPaintFunc, (ClientData) pa->pa_type);

	    /*
	     * Allow merging, as long as no tiles get merged across
	     * channel boundaries.
	     */
	    while (DBSrPaintArea((Tile *) NULL, glChanPlane, &pa->pa_area,
		    &DBAllTypeBits, glChanMergeFunc, (ClientData) NULL));
	}

	/* Second pass to propagate blockages to nearby areas */
	for (pa = glChanPaintList, glChanPaintList = NULL; pa; pa = pa->pa_next)
	{
	    glChanFlood(&pa->pa_area, pa->pa_type);
	    freeMagic((char *) pa);
	}
    } while (glChanPaintList != NULL);
}

int
glChanPaintFunc(tile, type)
    Tile *tile;
    TileType type;
{
    static TileType changeTable[4][4] = {
	    /* Paint atop CHAN_NORMAL */
	    { CHAN_NORMAL, CHAN_HRIVER, CHAN_VRIVER, CHAN_BLOCKED },

	    /* Paint atop CHAN_HRIVER */
	    { CHAN_HRIVER, CHAN_HRIVER, CHAN_BLOCKED, CHAN_BLOCKED },

	    /* Paint atop CHAN_VRIVER */
	    { CHAN_VRIVER, CHAN_BLOCKED, CHAN_VRIVER, CHAN_BLOCKED },

	    /* Paint atop CHAN_BLOCKED */
	    { CHAN_BLOCKED, CHAN_BLOCKED, CHAN_BLOCKED, CHAN_BLOCKED },
    };

    TiSetBody(tile, changeTable[TiGetType(tile)][type]);
    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * glChanFlood --
 *
 * Search all four sides of 'area' (an area that has just been painted
 * as type) for river tiles of the appropriate type (where appropriate
 * means tiles that are completely blocked by a tile of type type to
 * the side to which it lies).
 *
 * Each time an appropriate river tiles is found, record a CHAN_BLOCKED
 * rectangle on the list glChanPaintList that extends from 'area' through to
 * the other side of the river channel.
 *
 * The purpose of this procedure is so that the presence of the blockage
 * on one side of a river tile will be visible from its other side.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	May prepend stuff to glChanPaintList.
 *
 * ----------------------------------------------------------------------------
 */

void
glChanFlood(area, type)
    Rect *area;
    TileType type;
{
    TileTypeBitMask hMask, vMask;
    Rect outside;

    TTMaskSetOnlyType(&hMask, CHAN_HRIVER);
    TTMaskSetOnlyType(&vMask, CHAN_VRIVER);

    if (type != CHAN_VRIVER)
    {
	/* TOP */
	outside = *area;
	outside.r_ybot = area->r_ytop;
	outside.r_ytop = area->r_ytop + 1;
	(void) DBSrPaintArea((Tile *) NULL, glChanPlane, &outside, &vMask,
		glChanFloodVFunc, (ClientData) area);

	/* BOTTOM */
	outside = *area;
	outside.r_ybot = area->r_ybot - 1;
	outside.r_ytop = area->r_ybot;
	(void) DBSrPaintArea((Tile *) NULL, glChanPlane, &outside, &vMask,
		glChanFloodVFunc, (ClientData) area);
    }

    if (type != CHAN_HRIVER)
    {
	/* LEFT */
	outside = *area;
	outside.r_xbot = area->r_xbot - 1;
	outside.r_xtop = area->r_xbot;
	(void) DBSrPaintArea((Tile *) NULL, glChanPlane, &outside, &hMask,
		glChanFloodHFunc, (ClientData) area);

	/* RIGHT */
	outside = *area;
	outside.r_xbot = area->r_xtop;
	outside.r_xtop = area->r_xtop + 1;
	(void) DBSrPaintArea((Tile *) NULL, glChanPlane, &outside, &hMask,
		glChanFloodHFunc, (ClientData) area);
    }
}

int
glChanFloodVFunc(tile, area)
    Tile *tile;
    Rect *area;
{
    PaintArea *pa;

    pa = (PaintArea *) mallocMagic((unsigned) (sizeof (PaintArea)));
    pa->pa_area.r_xbot = MAX(area->r_xbot, LEFT(tile));
    pa->pa_area.r_xtop = MIN(area->r_xtop, RIGHT(tile));
    pa->pa_area.r_ybot = BOTTOM(tile);
    pa->pa_area.r_ytop = TOP(tile);
    pa->pa_next = glChanPaintList;
    pa->pa_type = CHAN_BLOCKED;
    glChanPaintList = pa;

    return 0;
}

int
glChanFloodHFunc(tile, area)
    Tile *tile;
    Rect *area;
{
    PaintArea *pa;

    pa = (PaintArea *) mallocMagic((unsigned)(sizeof (PaintArea)));
    pa->pa_area.r_ybot = MAX(area->r_ybot, BOTTOM(tile));
    pa->pa_area.r_ytop = MIN(area->r_ytop, TOP(tile));
    pa->pa_area.r_xbot = LEFT(tile);
    pa->pa_area.r_xtop = RIGHT(tile);
    pa->pa_next = glChanPaintList;
    pa->pa_type = CHAN_BLOCKED;
    glChanPaintList = pa;

    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * glChanSplitRiver --
 *
 * Called for each river tile (CHAN_HRIVER or CHAN_VRIVER) in glChanPlane.
 * Search along the two sides of this tile from which signals can enter
 * (the LHS and RHS for CHAN_HRIVER, or the top and bottom for CHAN_VRIVER),
 * and make sure that there are no tile boundaries involving some type of
 * tile other than CHAN_BLOCKED along that side.  If there is a boundary,
 * split tile at the boundary.
 *
 * Results:
 *	Returns 1 if we did any splitting, 0 if not.
 *
 * Side effects:
 *	See above.
 *
 * ----------------------------------------------------------------------------
 */

int
glChanSplitRiver(tile)
    Tile *tile;
{
    ClientData tileClient = tile->ti_client;
    Tile *tp, *newTile;
    int ret;

    ret = 0;
    if (TiGetType(tile) == CHAN_HRIVER)
    {
	for (tp = BL(tile); TOP(tp) < TOP(tile); tp = RT(tp))
	{
	    if (NOTBLOCKED(tp) || NOTBLOCKED(RT(tp)))
	    {
		tile = TiSplitY(tile, TOP(tp));
		TiSetBody(tile, CHAN_HRIVER);
		tile->ti_client = tileClient;
		ret = 1;
	    }
	}
	for (tp = TR(tile); BOTTOM(tp) > BOTTOM(tile); tp = LB(tp))
	{
	    if (NOTBLOCKED(tp) || NOTBLOCKED(LB(tp)))
	    {
		newTile = TiSplitY(tile, BOTTOM(tp));
		TiSetBody(newTile, CHAN_HRIVER);
		newTile->ti_client = tileClient;
		ret = 1;
	    }
	}
    }
    else
    {
	for (tp = RT(tile); LEFT(tp) > LEFT(tile); tp = BL(tp))
	{
	    if (NOTBLOCKED(tp) || NOTBLOCKED(BL(tp)))
	    {
		newTile = TiSplitX(tile, LEFT(tp));
		TiSetBody(newTile, CHAN_VRIVER);
		newTile->ti_client = tileClient;
		ret = 1;
	    }
	}
	for (tp = LB(tile); RIGHT(tp) < RIGHT(tile); tp = TR(tp))
	{
	    if (NOTBLOCKED(tp) || NOTBLOCKED(TR(tp)))
	    {
		tile = TiSplitX(tile, RIGHT(tp));
		TiSetBody(tile, CHAN_VRIVER);
		tile->ti_client = tileClient;
		ret = 1;
	    }
	}
    }

    return ret;
}

/*
 * ----------------------------------------------------------------------------
 *
 * glChanRiverBlock --
 *
 * Called for each river routing tile.  Checks to make sure that at least
 * some of the pins along the usable sides of this channel are still free;
 * if they're not, we change this tile's type to CHAN_BLOCKED.
 *
 * Note: river routing tiles can overlay portions of a normal channel
 * in which the density of the channel equals its capacity in either
 * the horizontal or vertical direction.
 *
 * Results:
 *	Always returns 0.
 *
 * Side effects:
 *	See above.
 *
 * ----------------------------------------------------------------------------
 */

int
glChanRiverBlock(tile)
    Tile *tile;
{
    GCRPin *pin, *pinLast;
    GCRChannel *ch;
    int lo, hi;

    ch = (GCRChannel *) tile->ti_client;
    if (TiGetType(tile) == CHAN_HRIVER)
    {
	lo = (BOTTOM(tile) - ch->gcr_origin.p_y) / RtrGridSpacing;
	hi = (TOP(tile) - ch->gcr_origin.p_y) / RtrGridSpacing;
	if (lo < 1) lo = 1;
	if (hi > ch->gcr_width) hi = ch->gcr_width;
	pinLast = &ch->gcr_lPins[hi];
	for (pin = &ch->gcr_lPins[lo]; pin <= pinLast; pin++)
	    if (pin->gcr_pId == NULL && pin->gcr_linked)
		return 0;
	pinLast = &ch->gcr_rPins[hi];
	for (pin = &ch->gcr_rPins[lo]; pin <= pinLast; pin++)
	    if (pin->gcr_pId == NULL && pin->gcr_linked)
		return 0;
    }
    else
    {
	/* CHAN_VRIVER */
	lo = (LEFT(tile) - ch->gcr_origin.p_x) / RtrGridSpacing;
	hi = (RIGHT(tile) - ch->gcr_origin.p_x) / RtrGridSpacing;
	if (lo < 1) lo = 1;
	if (hi > ch->gcr_length) hi = ch->gcr_length;
	pinLast = &ch->gcr_bPins[hi];
	for (pin = &ch->gcr_bPins[lo]; pin <= pinLast; pin++)
	    if (pin->gcr_pId == NULL && pin->gcr_linked)
		return 0;
	pinLast = &ch->gcr_tPins[hi];
	for (pin = &ch->gcr_tPins[lo]; pin <= pinLast; pin++)
	    if (pin->gcr_pId == NULL && pin->gcr_linked)
		return 0;
    }

    TiSetBody(tile, CHAN_BLOCKED);
    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * glChanPinToTile --
 *
 * Find the tile corresponding to the pin 'pin'.
 * The pin mustn't be in one of the corners of a channel.
 *
 * Results:
 *	Returns a pointer to the tile found.
 *	Returns NULL if the tile found was of type CHAN_BLOCKED.
 *
 * Side effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */

Tile *
glChanPinToTile(hintTile, pin)
    Tile *hintTile;	/* Hint for starting the tile search */
    GCRPin *pin;	/* Find tile containing this pin */
{
    GCRChannel *ch;
    Tile *tp;
    Point p;
    Rect r;

    /* Figure out which channel tile the output point lies in */
    p = pin->gcr_point;
    switch (pin->gcr_side)
    {
	case GEO_NORTH:	p.p_y--; break;
	case GEO_EAST:	p.p_x--; break;
    }

    tp = TiSrPoint(hintTile, glChanPlane, &p);
    if (TiGetType(tp) == CHAN_BLOCKED)
	return (Tile *) NULL;

    ASSERT(tp->ti_client != (ClientData) CLIENTDEFAULT, "glChanPinToTile");
    ch = (GCRChannel *) tp->ti_client;
    TITORECT(tp, &r);
    ASSERT(GEO_SURROUND(&ch->gcr_area, &r), "glChanPinToTile");

    return tp;
}
