/*
 * DBexpand.c --
 *
 * Expansion and unexpansion of cells
 *
 *     *********************************************************************
 *     * 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/DBexpand.c,v 1.3 2010/08/15 14:35:47 tim Exp $";
#endif  /* not lint */

#include <stdio.h>
#include "utils/magic.h"
#include "utils/geometry.h"
#include "tiles/tile.h"
#include "utils/hash.h"
#include "database/database.h"
#include "database/databaseInt.h"
#include "textio/textio.h"
#include "utils/utils.h"
#include "utils/stack.h"

    /*
     * Argument passed down to search functions when searching for
     * cells to expand or unexpand.
     */
struct expandArg
{
    bool	ea_deref;	/* TRUE if root def dereference flag is set */
    int		ea_xmask;	/* Expand mask. */
    int		ea_type;	/* Expand, unexpand, or toggle */
    int		(*ea_func)();	/* Function to call for each cell whose
				 * status is changed.
				 */
    ClientData	ea_arg;		/* Argument to pass to func. */
};

/*
 * ----------------------------------------------------------------------------
 *
 * DBExpand --
 *
 * Expand/unexpand a single CellUse.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	If expandFlag is TRUE, sets all the bits of expandMask in
 *	the flags of the given cellUse expandFlag is FALSE, clears
 *	all bits of expandMask.
 *
 *	If expandFlag is TRUE and the cell being expanded has not
 *	been read in, reads it in from disk.
 *
 * ----------------------------------------------------------------------------
 */

void
DBExpand(
    CellUse *cellUse,
    int expandMask,
    int expandType)
{
    CellDef *def;
    bool expandFlag, expandTest;
 
    expandTest = DBDescendSubcell(cellUse, expandMask);
    if ((expandType & DB_EXPAND_MASK) == DB_EXPAND_TOGGLE)
	expandFlag = expandTest;
    else
    {
	expandFlag = ((expandType & DB_EXPAND_MASK) == DB_EXPAND) ? TRUE : FALSE;
	if (expandFlag == expandTest) return;
    }

    if (expandFlag)
    {
	def = cellUse->cu_def;
	if ((def->cd_flags & CDAVAILABLE) == 0)
	{
	    if (!DBCellRead(def, TRUE, TRUE, NULL))
		return;
	    /* Note:  we don't have to recompute the bbox here, because
	     * if it changed, then a timestamp violation must have occurred
	     * and the timestamp manager will take care of recomputing
	     * the bbox.
	     */
	}
	cellUse->cu_expandMask |= expandMask;
    }
    else
	cellUse->cu_expandMask &= ~expandMask;
}

/*
 * ----------------------------------------------------------------------------
 *
 * DBExpandAll --
 *
 * Recursively expand/unexpand all cells which intersect or are
 * contained within the given rectangle.  Furthermore, if func is
 * non-NULL, it is invoked for each cell whose status has changed,
 * just after the change has been made.  The calling sequence is
 *
 *     int
 *     func(cellUse, cdarg)
 *	   CellUse *cellUse;
 *	   ClientData cdarg;
 *     {
 *     }
 *
 * In the calls to func, cellUse is the use whose expand bit has just
 * been changed, and cdarg is the argument that the caller gave to us.
 * Func should normally return 0.  If it returns a non-zero value, then
 * the call terminates immediately and no more cells are expanded.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	If expandFlag is TRUE, sets all the bits specified by
 *	expandMask in the flags of each CellUse found to intersect
 *	the given rectangle.  If expandFlag is FALSE, clears all bits
 *	of expandMask.
 * ----------------------------------------------------------------------------
 */

void
DBExpandAll(
    CellUse *rootUse,	/* Root cell use from which search begins */
    Rect *rootRect,	/* Area to be expanded, in root coordinates */
    int expandMask,	/* Window mask in which cell is to be expanded */
    int expandType,	/* DB_EXPAND, DB_UNEXPAND, DB_EXPAND_TOGGLE */
    int (*func)(),	/* Function to call for each cell whose expansion
			 * status is modified.  NULL means don't call anyone.
			 */
    ClientData cdarg)	/* Argument to pass to func. */
{
    int dbExpandFunc();
    SearchContext scontext;
    struct expandArg arg;

    if ((rootUse->cu_def->cd_flags & CDAVAILABLE) == 0)
	(void) DBCellRead(rootUse->cu_def, TRUE, TRUE, NULL);

    /*
     * Walk through the area and set the expansion state appropriately.
     */

    arg.ea_xmask = expandMask;
    arg.ea_func = func;
    arg.ea_arg = cdarg;
    arg.ea_type = expandType;
    arg.ea_deref = (rootUse->cu_def->cd_flags & CDDEREFERENCE) ? TRUE : FALSE;

    scontext.scx_use = rootUse;
    scontext.scx_trans = GeoIdentityTransform;
    scontext.scx_area = *rootRect;
    DBCellSrArea(&scontext, dbExpandFunc, (ClientData) &arg);
}

/*
 * dbExpandFunc --
 *
 * Filter function called by DBCellSrArea on behalf of DBExpandAll above
 * when cells are being expanded, unexpanded, or toggled.
 */

int
dbExpandFunc(
    SearchContext *scx,	/* Pointer to search context containing
					 * child use, search area in coor-
					 * dinates of the child use, and
					 * transform back to "root".
					 */
    struct expandArg *arg)	/* Client data from caller */
{
    CellUse *childUse = scx->scx_use;
    int n = DBLambda[1];
    int expandTest;
    int expandType = (arg->ea_type & DB_EXPAND_MASK);
    int expandSurround = (arg->ea_type & DB_EXPAND_SURROUND_MASK);
    bool surround;

    expandTest = DBDescendSubcell(childUse, arg->ea_xmask);

    /*
     * Change the expansion status of this cell if necessary.  Call the
     * client's function if the expansion status has changed.
     */

    if (!expandTest && ((expandType == DB_EXPAND) || (expandType == DB_EXPAND_TOGGLE)))
    {
	surround = (!GEO_SURROUND(&childUse->cu_def->cd_bbox, &scx->scx_area)
			|| GEO_SURROUND(&scx->scx_area, &childUse->cu_def->cd_bbox));
	if (surround || (expandSurround == DB_EXPAND_OVERLAP))
	{
	    /* If the cell is unavailable, then don't expand it.
	     */
	    if ((childUse->cu_def->cd_flags & CDAVAILABLE) == 0)
	    {
		/* If the parent is dereferenced, then the child should be, too */
		if (arg->ea_deref) childUse->cu_def->cd_flags |= CDDEREFERENCE;
		if (!DBCellRead(childUse->cu_def, TRUE, TRUE, NULL))
		{
		    TxError("Cell %s is unavailable.  It could not be expanded.\n",
				childUse->cu_def->cd_name);
		    return 2;
		}
	    }

	    childUse->cu_expandMask |= arg->ea_xmask;
	    expandTest = TRUE;
	    if (arg->ea_func != NULL)
	    {
		if ((*arg->ea_func)(childUse, arg->ea_arg) != 0) return 1;
	    }
	}
    }
    else if (expandTest && ((expandType == DB_UNEXPAND) ||
			(expandType == DB_EXPAND_TOGGLE)))
    {
	surround = (!GEO_SURROUND(&childUse->cu_def->cd_bbox, &scx->scx_area)
			|| GEO_SURROUND(&scx->scx_area, &childUse->cu_def->cd_bbox));
	if (surround || (expandSurround == DB_EXPAND_OVERLAP))
	{
	    childUse->cu_expandMask &= ~arg->ea_xmask;
	    expandTest = FALSE;

	    /* Call the client's function, if there is one. */

	    if (arg->ea_func != NULL)
	    {
		if ((*arg->ea_func)(childUse, arg->ea_arg) != 0) return 1;
	    }
	}
    }

    if (DBCellSrArea(scx, dbExpandFunc, (ClientData) arg))
	return 1;
    return 2;
}

/*
 * ----------------------------------------------------------------------------
 *
 * DBCellReadArea --
 *
 * Recursively read all cells which intersect or are contained within
 * the given rectangle.
 *
 * Results:
 *	If "halt_on_error" is TRUE, then return a pointer to the first
 *	subcell that could not be read.  Otherwise, return NULL.
 *
 * Side effects:
 *	May make new cells known to the database.  Sets the CDAVAILABLE
 *	bit in all cells intersecting the search area.
 *
 * ----------------------------------------------------------------------------
 */

CellDef *
DBCellReadArea(
    CellUse *rootUse,	/* Root cell use from which search begins */
    Rect *rootRect,	/* Area to be read, in root coordinates */
    bool halt_on_error)	/* If TRUE, failure to find a cell causes a halt */
{
    int dbReadAreaFunc();
    SearchContext scontext;
    CellDef *err_def = NULL;

    scontext.scx_use = rootUse;
    scontext.scx_trans = GeoIdentityTransform;
    scontext.scx_area = *rootRect;
    if (dbReadAreaFunc(&scontext, ((halt_on_error == TRUE) ? &err_def : NULL)) == 1)
	return err_def;

    return NULL;
}

int
dbReadAreaFunc(
    SearchContext *scx,	/* Pointer to context specifying
					 * the cell use to be read in, and
					 * an area to be recursively read in
					 * coordinates of the cell use's def.
					 */
    CellDef  **err_ptr)	/* If non-NULL, failure to find a cell causes a halt
			 * and the CellDef in error is returned in err_def.
			 * If NULL, failure to find a cell still causes a
			 * halt but no information is passed back to the
			 * calling routine.
			 */
{
    CellDef *def = scx->scx_use->cu_def;

    if ((def->cd_flags & CDAVAILABLE) == 0)
    {
	if (DBCellRead(def, TRUE, TRUE, NULL) == FALSE)
	{
	    if (err_ptr != NULL) {
		*err_ptr = def;
		return 1;
	    }
	    else
		return 0;
	}

	/* Note: we don't have to invoke DBReComputeBbox here because
	 * if the bbox changed then there was a timestamp mismatch and
	 * the timestamp code will take care of the bounding box later.
	 */
    }

    if (DBCellSrArea(scx, dbReadAreaFunc, (ClientData)err_ptr))
	return 1;

    /* Be clever about handling arrays:  if the search area covers this
     * whole definition, then there's no need to look at any other
     * array elements, since we've already expanded the entire area
     * of the definition.
     */

    if (GEO_SURROUND(&scx->scx_area, &scx->scx_use->cu_def->cd_bbox))
	return 2;

    return 0;
}
