/*

Picture viewer using svgalib
Copyright 1993 H. Hanemaayer 

This program is freely distributable, but there is no warranty 
of any kind.

It now uses the vgagl framebuffer library in svgalib, and uses significantly
less memory.

*/


#include <stdlib.h>
#include <stdio.h>
#include <malloc.h>
#include <vga.h>
#include <vgagl.h>
#include <vgamouse.h>

#include "config.h"
#include "shared.h"


int image_fileformat;
int image_width;
int image_height;
unsigned char *image_data;
unsigned char *image_palette;
int image_bytesperpixel;
int image_colors;

int VGAMODE;
int logicalwidth, logicalheight;
int screen_colors;


unsigned char *virtualscreen;

int mouse_type = -1;
int graphics_mode = -1;
int logical_mode = -1;


void identify( FILE *f ) {
	char buffer[8];
	fread(buffer, 8, 1, f);
   	if (memcmp(buffer, "hsi1", 4) == 0) {
   		printf("File is in the obsolete JPEG format generated by old "
   		"versions of gif2jpg for\nDOS. Flame your source!\n");
   		exit(1);
   	}
}


void configure() {
	int allowed[GLASTMODE + 1];
	vga_modeinfo *modeinfo;
	int i;

	vga_hasmode(TEXT);		/* Force driver message */

	if (graphics_mode != -1)
		VGAMODE = graphics_mode;	/* option override */
	else
		VGAMODE = vga_getdefaultmode();

	if (VGAMODE == -1)
		for (;;) {
			int i;
			int m;
			printf("\nSelect a mode:\n\n");
			for (i = G320x200x256; i <= GLASTMODE; i++) {
				allowed[i] = 0;
				if (vga_hasmode(i))
				if (vga_getmodeinfo(i)->bytesperpixel >= 1) {
					printf("%2d  %s\n", i, vga_getmodename(i));
					allowed[i] = 1;
				}
			}

			printf("\nWhich mode (0 to quit)? ");
			scanf("%d", &m);
			getchar();
			printf("\n");
			if (m == 0)	
				exit(-1);
			if (m >= G320x200x256 && m <= GLASTMODE	&& allowed[m]) {
				VGAMODE = m;
				break;
			}
			printf("Invalid mode.\n\n");
			/* -- loop -- */
		}
	else {
		if (!vga_hasmode(VGAMODE)) {
			printf("Default mode not available.\n");
			exit(-1);
		}
		if (vga_getmodeinfo(VGAMODE)->bytesperpixel == 0) {
			printf("Default mode not supported.\n");
			exit(-1);
		}
	}

	modeinfo = vga_getmodeinfo(VGAMODE);
	logicalwidth = modeinfo->width;
	logicalheight = modeinfo->height;

	{
		int w, h;
		int vm_width[] = { 0, 640, 640, 640, 800, 800, 912, 1024, 1024, 1024, 1600, 2048, 2048 };
		int vm_height[] = { 0, 480, 800, 1600, 600, 1200, 684, 512, 1024, 2048, 1200, 512, 1024 };
		char vm_valid[13];
		int vm;
		int i;
		int count;
		count = 0;
		for (i = 1; i < 13; i++)
			if (((vm_width[i] > logicalwidth && vm_height[i] >= logicalheight) ||
			(vm_width[i] >= logicalwidth && vm_height[i] > logicalheight)) &&
			modeinfo->maxpixels >= vm_width[i] * vm_height[i]) {
				vm_valid[i] = 1;
				count++;
			}
			else
				vm_valid[i] = 0;
		if (count == 0)
			/* Only one choice available, don't prompt. */
			logical_mode = 0;
		if (logical_mode == -1) {
			printf(" 0  Default (%dx%d)\n", logicalwidth, logicalheight);
			for (i = 1; i < 13; i++)
				if (vm_valid[i])
					printf("%2d  %dx%d\n", i, vm_width[i], vm_height[i]);
			printf("\nWhich logical screen size? ");
			scanf("%d", &vm);
			getchar();
			printf("\n");
		}
		else
			vm = logical_mode;
		if (vm >= 1 && vm < 13 && vm_valid[vm]) {
			logicalwidth = vm_width[vm];
			logicalheight = vm_height[vm];
		}
	}
}

void show_picture() {
	int i;
	int brightestcolor;
	char *exp_font;
	int mouse;	/* flag */
	void *optionsline;
	int realwidth;
	int realheight;

	vga_setmode(VGAMODE);
	if (logicalwidth != vga_getmodeinfo(VGAMODE)->width ||
 	logicalheight != vga_getmodeinfo(VGAMODE)->height) {
		/* more than one screen of video memory is used */
		/* claim it before setting context */
		vga_claimvideomemory(vga_getmodeinfo(VGAMODE)->bytesperpixel *
			logicalwidth * logicalheight);
 	}
	gl_setcontextvga(VGAMODE);
	realwidth = WIDTH;
	realheight = HEIGHT;

	if (realwidth != logicalwidth) {
		vga_setlogicalwidth(logicalwidth * BYTESPERPIXEL); /* bytes */
		gl_setcontextwidth(logicalwidth);	/* pixels */
	}
	if (realheight != logicalheight) {
		gl_setcontextheight(logicalheight);
	}
	gl_enableclipping();

	if (COLORS == 256) {
		int maxintensity = -1;
    		for (i = 0; i < 256; i++) {
        		int intensity;
        		#ifdef SVGALIB081
        		/* Works around a bug. */
	        	gl_setpalettecolor(i,
        			image_palette[i * 3] / 4, 
        			image_palette[i * 3 + 2] / 4, 
        			image_palette[i * 3 + 1] / 4);
        		#else
	        	gl_setpalettecolor(i,
        			image_palette[i * 3] / 4, 
        			image_palette[i * 3 + 1] / 4, 
        			image_palette[i * 3 + 2] / 4);
        		#endif
			intensity =
				image_palette[i * 3] +
				image_palette[i * 3 + 1] +
				image_palette[i * 3 + 2];
			if (intensity > maxintensity) {
				maxintensity = intensity;
				brightestcolor = i; 
			}
		}
	}
	else
		brightestcolor = (COLORS == 65536 * 256) ?
			0xffffff : (COLORS == 65536) ?
			0xffff : 0x7fff;

	gl_clearscreen(0);

	{
		void *font;
		font = malloc(256 * 8 * 8 * BYTESPERPIXEL);
		gl_expandfont(8, 8, brightestcolor, gl_font8x8, font);
		gl_setfont(8, 8, font);
		gl_setwritemode(WRITEMODE_MASKED);
	}

	if (mouse_init("/dev/mouse", vga_getmousetype(), 100))
		mouse = 0;
	else {
		mouse = 1;
		mouse_setxrange(0, WIDTH - 1);
		mouse_setyrange(0, HEIGHT - 1);
		mouse_setposition(0, 0);
	}

	{
	int scaled = 0;
	unsigned char *original_image_data = image_data;
	int original_image_width= image_width;
	int original_image_height = image_height;
	int xshift = 0, yshift = 0;
	int mousescroll = 0;
	int screenx = 0, screeny = 0;
	int hidetext = 0;

	for (;;) {
		int c;

		if (image_width <= WIDTH)
			gl_putbox(0, 0, image_width, min(image_height, HEIGHT),
	    			image_data + yshift *
	    			image_width * BYTESPERPIXEL);
    		else
    			gl_putboxpart(0, 0, WIDTH, min(image_height, HEIGHT),
    				image_width, image_height,
    				image_data, xshift, yshift);

		if (mousescroll) {
			int x, y;
			int ox, oy;
			static unsigned char cursordata[8 * 8] = {
				1, 1, 1, 1, 0, 0, 0, 0,
				1, 1, 1, 0, 0, 0, 0, 0,
				1, 1, 1, 0, 0, 0, 0, 0,
				1, 1, 1, 1, 0, 0, 0, 0,
				1, 0, 0, 1, 1, 0, 0, 0,
				0, 0, 0, 0, 1, 1, 0, 0,
				0, 0, 0, 0, 0, 1, 1, 0,
				0, 0, 0, 0, 0, 0, 1, 1
			};
			unsigned char box[8 * 8 * BYTESPERPIXEL];
			unsigned char cursor[8 * 8 * BYTESPERPIXEL];
			for (i = 0; i < 64; i++) {
				int c;
				c = cursordata[i] ? brightestcolor : 0;
				cursor[i * BYTESPERPIXEL] = c;
				if (BYTESPERPIXEL >= 2)
					cursor[i * BYTESPERPIXEL + 1] = c >> 8;
				if (BYTESPERPIXEL >= 3)
					cursor[i * BYTESPERPIXEL + 2] = c >> 16;
			}
			x = mouse_getx();
			y = mouse_gety();
			/* save */
			gl_getbox(x, y, 8, 8, box);
			/* draw */
			gl_putboxmask(x, y, 8, 8, cursor);
			for (;;) {
				ox = x;
				oy = y;
				mouse_update();
				x = mouse_getx();
				y = mouse_gety();
				if (x != ox || y != oy) {
					/* erase */
					gl_putbox(ox, oy, 8, 8, box);
					/* save */
					gl_getbox(x, y, 8, 8, box);
				}
				/* draw */
				gl_putboxmask(x, y, 8, 8, cursor);
				if (x >= screenx + realwidth ||
				y >= screeny + realheight ||
				x < screenx || y < screeny) {
					char s[11];
					if (x >= screenx + realwidth)
						screenx = x - realwidth;
					else if (x < screenx)
						screenx = x;
					if (y >= screeny + realheight)
						screeny = y - realheight;
					else if (y < screeny)
						screeny = y;
					gl_setdisplaystart(screenx, screeny);
				}
				if (mouse_getbutton()) {
					gl_putbox(x, y, 8, 8, box);
					break;
				}
			}
			mousescroll = 0;
		}

		if (!hidetext) {
			void *line;
			line = malloc(realwidth * 8 * BYTESPERPIXEL);
			gl_getbox(0 + screenx, realheight - 8 + screeny,
				realwidth, 8, line);
			gl_write(0 + screenx, realheight - 8 + screeny,
				(realwidth < 640) ?
				"Arrows (F)it (M)ouse (Q)uit LRDU (H)ide" :
"Arrows: shift  F: Fit to screen  M: Mouse  Q: Quit  L/R/D/U: scroll H: Hide");

	   		c = getchar();

			gl_putbox(0 + screenx, realheight - 8 + screeny,
				realwidth, 8, line);
			free(line);
		}
		else
	   		c = getchar();
   		
   		switch (c) {
  		case 'f' :
			/* fit to screen */
			if (scaled) {
				free(image_data);
				image_data = original_image_data;
				image_width = original_image_width;
				image_height = original_image_height;
				gl_clearscreen(0);
			}
			else {
				image_data = malloc(BYTEWIDTH * HEIGHT);
				image_width = WIDTH;
				image_height = HEIGHT;
				gl_scalebox(original_image_width,
					original_image_height, original_image_data,
					image_width, image_height, image_data);
				xshift = yshift = 0;
			}
			scaled ^= 1;
			break;
		case 'm' :
			if (mouse)
				mousescroll = 1;
			break;
		case 'h' :
			hidetext ^= 1;
			break;
		case 'r' :
			while (screenx + SCROLLSTEP <= WIDTH - realwidth) {
				screenx += SCROLLSTEP;
				vga_waitretrace();
				gl_setdisplaystart(screenx, screeny);
			}
			screenx = WIDTH - realwidth;
			gl_setdisplaystart(screenx, screeny);
			break;
		case 'l' :
			while (screenx - SCROLLSTEP >= 0) {
				screenx -= SCROLLSTEP;
				vga_waitretrace();
				gl_setdisplaystart(screenx, screeny);
			}
			screenx = 0;
			gl_setdisplaystart(screenx, screeny);
			break;
		case 'd' :
			while (screeny + SCROLLSTEP <= HEIGHT - realheight) {
				screeny += SCROLLSTEP;
				vga_waitretrace();
				gl_setdisplaystart(screenx, screeny);
			}
			screeny = HEIGHT - realheight;
			gl_setdisplaystart(screenx, screeny);
			break;
		case 'u' :
			while (screeny - SCROLLSTEP >= 0) {
				screeny -= SCROLLSTEP;
				vga_waitretrace();
				gl_setdisplaystart(screenx, screeny);
			}
			screeny = 0;
			gl_setdisplaystart(screenx, screeny);
			break;
		case 27 :	/* cursor keys (shift picture) */
			{
			int k;
			k = getchar();
			if (k == 91 || k == 79) {
				switch (getchar()) {
				case 68 :	/* left arrow */
					if (image_width <= WIDTH) {	/* SVGA scroll */
						screenx = max(screenx - SHIFTSTEP, 0);
						gl_setdisplaystart(screenx, screeny);
					}
					else {
						if (xshift < SHIFTSTEP)
							xshift = 0;
						else
							xshift -= SHIFTSTEP;
					}
					break;
				case 67 :	/* right arrow */
					if (image_width <= WIDTH) {	/* SVGA scroll */
						screenx = min(screenx + SHIFTSTEP,
							WIDTH - realwidth);
						gl_setdisplaystart(screenx, screeny);
					}
					else {	/* picture shift */
						if (xshift > image_width - WIDTH - SHIFTSTEP)
							xshift = image_width - WIDTH;
						else
							xshift += SHIFTSTEP;
						if (xshift < 0)
							xshift = 0;
					}
					break;
				case 65 :	/* up arrow */
					if (image_height <= HEIGHT) {	/* SVGA scroll */
						screeny = max(screeny - SHIFTSTEP, 0);
						gl_setdisplaystart(screenx, screeny);
					}
					else {	/* picture shift */
						if (yshift < SHIFTSTEP)
							yshift = 0;
						else
							yshift -= SHIFTSTEP;
					}
					break;
				case 66 :	/* down arrow */
					if (image_height <= HEIGHT) {	/* SVGA scroll */
						screeny = min(screeny + SHIFTSTEP,
							HEIGHT - realheight);
						gl_setdisplaystart(screenx, screeny);
					}
					else {	/* picture shift */
						if (yshift > image_height - HEIGHT - SHIFTSTEP)
							yshift = image_height - HEIGHT;
						else
							yshift += SHIFTSTEP;
						if (yshift < 0)
							yshift = 0;
					}
					break;
				}
			}
			break;	
			}
		}
		if (c == 'q')
			break;
   	}
	}

    	vga_setmode(TEXT);
    	if (mouse)
    		mouse_close();
}


void instruction_exit() {    		
	printf(
        	"Syntax: spic [options] <filename>\n"
        	"Supported formats: GIF, JPEG (JFIF)\n"
		"Options:\n"
		"	-g <graphics mode>\n"
		"	-l <logical screen mode number>\n"
		"Environment variables:\n"
		"	GSVGAMODE=<graphics mode>\n"
		"Graphics modes (svgalib):\n"
		"	G320x200x256, G640x480x256, G640x480x32K, G640x480x16M, etc.\n"
        );
        exit(1);
}


void main(int argc, char **argv)
{
   	char *filename;
	FILE *f;
	int argi = 1;

    	if (argc < 2)
    		instruction_exit();

   	vga_init();
   
    	while (argi < argc && argv[argi][0] == '-') {
    		switch(argv[argi][1]) {
	    	case 'g' :
    			graphics_mode = vga_getmodenumber(argv[argi + 1]);
    			if (graphics_mode != -1)
	    			logical_mode = 0;
	    		else 
	    			logical_mode = -1; /* prompt */
    			argi += 2;
    			break;
    		case 'l' :
    			logical_mode = atoi(argv[argi + 1]);
    			argi += 2;
    			break;
    		}
    	}

	if (argi >= argc)
		instruction_exit();
    	filename = argv[argi];

   	if ((f = fopen(filename, "rb")) == NULL) {
      		printf("Error opening %s for reading.\n", filename);
      		exit(1);
   	}

	identify(f);	/* Detects obsolete JPEG files. */

 	image_palette = malloc(768);

	configure();

	image_colors = vga_getmodeinfo(VGAMODE)->colors;
	image_bytesperpixel = vga_getmodeinfo(VGAMODE)->bytesperpixel;

 	loadfile(filename);
 
   	show_picture();
   	fclose(f);
	printf("\n");
	exit(0);
}
