/* ibm1130_plot.c: IBM 1130 1627 plotter emulation

   Based on the SIMH simulator package written by Robert M Supnik

   Brian Knittel
   Revision History

   2004.10.22 - Written.
   2006.1.2 - Rewritten as plotter routine by Carl V Claunch
   2012.11.23 - added -d option in detach, which we'll use in the CGI simulator. BK.

 * (C) Copyright 2004, Brian Knittel.
 * You may freely use this program, but: it offered strictly on an AS-IS, AT YOUR OWN
 * RISK basis, there is no warranty of fitness for any purpose, and the rest of the
 * usual yada-yada. Please keep this notice and the copyright in any distributions
 * or modifications.
 *
 * This is not a supported product, but I welcome bug reports and fixes.
 * Mail to simh@ibm1130.org
 */

#include "ibm1130_defs.h"

#ifndef ENABLE_PLOT_SUPPORT

    DEVICE plot_dev = {
        "PLOT", NULL, NULL, NULL,
        0, 16, 16, 1, 16, 16,
        NULL, NULL, NULL,
        NULL, NULL, NULL, NULL,
        DEV_DIS};

    void xio_1627_plotter   (int32 addr, int32 func, int32 modify)
    {
        /* silently eat any plotter commands */
    }

#else

#define NONDLL      // I am linking statically to avoid some issues.
#include "gd.h"

/***************************************************************************************
 *  1627 model 1 plotter (based on Calcomp 535 which was sold as IBM 1627)
 *
 *  - 11" wide carriage, addressible in .01" steps
 *  - continous sheet paper up to 120' in length
 *  - sheet moveable in .01" steps, either direction
 *  - switchable pen, in various colors and line widths
 *
 *  Notice that the WIDTH is 11" and the LENGTH can be anything up to 120'. And, the WIDTH
 *  was the plotter's Y direction, and the LENGTH was the plotter's X direction.
 * 
 *  The simulator creates a GIF image corresponding to a landscape mode sheet of paper. That is,
 *  the plotter's Y direction is the image's horizontal dimension, and the plotter's X direction
 *  is the image's vertical dimension. The WIDTH of the image is always 1100 pixels (11 inches at
 *  100 dpi), and the LENGTH (height) of the image can be set. The default is 800 pixels (8 
 *  inches at 100 dpi). A diagram of more than 8" in length (X direction) will span more than 
 *  one printed page in landscape mode. 
 *
 *  When an 'att plot' command is issued a file is created based on the
 *  default or currently set values of paper length, pen position, pen color and pen width.
 *  
 *  When a 'det plot' command is issued, the plotter image will be
 *  written to the GIF that was created during the attach process. The
 *  image is not viewable until this point. (You could implement an EXAMINE PLOT command
 *  of some sort to write out an intermediate version of the image, but this is not currently
 *  implemented).
 *  
 *  The 'set plot' command can set pen width, paper length, pen color,
 *  current carriage X and Y coordinates, as discussed below. Paper length can be set
 *  to alter the default of 800 (8"); changes are ignored until
 *  the next 'attach' command. The current carriage x and y positions
 *  can be set at any time and will go into effect immediately, just
 *  as the pen color and pen width can be altered on the fly.
 *
 * NOTE: requires the libgd library and definition of ENABLE_PLOT_SUPPORT in makefile or Visual C configuration
 * gd source is not included in the main simh and ibm1130.org source distributions at the present time due to
 * licensing issues.
 *
 * NOTE: On Windows, you need to either:
 *  +   compile both LIBGD and SIMH to use the static C runtime libraries, compile
 *         LIBGD to a static library, and link LIBGD into ibm1130.exe (which is
 *         what we do at IBM1130.org, so that gd is built into the version of ibm1130.exe
 *         we distribute), or, 
 *  +   Compile both LIBGD and IBM1130 to use the DLL version of the C runtime, and compile
 *         GD to either a static library or a DLL, but, static is easier since you don't
 *         need to copy LIBGD.DLL along with ibm1130.exe
 *
 * SIMH commands:
 *
 * attach [-w] plot filename.gif
 *      Creates file filename.gif and attaches the plotter device to it.
 *      The file is empty at this point. The pen is raised. If the -w option is specified, and the 
 *      simulator does not draw on the plotter between attach and detach, the gif file will be deleted
 *      on detach. (This is useful for the the cgi version of the simulator).
 *
 * detach plot filename.gif
 *      Detach the plot device. The gif data is written at this point, not before.
 *      If the -w flag was used on attach, and there was no plot activity, the gif file will be deleted.
 *
 * set plot black | red | blue | green | yellow | purple | lgrey | grey
 *      Sets the pen to the named color. Default is black.
 *
 * set plot 1.0 | 2.0 | 3.0 | 4.0
 *      Sets the pen thickness to the specified number of hundredths of an inch. Default is 1.0
 *
 * set plot penup | pendown
 *      Moves the pen up or down (onto the paper).
 *
 * set plot length NNN
 *      Sets the plot length (plotter X direction, GIF vertical dimension) to the NNN hundredths of 
 *      an inch. Default is 800. The plot width (plotter Y direction, GIF horizontal dimension) is always 
 *      1100 (11 inches). NOTE: Changing this setting has no affect on the current plot. It takes affect at 
 *      the next "attach plot" command.
 *
 * set plot xpos NNN
 * set plot ypos NNN
 *      Sets the pen x or y position to NNN hundredths of an inch.
 * 
 * (You cannot manually create a plot by issuing set plot pendown, xpos and ypos commands. The xpos and ypos
 *  settings only change the starting point for the simulated program).
 ***************************************************************************************/

#define PLOT1627_DSW_OP_COMPLETE            0x8000
#define PLOT1627_DSW_BUSY                   0x0200
#define PLOT1627_DSW_NOT_READY              0x0100

#define IS_ONLINE(u) (((u)->flags & (UNIT_ATT|UNIT_DIS)) == UNIT_ATT)
#define IS_DEBUG     ((plot_unit->flags & UNIT_DEBUG) == UNIT_DEBUG)
#define IS_PENDOWN   ((plot_unit->flags & UNIT_PEN) != 0)

static t_stat plot_svc    (UNIT *uptr);             /* activity routine */
static t_stat plot_reset  (DEVICE *dptr);           /* reset of 1130 */
static t_stat plot_attach (UNIT *uptr, CONST char *cptr);   /* attach, loads plotter */
static t_stat plot_detach (UNIT *uptr);             /* detach and save image */
static t_stat plot_examine (UNIT *uptr);            /* update file with current canvas */
static t_stat plot_set_length (UNIT *uptr, int32 val, CONST char * ptr, void *desc);  /* set paper length */
static t_stat plot_set_pos (UNIT *uptr, int32 val, CONST char * ptr, void *desc);       /* reset current X/Y position */
static t_stat plot_show_vals(FILE *fp, UNIT *uptr, int32 val, CONST void *descrip); /* print x, y and length */
static t_stat plot_show_nl(FILE *fp, UNIT *uptr, int32 val, CONST void *descrip);   /* overcome wacky simh behavior */
static void   update_pen(void);                      /* will ensure pen action is correct when changes made */
static t_stat plot_validate_change (UNIT *uptr, int32 val, CONST char * ptr, void *desc); /* when set command issued */
static void   process_cmd(void);                    /* does actual drawing for plotter */

static int16 plot_dsw  = 0;                         /* device status word */
static int16 plot_cmd  = 0;                         /* the command to process */
static int32 plot_wait = 1000;                      /* plotter movement wait */
static int32 plot_xpos = 0;                         /* current X position */
static int32 plot_xmax = 799;                       /* end of paper */
static int32 plot_ypos = 0;                         /* current Y position */
static int32 plot_ymax = 1099;                      /* right edge of carriage */

#define PEN_DOWN 0x80000000
#define PEN_UP   0x00000000
static int32 plot_pen = PEN_UP;                     /* current pen position. This duplicates the device flag PLOT_PEN. Makes the show dev plot command nicer. */

static int black_pen;                               /* holds color black */
static int blue_pen;                                /* holds color blue */
static int red_pen;                                 /* holds color red */
static int green_pen;                               /* holds color green */
static int yellow_pen;                              /* holds yellow color */
static int purple_pen;                              /* holds color purple */
static int ltgrey_pen;                              /* holds light grey */
static int grey_pen;                                /* holds grey */
static int white_background;                        /* holds white of paper roll */
static int plot_pwidth;                             /* set and display variable */
static int plot_pcolor;                             /* set and display variable */
static int need_update = FALSE;                     /* flag to force and update_pen() */
static int plot_used = FALSE;                       /* flag set to true if anything was actually plotted between attach and detach */
static int delete_if_unused = FALSE;                /* if TRUE and no plotter activity was seen, delete file on detach. This flag is set by -w option on attach command. */
static gdImagePtr image = NULL;                     /* pointer to our canvas */

#define UNIT_V_COLOR    (UNIT_V_UF + 0)             /* color of selected pen - 3 bits */
#define UNIT_V_WIDTH    (UNIT_V_UF + 3)             /* width of pen - two bits */
#define UNIT_V_NOOP     (UNIT_V_UF + 5)             /* dummy for set/show commands */
#define UNIT_V_DEBUG    (UNIT_V_UF + 6)             /* for -d switch on attach command */
#define UNIT_V_PEN      (UNIT_V_UF + 7)             /* track pen state */

#define UNIT_WIDTH       (3u << UNIT_V_WIDTH)       /* two bits */
#define UNIT_COLOR       (7u << UNIT_V_COLOR)       /* three bits */
#define UNIT_NOOP        (1u << UNIT_V_NOOP)        /* dummy for set/show */
#define UNIT_DEBUG       (1u << UNIT_V_DEBUG)       /* shows debug mode on */
#define UNIT_PEN         (1u << UNIT_V_PEN)         /* the pen state bit */

#define PEN_BLACK        (0u << UNIT_V_COLOR)
#define PEN_RED          (1u << UNIT_V_COLOR)
#define PEN_BLUE         (2u << UNIT_V_COLOR)
#define PEN_GREEN        (3u << UNIT_V_COLOR)
#define PEN_YELLOW       (4u << UNIT_V_COLOR)
#define PEN_PURPLE       (5u << UNIT_V_COLOR)
#define PEN_LTGREY       (6u << UNIT_V_COLOR)
#define PEN_GREY         (7u << UNIT_V_COLOR)

#define SET_COLOR(op)   (plot_unit[0].flags = (plot_unit[0].flags & ~UNIT_COLOR) | (op))
#define GET_COLOR       (plot_unit[0].flags & UNIT_COLOR)

#define BLACK           0,0,0
#define BLUE            0,0,255
#define RED             255,0,0
#define GREEN           0,255,0
#define YELLOW          200,200,0
#define PURPLE          150,0,150
#define LTGREY          200,200,200
#define GREY            120,120,120
#define WHITE           255,255,255

#define PEN_SINGLE      (0u << UNIT_V_WIDTH)
#define PEN_DOUBLE      (1u << UNIT_V_WIDTH)
#define PEN_TRIPLE      (2u << UNIT_V_WIDTH)
#define PEN_QUAD        (3u << UNIT_V_WIDTH)

#define GET_WIDTH()     (plot_unit[0].flags & UNIT_WIDTH)
#define SET_WIDTH(cd)   (plot_unit[0].flags = (plot_unit[0].flags & ~UNIT_WIDTH) | (cd))

UNIT plot_unit[] = {
    { UDATA (&plot_svc, UNIT_ATTABLE, 0) },
};

REG plot_reg[] = {
    { HRDATA (DSW,      plot_dsw,  16) },           /* device status word */
    { DRDATA (WTIME,    plot_wait, 24), PV_LEFT },  /* plotter movement wait */
    { DRDATA (Xpos, plot_xpos,  32), PV_LEFT },     /* Current X Position*/
    { DRDATA (Ypos, plot_ypos,  32), PV_LEFT },     /* Current Y Position*/
    { FLDATA (PenDown, plot_pen, 0)},               /* Current pen position: 1 = down */
    { DRDATA (PaperSize, plot_xmax, 32), PV_LEFT }, /* Length of paper in inches */
    { NULL }  };

MTAB plot_mod[] = {
    { UNIT_COLOR,    PEN_BLACK,     "black",    "BLACK",    &plot_validate_change},
    { UNIT_COLOR,    PEN_RED,       "red",      "RED",      &plot_validate_change},
    { UNIT_COLOR,    PEN_BLUE,      "blue",     "BLUE",     &plot_validate_change},
    { UNIT_COLOR,    PEN_GREEN,     "green",    "GREEN",    &plot_validate_change},
    { UNIT_COLOR,    PEN_YELLOW,    "yellow",   "YELLOW",   &plot_validate_change},
    { UNIT_COLOR,    PEN_PURPLE,    "purple",   "PURPLE",   &plot_validate_change},
    { UNIT_COLOR,    PEN_LTGREY,    "ltgrey",   "LTGREY",   &plot_validate_change},
    { UNIT_COLOR,    PEN_GREY,      "grey",     "GREY",     &plot_validate_change},
    { UNIT_WIDTH,    PEN_SINGLE,    "1.0",      "1.0",      &plot_validate_change},
    { UNIT_WIDTH,    PEN_DOUBLE,    "2.0",      "2.0",      &plot_validate_change},
    { UNIT_WIDTH,    PEN_TRIPLE,    "3.0",      "3.0",      &plot_validate_change},
    { UNIT_WIDTH,    PEN_QUAD,      "4.0",      "4.0",      &plot_validate_change},
    { UNIT_PEN,      UNIT_PEN,      "pendown",  "PENDOWN",  &plot_validate_change},
    { UNIT_PEN,      0,             "penup",    "PENUP",    &plot_validate_change},
    /* below is dummy entry to trigger the show routine and print extended values */
    { UNIT_NOOP,     0,              "",        NULL,       NULL, &plot_show_vals},
    /* extended entries must allow parm for both unit and dev, but
     * then they will print the value twice for a 'show plot' command
     * therefore they are set to not display unless explicity requested
     * and the special dummy NOOP entry will cause the print of these values */
    { MTAB_XTD | MTAB_VALR | MTAB_VUN | MTAB_VDV | MTAB_NMO,   2,
             "length",  "LENGTH", &plot_set_length, &plot_show_nl, &plot_reg[5]},
    { MTAB_XTD | MTAB_VALR | MTAB_VDV | MTAB_VUN | MTAB_NMO,   0,
             "Xpos",    "XPOS", &plot_set_pos, &plot_show_nl, &plot_reg[2]},
    { MTAB_XTD | MTAB_VALR | MTAB_VDV | MTAB_VUN | MTAB_NMO,   1,
             "Ypos",    "YPOS", &plot_set_pos, &plot_show_nl, &plot_reg[3]},
    { 0 }  };

DEVICE plot_dev = {
    "PLOT", plot_unit, plot_reg, plot_mod,
    1, 16, 16, 1, 16, 16,
    NULL, NULL, plot_reset,
    NULL, plot_attach, plot_detach};

/* xio_1627_plotter - XIO command interpreter for the 1627 plotter model 1 */

void xio_1627_plotter (int32 iocc_addr, int32 iocc_func, int32 iocc_mod)
{
    char msg[80];
    int16 v;

    if (! IS_ONLINE(plot_unit) ) {
        SETBIT(plot_dsw, PLOT1627_DSW_NOT_READY);                   /* set not ready */
        if (IS_DEBUG) printf("Plotter has no paper, ignored\n");
        return;                                                     /* and ignore */
    }

    switch (iocc_func) {
        case XIO_READ:                                              /* read XIO */
            xio_error("Read XIO not supported by 1627 plotter");
            break;

        case XIO_WRITE:                                             /* write: do one plotter operation */
            if ((plot_dsw & PLOT1627_DSW_NOT_READY)) {
                 if (IS_DEBUG) printf("Wrote to non-ready Plotter\n");
                 break;
            }
            plot_cmd = (uint16) ( M[iocc_addr & mem_mask] >> 10 );  /* pick up command */
            process_cmd();                                          /* interpret command */
            sim_activate(plot_unit, plot_wait);                     /* schedule interrupt */
            SETBIT(plot_dsw, PLOT1627_DSW_BUSY);                    /* mark it busy */
            break;

        case XIO_SENSE_DEV:                                         /* sense device status */
            ACC = plot_dsw;                                         /* get current status */
            if (iocc_mod & 0x01) {                                  /* reset interrupts */
                CLRBIT(plot_dsw, PLOT1627_DSW_OP_COMPLETE); 
                CLRBIT(ILSW[3], ILSW_3_1627_PLOTTER);
            }
            break;

        case XIO_CONTROL:                                           /* control XIO */
            // xio_error("Control XIO not supported by 1627 plotter");
            // Well, not on a real 1130. But on our simulator, let's use XIO_CONTROL to
            // allow programmatic control of the pen. Nifty, eh?
            //
            // Functions: XIO_CONTROL 0 clr     - sets pen color (0=black, 1=red, 2=blue, 3=green, 4=yellow, 5=purple, 6=ltgrey, 7=grey)
            //            XIO_CONTRLL 1 wid     - sets pen width (1..4)
            //            XIO_CONTROL 2 xpos    - sets pen xpos
            //            XIO_CONTROL 3 ypos    - sets pen ypos

            v = (int16) iocc_addr;                                  /* get signed 16 bit value passed in addr arg */
            switch (iocc_mod) {
                case 0:                                             /* set pen color */
                    if (BETWEEN(v,0,7)) {
                        SET_COLOR(v << UNIT_V_COLOR);
                        update_pen();
                    }
                    break;

                case 1:                                             /* set pen width 1..4*/
                    if (BETWEEN(v,1,4)) {
                        SET_WIDTH((v-1) << UNIT_V_WIDTH);
                        update_pen();
                    }
                    break;

                case 2:                                             /* set xpos. (Programmatic xpos and ypos are probably not that valuable) */
                    plot_xpos = v;                                  /* Note that it's possible to move the pen way off the paper */
                    break;

                case 3:                                             /* set ypos. Clip to valid range! */
                    if (v <= 0)
                        plot_ypos = 0;
                    else if (v > plot_ymax)
                        plot_ypos = plot_ymax;
                    else
                        plot_ypos = v;
                    break;
            }
            break;

        default:
            sprintf(msg, "Invalid 1627 Plotter XIO function %x", iocc_func);
            xio_error(msg);
    }
    return;
}

// plot_svc - 1627 plotter operation complete

static t_stat plot_svc (UNIT *uptr)
{
    CLRBIT(plot_dsw, PLOT1627_DSW_BUSY);            /* clear reader busy flag */

    SETBIT(plot_dsw, PLOT1627_DSW_OP_COMPLETE);     /* indicate read complete */

    SETBIT(ILSW[3], ILSW_3_1627_PLOTTER);           /* initiate interrupt */
    calc_ints();

    return SCPE_OK;
}

/* plot_reset - reset emulated plotter */

static t_stat plot_reset (DEVICE *dptr)
{
#ifdef NONDLL
    static int show_notice = FALSE;

    if (show_notice && ! cgi) {
        printf("Plotter support included. Please see www.libgd.org for libgd copyright information.\n");
        show_notice = FALSE;
    }
#endif

    sim_cancel(plot_unit);

    CLRBIT(plot_dsw, PLOT1627_DSW_BUSY | PLOT1627_DSW_OP_COMPLETE);

    if (IS_DEBUG) printf("reset routine for Plotter\n");

    CLRBIT(ILSW[3], ILSW_3_1627_PLOTTER);
    calc_ints();

    return SCPE_OK;
}


/* plot_attach - attach file to simulated plotter */

static t_stat plot_attach (UNIT *uptr, CONST char *cptr)
{
    t_stat result;

    SETBIT(plot_dsw, PLOT1627_DSW_NOT_READY);               /* assume failure */

    CLRBIT(uptr->flags, UNIT_DEBUG);
    if (sim_switches & SWMASK('D')) SETBIT(uptr->flags, UNIT_DEBUG);

    if (cptr == NULL || ! *cptr)                            /* filename must be passed */
        return SCPE_ARG;

    /* set up our canvas at the desired size */
    image = gdImageCreate(plot_ymax+1,plot_xmax+1);         /* create our canvas */
    if (image == NULL) {
       if (IS_DEBUG) printf("problem creating image canvas\n");
       return SCPE_MEM;
    }

    delete_if_unused = (sim_switches & SWMASK('W')) != 0;

    remove(cptr);                                           /* delete file if it already exists. Otherwise, attach_unit() would open r+w */
    /* get the output file by using regular attach routine */
    result = attach_unit(uptr, cptr);

    if (result != SCPE_OK) {
       if (IS_DEBUG) printf("problem attaching file\n");
       gdImageDestroy(image);           /* free up the canvas memory */
       image = NULL;
       return result;
    }

    /* set up the basic colors after image created */
    /* (by the way, these calls don't allocate any memory in or out of the image buffer. They just populate its "colors-used" table */
    white_background = gdImageColorAllocate(image,WHITE);   /* white is background */
    black_pen  = gdImageColorAllocate(image,BLACK);         /* load up black color */
    blue_pen   = gdImageColorAllocate(image,BLUE);          /* load up blue color */
    red_pen    = gdImageColorAllocate(image,RED);           /* load up red color */
    green_pen  = gdImageColorAllocate(image,GREEN);         /* load up green color */
    yellow_pen = gdImageColorAllocate(image,YELLOW);        /* load up yellow color */
    purple_pen = gdImageColorAllocate(image,PURPLE);        /* load up purple color */
    ltgrey_pen = gdImageColorAllocate(image,LTGREY);        /* load up light grey color */
    grey_pen   = gdImageColorAllocate(image,GREY);          /* load up grey color */

    if ( (white_background == -1) || (black_pen == -1) ||
         (red_pen    == -1) || (blue_pen == -1) || (green_pen == -1) ||
         (purple_pen == -1) || (ltgrey_pen == -1) || (grey_pen == -1) ) {
           if (IS_DEBUG) printf("problem allocating pen colors\n");
           return SCPE_MEM;
    }

    CLRBIT(plot_dsw, PLOT1627_DSW_NOT_READY);               /* we're in business */

    plot_pen = PEN_UP;
    CLRBIT(plot_unit->flags, UNIT_PEN);

    update_pen();                                           /* routine to ensure pen is okay */
    plot_used = FALSE;                                      /* plotter page is blank */
    return SCPE_OK;
}

/* pen updating routine, called at attach and whenever we reset the values */

static void update_pen (void)
{
    int color;
    int width;

     if (! IS_ONLINE(plot_unit)) return;     /* only do this if attached */

     /* pick up latest color as active pen */
     color = GET_COLOR;
     switch (color) {
         case PEN_BLACK:
              plot_pcolor = black_pen;
              break;

         case PEN_RED:
              plot_pcolor = red_pen;
              break;

         case PEN_BLUE:
              plot_pcolor = blue_pen;
              break;

         case PEN_GREEN:
              plot_pcolor = green_pen;
              break;

         case PEN_YELLOW:
              plot_pcolor = yellow_pen;
              break;

         case PEN_PURPLE:
              plot_pcolor = purple_pen;
              break;

         case PEN_LTGREY:
              plot_pcolor = ltgrey_pen;
              break;

         case PEN_GREY:
              plot_pcolor = grey_pen;
              break;

         default:
              if (IS_DEBUG) printf("invalid pen color state\n");
              plot_pcolor = black_pen;
              break;
     }

     /* set up anti-aliasing for the line */
     gdImageSetAntiAliased(image, plot_pcolor);

     /* pick up latest width for pen */
     width = GET_WIDTH();
     switch (width) {
         case PEN_SINGLE:
             plot_pwidth = 1;
             gdImageSetThickness(image, 1);
             break;

         case PEN_TRIPLE:
             plot_pwidth = 3;
             gdImageSetThickness(image, 3);
             break;

         case PEN_DOUBLE:
             plot_pwidth = 2;
             gdImageSetThickness(image, 2);
             break;

         case PEN_QUAD:
             plot_pwidth = 4;
             gdImageSetThickness(image, 4);
             break;

         default:
             if (IS_DEBUG) printf("invalid pen width\n");
             plot_pwidth = 1;
             gdImageSetThickness(image, 1);
             break;
     }

     /* now ensure the pen state is accurate */
     plot_pen = IS_PENDOWN ? PEN_DOWN : PEN_UP;
     return;
}

/* plot_detach - detach file from simulated plotter */

static t_stat plot_detach (UNIT *uptr)
{
    char * buf, * fname;
    int32 size, result, saveit;
    FILE * fp;
    t_stat rval = SCPE_OK;          /* return value */

    SETBIT(plot_dsw, PLOT1627_DSW_NOT_READY);

    if (! (uptr->flags & UNIT_ATT)) /* not currently attached; don't proceed */
        return SCPE_OK;

                                    /* if -w flag was passed on attach: save file if there was plotter activity, otherwise delete it */
                                    /* if -w flag was not passed on attached, always save the file */
    saveit = (plot_used || ! delete_if_unused) && (image != NULL);

    if (saveit) {                   /* copy images to files, close files, set device to detached, free gd memory */
        if ((buf = gdImageGifPtr(image,&size)) == NULL) {
           if (IS_DEBUG) printf("failure creating GIF in-memory\n");
           return SCPE_MEM;
        }

        fp = uptr->fileref;         /* get file attached to unit */

        if (fseek(fp,0,SEEK_SET) == 0) {        /* first we reset to begin of file */
            if (IS_DEBUG) printf("wrote out GIF to file\n");
            result = fwrite(buf,1,size,fp);     /* write out our image to the file */
        }
        else
            result = 0;             /* make it look like the write failed so we return error status */
    
        gdFree(buf);                /* free up the memory of GIF format */
    }
    else {                          /* make a copy of the filename so we can delete it after detach */
        if ((fname = malloc(strlen(uptr->filename)+1)) != NULL)
            strcpy(fname, uptr->filename);
    }

    if (image != NULL) {
        gdImageDestroy(image);      /* free up the canvas memory */
        image = NULL;
    }

    rval = detach_unit(uptr);       /* have simh close the file */

    if (saveit) {                   /* if we wrote the file, check that write was OK */
        if (result != size) {       /* report error writing file */
            if (IS_DEBUG) printf("error in write of image file\n");
            rval = SCPE_IOERR;
        }
    }
    else {                          /* if we did not write the file, delete the file */
        if (fname == NULL) {
            rval = SCPE_MEM;        /* we previously failed to allocate a copy of the filename (this will never happen) */
        }
        else {
            remove(fname);          /* remove the file and free the copy of the filename */
            free(fname);
        }
    }

    return rval;
}

/* process_cmd - implement the drawing actions of the plotter */

static void process_cmd (void)
{
    int32 oldx, oldy;

    /* first see if we set any changes to pen or position, do an update */
    if (need_update) {
       update_pen();
       need_update = FALSE;
    }

    /* will move pen one step or flip pen up or down */
    oldx = plot_xpos;
    oldy = plot_ypos;

    switch (plot_cmd) {
        case 1:            /* raise pen command */
             plot_pen = PEN_UP;
             CLRBIT(plot_unit->flags, UNIT_PEN);
             return;
             break;

        case 2:            /* +Y command */
             ++plot_ypos;
             break;

        case 4:            /* -Y command */
             --plot_ypos;
             break;

        case 8:            /* -X command */
             --plot_xpos;
             break;

        case 10:            /* -X +Y command */
             --plot_xpos;
             ++plot_ypos;
             break;

        case 12:            /* -X -Y command */
             --plot_xpos;
             --plot_ypos;
             break;

        case 16:            /* +X command */
             ++plot_xpos;
             break;

        case 18:            /* +X +Y command */
             ++plot_xpos;
             ++plot_ypos;
             break;

        case 20:            /* +X -Y pen command */
             ++plot_xpos;
             --plot_ypos;
             break;

        case 32:            /* lower pen command */
             plot_pen = PEN_DOWN;
             SETBIT(plot_unit->flags, UNIT_PEN);
             return;
             break;

        default:
             if (IS_DEBUG) printf("invalid plotter command\n");
             return;
             break;
    }

    /* On the real plotter, y motions were physically restricted at the ends of travel.
     * We simulate this by clipping the plot_ypos value. Three +y movements at the right
     * end of travel followed by three -y movements will back up 3 positions, just as it would have on
     * the physical plotter. Without clipping, the pen would end up where it started, which 
     * is incorrect. (Hopefully, good 1130 plotting software would never make this happen anyhow!)
     */

     if (plot_ypos < 0)
        plot_ypos = 0;
     else if (plot_ypos > plot_ymax)
        plot_ypos = plot_ymax;

    /* We do allow X overtravel though, as the drum simply turned past the end of the paper. Three +x
     * movements past the end of the paper followed by three -x movements would put the pen back at the
     * edge of the paper.
     */

     if ((plot_xpos < 0) || (plot_xpos > plot_xmax)) {
           /* if so, ignore as 1627 has no way of signalling error */
           if (IS_DEBUG) printf(
              "attempted to move carriage off paper edge %d %d for command %d\n",
              plot_xpos,plot_ypos,plot_cmd);

           return;      // no drawing takes place if the pen is off of the paper!
    }

    /* only draw a line if the pen was down during the movement command */
    if (plot_pen) {
        gdImageLine(image, plot_ymax-plot_ypos, plot_xmax-plot_xpos, plot_ymax-oldy, plot_xmax-oldx, gdAntiAliased);
        plot_used = TRUE;                                       /* remember that we drew something */
       /* semantics are 0,0 point is lower right */
    }

    return;
}

/* plot_set_length - validate and store the length of the paper */

static t_stat plot_set_length (UNIT *uptr, int32 set, CONST char *ptr, void *desc)
{
    CONST char *cptr;
    int32 val;

#define LONGEST_ROLL 1440000                    /* longest is 120', 14400", 1,440,000 .01"s */

    if (ptr == NULL) {                          /* check for missing argument */
        printf("Command format is: set plot length=nnn\n");
        return SCPE_ARG;
    }

    val = strtotv (ptr, &cptr, (uint32) 10);   /* sim routine to get value */
    if ((val < 1) | (val >= LONGEST_ROLL)) {   /* check valid range */
        if (IS_DEBUG) printf("setting paper more than 120' or less than 1 inch\n");
        return SCPE_ARG;
    }

    /* origin zero drawing, reduce by 1 but show command will fudge by adding it back */
    *((int32 *)((REG *) desc)->loc) = val - 1;

    return SCPE_OK;
}

/* plot_set_pos - validate and store the new position of the carriage */

static t_stat plot_set_pos (UNIT *uptr, int32 set, CONST char *ptr, void *desc)
{
    CONST char *cptr;
    int32 val;
    int32 max;

    max = (set == 1) ? plot_ymax : plot_xmax;
    val = strtotv (ptr, &cptr, (uint32) 10);
    if ((val < 0) | (val > max)) {
        if (IS_DEBUG) printf("error moving carriage off paper edge\n");
            return SCPE_ARG;
    }

    *((int32 *)((REG *) desc)->loc) = val;

    return SCPE_OK;
}

/* routine to display the paper length and carriage position
 * cannot use regular simh routine because it prints values twice,
 * once for device and once for unit
 */

static t_stat plot_show_vals (FILE *fp, UNIT *uptr, int32 val, CONST void *descrip)
{
    fprintf(fp, "length=%d, Xpos=%d, Ypos=%d",plot_xmax+1, plot_xpos,plot_ypos);
    return SCPE_OK;
}

/* routine to add a terminating NL character when 'show plot length'
 * or equivalent for xpos or ypos is issued, as simh will not append for us */

static t_stat plot_show_nl(FILE *fp, UNIT *uptr, int32 val, CONST void *descrip)
{
    int32 disp;
    char *label;

    disp  = (val == 2) ? plot_xmax + 1 : ((val == 1) ? plot_ypos : plot_xpos);
    label = (val == 2) ? "length=" : ((val == 1) ? "Ypos=" : "Xpos=");

    fprintf(fp, "%s%d\n", label, disp);
    return SCPE_OK;
}

/* plot_validate_change - force the update_pen routine to be called after user changes pen setting */

static t_stat plot_validate_change (UNIT *uptr, int32 set, CONST char *ptr, void *desc)
{
    need_update = TRUE;
    return SCPE_OK;
}

#endif /* ENABLE_PLOT_SUPPORT */