/* grMain.c -
 *
 *     *********************************************************************
 *     * 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.    *
 *     *********************************************************************
 *
 * This file contains a few core variables and routines for
 * manipulating color graphics displays.  Its main function is
 * to provide a central dispatch point to various routines for
 * different display types.
 */

#ifndef lint
static char rcsid[] __attribute__ ((unused)) = "$Header: /usr/cvsroot/magic-8.0/graphics/grMain.c,v 1.4 2010/06/24 12:37:18 tim Exp $";
#endif  /* not lint */

/*
 * The following display types are currently suported by Magic:
 *
 *	NULL		A null device for running Magic without using
 *			a graphics display.  This device does nothing
 *			when its routines are called.
 *
 *	X11		A port to the X11 window system, based on the Stanford
 *	XWIND		X10 driver, mods done at Brown University, an X11 port
 *			done at the University of Washington, and the X10a
 *			driver from Lawrence Livermore Labs.  This driver was
 *			developed by Don Stark (Stanford & decwrl).
 *	8BIT		X11 driver, force 8-bit graphics mode.
 *	16BIT		X11 driver, force 16-bit graphics mode.
 *	24BIT		X11 driver, force 24-bit graphics mode.
 *
 *	OpenGL		A port to OpenGL or Mesa.  Developed by Tim Edwards
 *			(Johns Hopkins University Applied Physics Lab)
 *
 * To port Magic to another type of display, you need to add its name to
 * the table 'grDisplayTypes' and then add a pointer to an initialization
 * routine to 'grInitProcs'.  The initialization routine will fill in all
 * of the graphics routine pointers so that they point to procedures that
 * can handle the new display type.  All calls to device-specific
 * procedures are made by indirecting through these pointers.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#include <errno.h>
#include <ctype.h>

/* C99 compat */
#include <unistd.h>

#include "utils/magic.h"
#include "textio/textio.h"
#include "utils/geometry.h"
#include "utils/hash.h"
#include "windows/windows.h"
#include "graphics/graphics.h"
#include "graphics/graphicsInt.h"

#define FAVORITE_DISPLAY	"NULL"	/* Default display type */

/* Correction between real-valued coordinate systems and pixel-based
 * coordinate systems, which can disagree by a pixel on the width of
 * polygons and position of lines.
 */
global int GrPixelCorrect = 1;

/* The following rectangle is describes the display area and is available
 * to the user of this module.
 */
global Rect GrScreenRect = {{0, 0}, {0, 0}};

/*
 * Interrupt status for the timer.  In TCL, this is used for graphics
 * interrupts to periodically check the X11 queue for pending events.
 * In both TCL and non-TCL versions, it can be used for a general-
 * purpose interrupt timer.
 */
global unsigned char GrDisplayStatus = DISPLAY_IDLE;

/* The first of the following tables defines the legal
 * display types and the second table defines an
 * initialization routine for each type.
 *
 * These entries MUST be all upper case, since what the user types will
 * be converted to upper case before comparison.
 */

static char *grDisplayTypes[] = {
#ifdef	X11
    "XWIND",
    "X11",
    "8BIT",
    "16BIT",
    "24BIT",
#endif
#ifdef  OGL
    "OPEN_GL",
    "OGL",
    "OPENGL",
#endif
#ifdef CAIRO
    "CAIRO",
    "XR",
#endif
    "NULL",
    NULL};

extern bool x11SetDisplay();
extern bool oglSetDisplay();
extern bool nullSetDisplay();
extern bool cairoSetDisplay();

static bool (*(grInitProcs[]))() = {
#ifdef	X11
    x11SetDisplay,
    x11SetDisplay,
    x11SetDisplay,
    x11SetDisplay,
    x11SetDisplay,
#endif	/* X11 */
#ifdef  OGL
    oglSetDisplay,
    oglSetDisplay,
    oglSetDisplay,
#endif
#ifdef CAIRO
    cairoSetDisplay,
    cairoSetDisplay,
#endif
    nullSetDisplay,
    NULL};

/* The following variables are pointers to the various graphics
 * procedures.  The macros in graphics.h cause these pointers
 * to be indirected through when calls occur to graphics procedures.
 * This indirection allows for several display types to be supported
 * by a single version of Magic.  The pointers are initially NULL,
 * but are rewritten by the various graphics initializers.
 */

void (*GrLockPtr)()		= NULL;
void (*GrUnlockPtr)()		= NULL;
bool (*GrInitPtr)()		= NULL;
void (*GrClosePtr)()		= NULL;
void (*GrSetCMapPtr)()		= NULL;

void (*GrSetCursorPtr)()	= NULL;
int  (*GrTextSizePtr)()		= NULL;
void (*GrDrawGlyphPtr)()	= NULL;
void (*GrBitBltPtr)()		= NULL;
int  (*GrReadPixelPtr)()	= NULL;
void (*GrFlushPtr)()		= NULL;
bool (*GrCreateWindowPtr)()	= NULL;
void (*GrDeleteWindowPtr)()	= NULL;
void (*GrConfigureWindowPtr)()	= NULL;
void (*GrOverWindowPtr)()	= NULL;
void (*GrUnderWindowPtr)()	= NULL;
void (*GrDamagedPtr)()		= NULL;
void (*GrUpdateIconPtr)()   	= NULL;
bool (*GrEventPendingPtr)()   	= NULL;
int (*GrWindowIdPtr)()   	= NULL;
char *(*GrWindowNamePtr)()	= NULL;
bool (*GrGetCursorPosPtr)()   	= NULL;
bool (*GrGetCursorRootPosPtr)()	= NULL;

void (*GrEnableTabletPtr)()	= NULL;
void (*GrDisableTabletPtr)()	= NULL;

bool (*GrGetBackingStorePtr)() 	  = NULL;
bool (*GrScrollBackingStorePtr)() = NULL;
void (*GrPutBackingStorePtr)() 	  = NULL;
void (*GrFreeBackingStorePtr)()   = NULL;
void (*GrCreateBackingStorePtr)() = NULL;

/* variables similar to the above, except that they are only used
 * internal to the graphics package
 */
void (*grPutTextPtr)()		= NULL;
void (*grFontTextPtr)()		= NULL;
void (*grGetCharSizePtr)()	= NULL;
void (*grSetSPatternPtr)()	= NULL;
void (*grDefineCursorPtr)()	= NULL;
void (*grFreeCursorPtr)()	= NULL;
bool (*grDrawGridPtr)()		= NULL;
void (*grDrawLinePtr)()		= NULL;
void (*grSetWMandCPtr)()	= NULL;
void (*grFillRectPtr)()		= NULL;
void (*grSetStipplePtr)()	= NULL;
void (*grSetLineStylePtr)()	= NULL;
void (*grSetCharSizePtr)()	= NULL;
void (*grFillPolygonPtr)()      = NULL;

/* The following variables are set by initialization routines for the
 * various displays.  They are strings that indicate what kind of
 * dstyle, cmap and cursor files should be used for this display.  Almost
 * all of the displays are happy with the default values given below.
 * Note:  a NULL grCMapType means that this display doesn't need a
 * color map (it's black-and-white).
 */

char *grDStyleType = "7bit";
char *grCMapType = "7bit";
char *grCursorType = "bw";

int grNumBitPlanes = 0;      /* Number of bit-planes we are using. */
int grBitPlaneMask = 0;      /* Mask of the valid bit-plane bits. */

/* Procedures called just before and after Magic is suspended (via ^Z). */
extern void grNullProc();
void (*GrStopPtr)() = grNullProc;
void (*GrResumePtr)() = grNullProc;


/*---------------------------------------------------------
 * GrSetDisplay --
 *	This routine sets a display type, opens files,  and initializes the
 *	display.
 *
 * Results:
 *	TRUE is returned if the display was found and initialized
 *	successfully.  If the type didn't register, or the file is
 *	NULL, then FALSE is returned.
 *
 * Side Effects:
 *	Tables are set up to control which display routines are
 *	used when communcating with the display.  The display
 *	is initialized and made ready for action.
 *---------------------------------------------------------
 */

bool
GrSetDisplay(type, outName, mouseName)
char *type;			/* Name of the display type. */
char *outName;			/* Filename used for communciation with
				 * display. */
char *mouseName;		/* Filename used for communciation
				 * with tablet. */

{
    char **ptr;
    char *cp;
    int i;
    bool res;

    if (outName == NULL)
    {
	TxError("No graphics device specified.\n");
	return FALSE;
    }
    if (mouseName == NULL)
    {
	TxError("No mouse specified.\n");
	return FALSE;
    }

    /* Skip any white space */
    while (isspace(*type)) type++;

    /* Convert display type to upper case. */
    for (cp = type; *cp; cp++) { if (islower(*cp)) *cp = toupper(*cp); }

    /* See if the display type is in our table. */
    ptr = grDisplayTypes;
    for (i = 0; *ptr; i++)
    {
	if (strncmp(*ptr, type, strlen(*ptr)) == 0) break;
	ptr++;
    }

    /* Did we find it? */
    if (*ptr == NULL)
    {
	TxError("Unknown display type:  %s\n", type);
 	TxError("These display types are available in this version of Magic:\n");
	ptr = grDisplayTypes;
	for (i = 0; *ptr; i++)
	{
	    TxError("        %s\n", *ptr);
	    ptr++;
	}
	TxError("Use '-d NULL' if you don't need graphics.\n");
	return FALSE;
    }

    /* Call the initialization procedure. */
    res = (*(grInitProcs[i]))(type, outName, mouseName);
    if (!res)
    {
	TxError("The graphics display couldn't be correctly initialized.\n");
	TxError("Use '-d NULL' if you don't need graphics.\n");
    }
    return res;
}

/*
 * ----------------------------------------------------------------------------
 *
 * GrIsDisplay --
 *
 *	Check if the first argument is the same type of display as the
 *	second argument.
 *
 * Results:
 *	TRUE if both strings represent the same display type, FALSE
 *	otherwise.  "same display type" is defined as both display
 *	strings in the grDisplayTypes list corresponding to the same
 *	initialization procedure in grInitProcs.
 *
 * Side Effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */
bool
GrIsDisplay(disp1, disp2)
    char *disp1, *disp2;
{
    char **ptr1, **ptr2;
    int i, j;

    /* See if the display type is in our table. */
    ptr1 = grDisplayTypes;
    for (i = 0; *ptr1; i++)
    {
	if (strncmp(*ptr1, disp1, strlen(*ptr1)) == 0) break;
	ptr1++;
    }
    if (*ptr1 == NULL)
    {
	TxError("Unknown display type:  %s\n", disp1);
	return FALSE;
    }

    ptr2 = grDisplayTypes;
    for (j = 0; *ptr2; j++)
    {
	if (strncmp(*ptr2, disp2, strlen(*ptr2)) == 0) break;
	ptr2++;
    }
    if (*ptr2 == NULL)
    {
	TxError("Unknown display type:  %s\n", disp2);
	return FALSE;
    }

    if (grInitProcs[i] == grInitProcs[j]) return TRUE;
    return FALSE;
}


/*
 * ----------------------------------------------------------------------------
 * GrGuessDisplayType --
 *
 *	Try to guess what sort of machine we are on, and set the display
 *	ports and type appropriately.  This info is overridden by
 *	$CAD_ROOT/magic/displays and by command line switches.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Modifies the strings passed in.
 * ----------------------------------------------------------------------------
 */

void
GrGuessDisplayType(graphics, mouse, display, monitor)
    char **graphics;		/* default device for sending out graphics */
    char **mouse;		/* default device for reading mouse (tablet) */
    char **display;		/* default type of device (OGL, etc...) */
    char **monitor;		/* default type of monitor (pale, std) */
{
    bool onSun;			/* Are we on a Sun? */
    bool haveX;			/* are we running under X? */
    char **ptr;

    *graphics = NULL;
    *mouse = NULL;
    *display = NULL;
    *monitor = "std";

    /* Check for signs of suntools. */
    onSun = (access("/dev/win0", 0) == 0);
    haveX = (getenv("DISPLAY") != NULL);

    if (haveX)
    {
	*mouse = *graphics = NULL;
	*display = "XWIND";
    }
    else if (onSun) {
	TxError("You are on a Sun but not running X.\n");
	*mouse = *graphics = NULL;
	*display = "NULL";
    }
    else {
	/* GUESS:  who knows, maybe a VAX? */
	*mouse = *graphics = NULL;
	*display = FAVORITE_DISPLAY;
    }

    /* If the guessed value is NOT in the known list of display types, then */
    /* choose the first display type in the list.	---Tim 3/13/00	    */

    ptr = grDisplayTypes;
    while ((*ptr != *display) && (*ptr != NULL)) ptr++;
    if ((*ptr == NULL) && (ptr != grDisplayTypes)) {
        ptr = grDisplayTypes;
	*display = *ptr;
    }
}


/*
 * ----------------------------------------------------------------------------
 * grFgets --
 *
 *	Just like fgets, except that it times out after 20 seconds, and prints
 *	a warning message.  After one second a warning message is also
 *	printed.
 *
 * Results:
 *	Pointer to the string returned by fgets (equal to the 1st argument)
 *
 * Side effects:
 *	None.
 * ----------------------------------------------------------------------------
 */

char *
grFgets(str, n, stream, name)
    char *str;
    int n;
    FILE *stream;
    char *name;		/* The user name of the stream, for the error msg */
{
    fd_set fn;
    char *newstr;
    struct timeval threeSec, twentySecs;

    threeSec.tv_sec = 3;
    threeSec.tv_usec = 0;
    twentySecs.tv_sec = 20;
    twentySecs.tv_usec = 0;

    const int fd = fileno(stream);
    ASSERT(fd >= 0 && fd < FD_SETSIZE, "fd>=0&&fd<FD_SETSIZE");
    if (fd < 0 || fd >= FD_SETSIZE)
    {
	TxError("WARNING: grFgets(fd=%d) called with fd out of range 0..%d\n", fd, FD_SETSIZE-1);
	return NULL; /* allowing things to continue is UB */
    }
    FD_ZERO(&fn);
    FD_SET(fd, &fn);
    newstr = str;
    n--;
    if (n < 0) return (char *) NULL;

    while (n > 0)
    {
	fd_set f;
	char ch;
        int sel;

	f = fn;
	sel = select(fd + 1, &f, (fd_set *) NULL, (fd_set *) NULL, &threeSec);
	if  (sel == 0)
	{
	    TxError("The %s is responding slowly, or not at all.\n", name);
	    TxError("I'll wait for 20 seconds and then give up.\n");
	    f = fn;
	    sel = select(fd + 1, &f, (fd_set *) NULL,
			(fd_set *) NULL, &twentySecs);
	    if (sel == 0)
	    {
		TxError("The %s did not respond.\n", name);
		return (char *) NULL;
	    }
	    else if (sel < 0)
	    {
		if (errno == EINTR) {
		    TxError("Timeout aborted.\n");
		}
		else
		{
		    perror("magic");
		    TxError("Error in reading the %s\n", name);
		}
		return (char *) NULL;
	    }
	    else
		TxError("The %s finally responded.\n", name);
	}
	else if (sel < 0)
	{
	    if (errno != EINTR)
	    {
		perror("magic");
		TxError("Error in reading the %s\n", name);
		return (char *) NULL;
	    }
	    /* else try again, back to top of the loop */
	    continue;
	}

	ch = getc(stream);
	*newstr = ch;
	n--;
	newstr++;
	if (ch == '\n')
	    break;
    }

    *newstr = '\0';
    return str;
}


/*---------------------------------------------------------------------------
 * grNullProc --
 *
 *	A procedure of the type 'void' that does absolutely nothing.
 *	Used when we need to point a procedure pointer to something, but
 *	don't want it to do anything.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	None.
 *
 *----------------------------------------------------------------------------
 */

void
grNullProc()
{
}
