/* $Id: plbuf.c,v 1.18 1994/03/23 06:39:20 mjl Exp $
 * $Log: plbuf.c,v $
 * Revision 1.18  1994/03/23  06:39:20  mjl
 * Added support for: color map 1 color selection, color map 0 or color map 1
 * state change (palette change), polygon fills.
 *
 * 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.17  1994/01/15  17:19:13  mjl
 * Eliminated include of pdf.h (not needed).
 *
 * Revision 1.16  1993/09/28  21:25:08  mjl
 * Now discards the old temp file and opens a new one on each new page.  This
 * avoids a lot of problems redisplaying partially filled pages -- we can
 * just read to EOF without worrying about junk in the file from previous
 * writes.  The old method never did work perfectly.
 *
 * Revision 1.15  1993/09/27  20:33:51  mjl
 * Insignificant change to eliminate a gcc -Wall warning.
 *
 * Revision 1.14  1993/09/08  02:27:11  mjl
 * Inserted change to ensure that streams that do not own the plot buffer
 * cannot write to it (was causing problems on plot saves).
 *
 * Revision 1.13  1993/08/30  19:11:51  mjl
 * Fixed the damage incurred by my previous "fix".  Plot buffer now appears
 * capable of accurately and unobtrusively replaying partially filled pages.
 *
 * Revision 1.12  1993/08/28  06:28:07  mjl
 * Fixed escape function reads & writes to be consistent.  Added plbuf_rewind
 * function, which writes EOP's to the end of file to make sure that dicking
 * around with the window settings before the page is complete doesn't end in
 * a core dump.
 *
 * Revision 1.11  1993/07/31  07:56:40  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:12:17  mjl
 * Simplified and slightly changed behavior to support plot dumps to cloned
 * plplot streams.
 *
 * Revision 1.9  1993/07/01  21:59:42  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_".
*/

/*
    plbuf.c -- handle plot buffer.

    Copyright 1992
    Maurice LeBrun

    This software may be freely copied, modified and redistributed without
    fee provided that this copyright notice is preserved intact on all
    copies and modified copies.

    There is no warranty or other guarantee of fitness of this software.
    It is provided solely "as is". The author(s) disclaim(s) all
    responsibility and liability with respect to this software's usage or
    its effect upon hardware or computer systems.

*/

#include "plplotP.h"
#include "drivers.h"
#include "metadefs.h"

#include <string.h>

/* Function prototypes */
/* INDENT OFF */

static int	rd_command	( PLStream *pls, U_CHAR * );
static int	wr_command	( PLStream *pls, U_CHAR );
static void	plbuf_control	( PLStream *pls, U_CHAR );

void rdbuf_init		(PLStream *);
void rdbuf_line		(PLStream *);
void rdbuf_polyline	(PLStream *);
void rdbuf_eop		(PLStream *);
void rdbuf_bop		(PLStream *);
void rdbuf_tidy		(PLStream *);
void rdbuf_state	(PLStream *);
void rdbuf_esc		(PLStream *);

static void plbuf_fill	(PLStream *pls);
static void rdbuf_fill	(PLStream *pls);

/* INDENT ON */
/*----------------------------------------------------------------------*\
* Routines to write to the plot buffer.
\*----------------------------------------------------------------------*/

/*----------------------------------------------------------------------*\
* plbuf_init()
*
* Initialize device.
* Actually just disables writes if plot buffer is already open (occurs
* when one stream is cloned, as in printing).
\*----------------------------------------------------------------------*/

void
plbuf_init(PLStream *pls)
{
    dbug_enter("plbuf_init");

    if (pls->plbufFile != NULL) 
	pls->plbuf_write = 0;
}

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

void
plbuf_line(PLStream *pls, short x1a, short y1a, short x2a, short y2a)
{
    short xpl[2], ypl[2];

    dbug_enter("plbuf_line");

    wr_command(pls, (U_CHAR) LINE);

    xpl[0] = x1a;
    xpl[1] = x2a;
    ypl[0] = y1a;
    ypl[1] = y2a;

    fwrite(xpl, sizeof(short), 2, pls->plbufFile);
    fwrite(ypl, sizeof(short), 2, pls->plbufFile);
}

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

void
plbuf_polyline(PLStream *pls, short *xa, short *ya, PLINT npts)
{
    dbug_enter("plbuf_polyline");

    wr_command(pls, (U_CHAR) POLYLINE);
    fwrite(&npts, sizeof(PLINT), 1, pls->plbufFile);

    fwrite(xa, sizeof(short), npts, pls->plbufFile);
    fwrite(ya, sizeof(short), npts, pls->plbufFile);
}

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

void
plbuf_eop(PLStream *pls)
{
    dbug_enter("plbuf_eop");
}

/*----------------------------------------------------------------------*\
* plbuf_bop()
*
* Set up for the next page.
* To avoid problems redisplaying partially filled pages, on each BOP the
* old file is thrown away and a new one is obtained.  This way we can just
* read up to EOF to get everything on the current page.
*
* Also write state information to ensure the next page is correct.
\*----------------------------------------------------------------------*/

void
plbuf_bop(PLStream *pls)
{
    dbug_enter("plbuf_bop");

    plbuf_tidy(pls);

    pls->plbufFile = tmpfile();
    if (pls->plbufFile == NULL) 
	plexit("plbuf_init: Error opening plot data storage file.");

    plP_state(PLSTATE_COLOR0);
    plP_state(PLSTATE_WIDTH);
}

/*----------------------------------------------------------------------*\
* plbuf_tidy()
*
* Close graphics file
\*----------------------------------------------------------------------*/

void
plbuf_tidy(PLStream *pls)
{
    dbug_enter("plbuf_tidy");

    if (pls->plbufFile == NULL)
	return;

    fclose(pls->plbufFile);
    pls->plbufFile = NULL;
}

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

void 
plbuf_state(PLStream *pls, PLINT op)
{
    dbug_enter("plbuf_state");

    wr_command(pls, (U_CHAR) CHANGE_STATE);
    wr_command(pls, (U_CHAR) op);

    switch (op) {

      case PLSTATE_WIDTH: {
	U_CHAR width = pls->width;

	fwrite(&width, sizeof(U_CHAR), 1, pls->plbufFile);
	break;
      }

      case PLSTATE_COLOR0: {
	U_CHAR icol0 = pls->icol0;
	U_CHAR r = pls->curcolor.r;
	U_CHAR g = pls->curcolor.g;
	U_CHAR b = pls->curcolor.b;

	fwrite(&icol0, sizeof(U_CHAR), 1, pls->plbufFile);
	if (icol0 == PL_RGB_COLOR) {
	    fwrite(&r, sizeof(U_CHAR), 1, pls->plbufFile);
	    fwrite(&g, sizeof(U_CHAR), 1, pls->plbufFile);
	    fwrite(&b, sizeof(U_CHAR), 1, pls->plbufFile);
	}
	break;
      }

      case PLSTATE_COLOR1: {
	U_CHAR icol1 = pls->icol1;

	fwrite(&icol1, sizeof(U_CHAR), 1, pls->plbufFile);
	break;
      }

      case PLSTATE_FILL:{
	signed char patt = pls->patt;

	fwrite(&patt, sizeof(signed char), 1, pls->plbufFile);
	break;
      }
    }
}

/*----------------------------------------------------------------------*\
* plbuf_esc()
*
* Escape function.  Note that any data written must be in device
* independent form to maintain the transportability of the metafile.
*
* Functions:
*
*	PLESC_FILL	Fill polygon
*
\*----------------------------------------------------------------------*/

void
plbuf_esc(PLStream *pls, PLINT op, void *ptr)
{
    dbug_enter("plbuf_esc");

    wr_command(pls, (U_CHAR) ESCAPE);
    wr_command(pls, (U_CHAR) op);

    switch (op) {
      case PLESC_FILL:
	plbuf_fill(pls);
	break;
    }
}

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

static void
plbuf_fill(PLStream *pls)
{
    dbug_enter("plbuf_fill");

    fwrite(&pls->dev_npts, sizeof(PLINT), 1, pls->plbufFile);
    fwrite(pls->dev_x, sizeof(short), pls->dev_npts, pls->plbufFile);
    fwrite(pls->dev_y, sizeof(short), pls->dev_npts, pls->plbufFile);
}

/*----------------------------------------------------------------------*\
* Routines to read from & process the plot buffer.
\*----------------------------------------------------------------------*/

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

void
rdbuf_init(PLStream *pls)
{
    dbug_enter("rdbuf_init");
}

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

void
rdbuf_line(PLStream *pls)
{
    short xpl[2], ypl[2];
    PLINT npts = 2;

    dbug_enter("rdbuf_line");

    fread(xpl, sizeof(short), npts, pls->plbufFile);
    fread(ypl, sizeof(short), npts, pls->plbufFile);

    plP_line(xpl, ypl);
}

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

void
rdbuf_polyline(PLStream *pls)
{
    short xpl[PL_MAXPOLY], ypl[PL_MAXPOLY];
    PLINT npts;

    dbug_enter("rdbuf_polyline");

    fread(&npts, sizeof(PLINT), 1, pls->plbufFile);
    fread(xpl, sizeof(short), npts, pls->plbufFile);
    fread(ypl, sizeof(short), npts, pls->plbufFile);

    plP_polyline(xpl, ypl, npts);
}

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

void
rdbuf_eop(PLStream *pls)
{
    dbug_enter("rdbuf_eop");
}

/*----------------------------------------------------------------------*\
* rdbuf_bop()
*
* Set up for the next page.
\*----------------------------------------------------------------------*/

void
rdbuf_bop(PLStream *pls)
{
    dbug_enter("rdbuf_bop");
}

/*----------------------------------------------------------------------*\
* rdbuf_tidy()
*
* Close graphics file
\*----------------------------------------------------------------------*/

void
rdbuf_tidy(PLStream *pls)
{
    dbug_enter("rdbuf_tidy");
}

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

void 
rdbuf_state(PLStream *pls)
{
    U_CHAR op;

    dbug_enter("rdbuf_state");

    fread(&op, sizeof(U_CHAR), 1, pls->plbufFile);

    switch (op) {

      case PLSTATE_WIDTH:{
	U_CHAR width;

	fread(&width, sizeof(U_CHAR), 1, pls->plbufFile);
	pls->width = width;
	plP_state(PLSTATE_WIDTH);

	break;
      }

      case PLSTATE_COLOR0:{
	U_CHAR icol0, r, g, b;

	fread(&icol0, sizeof(U_CHAR), 1, pls->plbufFile);
	if (icol0 == PL_RGB_COLOR) {
	    fread(&r, sizeof(U_CHAR), 1, pls->plbufFile);
	    fread(&g, sizeof(U_CHAR), 1, pls->plbufFile);
	    fread(&b, sizeof(U_CHAR), 1, pls->plbufFile);
	}
	else {
	    if ((int) icol0 > 15) {
		plwarn("rdbuf_state: Color map 0 entry hosed");
		icol0 = 1;
	    }
	    r = pls->cmap0[icol0].r;
	    g = pls->cmap0[icol0].g;
	    b = pls->cmap0[icol0].b;
	}
	pls->icol0 = icol0;
	pls->curcolor.r = r;
	pls->curcolor.g = g;
	pls->curcolor.b = b;

	plP_state(PLSTATE_COLOR0);
	break;
      }

      case PLSTATE_COLOR1: {
	U_CHAR icol1;

	fread(&icol1, sizeof(U_CHAR), 1, pls->plbufFile);

	pls->icol1 = icol1;
	pls->curcolor.r = pls->cmap1[icol1].r;
	pls->curcolor.g = pls->cmap1[icol1].g;
	pls->curcolor.b = pls->cmap1[icol1].b;

	plP_state(PLSTATE_COLOR1);
	break;
      }

      case PLSTATE_FILL: {
	signed char patt;

	fread(&patt, sizeof(signed char), 1, pls->plbufFile);

	pls->patt = patt;
	plP_state(PLSTATE_FILL);
	break;
      }
    }
}

/*----------------------------------------------------------------------*\
* rdbuf_esc()
*
* Escape function.
* Must fill data structure with whatever data that was written,
* then call escape function.
*
* Note: it is best to only call the escape function for op-codes that
* are known to be supported.
*
* Functions:
*
*	PLESC_FILL	Fill polygon
*
\*----------------------------------------------------------------------*/

void
rdbuf_esc(PLStream *pls)
{
    U_CHAR op;

    dbug_enter("rdbuf_esc");

    fread(&op, sizeof(U_CHAR), 1, pls->plbufFile);

    switch (op) {
      case PLESC_FILL:
	rdbuf_fill(pls);
	break;
    }
}

/*----------------------------------------------------------------------*\
* rdbuf_fill()
*
* Fill polygon described by input points.
\*----------------------------------------------------------------------*/

static void
rdbuf_fill(PLStream *pls)
{
    short xpl[PL_MAXPOLY], ypl[PL_MAXPOLY];
    PLINT npts;

    dbug_enter("rdbuf_fill");

    fread(&npts, sizeof(PLINT), 1, pls->plbufFile);
    fread(xpl, sizeof(short), npts, pls->plbufFile);
    fread(ypl, sizeof(short), npts, pls->plbufFile);

    plP_fill(xpl, ypl, npts);
}

/*----------------------------------------------------------------------*\
* plRemakePlot()
*
* Rebuilds plot from plot buffer, usually in response to a window
* resize or exposure event.
\*----------------------------------------------------------------------*/

void
plRemakePlot(PLStream *pls)
{
    U_CHAR c;
    int plbuf_status;

    dbug_enter("plRemakePlot");

    if (pls->plbufFile == NULL)
	return;

    rewind(pls->plbufFile);

    plbuf_status = pls->plbuf_write;
    pls->plbuf_write = FALSE;
    while (rd_command(pls, &c)) {
	plbuf_control(pls, c);
    }

    pls->plbuf_write = plbuf_status;
}

/*----------------------------------------------------------------------*\
* plbuf_control()
*
* Processes commands read from the plot buffer.
\*----------------------------------------------------------------------*/

static void
plbuf_control(PLStream *pls, U_CHAR c)
{
    static U_CHAR c_old = 0;

    dbug_enter("plbuf_control");

    switch ((int) c) {

      case INITIALIZE:
	rdbuf_init(pls);
	break;

      case EOP:
	rdbuf_eop(pls);
	break;

      case BOP:
	rdbuf_bop(pls);
	break;

      case CHANGE_STATE:
	rdbuf_state(pls);
	break;

      case LINE:
	rdbuf_line(pls);
	break;

      case POLYLINE:
	rdbuf_polyline(pls);
	break;

      case ESCAPE:
	rdbuf_esc(pls);
	break;

      default:
	fprintf(stderr,
		"plbuf_control: Unrecognized command %d, previous %d\n",
		c, c_old);
    }
    c_old = c;
}

/*----------------------------------------------------------------------*\
*  rd_command()
*
*  Read & return the next command
\*----------------------------------------------------------------------*/

static int
rd_command(PLStream *pls, U_CHAR *p_c)
{
    int count;
    
    count = fread(p_c, sizeof(U_CHAR), 1, pls->plbufFile);
    return (count);
}

/*----------------------------------------------------------------------*\
*  wr_command()
*
*  Write the next command
\*----------------------------------------------------------------------*/

static int
wr_command(PLStream *pls, U_CHAR c)
{
    U_CHAR c1 = c;
    int count;

    count = fwrite(&c1, sizeof(U_CHAR), 1, pls->plbufFile);
    return (count);
}
