/*
 * mzStart.c --
 *
 * Code for making initial legs of route within the blocked areas ajacent to
 * start areas.
 *
 *     *********************************************************************
 *     * Copyright (C) 1988, 1990 Michael H. Arnold 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:";
#endif  /* not lint */

#include <stdio.h>

#include "utils/magic.h"
#include "utils/geometry.h"
#include "utils/geofast.h"
#include "tiles/tile.h"
#include "utils/hash.h"
#include "utils/heap.h"
#include "database/database.h"
#include "utils/signals.h"
#include "textio/textio.h"
#include "utils/list.h"
#include "debug/debug.h"
#include "mzrouter/mzrouter.h"
#include "mzrouter/mzInternal.h"

/* Forward declarations */

extern bool mzExtendInitPath(RoutePath *, RouteLayer *, Point, dlong, int, int);

extern bool mzAddInitialContacts();

/*
 * ----------------------------------------------------------------------------
 *
 * Simple search function for start tiles.  This function is called only if
 * a tile of type TT_SAMENODE is found in the search area.
 *
 * Results:
 *	Always return 1 (break on the first acceptable tile found)
 *
 * Side effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */

int
mzFindSamenodeFunc(Tile *tile, TileType dinfo, Point *point)
{
    *point = tile->ti_ll;
    return 1;
}


/*
 * ----------------------------------------------------------------------------
 *
 * mzStart --
 *
 * Establish initial path segments from start term, considering inital
 * contacts and leading out of any SAMENODE blocks
 * present at start point.
 *
 * Results:
 *      TRUE normally, FALSE if mzExtendInitPath discovered that the
 *	start node is already connected to the destination node.
 *
 * Side effects:
 *	mzAddPoint() called to add paths to appropriate queue.
 *
 * ----------------------------------------------------------------------------
 */

#define EC_ALL_DIRECTIONS  (EC_RIGHT | EC_LEFT | EC_UP | EC_DOWN)

bool
mzStart(term)
    ColoredRect *term;
{
    RouteLayer *rL;
    RouteContact *rC;
    Tile *tp;
    bool returnCode = TRUE;
    Point point;
    int result;
    Rect srect;

    /* Find routelayer corresponding to type */
    for(rL = mzActiveRLs; rL != NULL; rL = rL->rl_nextActive)
    {
	if(rL->rl_routeType.rt_tileType == term->cr_type) break;
    }

    /* Expand area to bottom and left to make sure we can find	*/
    /* SAMENODE tiles to the bottom and left of the terminal.	*/

    srect = term->cr_rect;
    srect.r_xbot--;
    srect.r_ybot--;

    /* Added by Tim 8/2/06---for start terminals on contact layers, */
    /* run mzExtendInitPath for layer1 and set rL to layer2.	    */

    if ((rL == NULL) && DBIsContact(term->cr_type))
    {
	for (rC = mzRouteContacts ; rC != NULL; rC = rC->rc_next)
	{
	    if (!(rC->rc_routeType.rt_active)) continue;

	    if (TTMaskHasType(&(DBConnectTbl[term->cr_type]),
			rC->rc_rLayer1->rl_routeType.rt_tileType) &&
			TTMaskHasType(&(DBConnectTbl[term->cr_type]),
			rC->rc_rLayer2->rl_routeType.rt_tileType))
	    {
		/* Search block plane for first unblocked tile	*/
		result = DBSrPaintArea((Tile *)NULL,
			rC->rc_rLayer1->rl_routeType.rt_hBlock,
			&srect, &mzStartTypesMask,
			mzFindSamenodeFunc, &point);

		if (result == 1)
		{
		    returnCode = mzExtendInitPath(NULL, rC->rc_rLayer1,
				point, (dlong)0, 0, EC_ALL_DIRECTIONS);
		    rL = rC->rc_rLayer2;
		    break;
		}
	    }
	}
    }

    /* If no corresponding route layer, check for layers that connect */

    if (rL == NULL)
    {
	for (rL = mzActiveRLs; rL != NULL; rL = rL->rl_nextActive)
	{
	    if (TTMaskHasType(&(DBConnectTbl[term->cr_type]),
                                   rL->rl_routeType.rt_tileType))
		break;
	}
    }

    /* If no corresponding route layer, return. 			  */
    /* This does not need a warning.  The search for connected tiles adds */
    /* tile types that may not correspond to an active route layer.  Just */
    /* ignore such tiles.						  */

    if (rL == NULL)
	return returnCode;

    /* Find a valid start point in this terminal */

    result = DBSrPaintArea((Tile *)NULL, rL->rl_routeType.rt_hBlock,
		&srect, &mzStartTypesMask, mzFindSamenodeFunc, &point);

    if (result == 1)
    {
	/* call mzExtendInitPath to do the real work */

	returnCode = mzExtendInitPath(NULL, 	/* path so far */
			 rL, 			/* layer of new point */
			 point, 		/* start point */
			 (dlong)0, 		/* cost of new segment */
			 0, 			/* length of path so far */
			 EC_ALL_DIRECTIONS);	/* how to extend init path */
    }
    return returnCode;
}


/*
 * ----------------------------------------------------------------------------
 *
 * mzExtendInitPath --
 *
 * Central routine for recursively building up initial path inside
 * SAMENODE.  Adds specified point to an initial path, if resulting
 * path end is outside block, mzAddPoint() is called to add init path
 * to appropriate queue.
 *
 * Results:
 *      TRUE normally, FALSE if a zero-length route was detected (start
 *	node and destination node are already connected).
 *
 * Side effects:
 *      Initial paths built up.
 *	mzAddPoint() eventually called to add init paths to appropriate queue.
 *
 * ----------------------------------------------------------------------------
 */
bool
mzExtendInitPath(path, rL, point, cost, length, directions)
    RoutePath *path;	/* Initial Path, being extended */
    RouteLayer *rL;     /* routelayer of new point */
    Point point;	/* new point for  initPath */
    dlong cost;		/* cost of new segment */
    int length;		/* length of path (excluding new segment) */
    int directions;	/* directions to extend init path in */
{
    Tile *tp;
    bool returnCode = TRUE;
    int orient;
    int extendCode = 0;

    /* Get tile in rL blockage plane under new point */

    tp = TiSrPointNoHint(rL->rl_routeType.rt_hBlock, &point);

    /* If new point blocked by a different node, just return */

    if (TiGetType(tp) == TT_BLOCKED)
	return returnCode;

    /* Consider initial contacts */

    if (path == NULL)
	returnCode = mzAddInitialContacts(rL, point);

    /* If no SAMENODE block, call mzAddPoint() to and initial path to
     * appropriate queue.
     */

    switch (TiGetType(tp))
    {
	/* Use standard extend code on TT_SAMENODE areas.	*/
	/* (Tim, 10/4/06)					*/

	case TT_SAMENODE:
	case TT_SPACE:
	    extendCode = EC_ALL_DIRECTIONS | EC_UDCONTACTS | EC_LRCONTACTS;
	    break;

	case TT_LEFT_WALK:
	    extendCode = EC_WALKRIGHT;
	    break;

	case TT_RIGHT_WALK:
	    extendCode = EC_WALKLEFT;
	    break;

	case TT_TOP_WALK:
	    extendCode = EC_WALKDOWN;
	    break;

	case TT_BOTTOM_WALK:
	    extendCode = EC_WALKUP;
	    break;

	case TT_ABOVE_LR_WALK:
	case TT_BELOW_LR_WALK:
	    extendCode = EC_WALKLRCONTACT;
	    break;

	case TT_ABOVE_UD_WALK:
	case TT_BELOW_UD_WALK:
	    extendCode = EC_WALKUDCONTACT;
	    break;

	case TT_DEST_AREA:
	    TxError("Zero length route!\n");
	    extendCode = EC_COMPLETE;
	    returnCode = FALSE;
	    break;
    }

    if (extendCode == 0) return FALSE;	/* This shouldn't happen */

    /* determine orientation of new segment */

    if (path == NULL)
	orient = 'O';
    else if (path->rp_rLayer != rL)
    {
	if (path->rp_entry.p_x == point.p_x)
	    orient = 'X';
	else
	    orient = 'O';
    }
    else if (path->rp_entry.p_x == point.p_x)
 	orient = 'V';
    else
    {
	ASSERT(path->rp_entry.p_y==point.p_y,"mzExtendInitPath");
	orient = 'H';
    }

    /* Add initial path to appropriate queue */
    mzAddPoint(path, &point, rL, orient, extendCode, &cost);

    return returnCode;
}

/*
 * ----------------------------------------------------------------------------
 *
 * mzAddInitialContacts --
 *
 *
 * Results:
 *      TRUE normally, FALSE if zero-length route
 *
 * Side effects:
 *	Calls mzExtendInitPath to add contact to initial path.
 *
 * ----------------------------------------------------------------------------
 */

bool
mzAddInitialContacts(rL, point)
    RouteLayer *rL;     /* routelayer of initial point */
    Point point;	/* initial point */
{
    List *cL;
    Tile *tp;
    RouteContact *rC;
    RouteLayer *newRLayer;
    dlong conCost;
    RoutePath *initPath;
    bool returnCode = TRUE;

    /* Loop through contacts that connect to current rLayer */
    for (cL=rL->rl_contactL; cL!=NULL; cL=LIST_TAIL(cL))
    {
	rC = (RouteContact *) LIST_FIRST(cL);

	/* Don't use inactive contacts */
	if (!(rC->rc_routeType.rt_active)) continue;

	/* Get "other" route Layer contact connects to */
	if (rC->rc_rLayer1 == rL)
	{
	    newRLayer = rC->rc_rLayer2;
	}
	else
	{
	    ASSERT(rC->rc_rLayer2 == rL,"mzStart");
	    newRLayer = rC->rc_rLayer1;
	}

	/* Don't spread to inactive layers */
	if (!(newRLayer->rl_routeType.rt_active)) continue;

	/* Don't place contact if blocked */

	tp = TiSrPointNoHint(rC->rc_routeType.rt_hBlock, &point);
	if (TiGetType(tp) == TT_SAMENODE)
	{
	    /* Check if the size of the tile meets min length requirement */
	    if (RIGHT(tp) - point.p_x <= rC->rc_routeType.rt_length
			- rC->rc_routeType.rt_width)
	    {

		/* compute cost of contact */

		conCost = (dlong) rC->rc_cost;

		/* build path consisting of initial point */

		initPath = NEWPATH();
		initPath->rp_rLayer = rL;
		initPath->rp_entry = point;
		initPath->rp_orient = 'O';
		initPath->rp_cost = 0;
		initPath->rp_back = NULL;

		/* Extend thru new point */
		returnCode = mzExtendInitPath(initPath, newRLayer, point,
			conCost, 0, EC_ALL_DIRECTIONS);
	    }
	}

	/* Check vertical planes for contact points, too */
	tp = TiSrPointNoHint(rC->rc_routeType.rt_vBlock, &point);
	if (TiGetType(tp) != TT_SAMENODE)
	    continue;

	/* Check if the size of the tile meets min length requirement */
	if (TOP(tp) - point.p_y <= rC->rc_routeType.rt_length
			- rC->rc_routeType.rt_width)
	    continue;

	/* compute cost of contact */

	conCost = (dlong) rC->rc_cost;

	/* build path consisting of initial point */

	initPath = NEWPATH();
	initPath->rp_rLayer = rL;
	initPath->rp_entry = point;
	initPath->rp_orient = 'X';
	initPath->rp_cost = 0;
	initPath->rp_back = NULL;

	/* Extend thru new point */
	returnCode = mzExtendInitPath(initPath, newRLayer, point, conCost,
		0, EC_ALL_DIRECTIONS);
    }
    return returnCode;
}

