/* $Id: gnusvga.c,v 1.2 1994/08/10 20:23:47 mjl Exp $
 * $Log: gnusvga.c,v $
 * Revision 1.2  1994/08/10  20:23:47  mjl
 * Latest changes for DJGPP port by Paul Kirschner.
 *
 * Revision 1.1  1994/08/10  01:08:29  mjl
 * New directory structure, config files, and other modifications for PLplot
 * 4.99h/djgpp DOS port, submitted by Paul Kirschner.  New driver supports
 * color fill.
 *
 * Revision 1.1  1994/05/25  09:19:22  mjl
 * The new locations for DJGPP (DOS 386) files.
 *
 * Revision 1.14  1994/04/08  11:35:57  mjl
 * Put nopause support back into the drivers where it is better off.
 * I don't know WHAT I was thinking.
 *
 * Revision 1.13  1994/03/23  06:34:23  mjl
 * All drivers: cleaned up by eliminating extraneous includes (stdio.h and
 * stdlib.h now included automatically by plplotP.h), extraneous clears
 * of pls->fileset, pls->page, and pls->OutFile = NULL (now handled in
 * driver interface or driver initialization as appropriate).  Special
 * handling for malloc includes eliminated (no longer needed) and malloc
 * prototypes fixed as necessary.
 *
 * Revision 1.12  1993/12/08  06:12:22  mjl
 * Miscellaneous bug fixes contributed by Paul Kirschner.
 *
 * Revision 1.11  1993/07/31  07:56:30  mjl
 * Several driver functions consolidated, for all drivers.  The width and color
 * commands are now part of a more general "state" command.  The text and
 * graph commands used for switching between modes is now handled by the
 * escape function (very few drivers require it).  The device-specific PLDev
 * structure is now malloc'ed for each driver that requires it, and freed when
 * the stream is terminated.
 *
 * Revision 1.10  1993/07/16  22:11:15  mjl
 * Eliminated low-level coordinate scaling; now done by driver interface.
 *
 * Revision 1.9  1993/07/01  21:59:33  mjl
 * Changed all plplot source files to include plplotP.h (private) rather than
 * plplot.h.  Rationalized namespace -- all externally-visible plplot functions
 * now start with "pl"; device driver functions start with "plD_".
*/

/*
	gnusvga.c
	Geoffrey Furnish
	20 March 1992
	
	This file constitutes the driver for an SVGA display under DOS
	using the GNU CC compiler and grpahics library by DJ Delorie.

	There are some things to note:
	1)  DJ Delorie's DOS port of GNU CC 386 uses a VCPI DOS extender.
		This means, among other things, that it won't run under
		any flavor of OS/2.  A DPMI port was rumored to be
		underway, so it is possible that by the time you read
		this, this restriction will have been lifted.
	2)  The currently available video drivers only support Super VGA
		type display cards, and only in resolutions with 256
		colors.  Thus, if you only have regular VGA, you're gonna
		get 320 x 200 resolution.  On the other hand, if you do
		have a card with say, 800 x 600 x 256 colors, then you're
		gonna get a picture much better than what could be delivered
		by the standard DOS version of PLPLOT using Micrsoft
		graphics.
	3)  This driver supports the PLPLOT RGB escape function.  So, you
		can draw lines of any color you like, which is again
		superior to the standard DOS version based on MS graphics.
*/
#include "plDevs.h"

#ifdef PLD_gnusvga		/* Only compile for DOS 386 with GNU CC
				   compiler */
#include "plplotP.h"
#include "drivers.h"
#include "plevent.h"
#include <graphics.h>

/* Prototypes:	Since GNU CC, we can rest in the safety of ANSI prototyping. */

static void	plpause		(PLStream *);
static void	init_palette	(void);
static void	svga_text	(PLStream *);
static void	svga_graph	(PLStream *);

static void	fill_polygon	(PLStream *pls);
static void	setcmap		(PLStream *pls);
static void	WaitForPage	(PLStream *pls);
static void	EventHandler	(PLStream *pls, int input_char);

static PLINT vgax = 639;
static PLINT vgay = 479;

/* A flag to tell us whether we are in text or graphics mode */

#define TEXT_MODE 0
#define GRAPHICS_MODE 1

/* gmf; should probably query this on start up... Maybe later. */

static int mode = TEXT_MODE;
static int col = 1;
static int totcol = 16;
static exit_eventloop = 0;

#define CLEAN 0
#define DIRTY 1

static page_state;

typedef struct {
    int r;
    int g;
    int b;
} RGB;

static RGB colors[] = {
    {0, 0, 0},			/* coral */
    {255, 0, 0},		/* red */
    {255, 255, 0},		/* yellow */
    {0, 255, 0},		/* green */
    {127, 255, 212},		/* acquamarine */
    {255, 192, 203},		/* pink */
    {245, 222, 179},		/* wheat */
    {190, 190, 190},		/* grey */
    {165, 42, 42},		/* brown */
    {0, 0, 255},		/* blue */
    {138, 43, 226},		/* Blue Violet */
    {0, 255, 255},		/* cyan */
    {64, 224, 208},		/* turquoise */
    {255, 0, 255},		/* magenta */
    {250, 128, 114},		/* salmon */
    {255, 255, 255},		/* white */
    {0, 0, 0},			/* black */
};


/*----------------------------------------------------------------------*\
 * plD_init_vga()
 *
 * Initialize device.
\*----------------------------------------------------------------------*/

void
plD_init_vga(PLStream *pls)
{
    pls->termin = 1;		/* is an interactive terminal */
    pls->icol0 = 1;
    pls->width = 1;
    pls->bytecnt = 0;
    pls->page = 0;
    pls->graphx = TEXT_MODE;

    pls->dev_fill0 = 1;

    if (!pls->colorset)
	pls->color = 1;

/* Set up device parameters */

    svga_graph(pls);		/* Can't get current device info unless in
				   graphics mode. */

    vgax = GrSizeX() - 1;	/* should I use -1 or not??? */
    vgay = GrSizeY() - 1;

    plP_setpxl(2.5, 2.5);	/* My best guess.  Seems to work okay. */

    plP_setphy(0, vgax, 0, vgay);
}

/*----------------------------------------------------------------------*\
 * plD_line_vga()
 *
 * Draw a line in the current color from (x1,y1) to (x2,y2).
\*----------------------------------------------------------------------*/

void
plD_line_vga(PLStream *pls, short x1a, short y1a, short x2a, short y2a)
{
    int x1 = x1a, y1 = y1a, x2 = x2a, y2 = y2a;

    y1 = vgay - y1;
    y2 = vgay - y2;

    GrLine(x1, y1, x2, y2, col);

    page_state = DIRTY;
}

/*----------------------------------------------------------------------*\
 * plD_polyline_vga()
 *
 * Draw a polyline in the current color.
\*----------------------------------------------------------------------*/

void
plD_polyline_vga(PLStream *pls, short *xa, short *ya, PLINT npts)
{
    PLINT i;

    for (i = 0; i < npts - 1; i++)
	plD_line_vga(pls, xa[i], ya[i], xa[i + 1], ya[i + 1]);
}

/*----------------------------------------------------------------------*\
 * plD_eop_vga()
 *
 * End of page.
\*----------------------------------------------------------------------*/

void
plD_eop_vga(PLStream *pls)
{
    if (page_state == DIRTY) {
	if ( ! pls->nopause) 
	    WaitForPage(pls);
    }

/*     init_palette(); */

    page_state = CLEAN;
}

/*----------------------------------------------------------------------*\
 * plD_bop_vga()
 *
 * Set up for the next page.
 * Advance to next family file if necessary (file output).
\*----------------------------------------------------------------------*/

void
plD_bop_vga(PLStream *pls)
{
    pls->page++;
    plD_eop_vga(pls);
    GrClearScreen(0);
}

/*----------------------------------------------------------------------*\
 * plD_tidy_vga()
 *
 * Close graphics file or otherwise clean up.
\*----------------------------------------------------------------------*/

void
plD_tidy_vga(PLStream *pls)
{
    svga_text(pls);
}

/*----------------------------------------------------------------------*\
 * plD_state_vga()
 *
 * Handle change in PLStream state (color, pen width, fill attribute, etc).
\*----------------------------------------------------------------------*/

void 
plD_state_vga(PLStream *pls, PLINT op)
{
    switch (op) {

    case PLSTATE_WIDTH:
	break;

    case PLSTATE_COLOR0:
	col = pls->icol0;
	if (col == PL_RGB_COLOR) {
	    int r = pls->curcolor.r;
	    int g = pls->curcolor.g;
	    int b = pls->curcolor.b;
	    if (totcol < GrNumColors()) {
		GrSetColor(++totcol, r, g, b);
		col = totcol;
	    }
	}
	break;

    case PLSTATE_COLOR1:
        { int icol1, ncol1, r, g, b;
        if ((ncol1 = MIN(GrNumColors() - pls->ncol0, pls->ncol1)) < 1)
            break;

        icol1 = pls->ncol0 + (pls->icol1 * (ncol1-1)) / (pls->ncol1-1);

        r = pls->curcolor.r;
        g = pls->curcolor.g;
        b = pls->curcolor.b;
        GrSetColor(icol1, r, g, b);
        col = icol1;
	}
	break;

    case PLSTATE_CMAP0:
    case PLSTATE_CMAP1:
	if (pls->color)
	    setcmap(pls);
	break;
    }
}

/*----------------------------------------------------------------------*\
 * plD_esc_vga()
 *
 * Escape function.
\*----------------------------------------------------------------------*/

void
plD_esc_vga(PLStream *pls, PLINT op, void *ptr)
{
    switch (op) {
      case PLESC_TEXT:
	svga_text(pls);
	break;

      case PLESC_GRAPH:
	svga_graph(pls);
	break;

      case PLESC_FILL:
	fill_polygon(pls);
	break;
    }
}

/*----------------------------------------------------------------------*\
 * fill_polygon()
 *
 * Fill polygon described in points pls->dev_x[] and pls->dev_y[].
\*----------------------------------------------------------------------*/

static void
fill_polygon(PLStream *pls)
{
    int i;
    int points[PL_MAXPOLY][2];

    if (pls->dev_npts < 1)
	return;

/* Select the fill pattern */

/* Specify boundary */

    for (i = 0; i < pls->dev_npts; i++) {
	points[i][0] = pls->dev_x[i];
        points[i][1] = vgay - pls->dev_y[i];
    }
    GrFilledPolygon(pls->dev_npts, points, col);

}

/*----------------------------------------------------------------------*\
 * svga_text()
 *
 * Switch to text mode.
\*----------------------------------------------------------------------*/

static void
svga_text(PLStream *pls)
{
    if (pls->graphx == GRAPHICS_MODE) {
	if (page_state == DIRTY)
	    plpause(pls);
	GrSetMode(GR_default_text);
	GrResetColors();
	pls->graphx = TEXT_MODE;
	page_state = CLEAN;
    }
}

/*----------------------------------------------------------------------*\
 * svga_graph()
 *
 * Switch to graphics mode.
 *
 * NOTE: Moving to a new page causes the RGB map to be reset so that
 * there will continue to be room.  This could conceivably cause a
 * problem if an RGB escape was used to start drawing in a new color, and
 * then it was expected that this would persist accross a page break.  If
 * this happens, it will be necessary to rethink the logic of how this is
 * handled.  Could wrap the index, for example.  This would require
 * saving the RGB info used so far, which is not currently done.
\*----------------------------------------------------------------------*/

static void
svga_graph(PLStream *pls)
{
    if (pls->graphx == TEXT_MODE) {
				/* Destroys the palette */
	GrSetMode(GR_default_graphics);
/*	GrSetMode(GR_320_200_graphics); */
	init_palette();		/* Fix the palette */
	totcol = 16;		/* Reset RGB map so we don't run out of
				   indicies */
	pls->graphx = GRAPHICS_MODE;
	page_state = CLEAN;
    }
}

/*----------------------------------------------------------------------*\
 * plpause()
 *
 * Wait for a keystroke.
\*----------------------------------------------------------------------*/

static void
plpause(PLStream *pls)
{
    if (pls->nopause) 
	return;

    GrTextXY(0, 0, "Pause->", 15, 0);
    getkey();
}

/*----------------------------------------------------------------------*\
 * setcmap()
 *
 * Sets up color palette.
\*----------------------------------------------------------------------*/

static void
setcmap(PLStream *pls)
{
    int i, ncol1;
    PLColor cmap1col;

/* Initialize cmap 0 colors */

    for (i = 0; i < pls->ncol0; i++) 
	GrSetColor(i, pls->cmap0[i].r, pls->cmap0[i].g, pls->cmap0[i].b);

/* Initialize any remaining slots for cmap1 */

    ncol1 = GrNumFreeColors();
    for (i = 0; i < ncol1; i++) {
	plcol_interp(pls, &cmap1col, i, ncol1);
	GrSetColor(i + pls->ncol0, cmap1col.r, cmap1col.g, cmap1col.b);
    }
}

/*----------------------------------------------------------------------*\
 * init_palette()
 *
 * Set up the nice RGB default color map.
\*----------------------------------------------------------------------*/

static void 
init_palette(void)
{
    int n;

    for (n = 0; n < sizeof(colors) / sizeof(RGB); n++)
	GrSetColor(n, colors[n].r, colors[n].g, colors[n].b);
}

/*----------------------------------------------------------------------*\
 * WaitForPage()
 *
 * This routine waits for the user to advance the plot, while handling
 * all other events.
\*----------------------------------------------------------------------*/

static void
WaitForPage(PLStream *pls)
{
    int input_char;

/*    putchar(BEL); */
/*    fflush(stdout); */

    while ( ! exit_eventloop) {
	input_char = getkey();
	EventHandler(pls, input_char);
    }
    exit_eventloop = FALSE;
}

/*----------------------------------------------------------------------*\
 * EventHandler()
 *
 * Event handler routine for xterm.  
 * Just reacts to keyboard input.
\*----------------------------------------------------------------------*/

static void
EventHandler(PLStream *pls, int input_char)
{
    PLKey key;

    key.code = 0;
    key.string[0] = '\0';

/* Translate keystroke into a PLKey */

    if ( ! isprint(input_char)) {
	switch (input_char) {
	case 0x0A:
	    key.code = PLK_Linefeed;
	    break;

	case 0x0D:
	    key.code = PLK_Return;
	    break;

	case 0x08:
	    key.code = PLK_BackSpace;
	    break;

	case 0x7F:
	    key.code = PLK_Delete;
	    break;
	}
    }
    else {
	key.string[0] = input_char;
	key.string[1] = '\0';
    }

#ifdef DEBUG
    pltext();
    fprintf(stderr, "Input char %x, Keycode %x, string: %s\n",
	    input_char, key.code, key.string);
    plgra();
#endif

/* Call user event handler */
/* Since this is called first, the user can disable all plplot internal
   event handling by setting key.code to 0 and key.string to '\0' */

    if (pls->KeyEH != NULL)
	(*pls->KeyEH) (&key, pls->KeyEH_data, &exit_eventloop);

/* Handle internal events */

/* Advance to next page (i.e. terminate event loop) on a <eol> */

    if (key.code == PLK_Linefeed)
	exit_eventloop = TRUE;
    if (key.code == PLK_Return)
	exit_eventloop = TRUE;

/* Terminate on a 'Q' (not 'q', since it's too easy to hit by mistake) */

    if (key.string[0] == 'Q') {
	pls->nopause = TRUE;
	plexit("");
    }
}

#else
int 
pldummy_gnusvga()
{
    return 0;
}

#endif				/* GNUSVGA */
