/* DBWelement.c -
 *
 *
 *	This file provides a standard set of procedures for Magic
 *	commands to use to handle drawing of various elements on
 *	top of the layout.  Elements can be lines, rectangles,
 *	and text (and hopefully will be expanded to include polygons
 *	and arcs).  These operate very similarly to feedback regions,
 *	in that they are persistant until destroyed, and do not
 *	interact with the layout in any way.
 */

#include <stdio.h>

#ifdef MAGIC_WRAPPER
#include "magic/tclmagic.h"
#endif
#include "misc/magic.h"
#include "utils/geometry.h"
#include "tiles/tile.h"
#include "utils/hash.h"
#include "database/database.h"
#include "windows/windows.h"
#include "graphics/graphics.h"
#include "dbwind/dbwind.h"
#include "utils/utils.h"
#include "misc/styles.h"
#include "utils/malloc.h"
#include "signals/signals.h"

/* Types of elements */

#define ELEMENT_RECT	0
#define ELEMENT_LINE	1
#define ELEMENT_TEXT	2

/* Linked list definition for styles */

typedef struct _style *styleptr;

typedef struct _style
{
    int style;
    styleptr next;
} stylestruct;
    
/* Each element is stored in a record that looks like this: */

typedef struct _element
{
    int type;			/* Type of element (see list above) */
    CellDef *rootDef;		/* Root definition of windows in which to
				 * display this element.
				 */
    stylestruct *stylelist;	/* Linked list of display styles with which
				 * to paint this element.  There must be at
				 * least one style, so first one is hardwired.
				 */
    Rect area;			/* The area of a rectangle element,
				 * or the two points of a line element,
				 * or the area box of a text element.
				 */
    char *text;			/* The text of a text element, or NULL. */

} DBWElement;

/* The following stuff describes all the feedback information we know
 * about.  The feedback is stored in a big array that grows whenever
 * necessary.
 */

HashTable elementTable;			/* Hash table holding elements info */

static CellDef *dbwelemRootDef;		/* To pass root cell definition from
					 * dbwelemGetTransform back up to
					 * DBWElementAdd.
					 */


/*
 * ----------------------------------------------------------------------------
 *
 * DBWElementRedraw --
 *
 * 	This procedure is called by the highlight manager to redisplay
 *	feedback highlights.  The window is locked before entry.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Any feedback information that overlaps a non-space tile in
 *	plane is redrawn.
 *
 * Tricky stuff:
 *	Redisplay is numerically difficult, particularly when feedbacks
 *	have a large internal scale factor:  the tendency is to get
 *	integer overflow and get everything goofed up.  Be careful
 *	when making changes to the code below.
 *
 * ----------------------------------------------------------------------------
 */

Void
DBWElementRedraw(window, plane)
    MagWindow *window;		/* Window in which to redraw. */
    Plane *plane;		/* Non-space tiles on this plane mark what
				 * needs to be redrawn.
				 */
{
    int curStyle, newStyle;
    styleptr stylePtr;
    CellDef *windowRoot;
    Rect screenArea;
    DBWElement *elem;
    HashSearch hs;
    HashEntry *entry;
    Point p;

    windowRoot = ((CellUse *) (window->w_surfaceID))->cu_def;
    curStyle = -1;

    HashStartSearch(&hs);
    while ((entry = HashNext(&elementTable, &hs)) != NULL)
    {
	elem = (DBWElement *) HashGetValue(entry);
	if (!elem) continue;
	else if (elem->rootDef != windowRoot) continue;

	/* Transform the feedback area to screen coords.  This is
	 * very similar to the code in WindSurfaceToScreen, except
	 * that there's additional scaling involved.
	 */

	WindSurfaceToScreen(window, &elem->area, &screenArea);
	if ((screenArea.r_xbot <= screenArea.r_xtop) &&
		(screenArea.r_ybot <= screenArea.r_ytop))
	{
	    for (stylePtr = elem->stylelist; stylePtr != NULL;
			stylePtr = stylePtr->next)
	    {
		newStyle = stylePtr->style;
		if (newStyle != curStyle)
		{
		    curStyle = newStyle;
		    GrSetStuff(curStyle);
		}
		switch (elem->type)
		{
		    case ELEMENT_RECT:
			GrFastBox(&screenArea, NULL);
			break;
		    case ELEMENT_LINE:
			GrClipLine(screenArea.r_xbot, screenArea.r_ybot,
				screenArea.r_xtop, screenArea.r_ytop);
			break;
		    case ELEMENT_TEXT:
			p.p_x = screenArea.r_xbot;
			p.p_y = screenArea.r_ybot;
			GrPutText(elem->text, curStyle, &p,
				GEO_CENTER, GR_TEXT_MEDIUM, FALSE,
				&(window->w_screenArea), (Rect *)NULL);
			break;
		}
	    }
	}
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * DBWElementUndraw --
 *
 *	Paint the element in style ERASE_ALL to erase it.
 *
 * ----------------------------------------------------------------------------
 */

Void
dbwElementUndraw(mw, elem)
    MagWindow *mw;
    DBWElement *elem;		/* The element to erase */
{
    CellDef *windowRoot;
    Rect screenArea, textArea;

    windowRoot = ((CellUse *) (mw->w_surfaceID))->cu_def;
  
    GrLock(mw, TRUE);

    WindSurfaceToScreen(mw, &elem->area, &screenArea);

    /* For text, determine the size of the text and expand the	*/
    /* screenArea rectangle accordingly.			*/

    if (elem->type == ELEMENT_TEXT)
    {
	GrLabelSize(elem->text, GEO_CENTER, GR_TEXT_MEDIUM, &textArea);
	screenArea.r_xbot += textArea.r_xbot;
	screenArea.r_ybot += textArea.r_ybot;
	screenArea.r_xtop += textArea.r_xtop;
	screenArea.r_ytop += textArea.r_ytop;
    }

    if ((screenArea.r_xbot <= screenArea.r_xtop) &&
		(screenArea.r_ybot <= screenArea.r_ytop))
    {
	GrSetStuff(STYLE_ERASEALL);
	GrFastBox(&screenArea, NULL);
        WindAreaChanged(mw, &screenArea);
    }
    GrUnlock(mw, TRUE);
}


/*
 * ----------------------------------------------------------------------------
 *
 * DBWElementDelete --
 *
 * 	This procedure deletes an element.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	An element is found in the hash table by name, and if it exists,
 *	it is deleted.
 *
 * ----------------------------------------------------------------------------
 */

void
DBWElementDelete(MagWindow *w, char *name)
{
    DBWElement *elem;
    CellDef *currentRoot;
    HashEntry *entry;
    styleptr stylePtr;

    entry = HashFind(&elementTable, name);

    if (entry == NULL) return;

    elem = (DBWElement *)HashGetValue(entry);

    if (elem == NULL) return;

    dbwElementUndraw(w, elem);

    for (stylePtr = elem->stylelist; stylePtr != NULL; stylePtr = stylePtr->next)
    {
	freeMagic(stylePtr);
    }
    if (elem->type == ELEMENT_TEXT)
	freeMagic(elem->text);

    HashSetValue(entry, NULL);

    /* Area of elem->area is set to be updated by dbwElementUndraw().	*/
    /* Can't do WindUpdate(), though, until the hash table entry is	*/
    /* removed, or DBWdisplay will try to draw it again.		*/

    WindUpdate();
}

/*
 * Initialize the Element hash table.
 */

void
DBWElementInit()
{
    HashInit(&elementTable, 10, HT_STRINGKEYS);
    DBWHLAddClient(DBWElementRedraw);
}

/*
 * ----------------------------------------------------------------------------
 *
 * DBWElementNames --
 *
 *	Go through the hash table and print the names of all elements
 *
 * ----------------------------------------------------------------------------
 */

void
DBWElementNames()
{
    DBWElement *elem;
    HashSearch hs;
    HashEntry *he;

#ifndef MAGIC_WRAPPER
    TxPrintf(stdout, "Known elements:");
#endif

    HashStartSearch(&hs);
    while (he = HashNext(&elementTable, &hs))
    {
	if (elem = (DBWElement *)HashGetValue(he))
	{
#ifdef MAGIC_WRAPPER
	    Tcl_AppendElement(magicinterp, he->h_key.h_name);
#else
	    TxPrintf(stdout, " %s", he->h_key.h_name);
#endif
	}
    }

#ifndef MAGIC_WRAPPER
    TxPrintf(stdout, "/n");
#endif

}

/*
 * ----------------------------------------------------------------------------
 *
 * DBWElementInbox --
 *
 *	Find the element that is nearest the box.
 *
 * ----------------------------------------------------------------------------
 */

void
DBWElementInbox(area)
    Rect *area;
{
    DBWElement *elem;
    HashSearch hs;
    HashEntry *he;
    int sqdist;

#ifndef MAGIC_WRAPPER
    TxPrintf(stdout, "Element(s) inside box: ");
#endif

    HashStartSearch(&hs);
    while (he = HashNext(&elementTable, &hs))
    {
	if (elem = (DBWElement *)HashGetValue(he))
	{
	    if (GEO_SURROUND(area, &elem->area))
	    {
#ifdef MAGIC_WRAPPER
		Tcl_AppendElement(magicinterp, he->h_key.h_name);
#else
		TxPrintf(stdout, " %s", he->h_key.h_name);
#endif
	    }
	}
    }

#ifndef MAGIC_WRAPPER
    TxPrintf(stdout, "/n");
#endif
}


/*
 * ----------------------------------------------------------------------------
 *
 * DBWElementAdd* --
 *
 * 	Adds a new element to the element hash table.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	CellDef's ancestors are searched until its first root definition
 *	is found, and the coordinates of area are transformed into the
 *	root.  Then the area is added to the element structure.
 *	This stuff will be displayed on the screen at the end of the
 *	current command.
 * ----------------------------------------------------------------------------
 */

/* Set up everything is generic to all element types */

DBWElement *
DBWElementAdd(w, name, area, cellDef, style)
    MagWindow *w;
    char *name;			/* Name of this element for the hash table */
    Rect *area;			/* The area of the element */
    CellDef *cellDef;		/* The cellDef in whose coordinates area
				 * is given.
				 */
    int style;			/* An initial display style to use */
{
    Transform transform;
    DBWElement *elem;
    HashEntry *entry;
    extern int dbwelemGetTransform();	/* Forward declaration. */

    /* Find a transform from this cell to the root, and use it to
     * transform the area.  If the root isn't an ancestor, just
     * return.
     */
    
    if (!DBSrRoots(cellDef, &GeoIdentityTransform,
	dbwelemGetTransform, (ClientData) &transform)) return;

    /* SigInterruptPending screws up DBSrRoots */
    if (SigInterruptPending)
	return NULL;
  
    /* If there is already an entry by this name, delete it */
    DBWElementDelete(w, name);

    entry = HashFind(&elementTable, name);
    elem = (DBWElement *)mallocMagic(sizeof(DBWElement));
    HashSetValue(entry, elem);

    elem->area = *area;
    elem->stylelist = (styleptr)mallocMagic(sizeof(stylestruct));
    elem->stylelist->style = style;
    elem->stylelist->next = NULL;
    elem->rootDef = dbwelemRootDef;
    elem->text = NULL;

    return elem;
}

void
DBWElementAddRect(w, name, area, cellDef, style)
    MagWindow *w;
    char *name;			/* Name of this element for the hash table */
    Rect *area;			/* The area to be highlighted. */
    CellDef *cellDef;		/* The cellDef in whose coordinates area
				 * is given.
				 */
    int style;			/* An initial display style to use */
{
    DBWElement *elem;

    elem = DBWElementAdd(w, name, area, cellDef, style);
    elem->type = ELEMENT_RECT;
    elem->text = NULL;
}

void
DBWElementAddLine(w, name, area, cellDef, style)
    MagWindow *w;
    char *name;			/* Name of this element for the hash table */
    Rect *area;			/* The area to be highlighted. */
    CellDef *cellDef;		/* The cellDef in whose coordinates area
				 * is given.
				 */
    int style;			/* An initial display style to use */
{
    DBWElement *elem;

    elem = DBWElementAdd(w, name, area, cellDef, style);
    elem->type = ELEMENT_LINE;
    elem->text = NULL;
}

void
DBWElementAddText(w, name, x, y, text, cellDef, style)
    MagWindow *w;
    char *name;			/* Name of this element for the hash table */
    int x, y;			/* Point of origin (x, y coordinates) */
    char *text;			/* The text of the label */
    CellDef *cellDef;		/* The cellDef in whose coordinates area
				 * is given.
				 */
    int style;			/* An initial display style to use */
{
    DBWElement *elem;
    Rect area;
 
    area.r_xbot = x;
    area.r_xtop = x;
    area.r_ybot = y;
    area.r_ytop = y;

    elem = DBWElementAdd(w, name, &area, cellDef, style);
    elem->type = ELEMENT_TEXT;
    elem->text = StrDup(NULL, text);
}

/* This utility procedure is invoked by DBSrRoots.  Save the root definition
 * in dbwRootDef, save the transform in the argument, and abort the search.
 * Make sure that the root we pick is actually displayed in a window
 * someplace (there could be root cells that are no longer displayed
 * anywhere).
 */

int
dbwelemGetTransform(use, transform, cdarg)
    CellUse *use;			/* A root use that is an ancestor
					 * of cellDef in DBWElementAdd.
					 */
    Transform *transform;		/* Transform up from cellDef to use. */
    Transform *cdarg;			/* Place to store transform from
					 * cellDef to its root def.
					 */
{
    extern int dbwElementAlways1();
    if (use->cu_def->cd_flags & CDINTERNAL) return 0;
    if (!WindSearch((ClientData) DBWclientID, (ClientData) use,
	    (Rect *) NULL, dbwElementAlways1, (ClientData) NULL)) return 0;
    if (SigInterruptPending)
	return 0;
    dbwelemRootDef = use->cu_def;
    *cdarg = *transform;
    return 1;
}

int
dbwElementAlways1()
{
    return 1;
}

/*
 * ----------------------------------------------------------------------------
 *
 * DBWElementText --
 *
 * 	Configures text of a text element
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Text element's text string is reallocated, or string is printed.
 *	If altered, the element is erased and redrawn.
 *
 * ----------------------------------------------------------------------------
 */

void
DBWElementText(MagWindow *w, char *ename, char *text)
{
    DBWElement *elem;
    HashEntry *entry;

    entry = HashFind(&elementTable, ename);
    if (entry == NULL) 
    {
	TxError("No such element %s\n", ename);
	return;
    }	

    elem = (DBWElement *)HashGetValue(entry);
    if (elem == NULL) return;

    if (elem->type != ELEMENT_TEXT)
    {
	TxError("Element %s is not a text element\n", ename);
	return;
    }

    if (text == NULL)	/* get text */
    {
#ifdef MAGIC_WRAPPER
	Tcl_SetResult(magicinterp, elem->text, NULL);
#else
	TxPrintf("%s\n", elem->text);
#endif
    }
    else  /* replace text */
    {
	dbwElementUndraw(w, elem);
	freeMagic(elem->text);
	elem->text = StrDup(NULL, text);
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * DBWElementStyle --
 *
 * 	Configures style of any element
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Element's style list is reallocated, or printed.
 *	If altered, the element is erased and redrawn.
 *
 * ----------------------------------------------------------------------------
 */

void
DBWElementStyle(MagWindow *w, char *ename, int style, bool add)
{
    DBWElement *elem;
    HashEntry *entry;
    styleptr sptr, newstyle;

    entry = HashFind(&elementTable, ename);
    if (entry == NULL)
    {
	TxError("No such element %s\n", ename);
	return;
    }	

    elem = (DBWElement *)HashGetValue(entry);
    if (elem == NULL) return;

    if (style== -1)	/* get style(s) */
    {
#ifdef MAGIC_WRAPPER
	for (sptr = elem->stylelist; sptr != NULL; sptr = sptr->next)
	{
	    Tcl_AppendElement(magicinterp, GrStyleTable[sptr->style].longname);
	}
#else
	for (sptr = elem->stylelist; sptr != NULL; sptr = sptr->next)
	{
	    TxPrintf("%s ", GrStyleTable[sptr->style].longname);
	}
	TxPrintf("\n");
#endif
    }
    else
    {
	dbwElementUndraw(w, elem);
	if (add == TRUE)
	{
	    /* add style */
	    for (sptr = elem->stylelist; sptr != NULL && sptr->next != NULL;
			sptr = sptr->next);
	    
	    newstyle = (styleptr)mallocMagic(sizeof(stylestruct));
	    newstyle->style = style;
	    newstyle->next = NULL;
	    if (sptr == NULL)
		elem->stylelist = newstyle;
	    else
		sptr->next = newstyle;
	}
	else
	{
	    /* find style in list */
	    for (sptr = elem->stylelist; sptr != NULL; sptr = sptr->next)
	    {
		if (sptr->next != NULL)
		    if (sptr->next->style == style)
			break;
	    }

	    /* delete style (if it is in the list) */

	    if ((sptr == NULL) && (elem->stylelist != NULL) &&
			(elem->stylelist->style == style))
	    {
		dbwElementUndraw(w, elem);
		freeMagic(elem->stylelist);
		elem->stylelist = elem->stylelist->next;
		if (elem->stylelist == NULL)
		    TxPrintf("Warning:  Element %s has no styles!\n", ename);
	    }
	    else if (sptr == NULL)
	    {
		TxError("Style %d is not in the style list for element %s\n",
			style, ename);
	    }
	    else if (sptr->next != NULL)
	    {
		dbwElementUndraw(w, elem);
		freeMagic(sptr->next);
		sptr->next = sptr->next->next;
	    }
	}
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * DBWElementPos --
 *
 * 	Configures position of a rect or line element
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Rect or line element's rect structure is altered, or else the
 *	position is printed.  If altered, the element is erased and redrawn.
 *
 * ----------------------------------------------------------------------------
 */

void
DBWElementPos(MagWindow *w, char *ename, Rect *crect)
{
    DBWElement *elem;
    HashEntry *entry;
    Rect prect;
    char ptemp[22];

    entry = HashFind(&elementTable, ename);
    if (entry == NULL)
    {
	TxError("No such element %s\n", ename);
	return;
    }	

    elem = (DBWElement *)HashGetValue(entry);
    if (elem == NULL) return;

    if (crect == NULL)	/* Get position */
    {
#ifdef MAGIC_WRAPPER
	snprintf(ptemp, 20, "%d", elem->area.r_xbot);
	Tcl_AppendElement(magicinterp, ptemp);
	snprintf(ptemp, 20, "%d", elem->area.r_ybot);
	Tcl_AppendElement(magicinterp, ptemp);
	if (elem->type == ELEMENT_RECT || elem->type == ELEMENT_LINE)
	{
	    snprintf(ptemp, 20, "%d", elem->area.r_xtop);
	    Tcl_AppendElement(magicinterp, ptemp);
	    snprintf(ptemp, 20, "%d", elem->area.r_ytop);
	    Tcl_AppendElement(magicinterp, ptemp);
	}
#else
	TxPrintf("(%d, %d)", elem->area.r_xbot, elem->area.r_ybot);

	if (elem->type == ELEMENT_RECT || elem->type == ELEMENT_LINE)
	    TxPrintf(" to (%d, %d)", elem->area.r_xtop, elem->area.r_ytop);

	TxPrintf("\n");
#endif
    }
    else		/* Change position */
    {
	dbwElementUndraw(w, elem);
	elem->area = *crect;
    }
}
