/*
 * $Id: browser.c,v 1.1.1.1 2005/09/18 22:04:26 dhmunro Exp $
 * Main for GIST CGM viewer
 */
/* Copyright (c) 2005, The Regents of the University of California.
 * All rights reserved.
 * This file is part of yorick (http://yorick.sourceforge.net).
 * Read the accompanying LICENSE file for details.
 */

#include <string.h>
#include "pstdlib.h"
#include "pstdio.h"

#include "xbasic.h"
#include "cgm.h"

/* OpenCGM, ReadCGM, CGMRelative, cgmLandscape, bg0fg1 in cgmin.h */
#include "cgmin.h"
/* GpEPSEngine declared in eps.h */
#include "eps.h"

extern int cgmScaleToFit;
extern int gtDoEscapes;

/* List of commands in alphabetical order, 0 terminated */
static char *commandList[]= {
  "cgm", "display", "draw", "eps", "free", "help", "info", "open",
  "ps", "quit", "send", 0 };

/* Corresponding functions */
static int MakeCGM(int help);
static int MakeX(int help);
static int Draw(int help);
static int EPS(int help);
static int FreeAny(int help);
static int Help(int help);
static int Info(int help);
static int OpenIn(int help);
static int MakePS(int help);
static int Quit(int help);
static int Send(int help);
static int Special(int help);  /* must correspond to 0 in commandList */
static int nPrefix, cSuffix;   /* arguments for Special */
typedef int (*Command)(int help);
static Command CommandList[]= {
  &MakeCGM, &MakeX, &Draw, &EPS, &FreeAny, &Help, &Info, &OpenIn,
  &MakePS, &Quit, &Send, &Special };
static int GetCommand(char *token);

int amBatch= 0;

int warningCount= 0;
int no_warnings= 0;

static int CreateCGM(int device, char *name);
static int CreatePS(int device, char *name);
static int CreateX(int device, char *name);

static int GetPageGroup(char *token);
static int CheckEOL(char *command);
static void DrawSend(int ds, char *token);
static int GetDeviceList(int ds, char *command);
static int FindDevice(void);
static int SaveName(int device, char *name);
static void DoSpecial(int nPrefix, int cSuffix);

static int xPrefix= 0;
#ifndef NO_XLIB
static void HandleExpose(Engine *engine, Drauing *drauing, int xy[]);
static void HandleOther(Engine *engine, int k, int md);
#endif
static int HelpAndExit(void);
static int MessageAndExit(char *msg);

extern int on_idle(void);
extern int on_quit(void);
extern void on_stdin(char *line);
static int did_startup = 0;
extern int do_startup(void);

int defaultDPI;

char *outNames[8];
int outLengths[8];
int outDraw[8], outSend[8], outMark[8];

int nPageGroups= 0;
int mPage[32], nPage[32], sPage[32];
int n_active_groups = 0;

static int nOut, noDisplay, x_only;
static char *inName;

int
on_launch(int argc, char *argv[])
{
  int i, j;
  char *arg;

  nOut= 0;
  for (i=0 ; i<8 ; i++) {
    outNames[i]= 0;
    outEngines[i]= 0;
    outDraw[i]= outSend[i]= outLengths[i]= 0;
  }
  noDisplay= amBatch= x_only= 0;
  defaultDPI= 100;
  inName= 0;

  p_quitter(&on_quit);
  p_idler(&on_idle);
  p_stdinit(&on_stdin);
  g_stdout = p_stdout;

  for (i=1 ; i<argc ; i++) {
    arg= argv[i];

    if (arg[0]=='-') {
      int fileType= -1;
      arg++;
      if (strcmp(arg, "cgm")==0) {
        fileType= 0;
        i++;
        if (i>=argc) return MessageAndExit("Missing file or display name");
        arg= argv[i];
      } else if (strcmp(arg, "ps")==0) {
        fileType= 1;
        i++;
        if (i>=argc) return MessageAndExit("Missing file or display name");
        arg= argv[i];
      } else if (strcmp(arg, "in")==0) {
        i++;
        if (i>=argc) return MessageAndExit("Missing file or display name");
        if (inName) return HelpAndExit();
        else inName= argv[i];
      } else if (strcmp(arg, "display")==0 || strcmp(arg, "d")==0) {
        fileType= 2;
        i++;
        if (i>=argc) return MessageAndExit("Missing file or display name");
        arg= argv[i];
      } else if (strcmp(arg, "f")==0) {
        amBatch= 1;
        fileType= 1;
        arg= "*stdout*";
      } else if (strcmp(arg, "nd")==0) noDisplay= 1;
      else if (strcmp(arg, "b")==0) amBatch= 1;
      else if (strcmp(arg, "nowarn")==0) no_warnings= 1;
      else if (strcmp(arg, "geometry")==0) {
#ifndef NO_XLIB
        char *suffix;
        int w=0,h=0;
        i++;
        if (i>=argc) MessageAndExit("Missing geometry");
        arg = argv[i];
        w = (int)strtol(arg, &suffix, 10);
        if (suffix!=arg && *suffix=='x') {
          arg = suffix+1;
          h = (int)strtol(arg, &suffix, 10);
        }
        if (w < 10 || h < 10) MessageAndExit("Invalid geometry");
        gx75width = gx100width = w;
        gx75height = gx100height = h;
#else
        MessageAndExit("-geometry illegal, no interactive graphics");
#endif
      } else if (strcmp(arg, "75")==0) defaultDPI= 75;
      else if (strcmp(arg, "100")==0) defaultDPI= 100;
      else if (strcmp(arg, "gks")==0) {
#ifndef NO_XLIB
        gx75width= gx75height= 600;     /* 8x8 X window size */
        gx100width= gx100height= 800;   /* 8x8 X window size */
#endif
        cgmScaleToFit= 1;               /* 8x8 PostScript plotting area */
        gtDoEscapes= 0;
      } else if (strcmp(arg, "x")==0) x_only= 1;
      else if (strcmp(arg, "gks")==0) {
        cgmScaleToFit= 1;
        gtDoEscapes= 0;
      }
      else if (strcmp(arg, "fmbug")==0) epsFMbug= 1;
      else if (strcmp(arg, "bg0fg1")==0) bg0fg1= 1;
      else if (strcmp(arg, "esc0")==0) gtDoEscapes= 0;
      else if (strcmp(arg, "esc1")==0) gtDoEscapes= 1;
      else return HelpAndExit();

      if (fileType>=0) {
        if (nOut>=8)
          return MessageAndExit("At most 8 output files/displays allowed");
        for (j=0 ; j<nOut ; j++) if (strcmp(outNames[j], arg)==0)
          return MessageAndExit("Duplicate output filenames not allowed");
        outNames[nOut]= arg;
        outTypes[nOut]= fileType;
        nOut++;
      }

    } else if (arg[0]<='9' && arg[0]>='0') {
      if (GetPageGroup(arg)) return MessageAndExit("Try again");

    } else if (!inName) {
      inName= arg;

    } else {
      return HelpAndExit();
    }
  }

  if (inName && OpenCGM(inName)) inName= 0;

  if (amBatch) {
    if (!inName)
      return MessageAndExit("Must specify an input CGM file to use -b or -f");
    if (!nOut)
      return MessageAndExit("Must specify some output file to use -b");
    noDisplay= 1;
  }

  /* Create CGM and PostScript engines */
  for (i=0 ; i<nOut ; i++) {
    if (outTypes[i]==0) CreateCGM(i, outNames[i]);
    else if (outTypes[i]==1) CreatePS(i, outNames[i]);
  }

  /* If page list specified, do implied send command */
  if (amBatch && nPageGroups<=0) {
    mPage[0]= 1;
    nPage[0]= 32767;
    sPage[0]= 1;
    nPageGroups= 1;
  }
  if (nPageGroups>0) {
    for (i=0 ; i<8 ; i++) {
      if (!outSend[i]) GpDeactivate(outEngines[i]);
      if (outSend[i] && !GpActivate(outEngines[i])) n_active_groups++;
    }
  }

#ifndef NO_XLIB
  g_initializer(&argc, argv);
#else
  {
    extern char *g_argv0;
    g_argv0 = argv? argv[0] : 0;
  }
#endif

  return 0;
}

int
do_startup(void)
{
  int i;
  did_startup = 1;

  if (!noDisplay) {
    int noX= 1;
    for (i=0 ; i<nOut ; i++) if (outTypes[i]==2) {
      if (!CreateX(i, outNames[i])) noX= 0;
    }
    if (noX && nOut<8) {
      if (!CreateX(nOut, 0)) {
        nOut++;
        noX= 0;
      }
    }
    noDisplay= noX;
  }
  if (noDisplay && x_only)
    return MessageAndExit("Must be an X display to use -x");
  if (x_only && !inName)
    return MessageAndExit("Must specify an input CGM file to use -x");

  if (n_active_groups)
    ReadCGM(mPage, nPage, sPage, nPageGroups);
  return 0;
}

static int need_prompt = 1;

int
on_idle(void)
{
  int flag;
  if (!did_startup && do_startup()) return 0;
  if (need_prompt && !x_only && !amBatch) p_stdout("gist> ");
  need_prompt = 0;
  flag = CatalogCGM();
  if (!flag && amBatch) p_quit();
  return flag;
}

int
on_quit(void)
{
  int i;
  for (i=0 ; i<8 ; i++) {
    if (outEngines[i]) {
      GpDeactivate(outEngines[i]);
      GpKillEngine(outEngines[i]);
    }
  }

  return 0;
}

/* ------------------------------------------------------------------------ */

static int HelpAndExit(void)
{
  p_stderr("Usage:   gist [cgminput] [options] [page list]\n\n");
  p_stderr("   options:  -in cgminput  (open cgminput for input)\n");
  p_stderr("             -cgm cgmout   (open cgmout for output)\n");
  p_stderr("             -ps psout     (open psout for output)\n");
  p_stderr("             -display host:n.m  (connect to X server)\n");
  p_stderr("             -75  (default to 75 dpi)\n");
  p_stderr("             -100 (default to 100 dpi)\n");
  p_stderr("             -gks (default to 8x8 inches)\n");
  p_stderr("             -geometry WxH (initial window size in pixels)\n");
  p_stderr("             -nd  (not even default X server)\n");
  p_stderr("             -b   (batch mode, implies -nd)\n");
  p_stderr("             -x   (graphics window only, no keyboard)\n");
  p_stderr("             -nowarn (only one warning message printed)\n");
  p_stderr("             -f   (PostScript to stdout, implies -b)\n");
  p_stderr("             -fmbug (if EPS files are for FrameMaker)\n");
  p_stderr("             -bg0fg1 (force color 0 to bg, 1 to fg)\n");
  p_stderr("             -esc0 (skip ^_! escapes-assumed if -gks)\n");
  p_stderr("             -esc1 (handle ^_! text escapes-default)\n");
  p_stderr("   page list:  [page group] [page group] ...\n");
  p_stderr("   page group:  n     (output page n)\n");
  p_stderr("   page group:  n-m   (output pages n->m inclusive)\n");
  p_stderr("   page group:  n-m-s (output every s-th page, n-m)\n");
  p_quit();
  return 1;
}

static int MessageAndExit(char *msg)
{
  p_stderr("gist: ");
  p_stderr(msg);
  p_stderr("\ngist: try    gist -help    for usage\n");
  p_quit();
  return 1;
}

void Warning(char *general, char *particular)
{
  if (++warningCount > 5) return;
  if (no_warnings) {
    if (no_warnings==1)
      p_stderr("gist: (WARNING) rerun without -nowarn to see warnings");
    no_warnings= 2;
    return;
  }
  p_stderr("gist: (WARNING) ");
  p_stderr(general);
  p_stderr(particular);
  p_stderr("\n");
}

/* ------------------------------------------------------------------------ */

static int GetCommand(char *token)
{
  char **command= commandList;
  char *cmd;
  char *now= token;
  int quitOK= 0;

  if (!now) return -1;

  /* Quit has two synonyms, ? is a synonym for help, and f, b, and g
     are special command forms.  */
  if (strcmp(token, "exit")==0 || strcmp(token, "end")==0 ||
      strcmp(token, "quit")==0) {
    /* Quit and its synonyms cannot be abbreviated */
    now= "quit";
    quitOK= 1;
  } else if (strcmp(token, "?")==0) now= "help";
  else if (strcmp(token, "f")==0) now= "1f";
  else if (strcmp(token, "b")==0) now= "1b";
  else if (strcmp(token, "g")==0) now= "1g";
  else if (strcmp(token, "s")==0) now= "send";

  /* Check for nf, nb, ng */
  if (*now<='9' && *now>='0') {
    char *suffix;
    nPrefix= (int)strtol(now, &suffix, 10);
    cSuffix= *suffix;
    if ((cSuffix=='f' || cSuffix=='b' || cSuffix=='g') &&
        *(suffix+1)=='\0') {
      while (*command) command++;
      return command-commandList;
    }
  }

  cmd= *command;
  while (cmd && *now>*cmd) cmd= *(++command);
  while (cmd && *now==*cmd) {
    while (*cmd && *now==*cmd) { now++;  cmd++; }
    if (!*now) break;  /* token matches cmd */
    now= token;
    cmd= *(++command);
  }
  if (!cmd || *now) {
    p_stderr("gist: Unknown command: ");
    p_stderr(token);
    p_stderr("\n      Type help for help.\n");
    return -1;
  }

  if (*cmd) {
    now= token;
    cmd= *(command+1);
    if (cmd && *now++==*cmd++) {
      while (*cmd && *now==*cmd) { now++;  cmd++; }
      if (!*now) {
        p_stderr("gist: Ambiguous command: ");
        p_stderr(token);
        p_stderr("\n      Type help for help.\n");
        return -1;
      }
    }
  }

  if (strcmp(*command, "quit")==0 && !quitOK) {
    p_stderr("gist: The quit command cannot be abbreviated.\n");
    return -1;
  }

  return command-commandList;
}

static int GetPageGroup(char *token)
{
  int n, m, s;
  char *suffix, *tok;

  s= 1;
  n= m= (int)strtol(token, &suffix, 10);
  if (suffix!=token && *suffix=='-') {
    tok= suffix+1;
    n= (int)strtol(tok, &suffix, 10);
    if (suffix!=tok && *suffix=='-') {
      tok= suffix+1;
      s= (int)strtol(tok, &suffix, 10);
      if (suffix==tok) suffix= tok-1;  /* this is an error */
    }
  }

  if (*suffix) {
    p_stderr("gist: (SYNTAX) ");
    p_stderr(token);
    p_stderr(" is not a legal page number group\n");
    return 1;
  }

  if (nPageGroups>=32) {
    p_stderr("gist: (SYNTAX) too many page number groups (32 max)\n");
    return 1;
  }

  mPage[nPageGroups]= m;
  nPage[nPageGroups]= n;
  sPage[nPageGroups]= s;
  nPageGroups++;
  return 0;
}

static int CheckEOL(char *command)
{
  if (strtok(0, " \t\n")) {
    p_stderr("gist: (SYNTAX) garbage after ");
    p_stderr(command);
    p_stderr(" command\n");
    return 1;
  } else {
    return 0;
  }
}

static int Help(int help)
{
  int cmd;

  if (help) {
    p_stderr("gist: help command syntax:\n     help [command]\n");
    p_stderr("  Print help message (for given command).\n");
    return 0;
  }

  cmd= GetCommand(strtok(0, " \t\n"));

  if (cmd<0) {
    int len;
    char line[80], **command= commandList;
    p_stderr("gist: Input Syntax:\n     command [arg1] [arg2] ...\n");
    strcpy(line, "  Available commands are:  ");
    len= 27;
    for (;;) {
      for (;;) {
        if (len+strlen(*command) > 72) {
          strcpy(line+len, "\n");
          p_stderr(line);
          break;
        }
        strcpy(line+len, *command);
        len+= strlen(*command++);
        if (!*command) {
          strcpy(line+len, "\n");
          p_stderr(line);
          break;
        }
        strcpy(line+len, ", ");
        len+= 2;
      }
      if (!*command) break;
      strcpy(line, "     ");
      len= 5;
    }
    p_stderr("  The quit command has two synonyms:  exit, end\n");
    p_stderr("  Any command except quit may be abbreviated by\n");
    p_stderr("  omitting characters from the right.\n");
    p_stderr("  The help command explains specific syntax, e.g.:\n");
    p_stderr("     help cgm\n");
    p_stderr("  describes the syntax of the cgm command.\n");
    p_stderr("  Five commands can be typed to a gist X window:\n");
    p_stderr("     nf   - forward n pages and draw (default 1)\n");
    p_stderr("     nb   - backward n pages and draw (default 1)\n");
    p_stderr("     ng   - go to page n and draw (default 1)\n");
    p_stderr("     s    - send current page\n");
    p_stderr("     q    - quit\n");

  } else {
    CommandList[cmd](1);
  }

  return 0;
}

static char line[256];

void
on_stdin(char *lin)
{
  int cmd;

  line[0] = '\0';
  strncat(line, lin, 255);
  cmd= GetCommand(strtok(line, " \t\n"));

  warningCount= xPrefix= 0;
  if (cmd>=0 && CommandList[cmd](0))
    p_quit();

  need_prompt = 1;
}

static int Quit(int help)
{
  if (help) {
    p_stderr("gist: quit command syntax:\n     quit\n");
    p_stderr("  Finish and close any output files, then exit.\n");
    p_stderr("  Synonyms:  exit, end   (no abbreviations allowed)\n");
    return 0;
  }
  return 1;
}

/* ------------------------------------------------------------------------ */

static int SaveName(int device, char *name)
{
  int len;

  if (name == outNames[device]) return 0;

  len= name? (int)strlen(name) : 0;
  if (len>outLengths[device]) {
    if (outLengths[device]>0) p_free(outNames[device]);
    outNames[device]= (char *)p_malloc(len+1);
    if (!outNames[device]) {
      p_stderr("gist: (SEVERE) memory manager failed in SaveName\n");
      outLengths[device]= 0;
      return 1;
    }
    outLengths[device]= len;
  }

  if (name) strcpy(outNames[device], name);
  else if (outLengths[device]>0) outNames[device][0]= '\0';
  else outNames[device]= 0;
  return 0;
}

static int CreateCGM(int device, char *name)
{
  if (SaveName(device, name)) return 1;

  outEngines[device]=
    GpCGMEngine("CGM Viewer", cgmLandscape, 0, name);
  if (!outEngines[device]) {
    Warning(gistError, "");
    Warning("Unable to create CGM engine ", name);
    return 1;
  }

  outTypes[device]= 0;
  outDraw[device]= 0;
  outSend[device]= 1;

  return 0;
}

static int CreatePS(int device, char *name)
{
  if (SaveName(device, name)) return 1;

  outEngines[device]=
    GpPSEngine("CGM Viewer", cgmLandscape, 0, name);
  if (!outEngines[device]) {
    Warning(gistError, "");
    Warning("Unable to create PostScript engine ", name);
    return 1;
  }

  outTypes[device]= 1;
  outDraw[device]= 0;
  outSend[device]= 1;

  return 0;
}

static int CreateX(int device, char *name)
{
#ifndef NO_XLIB
  if (SaveName(device, name)) return 1;

  gist_input_hint = 1;
  outEngines[device]=
    GpBXEngine("CGM Viewer", cgmLandscape, defaultDPI, name);
  if (!outEngines[device]) {
    Warning(gistError, "");
    Warning("Unable to open X server ", name? name : "(default)");
    return 1;
  }

  outTypes[device]= 2;
  outDraw[device]= 1;
  outSend[device]= 0;

  GxInput(outEngines[device], &HandleExpose, 0, 0, &HandleOther);

  return 0;
#else
  return 1;
#endif
}

/* ------------------------------------------------------------------------ */

static int OpenIn(int help)
{
  char *token;

  if (help) {
    p_stderr("gist: open command syntax:\n     open cgminput\n");
    p_stderr("  Closes the current CGM input file, then opens cgminput.\n");
    p_stderr("  Only a Gist-compliant binary CGM file is legal.\n");
    p_stderr("  The cgminput may be the first file of a family.\n");
    p_stderr("  Subsequent page numbers refer to this input file.\n");
    return 0;
  }

  token= strtok(0, " \t\n");
  if (!token) {
    p_stderr("gist: (SYNTAX) cgminput name missing in open command\n");
    return 0;
  }
  if (CheckEOL("open")) return 0;

  if (no_warnings) no_warnings= 1;  /* one warning per file family */
  OpenCGM(token);
  return 0;
}

static int FindDevice(void)
{
  int i;
  for (i=0 ; i<8 ; i++) if (!outEngines[i]) break;
  if (i>=8)
    Warning("8 devices already open for output, command ignored", "");
  return i;
}

static int MakeCGM(int help)
{
  char *token, *cgmout;
  long size= 0;
  int device;

  if (help) {
    p_stderr("gist: cgm command syntax:\n     cgm cgmout [size]\n");
    p_stderr("  Opens a CGM file cgmout for output.\n");
    p_stderr("  The size (default 1000000) is the maximum size of a\n");
    p_stderr("  single file in the output family, in bytes.\n");
    p_stderr("  Subsequent send commands will write to cgmout,\n");
    p_stderr("  unless the send to list is modified (see send).\n");
    return 0;
  }

  token= strtok(0, " \t\n");
  if (!token) {
    p_stderr("gist: (SYNTAX) cgmout name missing in cgm command\n");
    return 0;
  }
  cgmout= token;
  token= strtok(0, " \t\n");
  if (token) {
    char *suffix;
    size= strtol(token, &suffix, 0);
    if (*suffix) {
      p_stderr("gist: (SYNTAX) size unintelligble in cgm command\n");
      return 0;
    }
    if (CheckEOL("cgm")) return 0;
  }

  device= FindDevice();
  if (device>=8) return 0;

  if (!CreateCGM(device, cgmout) &&
      size>0) ((CGMEngine *)outEngines[device])->fileSize= size;

  return 0;
}

static int MakePS(int help)
{
  char *token;
  int device;

  if (help) {
    p_stderr("gist: ps command syntax:\n     ps psout\n");
    p_stderr("  Opens a PostScript file psout for output.\n");
    p_stderr("  Subsequent send commands will write to psout,\n");
    p_stderr("  unless the send to list is modified (see send).\n");
    return 0;
  }

  token= strtok(0, " \t\n");
  if (!token) {
    p_stderr("gist: (SYNTAX) psout name missing in ps command\n");
    return 0;
  }
  if (CheckEOL("ps")) return 0;

  device= FindDevice();
  if (device>=8) return 0;

  CreatePS(device, token);

  return 0;
}

static int MakeX(int help)
{
  char *token, *server;
  int dpi, device, defDPI;

  if (help) {
    p_stderr("gist: display command syntax:\n     ");
    p_stderr("display host:server.screen [dpi]\n");
    p_stderr("  Connects to the specified X server.\n");
    p_stderr("  Subsequent draw commands will write to server,\n");
    p_stderr("  unless the draw to list is modified (see draw).\n");
    p_stderr("  If specified, 40<=dpi<=200 (default 100).\n");
    return 0;
  }

  token= strtok(0, " \t\n");
  if (!token) {
    p_stderr("gist: (SYNTAX) cgmoutput name missing in cgm command\n");
    return 0;
  }
  server= token;
  token= strtok(0, " \t\n");
  if (token) {
    char *suffix;
    dpi= (int)strtol(token, &suffix, 0);
    if (*suffix) {
      p_stderr("gist: (SYNTAX) dpi unintelligble in display command\n");
      return 0;
    }
    if (dpi<40 && dpi>200) {
      p_stderr(
        "gist: (SYNTAX) dpi not between 40 and 200 in display command\n");
      return 0;
    }
    if (CheckEOL("display")) return 0;
  } else {
    dpi= 100;
  }

  device= FindDevice();
  if (device>=8) return 0;

  defDPI= defaultDPI;
  defaultDPI= dpi;
  CreateX(device, server);
  defaultDPI= defDPI;

  return 0;
}

static int EPS(int help)
{
  char *token;
  int device;

  if (help) {
    p_stderr("gist: eps command syntax:\n     eps epsout\n");
    p_stderr("  Open an Encapsulated PostScript file epsout, write\n");
    p_stderr("  the current page to it, then close epsout.\n");
    p_stderr("  (Note that an EPS file can have only a single page.)\n");
    return 0;
  }

  token= strtok(0, " \t\n");
  if (!token) {
    p_stderr("gist: (SYNTAX) epsout name missing in eps command\n");
    return 0;
  }
  if (CheckEOL("eps")) return 0;

  device= FindDevice();
  if (device>=8) return 0;

  device= FindDevice();
  if (device>=8) return 0;

  outEngines[device]=
    GpPSEngine("CGM Viewer", cgmLandscape, 0, "_tmp.eps");
  if (!outEngines[device]) {
    Warning(gistError, "");
    Warning("Unable to create PostScript engine ", token);
    return 0;
  }

  GpPreempt(outEngines[device]);

  nPage[0]= mPage[0]= CGMRelative(0);
  sPage[0]= 1;
  nPageGroups= 1;

  /* First read does PS part, second computes EPS preview */
  if (!ReadCGM(mPage, nPage, sPage, nPageGroups)) {
    GpPreempt(0);
    outEngines[device]= EPSPreview(outEngines[device], token);
    if (outEngines[device]) {
      GpPreempt(outEngines[device]);
      ReadCGM(mPage, nPage, sPage, nPageGroups);
    } else {
      Warning("memory manager failed creating EPS engine ", token);
      return 0;
    }
  }

  if (outEngines[device]) {
    GpPreempt(0);

    GpKillEngine(outEngines[device]);
    outEngines[device]= 0;
  }

  return 0;
}

static int FreeAny(int help)
{
  int i;

  if (help) {
    p_stderr("gist: free command syntax:\n     free [device# ...]\n");
    p_stderr("  Finish and close the device#(s).  If none given,\n");
    p_stderr("  frees all send devices,\n");
    p_stderr("  (Use the info command to describe current device numbers.)\n");
    return 0;
  }

  if (GetDeviceList(1, "free")) return 0;

  for (i=0 ; i<8 ; i++) {
    if (outMark[i]) {
      GpKillEngine(outEngines[i]);
      outEngines[i]= 0;
    }
  }

  return 0;
}

static char *yorn[2]= { "No  ", "Yes " };
static char *tname[3]= { "CGM", "PS ", "X  " };

extern void CGMinfo(void);  /* cgmin.c */

static int Info(int help)
{
  int i;

  if (help) {
    p_stderr("gist: info command syntax:\n     info\n");
    p_stderr("  Print descriptions of all current output files.\n");
    return 0;
  }

  p_stdout("\n");
  CGMinfo();
  p_stdout("Number Draw Send Type   Name\n");
  for (i=0 ; i<8 ; i++) {
    if (outEngines[i]) {
      char msg[80];
      sprintf(msg, "%3d    %s %s %s  ", i, yorn[outDraw[i]], yorn[outSend[i]],
             tname[outTypes[i]]);
      p_stdout(msg);
      p_stdout(outNames[i]? outNames[i] : "");
      p_stdout("\n");
    }
  }
  p_stdout("\n");

  return 0;
}

/* ------------------------------------------------------------------------ */

static int Draw(int help)
{
  int i, n;
  char *token;

  if (help) {
    p_stderr("gist: draw command syntax:\n     draw [page list]\n");
    p_stderr(
    "  Copy the page(s) (default current page) from the current CGM input\n");
    p_stderr("  to all display output devices.\n");
    p_stderr("  By default, these are all X windows.\n");
    p_stderr("  Use alternate syntax:\n     draw to [device#1 ...]\n");
    p_stderr("  to specify a particular list of devices to be used\n");
    p_stderr("  by the draw command.  Without any device numbers,\n");
    p_stderr("  draw to restores the default list of devices.\n");
    p_stderr("  (Use the info command to describe current device numbers.)\n");
    p_stderr("  Page list syntax:   group1 [group2 ...]\n");
    p_stderr("  Page group syntax:   n   - just page n\n");
    p_stderr("                     m-n   - pages n thru m\n");
    p_stderr("                   m-n-s   - pages n thru m, step s\n");
    return 0;
  }

  token= strtok(0, " \t\n");
  if (token && strcmp(token, "to")==0) {
    DrawSend(1, token);
    return 0;
  }

  n= 0;
  for (i=0 ; i<8 ; i++) {
    if (!outDraw[i]) GpDeactivate(outEngines[i]);
    if (outDraw[i] && !GpActivate(outEngines[i])) n++;
  }

  if (!n && (i= FindDevice())<8) {
    if (!CreateX(i, 0) && !GpActivate(outEngines[i])) n++;
  }

  if (n) DrawSend(0, token);
  else Warning("no devices active for draw command", "");
  return 0;
}

static int Send(int help)
{
  int i, n;
  char *token;

  if (help) {
    p_stderr("gist: send command syntax:\n     send [page list]\n");
    p_stderr(
    "  Copy the page(s) (default current page) from the current CGM input\n");
    p_stderr("  to all display output devices.\n");
    p_stderr("  By default, these are all X windows.\n");
    p_stderr("  Use alternate syntax:\n     send to [device#1] ...\n");
    p_stderr("  to specify a particular list of devices to be used\n");
    p_stderr("  by the send command.  Without any device numbers,\n");
    p_stderr("  send to restores the default list of devices.\n");
    p_stderr("  (Use the info command to describe current device numbers.)\n");
    p_stderr("  Page list syntax:   group1 [group2 ...]\n");
    p_stderr("  Page group syntax:   n   - just page n\n");
    p_stderr("                     m-n   - pages n thru m\n");
    p_stderr("                   m-n-s   - pages n thru m, step s\n");
    return 0;
  }

  token= strtok(0, " \t\n");
  if (token && strcmp(token, "to")==0) {
    DrawSend(1, token);
    return 0;
  }

  n= 0;
  for (i=0 ; i<8 ; i++) {
    if (!outSend[i]) GpDeactivate(outEngines[i]);
    if (outSend[i] && !GpActivate(outEngines[i])) n++;
  }

  if (n) DrawSend(1, token);
  else Warning("no devices active for send command", "");
  return 0;
}

static int GetDeviceList(int ds, char *command)
{
  int device;
  char *token= strtok(0, " \t\n");

  if (token) {
    char *suffix;
    for (device=0 ; device<8 ; device++) outMark[device]= 0;
    do {
      device= (int)strtol(token, &suffix, 10);
      if (*suffix || device<0 || device>7) {
        p_stderr("gist: (SYNTAX) ");
        p_stderr(token);
        p_stderr(" not a legal device# in ");
        p_stderr(command);
        p_stderr(" command\n");
        return 1;
      }
      if (!outEngines[device])
        Warning("ignoring non-existent device# ", token);
      else
        outMark[device]= 1;
    } while ((token= strtok(0, " \t\n")));
    if (ds==0) {
      for (device=0 ; device<8 ; device++) if (outMark[device]) break;
      if (device>=8) {
        Warning(command, " command with no legal devices, no action taken");
        return 1;
      }
    }

  } else if (ds==0) {
    for (device=0 ; device<8 ; device++) outMark[device]= 0;
  } else {
    for (device=0 ; device<8 ; device++) outMark[device]= outSend[device];
  }

  return 0;
}

static char *dsName[]= { "draw to", "send to" };

static void DrawSend(int ds, char *token)
{
  nPageGroups= 0;

  if (token && strcmp(token, "to")==0) {
    /* draw to  or send to  merely resets outDraw or outSend list */
    int i, n= 0;
    int *outDS= ds? outSend : outDraw;
    if (GetDeviceList(0, dsName[ds])) return;
    for (i=0 ; i<8 ; i++) if ((outDS[i]= outMark[i])) n++;
    if (!n) for (i=0 ; i<8 ; i++)
      outDS[i]= outEngines[i]? (ds? outTypes[i]<2 : outTypes[i]==2) : 0;
    return;

  } else if (token) {
    do {
      if (GetPageGroup(token)) return;
    } while ((token= strtok(0, " \t\n")));
  } else {
    nPage[0]= mPage[0]= CGMRelative(0);
    sPage[0]= 1;
    nPageGroups= 1;
  }

  ReadCGM(mPage, nPage, sPage, nPageGroups);
}

static int Special(int help)
{
  if (help) {
    char msg[80];
    sprintf(msg, "gist: n%c command syntax:\n     n%c\n",
            cSuffix, cSuffix);
    p_stderr(msg);
    if (cSuffix=='f')
      p_stderr("  Forward n (default 1) pages, then draw\n");
    else if (cSuffix=='b')
      p_stderr("  Backward n (default 1) pages, then draw\n");
    else
      p_stderr("  Go to page n (default 1), then draw\n");
    return 0;
  }

  if (CheckEOL("nf, nb, or ng")) return 0;

  DoSpecial(nPrefix, cSuffix);
  return 0;
}

static void DoSpecial(int nPrefix, int cSuffix)
{
  int i, n;

  n= 0;
  for (i=0 ; i<8 ; i++) {
    if (!outDraw[i]) GpDeactivate(outEngines[i]);
    if (outDraw[i] && !GpActivate(outEngines[i])) n++;
  }

  if (cSuffix=='f') mPage[0]= CGMRelative(nPrefix);
  else if (cSuffix=='b') mPage[0]= CGMRelative(-nPrefix);
  else mPage[0]= nPrefix;
  nPage[0]= mPage[0];
  sPage[0]= 1;
  nPageGroups= 1;

  if (n) ReadCGM(mPage, nPage, sPage, nPageGroups);
  else Warning("no devices active for nf, nb, or ng command", "");
}

/* ------------------------------------------------------------------------ */

#ifndef NO_XLIB
/* ARGSUSED */
static void HandleExpose(Engine *engine, Drauing *drauing, int xy[])
{
  XEngine *xEngine= GisXEngine(engine);

  if (!xEngine) return;

  /* Redraw current picture on this engine only */

  GpPreempt(engine);

  nPage[0]= mPage[0]= CGMRelative(0);
  sPage[0]= 1;
  nPageGroups= 1;

  ReadCGM(mPage, nPage, sPage, nPageGroups);

  GpPreempt(0);
  GxExpose(engine, drauing, xy);
}

static int cSuffices[]= { 'f', 'b', 'g' };

/* ARGSUSED */
static void HandleOther(Engine *engine, int k, int md)
{
  XEngine *xEngine= GisXEngine(engine);
  int go, bad;

  if (!xEngine) return;

  go= bad= 0;

  if (k == '0') xPrefix= 10*xPrefix;
  else if (k == '1') xPrefix= 10*xPrefix+1;
  else if (k == '2') xPrefix= 10*xPrefix+2;
  else if (k == '3') xPrefix= 10*xPrefix+3;
  else if (k == '4') xPrefix= 10*xPrefix+4;
  else if (k == '5') xPrefix= 10*xPrefix+5;
  else if (k == '6') xPrefix= 10*xPrefix+6;
  else if (k == '7') xPrefix= 10*xPrefix+7;
  else if (k == '8') xPrefix= 10*xPrefix+8;
  else if (k == '9') xPrefix= 10*xPrefix+9;
  else if (k=='f' || k=='F' || (k=='+' && (md&P_KEYPAD))) go= 1;
  else if (k=='b' || k=='B' || (k=='-' && (md&P_KEYPAD))) go= 2;
  else if (k=='g' || k=='G' || k=='\r') go= 3;
  else if (k=='s' || k=='S' || (k=='=' && (md&P_KEYPAD))) go= 4;
  else if (k=='q' || k=='Q') go= 5;
  else bad= 1;

  if ((go==4||go==5) && xPrefix!=0) bad= 1;
  if (go && !bad) {
    if (go<4) {
      if (xPrefix==0) xPrefix= 1;
      DoSpecial(xPrefix, cSuffices[go-1]);
    } else if (go==4) {
      int i, n= 0;
      for (i=0 ; i<8 ; i++) {
        if (!outSend[i]) GpDeactivate(outEngines[i]);
        if (outSend[i] && !GpActivate(outEngines[i])) n++;
      }

      nPage[0]= mPage[0]= CGMRelative(0);
      sPage[0]= 1;
      nPageGroups= 1;

      if (n) ReadCGM(mPage, nPage, sPage, nPageGroups);
      else Warning("no devices active for send command", "");
    } else if (go==5) {
      p_quit();
    }
    xPrefix= 0;
    warningCount= 0;
  } else if (bad) {
    p_feep(xEngine->win);
    xPrefix= 0;
  }
}
#endif

/* ------------------------------------------------------------------------ */
