//  Copyright 1993, 1994, 1995
//  Maurice LeBrun
//  IFS, University of Texas at Austin
//
//  This software may be freely copied, modified and redistributed under the
//  terms of the GNU Library General Public License.
//
//  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.
//
//--------------------------------------------------------------------------
//
// Support routines to render a PLplot byte stream, interpreting the PLplot
// metacode.
//
// Although this code is duplicated to some extent by plrender and the
// plot buffer redraw code (in plbuf.c), they are all different in some
// significant ways, namely:
//
//    - plrender must always retain backward compatibility code to
//      handle old metafiles, as well as stuff to support seeking.
//
//    - plbuf by definition is redrawing on the same machine so no
//      special effort is necessary to make the byte stream portable,
//      also, only low-level graphics calls are used (grline, etc)
//
// The rendering code here must (by contrast to plbuf) use the high level
// plot routines (as does plrender), to support local zooms as well as the
// ability to dump the associated window into plot space to a file, but is
// otherwise pretty minimal.  A portable byte stream is used since network
// communication over a socket may be used.
//
//--------------------------------------------------------------------------

//
// #define DEBUG
// #define DEBUG_ENTER
//

#include "plserver.h"
#include "plevent.h"
#include "metadefs.h"

// Some wrapper macros to return (-1) on error

// Note we use %lu and an explicit cast to unsigned long to print size_t pointers.
// C99 adds %zd as an explicit format specifier for size_t but this is not yet
// fully adopted.

#define plr_rd( code )                                                                           \
    if ( code ) { fprintf( stderr,                                                               \
                      "Unable to read from %s in %s at line %d, bytecount %lu\n",                \
                      plr->iodev->typeName, __FILE__, __LINE__, (unsigned long) plr->pdfs->bp ); \
                  return -1; }

#define plr_cmd( code ) \
    if ( ( code ) == -1 ) return -1;

// Error termination

#define barf( msg )                                               \
    { fprintf( stderr, "%s\nCommand code: %d, byte count: %lu\n", \
          msg, csave, (unsigned long) plr->pdfs->bp ); return -1; }

// Static function prototypes.

static int      plr_process1( PLRDev *plr, int c );
static int      plr_init( PLRDev *plr );
static int      plr_line( PLRDev *plr, int c );
static int      plr_eop( PLRDev *plr );
static int      plr_bop( PLRDev *plr );
static int      plr_state( PLRDev *plr );
static int      plr_esc( PLRDev *plr );
static int      plr_get( PLRDev *plr );
static int      plr_unget( PLRDev *plr, U_CHAR c );
static int      get_ncoords( PLRDev *plr, PLFLT *x, PLFLT *y, PLINT n );
static int      plresc_fill( PLRDev *plr );

// variables

static int     csave = -1;
static U_CHAR  dum_uchar;
static U_SHORT dum_ushort;
static PLFLT   xstatic[PL_MAXPOLY], ystatic[PL_MAXPOLY];

//--------------------------------------------------------------------------
// plr_start()
//
// Set default state parameters before anyone else has a chance to.
//--------------------------------------------------------------------------

void
plr_start( PLRDev *plr )
{
    dbug_enter( "plr_start" );

    plr->xmin = 0;
    plr->xmax = PIXELS_X - 1;
    plr->ymin = 0;
    plr->ymax = PIXELS_Y - 1;

    plr->xold = PL_UNDEFINED;
    plr->yold = PL_UNDEFINED;
}

//--------------------------------------------------------------------------
// plr_process()
//
// Read & process commands until plr->nbytes bytes have been read.
//--------------------------------------------------------------------------

int
plr_process( PLRDev *plr )
{
    int c;

    dbug_enter( "plr_process" );

    while ( plr->pdfs->bp < (size_t) plr->nbytes )
    {
        plr_cmd( c = plr_get( plr ) );
        csave = c;
        plr_cmd( plr_process1( plr, c ) );
    }
    return 0;
}

//--------------------------------------------------------------------------
// plr_process1()
//
// Process a command.  Note: because of line->polyline compression, this
// may actually process an arbitrary number of LINE or LINETO commands.
// Since the data buffer (fifo or socket) is only flushed after a complete
// command, there should be no danger in rushing blindly ahead to execute
// each plot command.
//--------------------------------------------------------------------------

static int
plr_process1( PLRDev *plr, int c )
{
    switch ( c )
    {
    case INITIALIZE:
        plr_cmd( plr_init( plr ) );
        break;

    case LINE:
    case LINETO:
    case POLYLINE:
        plr_cmd( plr_line( plr, c ) );
        break;

    case EOP:
        plr->at_eop = 1;
        plr_cmd( plr_eop( plr ) );
        break;

    case BOP:
        plr->at_bop = 1;
        plr_cmd( plr_bop( plr ) );
        break;

    case CHANGE_STATE:
        plr_cmd( plr_state( plr ) );
        break;

    case ESCAPE:
        plr_cmd( plr_esc( plr ) );
        break;

    default:
        fprintf( stderr, "plr_process1: Unrecognized command code %d\n", c );
    }

    return 0;
}

//--------------------------------------------------------------------------
// void plr_init()
//
// Handle initialization.
//--------------------------------------------------------------------------

static int
plr_init( PLRDev *plr )
{
    char tk_magic[80], tk_version[80], tag[80];

    dbug_enter( "plr_init" );

// Read header info

    plr_cmd( pdf_rd_header( plr->pdfs, tk_magic ) );
    if ( strcmp( tk_magic, PLSERV_HEADER ) )
        barf( "plr_init: Invalid header" );

// Read version field of header.  We need to check that we can read the
// byte stream, in case this is an old version of plserver.

    plr_cmd( pdf_rd_header( plr->pdfs, tk_version ) );
    if ( strcmp( tk_version, PLSERV_VERSION ) > 0 )
    {
        fprintf( stderr,
            "Error: incapable of reading output of version %s.\n", tk_version );
        barf( "plr_init: Please obtain a newer copy of plserver." );
    }

// Read tagged initialization info.
// Overkill, but a no-brainer since plrender already uses this

    for (;; )
    {
        plr_cmd( pdf_rd_header( plr->pdfs, tag ) );
        if ( *tag == '\0' )
            break;

        if ( !strcmp( tag, "xmin" ) )
        {
            plr_rd( pdf_rd_2bytes( plr->pdfs, &dum_ushort ) );
            plr->xmin = (short) dum_ushort;
            continue;
        }

        if ( !strcmp( tag, "xmax" ) )
        {
            plr_rd( pdf_rd_2bytes( plr->pdfs, &dum_ushort ) );
            plr->xmax = (short) dum_ushort;
            continue;
        }

        if ( !strcmp( tag, "ymin" ) )
        {
            plr_rd( pdf_rd_2bytes( plr->pdfs, &dum_ushort ) );
            plr->ymin = (short) dum_ushort;
            continue;
        }

        if ( !strcmp( tag, "ymax" ) )
        {
            plr_rd( pdf_rd_2bytes( plr->pdfs, &dum_ushort ) );
            plr->ymax = (short) dum_ushort;
            continue;
        }

        if ( !strcmp( tag, "width" ) )
        {
            plr_rd( pdf_rd_1byte( plr->pdfs, &dum_uchar ) );
            plwidth( dum_uchar );
            continue;
        }

        barf( "plr_init: Unrecognized initialization tag." );
    }

    return 0;
}

//--------------------------------------------------------------------------
// plr_line()
//
// Draw a line or polyline.
//--------------------------------------------------------------------------

static int
plr_line( PLRDev *plr, int c )
{
    int     c1;
    U_SHORT npts;
    PLFLT   *x, *y;

    // "Temporary" logic until can figure out what value of npts will
    // actually be required which would allow use of malloc whenever
    // that npts value > PL_MAXPOLY.
    x = xstatic;
    y = ystatic;

    npts = 1;
    x[0] = plr->xold;
    y[0] = plr->yold;

    switch ( (int) c )
    {
    case LINE:
        plr_cmd( get_ncoords( plr, x, y, 1 ) );
    // n.b. falls through to LINETO case.

    case LINETO:
        for (;; )
        {
            plr_cmd( get_ncoords( plr, x + npts, y + npts, 1 ) );

            npts++;
            if ( npts == PL_MAXPOLY || ( plr->pdfs->bp == (size_t) plr->nbytes ) )
                break;

            plr_cmd( c1 = plr_get( plr ) );
            if ( c1 != LINETO )
            {
                plr_cmd( plr_unget( plr, (U_CHAR) c1 ) );
                break;
            }
        }
        break;

    case POLYLINE:
        plr_rd( pdf_rd_2bytes( plr->pdfs, &npts ) );
        plr_cmd( get_ncoords( plr, x, y, npts ) );
        break;
    }

    plline( npts, x, y );

    plr->xold = x[npts - 1];
    plr->yold = y[npts - 1];

    return 0;
}

//--------------------------------------------------------------------------
// get_ncoords()
//
// Read n coordinate vectors.
//--------------------------------------------------------------------------

#define plr_rdn( code )                                           \
    if ( code ) { fprintf( stderr,                                \
                      "Unable to read from %s in %s at line %d, bytecount %d\n\
Bytes requested: %d\n", plr->iodev->typeName, __FILE__, __LINE__, \
                      (int) plr->pdfs->bp, (int) 2 * n ); return -1; }

static int
get_ncoords( PLRDev *plr, PLFLT *x, PLFLT *y, PLINT n )
{
    PLINT i;
    short _xs[PL_MAXPOLY], _ys[PL_MAXPOLY];
    short *xs, *ys;

    if ( n > PL_MAXPOLY )
    {
        xs = (short *) malloc( sizeof ( short ) * (size_t) n );
        ys = (short *) malloc( sizeof ( short ) * (size_t) n );
    }
    else
    {
        xs = _xs;
        ys = _ys;
    }

    plr_rdn( pdf_rd_2nbytes( plr->pdfs, (U_SHORT *) xs, n ) );
    plr_rdn( pdf_rd_2nbytes( plr->pdfs, (U_SHORT *) ys, n ) );

    for ( i = 0; i < n; i++ )
    {
        x[i] = xs[i];
        y[i] = ys[i];
    }

    if ( n > PL_MAXPOLY )
    {
        free( xs );
        free( ys );
    }

    return 0;
}

//--------------------------------------------------------------------------
// plr_eop()
//
// Clear screen.
//--------------------------------------------------------------------------

static int
plr_eop( PLRDev * PL_UNUSED( plr ) )
{
    dbug_enter( "plr_eop" );

    pleop();
    return 0;
}

//--------------------------------------------------------------------------
// plr_bop()
//
// Page advancement.
//--------------------------------------------------------------------------

static int
plr_bop( PLRDev *plr )
{
    dbug_enter( "plr_bop" );

// Advance and setup the page

    plbop();
    plvpor( 0., 1., 0., 1. );
    plwind( plr->xmin, plr->xmax, plr->ymin, plr->ymax );

    return 0;
}

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

static int
plr_state( PLRDev *plr )
{
    U_CHAR op;
    int    i;

    plr_rd( pdf_rd_1byte( plr->pdfs, &op ) );

    switch ( op )
    {
    case PLSTATE_WIDTH: {
        U_SHORT width;

        plr_rd( pdf_rd_2bytes( plr->pdfs, &width ) );

        plwidth( width );
        break;
    }

    case PLSTATE_COLOR0: {
        short icol0;

        plr_rd( pdf_rd_2bytes( plr->pdfs, (unsigned short *) &icol0 ) );

        if ( icol0 == PL_RGB_COLOR )
        {
            U_CHAR r, g, b;
            plr_rd( pdf_rd_1byte( plr->pdfs, &r ) );
            plr_rd( pdf_rd_1byte( plr->pdfs, &g ) );
            plr_rd( pdf_rd_1byte( plr->pdfs, &b ) );
            plscol0( icol0, r, g, b );
        }
        else
        {
            plcol0( icol0 );
        }
        break;
    }

    case PLSTATE_COLOR1: {
        U_SHORT icol1;
        PLFLT   col1;

        plr_rd( pdf_rd_2bytes( plr->pdfs, &icol1 ) );
        col1 = (double) icol1 / (double) plsc->ncol1;
        plcol1( col1 );
        break;
    }

    case PLSTATE_FILL: {
        signed char patt;

        plr_rd( pdf_rd_1byte( plr->pdfs, (U_CHAR *) &patt ) );
        plpsty( patt );
        break;
    }

    case PLSTATE_CMAP0: {
        U_SHORT ncol0;

        plr_rd( pdf_rd_2bytes( plr->pdfs, &ncol0 ) );
        plscmap0n( (PLINT) ncol0 );
        for ( i = 0; i < plsc->ncol0; i++ )
        {
            plr_rd( pdf_rd_1byte( plr->pdfs, &plsc->cmap0[i].r ) );
            plr_rd( pdf_rd_1byte( plr->pdfs, &plsc->cmap0[i].g ) );
            plr_rd( pdf_rd_1byte( plr->pdfs, &plsc->cmap0[i].b ) );
        }
        plP_state( PLSTATE_CMAP0 );
        break;
    }

    case PLSTATE_CMAP1: {
        U_SHORT ncol1, ncp1;
        float   h, l, s;
        U_CHAR  rev;

        plr_rd( pdf_rd_2bytes( plr->pdfs, &ncol1 ) );
        plscmap1n( (PLINT) ncol1 );
        for ( i = 0; i < plsc->ncol1; i++ )
        {
            plr_rd( pdf_rd_1byte( plr->pdfs, &plsc->cmap1[i].r ) );
            plr_rd( pdf_rd_1byte( plr->pdfs, &plsc->cmap1[i].g ) );
            plr_rd( pdf_rd_1byte( plr->pdfs, &plsc->cmap1[i].b ) );
        }
        // Get the control points
        plr_rd( pdf_rd_2bytes( plr->pdfs, &ncp1 ) );
        plsc->ncp1 = ncp1;
        for ( i = 0; i < plsc->ncp1; i++ )
        {
            plr_rd( pdf_rd_ieeef( plr->pdfs, &h ) );
            plr_rd( pdf_rd_ieeef( plr->pdfs, &l ) );
            plr_rd( pdf_rd_ieeef( plr->pdfs, &s ) );
            plr_rd( pdf_rd_1byte( plr->pdfs, &rev ) );

            plsc->cmap1cp[i].c1           = h;
            plsc->cmap1cp[i].c2           = l;
            plsc->cmap1cp[i].c3           = s;
            plsc->cmap1cp[i].alt_hue_path = rev;
        }
        plP_state( PLSTATE_CMAP1 );
        break;
    }
    }

    return 0;
}

//--------------------------------------------------------------------------
// plr_esc()
//
// Handle all escape functions.
// Only those that require additional data to be read need to be
// explicitly handled; the others are merely passed on to the actual
// driver.
//--------------------------------------------------------------------------

static int
plr_esc( PLRDev *plr )
{
    U_CHAR op;

    plr_rd( pdf_rd_1byte( plr->pdfs, &op ) );

    switch ( op )
    {
    case PLESC_FILL:
        plr_cmd( plresc_fill( plr ) );
        break;

    default:
        pl_cmd( (PLINT) op, NULL );
        break;
    }

    return 0;
}

//--------------------------------------------------------------------------
// plresc_fill()
//
// Fill polygon described in points plsc->dev_x[] and plsc->dev_y[].
//--------------------------------------------------------------------------

static int
plresc_fill( PLRDev *plr )
{
    U_SHORT npts;
    PLFLT   *x, *y;

    dbug_enter( "plresc_fill" );

    plr_rd( pdf_rd_2bytes( plr->pdfs, &npts ) );
    if ( npts > PL_MAXPOLY )
    {
        x = (PLFLT *) malloc( sizeof ( PLFLT ) * npts );
        y = (PLFLT *) malloc( sizeof ( PLFLT ) * npts );
    }
    else
    {
        x = xstatic;
        y = ystatic;
    }
    get_ncoords( plr, x, y, npts );
    plfill( npts, x, y );

    if ( npts > PL_MAXPOLY )
    {
        free( x );
        free( y );
    }
    return 0;
}

//--------------------------------------------------------------------------
// plr_get()
//
// Read & return the next command
//--------------------------------------------------------------------------

static int
plr_get( PLRDev *plr )
{
    int c;

    c = pdf_getc( plr->pdfs );
    if ( c == EOF )
    {
        barf( "plr_get: Unable to read character" );
    }

    return c;
}

//--------------------------------------------------------------------------
// plr_unget()
//
// Push back the last command read.
//--------------------------------------------------------------------------

static int
plr_unget( PLRDev *plr, U_CHAR c )
{
    if ( pdf_ungetc( c, plr->pdfs ) == EOF )
    {
        barf( "plr_unget: Unable to push back character" );
    }

    return 0;
}
