/*
 * irRoute.c --
 *
 * Sets up route and calls maze router to do it.
 *
 *     ********************************************************************* 
 *     * Copyright (C) 1987, 1990 Michael H. Arnold, Walter S. Scott, and  *
 *     * the 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-7.5/irouter/irRoute.c,v 1.8 2008/03/06 17:56:53 tim Exp $";
#endif  /* not lint */

/* --- Includes --- */

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

#include "utils/magic.h"
#include "utils/geometry.h"
#include "utils/hash.h"
#include "tiles/tile.h"
#include "database/database.h"
#include "drc/drc.h"
#include "textio/textio.h"
#include "windows/windows.h"
#include "utils/main.h"
#include "dbwind/dbwind.h"
#include "debug/debug.h"
#include "utils/undo.h"
#include "utils/signals.h"
#include "utils/malloc.h"
#include "utils/list.h"
#include "utils/geofast.h"
#include "utils/touchingtypes.h"
#include "select/select.h"
#include "../mzrouter/mzrouter.h"
#include "irouter/irouter.h"
#include "irouter/irInternal.h"

/* --- Routines local to this file that are referenced before they are 
 *     defined --- */
List *irChooseEndPtLayers();

/* -------------------- Structures Local to this File -------------------- */

/* clientdata structure passed to filter functions when searching for
 * start and destination labels.
 */
typedef struct labelSearchData
{
    Rect 	lsd_locRect;	/* set to labels location by filter func */
    char	*lsd_name;	/* label name to search for */
    TileType	lsd_type;	/* layer that label is attached to */
    int 	lsd_result;	/* code giving result of search */
} LabelSearchData;

/* result codes for lsd_result above */
#define LSR_NOTFOUND	10
#define LSR_NOTUNIQUE	20
#define LSR_FOUND	30


/*
 * ----------------------------------------------------------------------------
 *
 * irRoute --
 *
 * Top level procedure for the routing code.  Initializes things, and
 * calls the maze router to make a connection.
 *
 * Results:
 *	Passes back the result code of MZRoute() (see mzrouter.h for codes)
 *
 * Side effects:
 *	Paint route into editcell.
 *
 * ----------------------------------------------------------------------------
 */

int
irRoute(cmdWindow, startType, argStartPt, argStartLabel, argStartLayers, 
		destType, argDestRect, argDestLabel, argDestLayers)
    MagWindow *cmdWindow;	/* window route command issued to */
    int startType;	/* how start is specified */
    Point *argStartPt;	/* location to route from (in edit cell coords) */
    char *argStartLabel;	/* label to route from */
    List *argStartLayers;	/* OK route layers to start route on */
    int destType;	/* how dest is specified */
    Rect *argDestRect;	/* location to route to (in edit cell coords) */
    char *argDestLabel; /* label to route to */
    List *argDestLayers;	/* OK route layers to end route on */
{
    CellUse *routeUse;		/* Toplevel cell visible during routing */
    int expansionMask;		/* Subcell expansion modes to use during 
				 * routing */
    Point startPt; 		/* start and dest terminals */
    List *startLayers = NULL;
    Rect destRect;
    List *destLayers = NULL;
    RoutePath *path = NULL;	/* resulting path */
    TileType startLayer = TT_SPACE;
    int mzResult = MZ_NO_ACTION;

    /* determine routeUse and expansionMask for this route. */
    {
	MagWindow *window = NULL;

	/* find global reference window */
	if(irRouteWid>=0)
	{
	    window = WindSearchWid(irRouteWid);
	    if (window == NULL)
	    {
		TxError("Couldn't find route window (%d),", irRouteWid);
	        TxError("using command window as reference.\n");
	    }
	}

	/* if no global reference window, use window command issued from */
	if(window == NULL)
	{
	    window = cmdWindow;
	}

	/* If reference window is nil, complain and exit */
	if(window==NULL)
	{
	    TxError("Point to a layout window first.\n");
	    return mzResult;
	}

	/* Set expansion mask to window route cmd issued to.  Used 
	 * during searches.  Subcells are treated as expanded if expanded in
	 * window cmd issued to.
	 */
        expansionMask = ((DBWclientRec *)(window->w_clientData))->dbw_bitmask;

	/* Set routeUse to rootuse of reference window - 
	 *  everything "visible" in 
	 *  the reference window is visible
	 *  during routing.  
	 * But resulting route
	 * is painted into edit cell.  This distinction is important only
	 * in the case of a subedit.  If the user subedits a cell, the
	 * context of the parent(s) will guide the route, but the route 
	 * will be painted into the edit cell.
	 */
	routeUse = (CellUse *) (window->w_surfaceID);

	/* make sure cmd issued from window in which edit cell 
	 * is being edited */
	if (!EditCellUse || EditRootDef != routeUse->cu_def)
	{
	    TxError("Nothing being edited in route window.\n");
	    return mzResult;
	}
    }

    /* initialize mzrouter */
    MZInitRoute(irMazeParms, routeUse, expansionMask);

    /* Figure out start coordinates */
    {
	Point irGetStartPoint();

	startPt = irGetStartPoint(startType, 
				  argStartPt, 
				  argStartLabel,
				  &startLayer,
				  routeUse);

	/* check for failure */
	if(startPt.p_x == MINFINITY) goto abort;
    }

    /* Set maze router dest area(s) */

    if(destType == DT_SELECTION)
    /* add destination area for each selected area on an appropriate layer */
    {
	int irSelectedTileFunc();

	if(argDestLayers == NULL)
	/* no layer arg specified, generate dest areas for each active
	 * route layer
	 */
	{
	    RouteLayer *rL;
		
	    for (rL = irRouteLayers; rL != NULL; rL = rL->rl_next)
	    {
		if(rL->rl_routeType.rt_active)
		{
		    /* set dest area for each selected tile 
		     * of type connecting to rL
		     */
		    SelEnumPaint(
				 &(DBConnectTbl[rL->rl_routeType.rt_tileType]),
				 FALSE,	/* TRUE = restricted to edit cell */
				 NULL, /* not used */
				 irSelectedTileFunc, 
				 (ClientData *) rL /* type of destarea */
				 );
		}
	    }
	}
	else
	/* generate dest areas for layers that are both specified and active */
	{
	    List *l;

	    for(l=argDestLayers; l!=NULL; l=LIST_TAIL(l))
	    {
		RouteLayer *rL = (RouteLayer *) LIST_FIRST(l);

		if(rL->rl_routeType.rt_active)
		{
		    /* set dest area for each selected tile 
		     * of type connecting to rL
		     */
		    SelEnumPaint(
				 &(DBConnectTbl[rL->rl_routeType.rt_tileType]),
				 FALSE,	/* TRUE = restricted to edit cell */
				 NULL, /* not used */
				 irSelectedTileFunc, 
				 (ClientData *) rL /* type of destarea */
				 );
		}
	    }
	}
    }
    else
    /* dest is defined by rectangle */
    {
	Rect irGetDestRect();
	TileType destLayer = TT_SPACE;

	destRect = irGetDestRect(destType, 
				 argDestRect, 
				 argDestLabel,
				 &destLayer,
				 routeUse);

	/* check for failure */
	if(destRect.r_xtop == MINFINITY) goto abort;

	/* set a maze router dest area for extent of destRect
	 * on each permitted dest layer.
	 */
	if (destLayer != TT_SPACE)
	{
	    RouteLayer *rL;

	    /* layer type returned by irGetDestRect */

	    for(rL = irRouteLayers; rL != NULL; rL = rL->rl_next)
	    {
		if (rL->rl_routeType.rt_active &&
			TTMaskHasType(&(DBConnectTbl[destLayer]),
                                   rL->rl_routeType.rt_tileType))
		{
		    MZAddDest(&destRect, rL->rl_routeType.rt_tileType);
		    break;
		}
	    }
	}
	else if (argDestLayers == NULL)
	{
	    /* no layer arg specified, permit all active route layers */
	    RouteLayer *rL;
	    
	    for (rL = irRouteLayers; rL != NULL; rL = rL->rl_next)
	    {
		if(rL->rl_routeType.rt_active)
		{
		    MZAddDest(&destRect,rL->rl_routeType.rt_tileType);
		}
	    }
	}
	else
	/* permit only layers that are both specified and active */
	{
	    List *l;
	    
	    for(l=argDestLayers; l!=NULL; l=LIST_TAIL(l))
	    {
		RouteLayer *rL = (RouteLayer *) LIST_FIRST(l);
		
		if(rL->rl_routeType.rt_active)
		{
		    MZAddDest(&destRect,rL->rl_routeType.rt_tileType);
		    
		}
	    }
	}	    
    }

    if (startLayer != TT_SPACE)
    {
	RouteLayer *rL;

	/* layer type returned by irGetStartPoint */

	for (rL = irRouteLayers; rL != NULL; rL = rL->rl_next)
	{
	    if (rL->rl_routeType.rt_active &&
			TTMaskHasType(&(DBConnectTbl[startLayer]),
                                   rL->rl_routeType.rt_tileType))

	    {
		MZAddStart(&startPt, rL->rl_routeType.rt_tileType);
		break;
	    }
	}
    }
    else
    {
	/* Determine OK start layers */

	List *l;

	startLayers = irChooseEndPtLayers(
					  routeUse, 
					  expansionMask, 
					  &startPt,
					  argStartLayers,
					  "start");
	if(SigInterruptPending) goto abort;

	if(DebugIsSet(irDebugID,irDebEndPts))
	{
	    TxPrintf("----- startLayers:\n");
	    MZPrintRLListNames(startLayers);
	}

	/* Set maze router start point(s) - one for each ok layer */

	for(l=startLayers; l!=NULL;l=LIST_TAIL(l))
	{
	    RouteLayer *rL = (RouteLayer *)LIST_FIRST(l);
	    TileType type = rL->rl_routeType.rt_tileType;

	    MZAddStart(&startPt, type);
	}
    }

    /* Do the Route */
    path = MZRoute(&mzResult);
		      /* If MZRoute is interrupted it returns best path
		       * found so far.
		       */
    if(SigInterruptPending)
    {
	if(path==NULL)
	{
	    goto abort;
	}
	else
	{
	    TxError("Search Interrupted!\n");
	    TxPrintf("Using best path found prior to interrupt.\n");

	    /* Clear interrupt to allow paint back of path */
	    SigInterruptPending = FALSE;
	}
    }

    /* paint route back into edit cell */
    if (path) 
    {
	
	RouteLayer *finalRL = path->rp_rLayer;
	CellUse *resultUse;

        /* Have MazeRouter paint path into resultCell */
	MZClean();
        resultUse = MZPaintPath(path);
	if(SigInterruptPending) goto abort;

	/* Copy to edit cell transforming from root to edit
	 * coords.
	 * Also select the entire route.
	 * This paint job is undoable.
	 */
	{
	    SearchContext scx;

	    scx.scx_use = resultUse;
	    scx.scx_area = resultUse->cu_def->cd_bbox;
	    scx.scx_trans = RootToEditTransform;
	    (void) DBCellCopyPaint(&scx, &DBAllButSpaceBits, 0, EditCellUse);
	    DBReComputeBbox(EditCellUse->cu_def);
	}

	/* Select the route */
	{
	    SearchContext scx;

	    /* Clear selection, and set selection display for reference
	     * window and other windows containing routeUse as root.
	     */
	    SelectClear();
	    if(SelectRootDef != routeUse->cu_def)
	    {
		SelectRootDef = routeUse->cu_def;
		SelSetDisplay(SelectUse, SelectRootDef);
	    }

	    /* Copy route to selection cell, notifying undo of change */
	    scx.scx_use = resultUse;
	    scx.scx_area = resultUse->cu_def->cd_bbox;
	    scx.scx_trans = GeoIdentityTransform;
	    SelRememberForUndo(TRUE, (CellDef *) NULL, (Rect *) NULL);
	    (void) DBCellCopyPaint(&scx, &DBAllButSpaceBits, 0, SelectUse);
	    SelRememberForUndo(FALSE, SelectRootDef, &(scx.scx_area));

	    /* Setup redisplay */
	    DBReComputeBbox(SelectDef);
	    DBWHLRedraw(SelectRootDef, &(scx.scx_area), TRUE);
	    DBWAreaChanged(SelectDef, &SelectDef->cd_bbox, DBW_ALLWINDOWS,
			   &DBAllButSpaceBits);
	}

	/* Notify dbwind module (for redisplay), and DRC module 
	 * of changed area */
	{
	    Rect changedArea;

	    GeoTransRect(
			 &RootToEditTransform,
			 &(resultUse->cu_def->cd_bbox),
			 &changedArea);
	    DBWAreaChanged(EditCellUse->cu_def, &changedArea, DBW_ALLWINDOWS,
			   &DBAllButSpaceBits);
	    DRCCheckThis(EditCellUse->cu_def, TT_CHECKPAINT, &changedArea);

	}

	/* Make sure we got here without interruption */
	if(SigInterruptPending) goto abort;

	TxPrintf("Done Routing.\n");
	TxFlushOut();
    }
    else
    {
	TxError("Route Failed.\n");
    }

abort:
    if(SigInterruptPending)
    {
	TxError("Route Interrupted!\n");
    }

    ListDealloc(startLayers);
    ListDealloc(destLayers);

    /* reclaim storage used by mzrouter */
    if(!DebugIsSet(irDebugID, irDebNoClean))
    {
	MZClean();
    }

    return mzResult;
}


/*
 * ---------------------------------------------------------------------
 *
 * irGetStartPoint --
 *
 * Compute start point.
 *
 * Results:
 *	Returns point.
 *
 * Side effects:
 *	Returns start layer type in startLayerPtr.
 *
 * ---------------------------------------------------------------------
 */

Point
irGetStartPoint(startType, argStartPt, argStartLabel, startLayerPtr, routeUse)
    int startType;		/* how start is specified */
    Point *argStartPt;		/* location to route from 
				 * (in edit cell coords) */
    char *argStartLabel;	/* label to route from */
    TileType *startLayerPtr;	/* layer type (returned value) */
    CellUse *routeUse;		/* toplevel cell visible to router */
{
    Point startPt;

    switch (startType)
    {
	case ST_POINT:
	/* start point coords given */
	{
	    /* convert from edit to routeUse coords (= root coordinates) */
	    GeoTransPoint(&EditToRootTransform,argStartPt,&startPt);
	}
	break;

	case ST_CURSOR:
	/* use cursor */
	{
	    MagWindow *pointWindow;

	    pointWindow = ToolGetPoint(&startPt, (Rect *) NULL);

	    if (pointWindow == NULL)
	    {
		TxError("Can not use cursor as start:");
		TxError("  cursor not in layout window.\n");
		goto abort;
	    }

	    if (routeUse->cu_def  != 
		((CellUse *)pointWindow->w_surfaceID)->cu_def)
	    {
		TxError("Can not use cursor as start:");
		TxError("cursor not in routecell.\n");
		goto abort;
	    }
	}
	break;

	case ST_LABEL:
	/* label name given */
	{   
	    int irSelLabelsFunc();
	    int irAllLabelsFunc();
	    LabelSearchData lSD;
	    lSD.lsd_name = argStartLabel;   /* name to match */
	    lSD.lsd_result = LSR_NOTFOUND;

	    /* first search selection */
	    (void) SelEnumLabels(&DBAllTypeBits,
				 FALSE,	/* TRUE = search only edit cell */
				 (bool *) NULL,
				 irSelLabelsFunc,
				 (ClientData) &lSD);

	    if(SigInterruptPending) goto abort;

	    if (lSD.lsd_result == LSR_NOTUNIQUE)
	    {
		TxError("Warning: Start label '%s' not unique.\n", argStartLabel);
	    }
	    else if (lSD.lsd_result == LSR_NOTFOUND)
	    /* No selected label matched, so search all labels */
	    {

		(void) DBSrLabelLoc(routeUse,
				    argStartLabel, 
				    irAllLabelsFunc, 
				    (ClientData) &lSD);

		if(SigInterruptPending) goto abort;
	
		if (lSD.lsd_result == LSR_NOTUNIQUE)
		{
		    TxError("Warning: Start label '%s' not unique.\n", argStartLabel);
		}
		else if (lSD.lsd_result == LSR_NOTFOUND)
		{
		    TxError("Start label '%s' not found.\n",
			    argStartLabel);
		    goto abort;
		}
	    }

	    startPt = lSD.lsd_locRect.r_ll;
	    if (startLayerPtr) *startLayerPtr = lSD.lsd_type;
	}
	break;

	default:
	/* shouldn't happen */
	{
	    ASSERT(FALSE,"irGetStartPoint");
	}
	break;
    }

    return startPt;

abort:
    startPt.p_x = MINFINITY;
    startPt.p_y = MINFINITY;
    return startPt;
}


/*
 * ---------------------------------------------------------------------
 *
 * irGetDestRect --
 *
 * Compute destination rectangle.
 *
 * Results:
 *	Returns rect.
 *
 * Side effects:
 *	Returns layer type in destLayerPtr.
 *
 * ---------------------------------------------------------------------
 */

Rect
irGetDestRect(destType, argDestRect, argDestLabel, destLayerPtr, routeUse)
    int destType;		/* how dest is specified */
    Rect *argDestRect;		/* location to route to 
				 * (in edit cell coords) */
    char *argDestLabel;		/* label to route to */
    TileType *destLayerPtr;	/* layer type (returned value) */
    CellUse *routeUse;		/* toplevel cell visible to router */
{
    Rect destRect;

    switch (destType)
    {
	case DT_RECT:
	/* dest rect coords given */
	{
	    /* convert from edit to routeUse coords (= root coordinates) */
	    GeoTransRect(&EditToRootTransform,argDestRect,&destRect);
	}
	break;

	case DT_LABEL:
	/* dest rect given as label */
	{   
	    int irSelLabelsFunc();
	    int irAllLabelsFunc();
	    LabelSearchData lSD;
	    lSD.lsd_name = argDestLabel;   /* name to match */
	    lSD.lsd_result = LSR_NOTFOUND;

	    /* first search selection */
	    (void) SelEnumLabels(&DBAllTypeBits,
				 FALSE,	/* TRUE = search only edit cell */
				 (bool *) NULL,
				 irSelLabelsFunc,
				 (ClientData) &lSD);
	    if(SigInterruptPending) goto abort;

	    if (lSD.lsd_result == LSR_NOTUNIQUE)
	    {
		TxError("Warning: Destination label '%s' not unique.\n", argDestLabel);
	    }
	    else if (lSD.lsd_result == LSR_NOTFOUND)
	    {
		/* No selected label matched, so search all labels */
		(void) DBSrLabelLoc(routeUse,
				    argDestLabel, 
				    irAllLabelsFunc, 
				    (ClientData) &lSD);
		if(SigInterruptPending) goto abort;

		if (lSD.lsd_result == LSR_NOTUNIQUE)
		{
		    TxError("Warning: Destination label '%s' not unique.\n",
				argDestLabel);
		}
		else if (lSD.lsd_result == LSR_NOTFOUND)
		{
		    TxError("Destination label '%s' not found.\n",
			    argDestLabel);
		    goto abort;
		}
	    }

	    destRect = lSD.lsd_locRect;
	    if (destLayerPtr) *destLayerPtr = lSD.lsd_type;
	}
	break;

	case DT_BOX:
	/* use box as dest rect */
	{
	    CellDef *boxDef;
	    Rect box;

	    if(!ToolGetBox(&boxDef,&box))
	    {
		TxError("Can not use box for dest:  No Box.\n");
		goto abort;
	    }

	    if (boxDef != routeUse->cu_def)
	    {
		TxError("Can not use box for dest:  ");
		TxError("box not in route cell.\n");
		goto abort;
	    }

	    destRect = box;
	}
	break;

	default:
	/* shouldn't happen */
	{
	    ASSERT(FALSE,"irGetDestRect");
	}
	break;
    }

    return destRect;

abort:
    destRect.r_xbot = MINFINITY;
    destRect.r_ybot = MINFINITY;
    destRect.r_xtop = MINFINITY;
    destRect.r_ytop = MINFINITY;
    return destRect;
}

/*
 * ---------------------------------------------------------------------
 *
 * irSelLabelsFunc --
 *
 * Called by SelEnumLabels on behalf of irRoute above, to find selected
 * label of given name.
 *
 * Results:
 *	Returns 0 on first match, 1 on second match (to terminate search)
 *
 * Side effects:
 *	Sets locRect in clientdata arg location off matching label.
 *
 * ---------------------------------------------------------------------
 */

int
irSelLabelsFunc(label, cellUse, transform, clientData)
    Label *label;
    CellUse *cellUse;
    Transform *transform;
    ClientData clientData;
{
    LabelSearchData *lsd = (LabelSearchData *)clientData;
    CellDef *cellDef = cellUse->cu_def;

    if (strcmp(lsd->lsd_name, label->lab_text) != 0)
    {
	/* this label doesn't match, continue search */
	return 0;
    }
    else if (lsd->lsd_result == LSR_FOUND)
    {
	/* second match, set result and terminate search */
	lsd->lsd_result = LSR_NOTUNIQUE;
	return 1;
    }
    else
    {
	/* first match, set location, result, and continue search */
	GeoTransRect(transform,
		&(label->lab_rect), &(lsd->lsd_locRect));
	lsd->lsd_result = LSR_FOUND;
	lsd->lsd_type = label->lab_type;
	return 0;
    }
}


/*
 * ---------------------------------------------------------------------
 *
 * irAllLabelsFunc --
 *
 * Called by DBSrLabelLoc on behalf of irRoute above, to convert labelName
 * to location of label with matching name.
 *
 * Results:
 *	Returns 0 on first match, 1 on second match (to terminate search)
 *
 * Side effects:
 *	Sets locRect in clientdata arg to location of matching label.
 *
 * ---------------------------------------------------------------------
 */

int
irAllLabelsFunc(rect, name, label, clientData)
    Rect *rect;
    char *name;
    Label *label;
    ClientData clientData;
{
    LabelSearchData *lsd = (LabelSearchData *)clientData;

    if (lsd->lsd_result == LSR_FOUND)
    {
	if (GEO_SAMERECT(lsd->lsd_locRect, *rect)) return 0;

	/* second match, so set result and terminate search */
	lsd->lsd_result = LSR_NOTUNIQUE;
	return 1;
    }
    else
    {
	/* first match, so set location, result, and continue search */
	lsd->lsd_locRect = *rect;
	lsd->lsd_type = label->lab_type;
	lsd->lsd_result = LSR_FOUND;
	return 0;
    }
}


/*
 * ---------------------------------------------------------------------
 *
 * irSelectedTileFunc --
 *
 * Called by SelEnumPaint on behalf of irRoute above, to process tile
 * associated with selection rect.
 *
 * Results:
 *	Always returns 0 to continue search.
 *
 * Side effects:
 *	Call MzAddDest on selected area.
 *
 * ---------------------------------------------------------------------
 */

int
irSelectedTileFunc(rect, type, c)
    Rect *rect;
    TileType type;
    ClientData c;
{
    RouteLayer *rL = (RouteLayer *) c;
    MZAddDest(rect, rL->rl_routeType.rt_tileType);
    
    /* return 0 to continue search */
    return 0;
}


/*
 * ----------------------------------------------------------------------------
 *
 * LayerInTouchingContact --
 *
 * A Predicate.  Checks whether the given routeLayer is a component of a 
 * contact type in touchingTypes.  Used by irChooseEndPtLayers below.
 *
 * Results:
 *	TRUE if the RouteLayer is a component of a contact type in 
 *	touchingTypes, else FALSE.
 *
 * Side effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */

bool
LayerInTouchingContact(rL,touchingTypes)
    RouteLayer *rL;
    TileTypeBitMask touchingTypes;
{
    RouteContact *rC;

    for(rC=irRouteContacts; rC!=NULL; rC=rC->rc_next)
    {
	if(TTMaskHasType(&touchingTypes,rC->rc_routeType.rt_tileType) &&
	        (rC->rc_rLayer1==rL || rC->rc_rLayer2==rL))
	    return(TRUE);
    }

    return(FALSE);
}


/*
 * ----------------------------------------------------------------------------
 *
 * irChooseEndPtLayers --
 *
 * Figure out what layers are ok at an endpoint - if ambiguous ask user.
 *
 * Results:
 *	List of ok layers at endpoint.
 *
 * Side effects:
 *	May query user about intended layers at an endpoint.
 *
 * ----------------------------------------------------------------------------
 */

List *
irChooseEndPtLayers(routeUse,expansionMask,endPt,argLayers,endPtName)
    CellUse *routeUse;
    int expansionMask; /* mask of expanded subcells */
    Point *endPt;
    List *argLayers;
    char *endPtName;
{
    List *activeLayers;
    List *presentLayers;
    List *presentContacts;
    List *presentContactLayers;

    static char *actionNames[] = { "no", "yes", 0 };

    List *l;
    RouteLayer *rL;

    if (DebugIsSet(irDebugID, irDebEndPts))
    {
	TxPrintf("----- argLayers:\n");
	MZPrintRLListNames(argLayers);
    }

    /* find activeLayers among argLayers (or irRouteLayers if argLayers NULL) */
    activeLayers = NULL;
    if(argLayers)
    {
	/* layers given in arg, search these */
	for(l=argLayers; l!=NULL; l=LIST_TAIL(l))
	{
	    rL = (RouteLayer*) LIST_FIRST(l);
	    if(rL->rl_routeType.rt_active)
	    {
		LIST_ADD(rL,activeLayers);
	    }
	}
    }
    else
    {
	/* no layers given as arg, so search all route layers */
        for(rL=irRouteLayers; rL!=NULL; rL=rL->rl_next)
	    if(rL->rl_routeType.rt_active)
	    {
		LIST_ADD(rL,activeLayers);
	    }
    }

    if (DebugIsSet(irDebugID, irDebEndPts))
    {
	TxPrintf("----- activeLayers:\n");
	MZPrintRLListNames(activeLayers);
    }

    /* make lists of contacts (connecting two active layers) and
     * active layers which are present at the end point.
     */
    {
	TileTypeBitMask touchingTypes;
	RouteContact *rC;
	
	touchingTypes = TouchingTypes(routeUse, expansionMask, endPt);

        /* Make list of present and active contacts */
	presentContacts = NULL;
	presentContactLayers = NULL;
	for(rC =irRouteContacts; rC!=NULL; rC=rC->rc_next)
	{
	    if(TTMaskHasType(&touchingTypes,rC->rc_routeType.rt_tileType) &&
		    ListContainsP(rC->rc_rLayer1, activeLayers) &&
		    ListContainsP(rC->rc_rLayer2, activeLayers))
	    {
		LIST_ADD(rC, presentContacts);
		LIST_ADD(rC->rc_rLayer1, presentContactLayers);
		LIST_ADD(rC->rc_rLayer2, presentContactLayers);
	    }
	}

	if (DebugIsSet(irDebugID, irDebEndPts))
	{
	    TxPrintf("----- presentContacts:\n");
	    MZPrintRCListNames(presentContacts);

	    TxPrintf("----- presentContactLayers:\n");
	    MZPrintRLListNames(presentContactLayers);
	}

	    
	/* make list of present layers that are not constituents of contacts
	 * above.  If a contact is touching the endpt but one of 
	 * its constituent layers in not active, the other constituent
	 * layer is treated as a presentLayer.
	 */
	presentLayers = NULL;
	for(l=activeLayers; l!=NULL; l=LIST_TAIL(l))
	{
	    rL = (RouteLayer *) LIST_FIRST(l);
	    if((TTMaskHasType(&touchingTypes,rL->rl_routeType.rt_tileType) ||
		    LayerInTouchingContact(rL,touchingTypes)) &&
		    !ListContainsP(rL, presentContactLayers))
	    {
		LIST_ADD(rL,presentLayers);
	    }
	}

	if (DebugIsSet(irDebugID, irDebEndPts))
	{
	    TxPrintf("----- presentLayers:\n");
	    MZPrintRLListNames(presentLayers);
	}
    }

    /* return appropriate layer list. */
    {
	int numContacts, numLayers;

	numContacts = ListLength(presentContacts);
	numLayers = ListLength(presentLayers);

	if(numLayers == 0 && numContacts == 0)
	{
	    /* No Layers present at endpt, return list of all active layers */
	    ListDealloc(presentLayers);
	    ListDealloc(presentContacts);
	    ListDealloc(presentContactLayers);

	    return(activeLayers);
	}
	else if(numLayers == 1 && numContacts == 0)
	{
	    /* Exactly one layer is both active and present, return list
	     * containing only this layer.
	     */
	    ListDealloc(activeLayers);
	    ListDealloc(presentContacts);
	    ListDealloc(presentContactLayers);

	    return(presentLayers); 
	}
	else if(numLayers == 0 && numContacts == 1)
	{
	    /* Just one active contact under endpoint, 
	     * return layers connecting to that contact.
	     */
	    List *l;
	    RouteContact *rC;

	    rC = (RouteContact *) LIST_FIRST(presentContacts);
	    l = (List *) NULL;
	    LIST_ADD(rC->rc_rLayer1,l);
	    LIST_ADD(rC->rc_rLayer2,l);

	    ListDealloc(activeLayers);
	    ListDealloc(presentLayers);
	    ListDealloc(presentContacts);
	    ListDealloc(presentContactLayers);

	    return(l);
	}
	else
	{
	    /* Multiple nodes active and present, ask user which one
	     * he wants to route to.
	     */
	    char answer[100];
	    RouteLayer *rL;
	    RouteLayer *pickedRL;
	    RouteContact *rC;
	    RouteContact *pickedRC;

	    TxPrintf("Multiple nodes present at %s point:",
		    endPtName);
	    for(l=presentContacts; l!=NULL; l=LIST_TAIL(l))
	    {
		rC=(RouteContact *) LIST_FIRST(l);
		TxPrintf("  %s", 
			DBTypeLongNameTbl[rC->rc_routeType.rt_tileType]);
	    }
	    for(l=presentLayers; l!=NULL; l=LIST_TAIL(l))
	    {
		rL=(RouteLayer *) LIST_FIRST(l);
		TxPrintf("  %s", 
			DBTypeLongNameTbl[rL->rl_routeType.rt_tileType]);
	    }
	    TxPrintf("\n");

	    for(pickedRC=FALSE,l=presentContacts; l && !pickedRC; l=LIST_TAIL(l))
	    {
		rC = (RouteContact *) LIST_FIRST(l);
		if (!LIST_TAIL(l) && !presentLayers)
		{
		    /* last choice, so take it */
		    pickedRC = rC;
		}
		else
		{
		    /* ask user */
		    TxPrintf("Connect to %s? [yes] ",
			    DBTypeLongNameTbl[rC->rc_routeType.rt_tileType]);
		    if (TxGetLine(answer, sizeof answer) == NULL || 
			    answer[0] == '\0')
			(void) strcpy(answer,"yes");

		    if(Lookup(answer, actionNames) == 1)
		    {
			/* Yes */
			pickedRC = rC;
		    }
		}
	    }

	    if(pickedRC)
	    {
		List *l;

		l=NULL;
		LIST_ADD(rC->rc_rLayer1,l);
		LIST_ADD(rC->rc_rLayer2,l);
		ListDealloc(activeLayers);
		ListDealloc(presentLayers);
		ListDealloc(presentContacts);
		ListDealloc(presentContactLayers);

		return(l);
	    }

	    for(pickedRL=NULL,l=presentLayers; l && !pickedRL; l=LIST_TAIL(l))
	    {
		rL = (RouteLayer *) LIST_FIRST(l);
	        if(!LIST_TAIL(l))
		{
		    /* Last choice so choose it automatically */
		    pickedRL=rL;
		}
		else
		{
		    /* Ask user */
		    TxPrintf("Connect to %s? [yes] ",
			    DBTypeLongNameTbl[rL->rl_routeType.rt_tileType]);
		    if (TxGetLine(answer, sizeof answer) == NULL || 
			    answer[0] == '\0')
			(void) strcpy(answer,"yes");

		    if(Lookup(answer, actionNames) == 1)
		    {
			/* Yes */
			pickedRL = rL;
		    }
		}
	    }

	    if(pickedRL)
	    {
		l=NULL;
		LIST_ADD(rL,l);
		ListDealloc(activeLayers);
		ListDealloc(presentLayers);
		ListDealloc(presentContacts);
		ListDealloc(presentContactLayers);

		return(l);
	    }

	    /* User didn't pick anything, return null list */
	    {
		ListDealloc(activeLayers);
		ListDealloc(presentLayers);
		ListDealloc(presentContacts);
		ListDealloc(presentContactLayers);

		return(NULL);
	    }

	}
    }
}
