/*
 * (c) SPDsoft 1993 + ATIC
 * Display a JFIF file using Silicon Graphics gl
 * version 2
 * Uses IJG code v6b
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

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

#include <jpeglib.h>

#include <setjmp.h>

#ifndef PATH_SEP
# define PATH_SEP	'/'
#endif


#define ALPHA_OPAC	0x00
#define YSTEREO 491
#define YOFFSET_LEFT 532
#define SCREEN_W	G_prefs.screen_w
#define SCREEN_H	G_prefs.screen_h

#define Boolean	int 
#define Output	if (G_prefs.Verbose) printf

#ifndef MIN
#define MIN(a,b) ((a)>(b)?(b):(a))
#endif

typedef struct { 
			char 	*AppName;
			Boolean Foreground;	
			Boolean FullScreen;	
			Boolean FillScreen;	
			Boolean Stereo;	
			Boolean Nosetmon;	
			Boolean Growable;	
			Boolean KeepAspectRatio;	
			Boolean Verbose;	
			int		SlideShow;	
			long	screen_w;
			long	screen_h;
		}  pref;


/*
 * Prototypes
 */
void	init_graphics(void);
int		event_loop(void);
void	redraw(void);
void    usage(char *name);
void    fatalError(char *str);
void	get_options(int argc,char *argv[]);
int		is_stereo(void);
/*
 * JPEG Prototypes
 */

static void		my_error_exit (j_common_ptr cinfo);
int				read_JPEG_file (char * filename, unsigned char **fb);


extern char     *optarg;
extern int      optind,opterr;

unsigned		image_H;
unsigned		image_W;
long			monitor;
long			mouse;
long			offset=0;
int				f;
#define framebuffer_r framebuffer
unsigned char	*framebuffer,
				*framebuffer_l,
				*pix;

pref			G_prefs;

static		char __ident[] = "@(#)(c) SPDsoft, (Uses IJG code v6b), Jul 1998";
#define VERS_STR    ((char*)&__ident[4])

/*****************************************************/

void    usage(char *name)
{
    fprintf(stderr,
    "usage: %s [-b|-F|-g|-G][-s sec][-f][-v][-h][-3|-4 left right|files...]"
	"\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n",
	name,
	"-f: Don't fork (run in foreground)",
	"-b: Full screen",
	"-3: Stereo",
	"-4: Stereo w/o setmon",
	"-F: Fill full screen",
	"-g: Growable, Keep aspect ratio",
	"-G: Growable",
	"-s: Slide Show (seconds)",
	"-v: Version, debug",
	"-h: This text. Note: Press <esc> to go to next image"
	);
    exit(-1);
}

void    fatalError(char *str)
{
    fprintf(stderr, "%s: Fatal Error: %s. bye...\n",G_prefs.AppName,str);
    fprintf(stderr, "%s: %s\n",G_prefs.AppName,strerror(errno));
    exit(-2);
}

struct my_error_mgr
{
	struct jpeg_error_mgr pub;	/* "public" fields */
	jmp_buf setjmp_buffer;		/* for return to caller */
};

typedef struct my_error_mgr * my_error_ptr;

static void my_error_exit (j_common_ptr cinfo)
{
	my_error_ptr myerr = (my_error_ptr) cinfo->err;

	(*cinfo->err->output_message) (cinfo);
	longjmp(myerr->setjmp_buffer, 1);
}

int read_JPEG_file (char * filename, unsigned char **fb)
{
	struct jpeg_decompress_struct cinfo;
	struct my_error_mgr jerr;

	FILE		*infile;			/* source file */
	JSAMPARRAY	buffer;				/* Output row buffer */
	int			row_stride;         /* physical row width in output buffer */
	register JSAMPROW inptr;

	int j;

	
	if (filename != NULL)
	{
		if ((infile = fopen(filename, "rb")) == NULL)
		{
			fatalError(filename);
		}
	}
	else
		infile = stdin;

	cinfo.err = jpeg_std_error(&jerr.pub);
	jerr.pub.error_exit = my_error_exit;
	if (setjmp(jerr.setjmp_buffer))
	{
	/* If we get here, the JPEG code has signaled an error.
	 * We need to clean up the JPEG object, close the input file, and return.
	 */
		jpeg_destroy_decompress(&cinfo);
		fclose(infile);
		return NULL;
	}


	jpeg_create_decompress(&cinfo);
	jpeg_stdio_src(&cinfo, infile);
	(void) jpeg_read_header(&cinfo, TRUE);

	jpeg_start_decompress(&cinfo);

	image_H = cinfo.output_height;
	image_W = cinfo.output_width;

	row_stride = cinfo.output_width * cinfo.output_components;
	buffer = (*cinfo.mem->alloc_sarray)
		((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);

/*
 * Framebuffer
 */
	Output("%03d %s\t%u\t%u\t",f,filename,image_H,image_W );
	if((( image_W > SCREEN_W ) || ( image_H > SCREEN_H))&&(G_prefs.FullScreen))
			G_prefs.FillScreen=1;
			
	*fb =
		(unsigned char *)calloc( (size_t)image_H * (size_t)image_W , (size_t)4);
	
	if ((*fb==NULL)||(buffer==NULL)) fatalError("Out of memory");
	pix = *fb + (size_t)4*image_W*(image_H-1);
/*
 * Draw to framebuffer
 */
	if ( cinfo.output_components != 1 )
	{
		Output("RGB image\n");
		while (cinfo.output_scanline < cinfo.output_height)
		{
			(void) jpeg_read_scanlines(&cinfo, buffer, 1);
			for (j=0, inptr=*buffer; j<cinfo.output_width; j++)
			{
				*pix++ = ALPHA_OPAC;
				*pix++ = (char)inptr[2];
				*pix++ = (char)inptr[1];
				*pix++ = (char)inptr[0];
				inptr += 3;
			}
			pix -= 8* image_W;
		}
	}
	else
	{
		Output("Grayscale image\n");
		while (cinfo.output_scanline < cinfo.output_height)
		{
			(void) jpeg_read_scanlines(&cinfo, buffer, 1);
            for (j=0, inptr=*buffer; j<cinfo.output_width; j++)
            {
				*pix++ = ALPHA_OPAC;
				*pix++ = (char)inptr[0];
				*pix++ = (char)inptr[0];
				*pix++ = (char)inptr[0];
				inptr += 1;
            }
			pix -= 8* image_W;
        }

	}

/*
 * Finish
 */
	(void) jpeg_finish_decompress(&cinfo);
	jpeg_destroy_decompress(&cinfo);
	fclose(infile);

	return 0;
}


/*****************************************************/

void	get_options(int argc,char *argv[])
{
	int opcion;
	int theError=0;

	if ((G_prefs.AppName = strrchr(argv[0], PATH_SEP))  != NULL)
			G_prefs.AppName = G_prefs.AppName+1;
	else
			G_prefs.AppName = argv[0];

	G_prefs.Foreground	= 0;
	G_prefs.FullScreen	= 0;
	G_prefs.FillScreen	= 0;
	G_prefs.SlideShow	= -1;
	G_prefs.Growable	= 0;
	G_prefs.Stereo		= 0;
	G_prefs.Nosetmon	= 0;
	G_prefs.KeepAspectRatio	= 1;
	G_prefs.Verbose=0;

	while ( (opcion=getopt(argc,argv,"s:43fbFgGvh")) != EOF )
	{
		switch(opcion)
		{
				case '4':
						G_prefs.Stereo = 1;
						G_prefs.Nosetmon = 1;
						G_prefs.FullScreen = 1;
						break;
				case '3':
						G_prefs.Stereo = 1;
						G_prefs.FullScreen = 1;
						break;
				case 'f':
						G_prefs.Foreground = 1;
						break;
				case 'b':
						G_prefs.FullScreen = 1;
						break;
				case 'F':
						G_prefs.FullScreen = 1;
						G_prefs.FillScreen = 1;
						break;
				case 'g':
						G_prefs.Growable = 1;
						break;
				case 's':
						G_prefs.SlideShow = atoi(optarg);
						break;
				case 'G':
						G_prefs.Growable = 1;
						G_prefs.KeepAspectRatio = 0;
						break;
				case 'v':
						printf("%s:%s\n",G_prefs.AppName,VERS_STR);
						G_prefs.Verbose=1;
						break;
				default:
						usage(G_prefs.AppName);
		}
		if ( theError!=0)  usage(G_prefs.AppName);
	}
}


main(int argc,char *argv[])
{
	Boolean	Fill;

	long gwid;
	long gw_x, gw_y;

	char tmpStr[64];

	get_options(argc,argv);
	init_graphics();

	if (G_prefs.Stereo)
	{
		if (is_stereo())
		{
			/* save original display mode and switch to stereo */
			monitor = getmonitor ();
			mouse = getvaluator(MOUSEY);
			if (!G_prefs.Nosetmon)
			{
			setmonitor (STR_RECT);
			/* limit cursor range to one eye's subfield */
			setvaluator (MOUSEY, (short) SCREEN_H / 2, 0, (short) SCREEN_H/2);
			}
		}
		else
		{
			fprintf(stderr,"Sorry, can't do stereo\n");
			G_prefs.Stereo = 0;
		}
	}

	if(G_prefs.FullScreen|G_prefs.Stereo) 
	{
			prefposition( 0, SCREEN_W, 0, SCREEN_H );
			gwid=winopen("");
			RGBmode();
			gconfig();
	}

	if (optind==argc)
	{
		argc ++;
	}

	if (G_prefs.Stereo)
	{
		read_JPEG_file(argv[optind],&framebuffer_l);
		read_JPEG_file(argv[optind+1],&framebuffer_r);
		redraw();
		event_loop();

		if(!G_prefs.FullScreen) winclose(gwid);
		if(framebuffer_l) free(framebuffer_l);
		if(framebuffer_r) free(framebuffer_r);
	}
	else
	{
	Fill = G_prefs.FillScreen;

	for(f=optind; f<argc; f++)
	{
		G_prefs.FillScreen = Fill;

		read_JPEG_file(argv[f],&framebuffer);

		if(G_prefs.FullScreen) 
		{
			if(G_prefs.FillScreen)
			{
			float scale=1.0;
					getsize(&gw_x, &gw_y);

					scale= MIN(	(float)gw_x/(float)image_W,
								(float)gw_y/(float)image_H);

					rectzoom(scale,scale);
			}
			redraw();
		}
		else
		{
				sprintf(tmpStr,"%s: %s", G_prefs.AppName, 
									(argv[f]==NULL?"stdin":argv[f]));
				prefsize((long)image_W,(long)image_H);
				gwid=winopen(tmpStr);
				clear();
				RGBmode();
				gconfig();
				if(G_prefs.Growable)
				{
					if(G_prefs.KeepAspectRatio)
						keepaspect((long)image_W,(long)image_H);
					winconstraints();
				}
		}
		if (event_loop())
			f=argc;

		if(!G_prefs.FullScreen) winclose(gwid);
		if(framebuffer) free(framebuffer);
	}
	}
	if(G_prefs.FullScreen) winclose(gwid);

	if(G_prefs.Stereo)
	{
		/* restore to original display, cursor range */
		if (!G_prefs.Nosetmon)
		{
		setmonitor ((short)monitor);
		setvaluator (MOUSEY, mouse , 0, (short) SCREEN_H);
		}
	}
	return(0);
}

int event_loop(void)
{
	Boolean	quitting=0;
	Boolean	end=0;
	short val;
	long MyEvent;
	static Boolean	NeedRedraw=1;
/*
 * just for IRIX 5.3 (and later, I guess)
 *
 * IRIX 5.2 sends REDRAW properly
 */
        redraw();
/*
 */

		for (quitting=0;!quitting;)
		{
			if ( 0 <= G_prefs.SlideShow )
			{
				redraw();

				if ( 0 < G_prefs.SlideShow )
				{
					sleep(G_prefs.SlideShow);
				}

				quitting=1;
			}
			else
			{
				while (qtest() || (!NeedRedraw ))
				{
				/* Event Loop */

				switch(MyEvent=qread(&val))
				{
					case ESCKEY:
						quitting=!val;
						break;

					case LEFTARROWKEY:
						if (val)
						{
							offset--;
							redraw();
						}
						break;

					case RIGHTARROWKEY:
						if (val)
						{
							offset++;
							redraw();
						}
						break;

					case REDRAW:
						NeedRedraw=1;
						redraw();
						break;

					case WINQUIT:
						quitting=1;
						end=1;
						break;

					case INPUTCHANGE:
						break;

					default:
						if(G_prefs.Verbose)
						{
							printf("Event: %ld\n", MyEvent);
							fflush(stdout);
						}
						break;

				}

					if (quitting) break;

				} /* while */
				redraw();
				NeedRedraw=0;
			}
		}
	return end;
}


void init_graphics(void)
{
		long savescrn;

#ifdef _CAN_LRECTWRITE_OVER_SOCKET_

		char *display;

		if (NULL!=(display=getenv("DISPLAY")))
		{
			if ( 0 > dglopen(display,DGLTSOCKET))
			{
				fatalError("Can't open dgl socket");
			}
		}
#endif

		qdevice(ESCKEY);
		qdevice(REDRAW);
		qdevice(WINSHUT);
		qdevice(WINQUIT);
	
		if (G_prefs.Stereo)
		{
			qdevice(LEFTARROWKEY);
			qdevice(RIGHTARROWKEY);
		}

		savescrn = scrnselect(getwscrn());

			G_prefs.screen_w = getgdesc(GD_XPMAX);
			G_prefs.screen_h = getgdesc(GD_YPMAX);

		scrnselect(savescrn);

		if (( G_prefs.screen_w == -1 ) || ( G_prefs.screen_h == -1 ))
			fatalError("getgdesc failed");

		if(G_prefs.Foreground) foreground();
}

void	redraw(void)
{
	long gw_x, gw_y;

	if(G_prefs.FullScreen) 
	{
		if(G_prefs.Stereo)
		{
			if ( image_H > YSTEREO )
			{
				rectzoom( (float)YSTEREO/(float)image_H,
					(float)YSTEREO/(float)image_H *0.5);
			}
			else
			{
				rectzoom(1.0, 0.5);
			}
/*			viewport (0, SCREEN_W, YOFFSET_LEFT, SCREEN_H);*/
			clear();
				lrectwrite(
				(Screencoord)(SCREEN_W-image_W+1)/2-offset,
				(Screencoord)((SCREEN_H-YOFFSET_LEFT)+image_H-1)/2+YOFFSET_LEFT,
				(Screencoord)(SCREEN_W+image_W-1)/2-offset,
				(Screencoord)((SCREEN_H-YOFFSET_LEFT)-image_H+1)/2+YOFFSET_LEFT,
				(unsigned long *)framebuffer_l);
/*			viewport (0, SCREEN_W, 0, YSTEREO);*/
				lrectwrite(
				(Screencoord)(SCREEN_W-image_W+1)/2+offset,
				(Screencoord)(YSTEREO+image_H-1)/2,
				(Screencoord)(SCREEN_W+image_W-1)/2+offset,
				(Screencoord)(YSTEREO-image_H+1)/2,
				(unsigned long *)framebuffer_r);
		}
		else
		{
			clear();
			if(G_prefs.FillScreen) 
			{
				lrectwrite(	0,(image_H-1),(image_W-1),0,
					(unsigned long *)framebuffer);
			}
			else
			{
				lrectwrite(
					(Screencoord)(SCREEN_W-image_W+1)/2,
					(Screencoord)(SCREEN_H+image_H-1)/2,
					(Screencoord)(SCREEN_W+image_W-1)/2,
					(Screencoord)(SCREEN_H-image_H+1)/2,
					(unsigned long *)framebuffer);
			}
		}
	}
	else
	{
		if(G_prefs.Growable)
		{
			getsize(&gw_x, &gw_y);
			viewport(0,(Screencoord)gw_x-1,0,(Screencoord)gw_y-1);
			rectzoom(	(float)gw_x/(float)image_W,
						(float)gw_y/(float)image_H);
		}
		lrectwrite(	0,(image_H-1),(image_W-1),0, (unsigned long *)framebuffer);
	}
}


/* --------------------------------------------------------------------- */
int is_stereo(void)
{
/* This routine returns 1 if Hardware is STEREO READY, 0 IF NOT*/

#ifdef PRE_IRIX33
	/* for: pre-IRIX 3.3 systems, */
	/* is read/write or read-onl:*/
	long rw1, rw2;
	rw1 = getvideo(DE_R1);
	rw2 = rw1 ^= DER1_STEREO;
	setvideo(DE_R1, rw2);
	rw2 = getvideo(DE_R1);
	if (! (rw1 == rw2))
		return(1);
	rw1 = rw2 ^= DER1_STEREO;
	setvideo(DE_R1, rw2);
	rw2 = getvideo(DE_R1);
	return ( ! (rw1 == rw2 ) ) ;
#else
	return(getgdesc(GD_STEREO));
#endif
}