
/* Copyright (C) 1992  AHPCRC, Univeristy of Minnesota
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program in a file named 'Copying'; if not, write to
 * the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139.
 */

/* Author:
 *	Ken Chin-Purcell (ken@ahpcrc.umn.edu)
 *	Army High Performance Computing Research Center (AHPCRC)
 *	Univeristy of Minnesota
 *
 * $Header: /usr/people/ken/gvl/raz/RCS/raz.c,v 2.1 92/10/19 17:05:38 ken Exp $
 *
 * $Log:	raz.c,v $
 * Revision 2.1  92/10/19  17:05:38  ken
 * *** empty log message ***
 * 
 * Revision 1.1  92/10/19  17:04:40  ken
 * Initial revision
 * 
 */

#include <stdio.h>
#include <string.h>
#include <malloc.h>
#include <unistd.h>
#include <bstring.h>
#include <ctype.h>
#include <signal.h>
#include <sys/prctl.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ulocks.h>

#include <gl.h>
#include <gl/device.h>

#ifdef UTAHRLE
#define USE_STDLIB_H
#define USE_PROTOTYPES
#include <rle.h>
rle_hdr		rleData;
#endif

#include "util.h"


extern int	getpagesize(void);
extern int	sginap(long);
extern int	select(int, fd_set *, fd_set *, fd_set *, struct timeval *);


#define	MATCH(strA, strB) (strncasecmp(strA, strB, MAX(1,strlen(strA))) == 0)

#define CMDSIZE		256

#define PSEUDO8		0
#define RGBBYTE		1
#define RGBPLANE	2

#define	READCMD		0
#define	READCOLOR	1
#define READFILE	2

#define PAINTPLAY	0
#define PAINTBOTH	1

#define RAWDISK 	"/dev/anim0"

static unsigned		ReadMode = READCMD;
static unsigned		ReadIndex;

static unsigned		UsePixmode;
static unsigned		PlayFromRaw = TRUE;
static unsigned		SourceFromRaw = FALSE;
static unsigned		FullColor = FALSE;
static unsigned		ImgFormat = PSEUDO8;
static char		RawDiskName[256];

static unsigned		DimX = 512;
static unsigned		DimY = 512;
static unsigned		FileX = 512;
static float		RepX = 1.0;
static float		RepY = 1.0;

static unsigned		BorderX = 0;
static unsigned		BorderY = 0;

static float		ZoomX = 4.0;
static float		ZoomY = 4.0;
static int		MouseX = 0;
static int		MouseY = 0;

static unsigned		Playing = FALSE;
static struct timeval	PlayTime;
static float		PlaySpeed;
static int		PlayDir = 1;
static unsigned		PlayBounce = FALSE;
static int		CurrFrame = 0;
static int		NumFrame = 0;
static int		StopFrame = -1;
static int		TopToBot = 0;
static unsigned		DoubleBuffer = TRUE;
static unsigned		DbufMainCount = 0;
static unsigned		DbufZoomCount = 0;
static long		MainWinID;
static long		ZoomWinID;
static unsigned		ShowZoom = FALSE;

static char		**Movie;
static char		*Image;
static char		*ImageChunk;
static unsigned		RawTape = FALSE;
static int		RawPosition = 0;
static int		RawFD = -1;
static unsigned long	RawOffset = 0;
static unsigned		BlockSize = 4096;
static unsigned		ChunkSize = 1;
static unsigned		PageSize;
static unsigned		NumRaw;
static unsigned		ImageSize;
static unsigned		FileSize;
static unsigned		NumPixels;
static unsigned		MovieSize;

static usptr_t		*SemaArena;
static usema_t		*ReadSema;
static usema_t		*ImageSema;
static unsigned long	TimerCount = 0;

static int		NumFile = 0;
static char		*FilePrefix = NULL;
static char		**FileName;

static char		*ProgramName;
static unsigned		Quit = FALSE;
static char		Command[CMDSIZE];

static short		SaveMap[256][3];
static unsigned		SaveMapLoaded = FALSE;
static short		Colormap[256][3];
static int		ColorBump = 0;

static int		ReadPID = -1;
static int		ReadFrame = -1;
static int		ReadQuit = 0;

static struct timeval	ZeroTime;
static struct timeval	MarkLast;
static unsigned long	MarkByte;
static unsigned long	MarkFrame;
static int		GLfd = 0;
static unsigned		AckCmd;


static char *NextToken(char **cmd)
{
    static char	token[CMDSIZE];
    char	*tp;
    
    tp = token;
    while (**cmd && isspace(**cmd))
	(*cmd)++;
    while (**cmd && !isspace(**cmd)) {
	*tp = **cmd;
	tp++;
	(*cmd)++;
    }
    *tp = '\0';
    
    return token;
}


static char *RoundToPage(char *p)
{
    unsigned	i = (unsigned) p % PageSize;
    
    if (i)
	return p + PageSize - i;
    return p;
}


static void FreeMovie(void)
{
    unsigned	i;
    
    for (i = 0; i < MovieSize; ++i)
	Free(Movie[i]);
    Free(Movie);
    
    Movie = NULL;
    Image = NULL;
    NumFrame = 0;
    MovieSize = 0;
    CurrFrame = 0;
    ReadFrame = -1;
}


static void CalcSizes(void)
{
    NumPixels = DimX * DimY;
    
    switch (ImgFormat) {
      case PSEUDO8:
	FileSize = NumPixels;
	break;
      case RGBPLANE:
      case RGBBYTE:
	FileSize = NumPixels * 3;
	break;
    }
    
    if (UsePixmode)
	ImageSize = FileSize;
    else
	switch (ImgFormat) {
	  case PSEUDO8:
	    ImageSize = NumPixels * sizeof(short);
	    break;
	  case RGBPLANE:
	  case RGBBYTE:
	    ImageSize = NumPixels * 4;
	    break;
	}
    
    ImageSize = RoundUp(ImageSize, BlockSize);
}


static void ConfigureBuffers(void)
{
    if (MainWinID) {
	winset(MainWinID);
	if (FullColor) {
	    RGBmode();
	    if (UsePixmode)
		pixmode(PM_SIZE, 24);
	} else {
	    cmode();
	    if (UsePixmode) {
		pixmode(PM_SIZE, 8);
		/* pixmode(PM_ADD24, ColorBump); */
	    }
	}
	if (UsePixmode)
	    pixmode(PM_TTOB, TopToBot);
	if (DoubleBuffer)
	    doublebuffer();
	else {
	    if (DbufMainCount % 2 == 1) {
		++DbufMainCount;
		swapbuffers();
	    }
	    singlebuffer();
	}
	gconfig();
	if (FullColor)
	    RGBcolor(0, 0, 0);
	else
	    color(0);
    }
    
    if (ZoomWinID) {
	winset(ZoomWinID);
	if (FullColor)
	    RGBmode();
	else
	    cmode();
	if (DoubleBuffer)
	    doublebuffer();
	else {
	    if (DbufZoomCount % 2 == 1) {
		++DbufZoomCount;
		swapbuffers();
	    }
	    singlebuffer();
	}
	gconfig();
    }
}


static void SetColorMap(void)
{
    register unsigned	i;
    
    if (!MainWinID)
	return;
    winset(MainWinID);
    
    if (!SaveMapLoaded) {
	for (i = 0; i < 256; ++i)
	    getmcolor(i, SaveMap[i], SaveMap[i]+1, SaveMap[i]+2);
	SaveMapLoaded = TRUE;
    }
    
    for (i = 0; i < 256; ++i)
	mapcolor(i /* + ColorBump */, 
		 Colormap[i][0], Colormap[i][1], Colormap[i][2]);
}


static void SetSavedMap(void)
{
    register unsigned	i;
    
    if (!MainWinID || !SaveMapLoaded)
	return;
    winset(MainWinID);
    
    for (i = 0; i < 256; ++i)
	mapcolor(i, SaveMap[i][0], SaveMap[i][1], SaveMap[i][2]);
}


static void MakeMainWin(void)
{
    if (MainWinID)
	return;
    
    prefsize((long)(DimX * RepX) + 2*BorderX, 
	     (long)(DimY * RepY) + 2*BorderY);
    MainWinID = winopen("Raz");
    
    qdevice(REDRAW);
    qdevice(WINQUIT);
    qdevice(WINSHUT);
    qdevice(MOUSEX);
    qdevice(MOUSEY);
    GLfd = qgetfd();
    
    if (ImgFormat == PSEUDO8)
	SetColorMap();
    
    ConfigureBuffers();
}


static void MakeZoomWin(void)
{
    if (ZoomWinID || !ShowZoom || !MainWinID)
	return;
    
    ZoomWinID = winopen("Zoom");
    
    qdevice(REDRAW);
    qdevice(WINQUIT);
    qdevice(WINSHUT);
    
    ConfigureBuffers();
}


static void SetWinSize(void)
{
    long	x1, x2, y1, y2;
    long	scrnX, scrnY;
    long	sizex = (long)(DimX * RepX) + 2*BorderX;
    long	sizey = (long)(DimY * RepY) + 2*BorderY;
    
    if (!MainWinID)
	return;
    winset(MainWinID);
    
    getorigin(&x1, &y1);
    scrnX = getgdesc(GD_XPMAX);
    scrnY = getgdesc(GD_YPMAX);
    
    x2 = x1 + sizex - 1;
    y2 = y1 + sizey - 1;
    if (x2 >= scrnX) {
	x2 = scrnX - 1;
	x1 = x2 - sizex + 1;
	if (x1 < 0) {
	    x1 = -BorderX;
	    x2 = x1 + sizex - 1;
	}
    }
    if (y2 >= scrnY) {
	y2 = scrnY - 1;
	y1 = y2 - sizey + 1;
	if (y1 < 0) {
	    y1 = -BorderY;
	    y2 = y1 + sizey - 1;
	}
    }
    
    winposition(x1, x2, y1, y2);
    reshapeviewport();
}


static void PaintZoom(unsigned paintMode)
{
    long	msizX, msizY;
    long	mlocX, mlocY;
    long	sizeX, sizeY;
    long	locX,  locY;
    
    winset(MainWinID);
    getorigin(&mlocX, &mlocY);
    getsize(&msizX, &msizY);
    
    mlocX += BorderX;
    mlocY += BorderY;
    msizX -= 2*BorderX;
    msizY -= 2*BorderY;
    
    if (!ZoomWinID)
	MakeZoomWin();
    
    if (ZoomWinID                &&
	(paintMode == PAINTBOTH  ||
	 (MouseX >= mlocX         &&
	  MouseX <= mlocX + msizX &&
	  MouseY >= mlocY         &&
	  MouseY <= mlocY + msizY))) {
	
	winset(ZoomWinID);
	if (DoubleBuffer  &&
	    DbufMainCount % 2  !=  DbufZoomCount % 2) {
	    /* coordinate buffers */
	    swapbuffers();
	    DbufZoomCount++;
	}
	
	if (Image) {
	    long	x1, x2, y1, y2;
	    
	    getsize(&sizeX, &sizeY);
	    getorigin(&locX, &locY);
	    sizeX = sizeX / (ZoomX * 2) + 1;
	    sizeY = sizeY / (ZoomY * 2) + 1;
	    x1 = MouseX - sizeX;	
	    x2 = MouseX + sizeX;
	    y1 = MouseY - sizeY;
	    y2 = MouseY + sizeY;
	    if (x1 < mlocX)	
		x2 = mlocX + x2 - x1, x1 = mlocX;
	    if (x2 > mlocX + msizX)	
		x1 = mlocX + msizX - x2 + x1, x2 = mlocX + msizX;
	    if (y1 < mlocY)	
		y2 = mlocY + y2 - y1, y1 = mlocY;
	    if (y2 > mlocY + msizY)	
		y1 = mlocY + msizY - y2 + y1, y2 = mlocY + msizY;
	    
	    rectzoom(ZoomX, ZoomY);
	    rectcopy(x1 - locX, y1 - locY, x2 - locX, y2 - locY, 0, 0);
	} else
	    clear();
	
	if (DoubleBuffer) {
	    swapbuffers();
	    DbufZoomCount++;
	}
    }
}

static void PaintImage(unsigned paintMode)
{
    if (!MainWinID)
	MakeMainWin();
    winset(MainWinID);
    
    if (!Image || BorderX || BorderY)
	clear();
    
    if (Image) {
	rectzoom(RepX, RepY);
	if (FullColor)
	    lrectwrite(BorderX, BorderY, 
		       BorderX + DimX - 1, BorderY + DimY - 1, 
		       (unsigned long *) Image);
	else if (UsePixmode)
	    lrectwrite(BorderX, BorderY, 
		       BorderX + DimX - 1, BorderY + DimY - 1, 
		       (unsigned long *) Image);
	else
	    rectwrite(BorderX, BorderY, 
		      BorderX + DimX - 1, BorderY + DimY - 1, 
		      (unsigned short *) Image);
	MarkByte += ImageSize;
	++MarkFrame;
    }
    
    if (ShowZoom)
	PaintZoom(paintMode);
    
    if (DoubleBuffer) {
	winset(MainWinID);
	swapbuffers();
	DbufMainCount++;
    }
}


#ifdef UTAHRLE
static void ReadUtahFile(char *dataBuf, FILE *ifile, char *fname)
{
    int		width, height;
    rle_pixel	**rows;	
    int		i, j, k;
    
    rleData.rle_file = ifile;
    rleData.is_init = 0;
    rle_names(&rleData, "raz", fname, 0);
    if (rle_get_setup(&rleData) != RLE_SUCCESS) {
	Error("Cannot get URT file header: %s.\n", fname);
	return;
    }

    switch (rleData.ncolors) {
      case 0:
	Error("No URT color channels in file %s.\n", fname);
	return;

      case 1:
	if (ImgFormat != PSEUDO8) {
	    Error("URT file has too few color channels: %s.\n", fname);
	    return;
	}
	break;
	
      case 3:
      case 4:
	if (ImgFormat == PSEUDO8) {
	    Error("URT file has too many color channels: %s.\n", fname);
	    return;
	}
	break;
	
      default:
	Error("URT file has strange number of color channels: %s.\n", fname);
	return;
    }
    
    width  = 1 + rleData.xmax - rleData.xmin;
    height = 1 + rleData.ymax - rleData.ymin;
    if (width != FileX  ||  height != DimY) {
	Error("URT file size does not equal raz image size: %s.\n", fname);
	Error("URT file = %dx%d, raz image size = %dx%d\n",
	      width, height, FileX, DimY);
	return;
    }

    Verify(rle_row_alloc(&rleData, &rows) >= 0, 
	   "Unable to allocate image memory.");

    k = 0;
    for (i = 0; i < height; ++i) {
	rle_getrow(&rleData, rows);
	
	if (rleData.ncolors < 3) {
	    for (j = 0; j < width; ++j)
		dataBuf[k++] = rows[0][j];
	    for (j = width; j < DimX; ++j)
		dataBuf[k++] = 0;
	} else if (ImgFormat == RGBBYTE) {
	    for (j = 0; j < width; ++j) {
		dataBuf[k++] = rows[0][j];
		dataBuf[k++] = rows[1][j];
		dataBuf[k++] = rows[2][j];
	    }
	    for (j = width; j < DimX; ++j) {
		dataBuf[k++] = 0;
		dataBuf[k++] = 0;
		dataBuf[k++] = 0;
	    }
	} else {
	    for (j = 0; j < width; ++j) {
		dataBuf[k              ] = rows[0][j];
		dataBuf[k +   NumPixels] = rows[1][j];
		dataBuf[k + 2*NumPixels] = rows[2][j];
		++k;
	    }
	    for (j = width; j < DimX; ++j) {
		dataBuf[k              ] = 0;
		dataBuf[k +   NumPixels] = 0;
		dataBuf[k + 2*NumPixels] = 0;
		++k;
	    }
	}
    }
    
    rle_row_free(&rleData, rows);
}
#endif


static int FileToBuf(char *dataBuf, int ix)
{
    int			nread;
    FILE		*ifile;
    char		fname[256];
    register unsigned	nline = FileSize / DimX;
    register char	*db;
    register int	i, j, dim3, file3;
    
    /* File is top to bottom, image is bottom to top.
     */
    
    if (FilePrefix) {
	(void) strcpy(fname, FilePrefix);
	(void) strcat(fname, FileName[ix]);
    } else
	(void) strcpy(fname, FileName[ix]);

    ifile = fopen(fname, "r");
    if (!ifile)
	goto fileError;
    
#ifdef UTAHRLE
    i = fgetc(ifile);
    j = fgetc(ifile);
    (void) ungetc(j, ifile);
    (void) ungetc(i, ifile);
    
    if (i == 0x52  &&  j == 0xcc)
	ReadUtahFile(dataBuf, ifile, fname);
    else
#endif
	switch (ImgFormat) {
	  case PSEUDO8:
	  case RGBPLANE:
	    for (i = nline - 1; i >= 0; --i) {
		db = dataBuf + i*DimX;
		nread = fread(db, FileX, 1, ifile);
		if (!nread)
		    goto shortRead;
		for (j = FileX; j < DimX; ++j)
		    db[j] = 0;
	    }
	    break;
	  case RGBBYTE:
	    dim3 = 3 * DimX;
	    file3 = 3 * FileX;
	    for (i = DimY - 1; i >= 0; --i) {
		db = dataBuf + i*dim3;
		nread = fread(db, file3, 1, ifile);
		if (!nread)
		    goto shortRead;
		for (j = file3; j < dim3; ++j)
		    db[j] = 0;
	    }
	    break;
	}
    
    (void) fclose(ifile);
    return 1;
    
  shortRead:
    Error("Short read on data file #%d, %s\n", ix, fname);
    (void) fclose(ifile);
    
  fileError:	
    perror("     raz");
    return 0;
}


static void BufToImage(char *dataBuf)
{
    register int	i, j;
    register char	*redp, *grnp, *blup;
    register short	*simg;
    
    switch (ImgFormat) {
      case RGBPLANE:
	redp = dataBuf + 2*NumPixels;
	grnp = dataBuf +   NumPixels;
	blup = dataBuf;
	
	for (i = 0, j = 0; i < NumPixels; ++i, j += 3) {
	    Image[j  ] = blup[i];
	    Image[j+1] = grnp[i];
	    Image[j+2] = redp[i];
	}
	break;
	
      case RGBBYTE:
	for (i = NumPixels, j = 0; i; --i, j += 3) {
	    Image[j  ] = dataBuf[j+2];
	    Image[j+1] = dataBuf[j+1];
	    Image[j+2] = dataBuf[j  ];
	}
	break;
	
      case PSEUDO8:
	(void) memcpy(Image, dataBuf, FileSize);
	break;
	
    }
    
    if (!UsePixmode)
	switch (ImgFormat) {
	  case RGBPLANE:
	  case RGBBYTE:
	    for (i = 4*NumPixels - 4, j = 3*NumPixels - 3;
		 i >= 0; i -= 4, j -= 3) {
		Image[i  ] = 0;
		Image[i+1] = Image[j];
		Image[i+2] = Image[j+1];
		Image[i+3] = Image[j+2];
	    }
	    break;
	    
	  case PSEUDO8:
	    simg = (short *) Image;
	    for (i = NumPixels - 1; i >= 0; --i)
		simg[i] = Image[i] /* + ColorBump */;
	    break;
	}
}


static int ReadRawImage(char *img, unsigned imgSize)
{
    int		status;
    unsigned	nbytes = imgSize;
    unsigned	nzero = 0;
    
    while (nbytes) {
	status = read(RawFD, img, nbytes);
	if (status < 0) {
	    perror("raz, error reading raw Image");
	    return 0;
	}
	if (status == 0) {
	    ++nzero;
	    if (nzero > 2) {
		Error("Zero bytes read more than twice\n");
		return 0;
	    }
	} else
	    nzero = 0;
	nbytes -= status;
	img += status;
    }
    
    RawPosition += imgSize;
    return 1;
}


static int FindNextFrame(int frame)
{
    int		newFrame;
    
    if (NumFrame < 1)
	return 0;
    
    newFrame = frame + PlayDir;
    if (PlayBounce) {
	if (newFrame < 0) {
	    PlayDir = -PlayDir;
	    newFrame = -newFrame;
	}
	if (newFrame >= NumFrame) {
	    PlayDir = -PlayDir;
	    newFrame = 2 * NumFrame - newFrame - 1;
	}
    } else {
	while (newFrame < 0)
	    newFrame = newFrame + NumFrame;
	if (newFrame >= NumFrame)
	    newFrame = newFrame % NumFrame;
    }
    
    return newFrame;
}


static int FindNextChunk(int frame)
{
    int		newFrame;
    int		chunkDir = PlayDir * ChunkSize;
    
    if (NumFrame < 1)
	return 0;
    
    newFrame = frame + chunkDir;
    if (PlayBounce) {
	if (newFrame < 0)
	    newFrame = -newFrame - chunkDir;
	if (newFrame >= NumFrame)
	    newFrame = 2 * NumFrame - newFrame - 1 - chunkDir;
    } else {
	while (newFrame < 0)
	    newFrame = newFrame + NumFrame;
	if (newFrame >= NumFrame)
	    newFrame = newFrame % NumFrame;
    }
    
    return (newFrame / ChunkSize) * ChunkSize;
}


static int SeekOnRaw(int newPos)
{
    int		status = 0;
    
    if (newPos != RawPosition) {
	if (RawTape) {
	    /* AMPEX
	     */
	} else
	    status = lseek(RawFD, (off_t) newPos, SEEK_SET);
	if (status < 0)
	    perror("raz, seek on raw disk");
	RawPosition = newPos;
    }
    
    return status >= 0;
}


/*ARGSUSED*/
static void ParallelRead(void *arg)
{
    int		nextFrame;
    int		readyFrame = -1;
    int		readyIndex = 0;
    char	*readyImage = NULL;
    int		imageIndex = 0;
    
    (void) prctl(PR_TERMCHILD);
    
    while (1) {
	/* Wait for parent to release read semaphore
	 */
	(void) uspsema(ReadSema);
	
	/* Parent is waiting on image semaphore
	 */
	if (ReadQuit) {
	    /* Release Image semaphore and die
	     */
	    (void) usvsema(ImageSema);
	    return;
	}
	nextFrame = ReadFrame;
	
	if (readyFrame == nextFrame) {
	    imageIndex = readyIndex;
	    ImageChunk = readyImage;
	    
	} else {
	    if (SeekOnRaw(RawOffset + nextFrame*ImageSize)) {
		ImageChunk = RoundToPage(Movie[imageIndex]);
		if (!ReadRawImage(ImageChunk, ImageSize*ChunkSize))
		    ImageChunk = NULL;
	    } else
		ImageChunk = NULL;
	}
	
	/* Let parent paint the image
	 */
	(void) usvsema(ImageSema);
	
	/* Read ahead next frame
	 */
	if (Playing) {
	    readyFrame = FindNextChunk(nextFrame);
	    if (SeekOnRaw(RawOffset + readyFrame*ImageSize)) {
		readyIndex = (imageIndex + 1) % 2;
		readyImage = RoundToPage(Movie[readyIndex]);
		if (!ReadRawImage(readyImage, ImageSize*ChunkSize))
		    readyFrame = -1;
	    } else
		readyFrame = -1;
	} else
	    readyFrame = -1;
    }
}


static void DoLoad(void);

static void GotoFrame(int frame)
{
    int		nextRead;
    
    if (NumFrame == 0)
	DoLoad();
    if (NumFrame == 0) {
	Playing = FALSE;
	return;
    }
    frame = MIN(frame, NumFrame - 1);
    frame = MAX(frame, 0);
    
    if (PlayFromRaw) {
	if (ReadPID < 0) {
	    ReadPID = sproc(ParallelRead, PR_SALL);
	    if (ReadPID < 0) {
		perror("raz: sproc");	
		exit(1);
	    }
	}
	
	nextRead = (frame / ChunkSize) * ChunkSize;
	if (ReadFrame != nextRead) {
	    ReadFrame = nextRead;
	    (void) usvsema(ReadSema);	/* Start child reading */
	    (void) uspsema(ImageSema);	/* Child will release ImageChunk */
	}
	Image = ImageChunk + (frame % ChunkSize) * ImageSize;
	
    } else
	Image = Movie[frame];
    
    CurrFrame = frame;
    if (CurrFrame == StopFrame) {
	Playing = 0;
	StopFrame = -1;
    }
}


static void DoSet(char *val)
{
    char	name[CMDSIZE];
    register	i;
    
    (void) strcpy(name, NextToken(&val));
    
    if (MATCH(name, "rawdiskname")) {
	(void) strcpy(RawDiskName, NextToken(&val));
	FreeMovie();
	if (RawFD >= 0) {
	    (void) close(RawFD);
	    RawFD = -1;
	}
	
    } else if (MATCH(name, "double")) {
	char	*mode = NextToken(&val);
	
	if (MATCH(mode, "on") || MATCH(mode, "yes"))
	    DoubleBuffer = 1;
	else
	    DoubleBuffer = 0;
	ConfigureBuffers();
	
    } else if (MATCH(name, "bounce")) {
	char	*mode = NextToken(&val);
	
	if (MATCH(mode, "on") || MATCH(mode, "yes"))
	    PlayBounce = 1;
	else
	    PlayBounce = 0;
	
    } else if (MATCH(name, "speed")) {
	float	ptime;
	
	PlaySpeed = atof(NextToken(&val));
	PlaySpeed = MAX(1, PlaySpeed);
	PlaySpeed = MIN(60, PlaySpeed);
	ptime = 1.0 / PlaySpeed;
	PlayTime.tv_sec  = ptime;
	ptime -= (int) ptime;
	PlayTime.tv_usec = 1000000 * ptime;
	
    } else if (MATCH(name, "direction")) {
	PlayDir = ScanLong(NextToken(&val));
	if (ABS(PlayDir) >= NumFrame  ||  PlayDir == 0)
	    PlayDir = 1;
	
    } else if (MATCH(name, "playmode")) {
	char	*mode = NextToken(&val);
	
	if (MATCH(mode, "raw"))
	    PlayFromRaw = TRUE;
	else if (MATCH(mode, "memory"))
	    PlayFromRaw = FALSE;
	else {
	    PlayFromRaw = TRUE;
	    Error("raz: unknown play mode %s\n", mode);
	}
	FreeMovie();
	
    } else if (MATCH(name, "source")) {
	char	*mode = NextToken(&val);
	
	RawTape = 0;
	if (MATCH(mode, "raw")) {
	    SourceFromRaw = TRUE;
	} else if (MATCH(mode, "tape")) {
	    SourceFromRaw = TRUE;
	    RawTape = 1;
	} else if (MATCH(mode, "file")) {
	    SourceFromRaw = FALSE;
	} else {
	    SourceFromRaw = FALSE;
	    Error("raz: unknown source %s\n", mode);
	}
	FreeMovie();
	
    } else if (MATCH(name, "offset")) {
	char	*tok = NextToken(&val);
	
	if (*tok)
	    RawOffset = ScanLong(tok);
	else
	    RawOffset = 0;
	
    } else if (MATCH(name, "blocksize")) {
	BlockSize = ScanLong(NextToken(&val));
	if (BlockSize < 1)
	    BlockSize = 1;
	
    } else if (MATCH(name, "chunksize")) {
	ChunkSize = ScanLong(NextToken(&val));
	if (ChunkSize < 1)
	    ChunkSize = 1;
	FreeMovie();
	
    } else if (MATCH(name, "cmap")) {
	unsigned char	cmap[768];
	
	ReadColormap(cmap, NextToken(&val));
	for (i = 0; i < 256; ++i) {
	    Colormap[i][0] = cmap[3*i + 0];
	    Colormap[i][1] = cmap[3*i + 1];
	    Colormap[i][2] = cmap[3*i + 2];
	}
	SetColorMap();
	
    } else if (MATCH(name, "colors")) {
	ReadMode = READCOLOR;
	ReadIndex = 0;
	
    } else if (MATCH(name, "imgformat")) {
	char	*format = NextToken(&val);
	
	if (MATCH(format, "fullcolor")) {
	    ImgFormat = RGBPLANE;
	    FullColor = TRUE;
	} else if (MATCH(format, "rgbbyte")) {
	    ImgFormat = RGBBYTE;
	    FullColor = TRUE;
	} else if (MATCH(format, "rgbplane")) {
	    ImgFormat = RGBPLANE;
	    FullColor = TRUE;
	} else if (MATCH(format, "pseudo8")) {
	    ImgFormat = PSEUDO8;
	    FullColor = FALSE;
	} else {
	    ImgFormat = PSEUDO8;
	    FullColor = FALSE;
	    Error("raz: unknown image format %s\n", format);
	}
	FreeMovie();
	ConfigureBuffers();
	
    } else if (MATCH(name, "toptobot")) {
	char	*mode = NextToken(&val);
	
	TopToBot = (MATCH(mode, "on") || MATCH(mode, "yes")) ? 1 : 0;
	if (MainWinID && UsePixmode)
	    pixmode(PM_TTOB, TopToBot);
	
    } else if (MATCH(name, "numraw")) {
	NumRaw = ScanLong(NextToken(&val));
	FreeMovie();
	
    } else if (MATCH(name, "datadim")) {
	FileX = ScanLong(NextToken(&val));
	DimX = RoundUp(FileX, 4);
	DimY = ScanLong(NextToken(&val));
	FreeMovie();
	if (MainWinID) {
	    SetWinSize();
	    PaintImage(PAINTPLAY);
	}
	
    } else if (MATCH(name, "border")) {
	BorderX = ScanLong(NextToken(&val));
	BorderY = ScanLong(NextToken(&val));
	if (MainWinID) {
	    SetWinSize();
	    PaintImage(PAINTPLAY);
	}
	
    } else if (MATCH(name, "replication")) {
	RepX = atof(NextToken(&val));
	if (RepX < 0.0) {
	    RepX = 1.0;
	    Error("raz: X replication less than zero!\n");
	}
	RepY = atof(NextToken(&val));
	if (RepY < 0.0) {
	    RepY = 1.0;
	    Error("raz: Y replication less than zero!\n");
	}
	if (MainWinID) {
	    SetWinSize();
	    PaintImage(PAINTPLAY);
	}
	
    } else if (MATCH(name, "zoom")) {
	ZoomX = atof(NextToken(&val));
	if (ZoomX <= 0.0) {
	    ZoomX = 1.0;
	    Error("raz: X zoom factor less than zero!\n");
	}
	ZoomY = atof(NextToken(&val));
	if (ZoomY <= 0.0) {
	    ZoomY = 1.0;
	    Error("raz: Y zoom factor less than zero!\n");
	}
	
    } else if (MATCH(name, "prefix")) {
	Free(FilePrefix);
	FilePrefix = NextToken(&val);
	if (FilePrefix) {
	    int	plen = strlen(FilePrefix);
	    FilePrefix = NewString(FilePrefix, 1);
	    if (plen < 1  ||  FilePrefix[plen-1] != '/')
		(void) strcat(FilePrefix, "/");
	}
	
    } else if (MATCH(name, "filename")) {
	if (FileName) {
	    for (i = 0; i < NumFile; ++i)
		free(FileName[i]);
	    free(FileName);
	}
	FileName = NULL;
	
	NumFile = ScanLong(NextToken(&val));
	if (NumFile < 1) {
	    NumFile = 0;
	    Error("raz: NumFile less than one!\n");
	} else {
	    FileName = CallocType(char *, NumFile);
	    MemCheck(FileName);
	}
	
	FreeMovie();
	Playing = FALSE;
	ReadMode = READFILE;
	ReadIndex = 0;
	
    } else {
	Error("raz: unknown variable name %s\n", name);
    }
}


static void DoSetFilename(char *val)
{
    char	*fname;

    fname = NextToken(&val);
    while (ReadIndex < NumFile  &&  strlen(fname) > 0) {
	FileName[ReadIndex++] = NewString(fname, 0);
	fname = NextToken(&val);
    }
    
    if (ReadIndex == NumFile) {
	ReadMode = READCMD;

#ifdef UTAHRLE
	if (NumFile > 0) {
	    int		width, height;
	    FILE	*ifile;
	    char	urtName[256];
    
	    if (FilePrefix) {
		(void) strcpy(urtName, FilePrefix);
		(void) strcat(urtName, FileName[0]);
	    } else
		(void) strcpy(urtName, FileName[0]);

	    rleData.is_init = 0;
	    rleData.rle_file = rle_open_f("raz", urtName, "r");
	    if (!rleData.rle_file)
		goto fileError;
	    rle_names(&rleData, "raz", urtName, 0);
	    if (rle_get_setup(&rleData) != RLE_SUCCESS)
		goto fileError;
	    
	    width  = 1 + rleData.xmax - rleData.xmin;
	    height = 1 + rleData.ymax - rleData.ymin;
	    if (width != FileX  ||  height != DimY) {
		printf("set dimension %d %d\n", width, height);
		FileX = width;
		DimX = RoundUp(FileX, 4);
		DimY = height;
		if (MainWinID) {
		    SetWinSize();
		    PaintImage(PAINTPLAY);
		}
	    }
	    
	    switch (rleData.ncolors) {
	      case 1:
		if (ImgFormat != PSEUDO8) {
		    printf("set imgformat pseudo8");
		    ImgFormat = PSEUDO8;
		    FullColor = FALSE;
		    ConfigureBuffers();
		}
		break;
	      case 3:
	      case 4:
		if (ImgFormat == PSEUDO8) {
		    printf("set imgformat rgbbyte");
		    ImgFormat = RGBBYTE;
		    FullColor = TRUE;
		    ConfigureBuffers();
		}
		break;
	    }
	    
	  fileError:
	    (void) fclose(rleData.rle_file);
	}
#endif
	
    }
}


static void DoSetColor(char *val)
{
    int	ix, r, g, b, n, nf;
    
    nf = sscanf(val, "%d%d%d%d%n", &ix, &r, &g, &b, &n);
    while (ReadIndex < 256  &&  nf == 4) {
	ix = MAX(ix, 0);
	ix = MIN(ix, 255);
	Colormap[ix][0] = r;
	Colormap[ix][1] = g;
	Colormap[ix][2] = b;
	val += n;
	++ReadIndex;
	nf = sscanf(val, "%d%d%d%d%n", &ix, &r, &g, &b, &n);
    }

    if (ReadIndex == 256) {
	SetColorMap();
	ReadMode = READCMD;
    }
}


static void PrintChoice(char *prefix, unsigned doA, char *strA, char *strB)
{
    (void) printf(prefix);
    if (doA)
	(void) printf(strA);
    else
	(void) printf(strB);
}


static void DoPrint(char *val)
{
    char	name[CMDSIZE];
    register	i, j;
    
    (void) strcpy(name, NextToken(&val));
    
    if (MATCH(name, "frame"))
	(void) printf("%d\n", CurrFrame);
    
    else if (MATCH(name, "numframe"))
	(void) printf("%d\n", NumFrame);
    
    else if (MATCH(name, "files")) {
	if (FileName) {
	    (void) printf("set filename %d\n", NumFile);
	    for (i = 0; i < NumFile; ++i)
		(void) printf("%s\n", FileName[i]);
	}
	
    } else if (MATCH(name, "colors")) {
	(void) printf("set colors\n");
	for (i = 0; i < 64; ++i) {
	    j = i;
	    (void) printf("%3d %3d %3d %3d   ", j, 
			  Colormap[j][0], Colormap[j][1], Colormap[j][2]);
	    j += 64;
	    (void) printf("%3d %3d %3d %3d   ", j, 
			  Colormap[j][0], Colormap[j][1], Colormap[j][2]);
	    j += 64;
	    (void) printf("%3d %3d %3d %3d   ", j, 
			  Colormap[j][0], Colormap[j][1], Colormap[j][2]);
	    j += 64;
	    (void) printf("%3d %3d %3d %3d\n", j, 
			  Colormap[j][0], Colormap[j][1], Colormap[j][2]);
	}
	
    } else {
	(void) printf("set rawdiskname %s\n", RawDiskName);
	(void) printf("set numraw %u\n", NumRaw);
	(void) printf("set offset %uk\n", (unsigned) (RawOffset / 1024));
	(void) printf("set blocksize %u\n", BlockSize);
	(void) printf("set chunksize %u\n", ChunkSize);
	(void) printf("set speed %f\n", PlaySpeed);
	(void) printf("set direction %d\n", PlayDir);
	PrintChoice("set playmode ", PlayFromRaw, "raw\n", "memory\n");
	if (SourceFromRaw) {
	    if (RawTape)
		(void) printf("set source tape\n");
	    else
		(void) printf("set source raw\n");
	} else
	    (void) printf("set source file\n");
	switch (ImgFormat) {
	  case RGBPLANE:
	    (void) printf("set imgformat rgbplane\n");
	    break;
	  case RGBBYTE:
	    (void) printf("set imgformat rgbbyte\n");
	    break;
	  case PSEUDO8:
	    (void) printf("set imgformat pseudo8\n");
	    break;
	}
	(void) printf("set datadim %u %u\n", DimX, DimY);
	(void) printf("set border %u %u\n", BorderX, BorderY);
	(void) printf("set replication %f %f\n", RepX, RepY);
	(void) printf("set zoom %f %f\n", ZoomX, ZoomY);
	PrintChoice("set double ",   DoubleBuffer, "on\n", "off\n");
	PrintChoice("set bounce ",   PlayBounce,   "on\n", "off\n");
    }
}


static void CleanupForExit(void)
{
    SetSavedMap();
    if (ReadPID >= 0) {
	ReadQuit = 1;
	(void) usvsema(ReadSema);	/* Start child */
	(void) uspsema(ImageSema);	/* Child is about to quit */
    }
}


static void DoZoom(char *val)
{
    char	*mode = NextToken(&val);
    
    if (MATCH(mode, "on") || MATCH(mode, "yes")) {
	ShowZoom = 1;
	PaintImage(PAINTBOTH);
    } else {
	ShowZoom = 0;
	if (ZoomWinID) {
	    winclose(ZoomWinID);
	    ZoomWinID = NULL;
	}
    }
}


static void HandleEvent(void)
{
    short	event[64];
    int		i, nread;
    int		update = FALSE;
    
    if (!MainWinID)
	return;
    
    while (qtest()) {
	nread = blkqread(event, 64);
	for (i = 0; i < nread; i += 2)
	    switch(event[i]) {
	      case WINQUIT:
	      case WINSHUT:
		if (event[i+1] == ZoomWinID)
		    DoZoom("off");
		else if (event[i+1] == MainWinID) {
		    CleanupForExit();
		    exit(0);
		}
		break;
	      case REDRAW:
		winset(event[i+1]);
		reshapeviewport();
		PaintImage(PAINTBOTH);
		break;
	      case MOUSEX:
		MouseX = event[i+1];
		update = TRUE;
		break;
	      case MOUSEY:
		MouseY = event[i+1];
		update = TRUE;
		break;
	    }
    }
    
    if (update && ShowZoom && !Playing)
	PaintImage(PAINTBOTH);
}


static void DoLoad(void)
{
    int		status;
    char	*dataBuf;
    char	buf[128];
    register	i;
    
    FreeMovie();
    CalcSizes();
    
    if (NumFile == 0  &&  NumRaw == 0)
	return;
    if (MainWinID)
	wintitle("Raz - Loading");
    
    if (PlayFromRaw) {
	if (RawFD < 0) {
	    if (RawTape)
		RawFD = open(RawDiskName, O_RDONLY);
	    else
		RawFD = open(RawDiskName, O_RDWR | O_CREAT, 00666);
	}
	if (RawFD < 0) {
	    perror("raz, raw disk open");
	    return;
	}
	if (RawTape) {
	    /* AMPEX
	     */
	    RawPosition = 0;
	} else {
	    status = lseek(RawFD, (off_t) RawOffset, SEEK_SET);
	    if (status == -1) {
		perror("raz: begining seek on raw disk");
		return;
	    }
	    RawPosition = RawOffset;
	}
	
	MovieSize = 2;
	Movie = CallocType(char *, MovieSize);
	MemCheck(Movie);
	
	Movie[0] = CallocType(char, ImageSize*ChunkSize + PageSize);
	MemCheck(Movie[0]);
	Movie[1] = CallocType(char, ImageSize*ChunkSize + PageSize);
	MemCheck(Movie[1]);
	Image = RoundToPage(Movie[0]);
	
    } else {
	/* Play from memory
	 */
	if (SourceFromRaw)
	    MovieSize = NumRaw;
	else
	    MovieSize = NumFile;
	Movie = CallocType(char *, MovieSize);
	MemCheck(Movie);
	(void) memset(Movie, 0, MovieSize * sizeof(char *));
    }
    
    dataBuf = CallocType(char, FileSize);
    MemCheck(dataBuf);
    
    if (SourceFromRaw) {
	NumFrame = NumRaw;
	
	if (PlayFromRaw)
	    CurrFrame = 0;
	
	else {
	    /* Load memory from raw disk
	     */
	    if (RawTape) {
		perror("raz: can't load memory from tape");	
		exit(1);
	    }
	    if (RawFD < 0)
		RawFD = open(RawDiskName, O_RDWR);
	    if (RawFD < 0) {
		perror("raz: raw disk");
		return;
	    }
	    
	    status = lseek(RawFD, (off_t) RawOffset, SEEK_SET);
	    if (status == -1) {
		perror("raz: begining seek on raw disk");
		return;
	    }
	    RawPosition = RawOffset;
	    
	    for (i = 0; i < NumFrame; ++i) {
		Movie[i] = CallocType(char, ImageSize + PageSize);
		MemCheck(Movie[i]);
		Image = Movie[i];
		
		CurrFrame = i;
		if (MainWinID) {
		    (void) sprintf(buf, "Raz - Loading Frame %d", CurrFrame);
		    wintitle(buf);
		}
		(void) ReadRawImage(Image, ImageSize);
		PaintImage(PAINTBOTH);
		HandleEvent();
	    }
	}
	
    } else {
	/* Source from file
	 */
	if (RawTape) {
	    perror("raz: can't write to tape");	
	    exit(1);
	}
	NumFrame = 0;
	
	for (i = 0; i < NumFile; ++i) {
	    if (!FileToBuf(dataBuf, i))
		continue;
	    
	    ++NumFrame;
	    CurrFrame = NumFrame - 1;
	    if (MainWinID) {
		(void) sprintf(buf, "Raz - Loading Frame %d", CurrFrame);
		wintitle(buf);
	    }
	    
	    if (!PlayFromRaw) {
		/* Allocate memory for image
		 */
		Movie[CurrFrame] = CallocType(char, ImageSize + PageSize);
		MemCheck(Movie[CurrFrame]);
		Image = Movie[CurrFrame];
	    }
	    
	    BufToImage(dataBuf);
	    PaintImage(PAINTBOTH);
	    
	    if (PlayFromRaw) {
		/* Write image to raw
		 */
		status = write(RawFD, Image, ImageSize);
		if (status != ImageSize) {
		    Error("Short write on Image #%d\n", CurrFrame);
		    perror("    raz");
		    continue;
		}
		RawPosition += ImageSize;
	    }
	    HandleEvent();
	}
	if (PlayFromRaw) {
	    status = lseek(RawFD, (off_t) RawOffset, SEEK_SET);
	    if (status == -1) {
		perror("raz: begining seek on raw disk");
		return;
	    }
	    RawPosition = RawOffset;
	}
    }
    
    if (MainWinID)
	wintitle("Raz");
    free(dataBuf);
}


static void DoGoto(char *val)
{
    char	*tok = NextToken(&val);
    
    if (*tok) {
	if (MATCH(tok, "begining"))
	    GotoFrame(0);
	else if (MATCH(tok, "end"))
	    GotoFrame(NumFrame - 1);
	else
	    GotoFrame(ScanLong(tok));
	PaintImage(PAINTBOTH);
    }
}


static void DoPlay(char *val)
{
    char	*tok;
    
    Playing = 1;
    tok = NextToken(&val);
    if (*tok) {
	if (MATCH(tok, "begining"))
	    GotoFrame(0);
	else if (MATCH(tok, "end"))
	    GotoFrame(NumFrame - 1);
	else
	    GotoFrame(ScanLong(tok));
    }
    tok = NextToken(&val);
    if (*tok) {
	if (MATCH(tok, "begining"))
	    StopFrame = 0;
	else if (MATCH(tok, "end"))
	    StopFrame = NumFrame - 1;
	else
	    StopFrame = ScanLong(tok);
    }
}


static void DoStop(void)
{
    Playing = 0;
    StopFrame = -1;
}


static void DoCmap(char *val)
{
    char	*mode = NextToken(&val);
    
    if (ImgFormat == PSEUDO8) {
	if (MATCH(mode, "on") || MATCH(mode, "yes"))
	    SetColorMap();
	else
	    SetSavedMap();
    }
}


static void DoPing(char *val)
{
    (void) printf("pong %s\n", NextToken(&val));
}


static void DoSleep(char *val)
{
    int		leftOver;

    leftOver = sginap(ScanLong(NextToken(&val)));
    if (leftOver)
	(void) sginap(leftOver);
}


static void DoMark(void)
{
    struct timeval	newTime;
    float		t;
    
    (void) gettimeofday(&newTime, NULL);
    t = newTime.tv_sec - MarkLast.tv_sec;
    t += (float) (newTime.tv_usec - MarkLast.tv_usec) / 1000000.0;
    
    (void) printf("%.2f %.2f\n", 
		  (float) MarkFrame / t,
		  (float) MarkByte / (t * 1024 * 1024));
    
    MarkFrame = 0;
    MarkByte = 0;
    MarkLast = newTime;
}


static void DispatchCommand(char *cmd, char *args)
{
    if (MATCH(cmd, "play"))
	DoPlay(args);
    else if (MATCH(cmd, "mark"))
	DoMark();
    else if (MATCH(cmd, "stop"))
	DoStop();
    else if (MATCH(cmd, "set"))
	DoSet(args);
    else if (MATCH(cmd, "goto"))
	DoGoto(args);
    else if (MATCH(cmd, "load"))
	DoLoad();
    else if (MATCH(cmd, "print"))
	DoPrint(args);
    else if (MATCH(cmd, "zoom"))
	DoZoom(args);
    else if (MATCH(cmd, "cmap"))
	DoCmap(args);
    else if (MATCH(cmd, "ping"))
	DoPing(args);
    else if (MATCH(cmd, "sleep"))
	DoSleep(args);
    else if (MATCH(cmd, "quit") || MATCH(cmd, "exit"))
	Quit = 1;
    else if (!MATCH(cmd, "#") && !MATCH(cmd, ""))
	Error("raz: unknown command: %s\n", cmd);
}


/*ARGSUSED*/
static void IncrementTimer(int signo)
{
    (void) signal(SIGALRM, IncrementTimer);
    ++TimerCount;
}


static void Setup(int argc, char *argv[])
{
    char	str[256];
    char	*tmpdir;
    unsigned	i;
    int		isVGX, isRE;
    
    foreground();
    gversion(str);
    isVGX = strncmp(str, "GL4DVGX", 7) == 0;
    isRE  = strncmp(str, "GL4DRE",  6) == 0;
    UsePixmode = isVGX || isRE;
    PageSize = getpagesize();
    
    ProgramName = argv[0];
    (void) strcpy(RawDiskName, RAWDISK);
    
    if (argc > 1)
	AckCmd = strcmp(argv[1], "-a") == 0;
    else
	AckCmd = 0;
    
    if (!isRE  &&  (getgdesc(GD_BITS_NORM_DBL_CMODE) >= 9))
	ColorBump = 1024;
    else
	ColorBump = 0;
    for (i = 0; i < 256; ++i)
	Colormap[i][0] = Colormap[i][1] = Colormap[i][2] = i;
    
    if (tmpdir = getenv("TMPDIR"))
	(void) sprintf(str, "%s/raz_sema.XXXXXX", tmpdir);
    else
	(void) sprintf(str, "/usr/tmp/raz_sema.XXXXXX");
    (void) mktemp(str);
    
    SemaArena = usinit(str);
    (void) unlink(str);
    if (!SemaArena) {
	Error("Cannot create semaphore arena\n");
	exit(1);
    }
    ReadSema  = usnewsema(SemaArena, 0);
    ImageSema = usnewsema(SemaArena, 0);
    
    ZeroTime.tv_sec  = 0;
    ZeroTime.tv_usec = 0;
    
    (void) signal(SIGALRM, IncrementTimer);
    DoSet("speed 30");
    
    MarkFrame = 0;
    MarkByte = 0;
    (void) gettimeofday(&MarkLast, NULL);
}


main(int argc, char *argv[])
{
    char		*cmd;
    char		token[CMDSIZE];
    int			nfound;
    fd_set		readfds;
    int			nextFrame;
    unsigned long	lastTimer = -1;
    struct itimerval	setTime;
    
    Setup(argc, argv);
    
    while (!Quit) {
	HandleEvent();
	
	if (Playing) {
	    nextFrame = FindNextFrame(CurrFrame);
	    GotoFrame(nextFrame);
	    while (lastTimer == TimerCount)
		(void) sginap(1);
	    lastTimer = TimerCount;
	    setTime.it_interval = ZeroTime;
	    setTime.it_value    = PlayTime;
	    (void) setitimer(ITIMER_REAL, &setTime, NULL);
	    PaintImage(PAINTPLAY);
	} else
	    lastTimer = -1;
	
	FD_ZERO(&readfds);
	FD_SET(0, &readfds);
	FD_SET(GLfd, &readfds);
	if (Playing)
	    nfound = select(GLfd+1, &readfds, 0, 0, &ZeroTime);
	else
	    nfound = select(GLfd+1, &readfds, 0, 0, NULL);
	if (nfound < 0  &&  !Playing)
	    lastTimer = -1;
	if (nfound <= 0)
	    continue;
	
	if (StopFrame >= 0)
	    continue;	/* ignore input until done playing */
	
	if (FD_ISSET(0, &readfds))
	    do {
		if (!fgets(Command, CMDSIZE, stdin)) {
		    if (feof(stdin)) {
			Quit = TRUE;
			break;
		    }
		    continue;
		}
		switch (ReadMode) {
		  case READCMD:
		    cmd = Command;
		    (void) strcpy(token, NextToken(&cmd));
		    DispatchCommand(token, cmd);
		    break;
		  case READCOLOR:
		    DoSetColor(Command);
		    break;
		  case READFILE:
		    DoSetFilename(Command);
		    break;
		}
		if (AckCmd)
		    (void) fputc('\0', stdout);
		(void) fflush(stdout);
	    } while (stdin->_cnt && !Quit);
    }
    
    CleanupForExit();
    return 0;
}
