/*
 * display.c
 *
 * Copyright (C) 1993, 1994 Evan Harris
 *
 * Permission is granted to freely redistribute and modify this code,
 * providing the author(s) get credit for having written it.
 */

/*
 * (Evan) PIX_ALIGN changes inspired by Michael Weller, who patched against
 * version 1.1:
 *
 * Patched to not depend on illegal assumptions (xbytes==bytesperpixel*xdim),
 *         to obey logical_width a multiple of 8 pixels (not bytes)
 *         to let logical_width be an even higher multiple of a power of two
 *         for spike free video signal on Mach32
 * 16. 3. 94 Michael Weller (eowmob@exp-math.uni-essen.de or
 * eowmob@pollux.exp-math.uni-essen.de or mat42b@vm.hrz.uni-essen.de or
 * mat42b@de0hrz1a.bitnet) (svgalib, mach32-driver)
 */

#include "seejpeg.h"
#include <string.h>
#include <unistd.h>
#include <vga.h>


/*
 * Set PIX_ALIGN to be 64 because it seems to be a nice safe value.
 * Some cards can handle values much lower (which is desirable).
 * If your card can I would recommend trying values of 8, 16 or 32
 * before 64 (in the Makefile).
 * My CLGD5426 works correctly with a value of 8. 
 */
#ifndef PIX_ALIGN
#define PIX_ALIGN 64
#endif


struct best_mode_struct {
    int min_width;
    int min_height;
    int mode;
    int mode_width;
    int mode_height;
    int mode_depth;
};

static struct best_mode_struct best_mode_256[] = {
{ 1024, 768, G1280x1024x256, 1280, 1024, 256 },
{  800, 600,  G1024x768x256, 1024,  768, 256 },
{  640, 480,   G800x600x256,  800,  600, 256 },
#if 0
{  360, 480,   G640x480x256,  640,  480, 256 },
    /* we currently can't cope with the planar video modes */
{  320, 400,   G360x480x256,  360,  480, 256 },
{  320, 240,   G320x400x256,  320,  400, 256 },
{  320, 200,   G320x240x256,  320,  240, 256 },
#else
{  320, 200,   G640x480x256,  640,  480, 256 },
#endif  
{    0,   0,   G320x200x256,  320,  200, 256 },
};

static struct best_mode_struct best_mode_32k16m[] = {
{ 1024, 768, G1280x1024x16M, 1280, 1024, 16777216 },
{ 1024, 768, G1280x1024x32K, 1280, 1024,    32768 },
{  800, 600,  G1024x768x16M, 1024,  768, 16777216 },
{  800, 600,  G1024x768x32K, 1024,  768,    32768 },
{  640, 480,   G800x600x16M,  800,  600, 16777216 },
{  640, 480,   G800x600x32K,  800,  600,    32768 },
{  320, 200,   G640x480x16M,  640,  480, 16777216 },
{  320, 200,   G640x480x32K,  640,  480,    32768 },
{    1,   1,   G320x200x16M,  320,  200, 16777216 },
{    0,   0,   G320x200x32K,  320,  200,    32768 },
};

static int logical_width, logical_height;
static int logical_byte_width, bytesperpixel;
static int mode_width, mode_height, mode_depth, start_row, mode_linewidth;


/*
 * Select the best video mode for the current video card.
 *
 * components: 3 for RGB, 1 for grayscale or colourmapped
 */

int
best_mode(int width, int height, int components)
{
    int mode, i;
#ifndef NO_32K_CASCADE
    vga_modeinfo *vgainfo;
    int bytesperpixel, logical_byte_width, logical_width, logical_height;
#endif  

    if (opt_forcemode != TEXT) {
	if (vga_hasmode(opt_forcemode)) {
	    if (components == 1) {
		i = 0;
		do {
		    if (best_mode_256[i].mode == opt_forcemode) {
			mode_width = best_mode_256[i].mode_width;
			mode_height = best_mode_256[i].mode_height;
			mode_depth = best_mode_256[i].mode_depth;
			return opt_forcemode;
		    }
		} while (best_mode_256[i++].min_width > 0);
	    } else if (components == 3) {
		i = 0;
		do {
		    if (best_mode_32k16m[i].mode == opt_forcemode) {
			mode_width = best_mode_32k16m[i].mode_width;
			mode_height = best_mode_32k16m[i].mode_height;
			mode_depth = best_mode_32k16m[i].mode_depth;
			return opt_forcemode;
		    }
		} while (best_mode_32k16m[i++].min_width > 0);
	    }

	    error_exit("Selected mode not supported for this image type");
	} else {
	    error_exit("Selected mode not supported by chipset");
	}
    }

    mode = TEXT;

    if (components == 1) {

	i = 0;
	do {
	    if ((opt_fuzz * width > best_mode_256[i].min_width
		 || (opt_fuzz * height > best_mode_256[i].min_height
		     && !opt_widthonly))
		&& vga_hasmode(best_mode_256[i].mode)) {
		mode = best_mode_256[i].mode;
		mode_width = best_mode_256[i].mode_width;
		mode_height = best_mode_256[i].mode_height;
		mode_depth = best_mode_256[i].mode_depth;
	    }
	} while (mode == TEXT && best_mode_256[i++].min_width > 0);
	if (mode == TEXT) {
	    error_exit("Cannot find a 256 colour video mode");
	}

    } else if (components == 3) {

	i = 0;
	do {
	    if ((opt_fuzz * width > best_mode_32k16m[i].min_width
		 || (opt_fuzz * height > best_mode_32k16m[i].min_height
		     && !opt_widthonly))
		&& vga_hasmode(best_mode_32k16m[i].mode)) {
		mode = best_mode_32k16m[i].mode;
		mode_width = best_mode_32k16m[i].mode_width;
		mode_height = best_mode_32k16m[i].mode_height;
		mode_depth = best_mode_32k16m[i].mode_depth;

#ifndef NO_32K_CASCADE
		vgainfo = vga_getmodeinfo(mode);
	
		bytesperpixel = vgainfo->bytesperpixel;
		mode_linewidth = vgainfo->linewidth;
		
		logical_byte_width =
		    MAX(mode_linewidth, width * bytesperpixel);
		if (logical_byte_width % PIX_ALIGN != 0) {
		    logical_byte_width +=
			PIX_ALIGN - logical_byte_width % PIX_ALIGN;
		}
		if (logical_byte_width % (bytesperpixel * PIX_ALIGN) != 0) {
		    logical_byte_width += (bytesperpixel * PIX_ALIGN)
			- logical_byte_width % (bytesperpixel * PIX_ALIGN);
		}
		logical_width = logical_byte_width / bytesperpixel;
		logical_height = MIN(vgainfo->maxpixels * bytesperpixel / logical_byte_width, height);
		if (height > logical_height && mode_depth == 16777216) {
		    mode = TEXT; /* assume a 32k mode will be available */
		}
#endif
	    }
	} while (mode == TEXT && best_mode_32k16m[i++].min_width > 0);
	if (mode == TEXT) {
	    error_exit("Cannot find a 32K/16M colour video mode");
	}

    } else {
	error_exit("Cannot cope with number of output components");
    }

    return mode;
}


void
clear_screen(int mode_width, int mode_height,
	     int logical_width, int logical_height)
{
  int line, lines;
  
  vga_screenoff();
  
  /* it's done by vga_setmode() but it can't hurt */
  vga_clear();			

  vga_setpalette(0, 0, 0, 0);
  vga_setcolor(0);
  
  logical_height = MAX(logical_height, mode_height);
  
  /* fillblit is much better, but it's not general */
  if ((logical_width * logical_height + mode_width - 1) / mode_width
      > mode_height) {
    lines = ((logical_width * logical_height + mode_width - 1) / mode_width
	     - mode_height);
    for (line = mode_height; line < mode_height + lines; line++) {
      vga_drawline(0, line, mode_width, line);
    }
  }

  vga_screenon();
}


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


void
display_init(int image_width, int image_height, int components)
{
    int mode;			/* our video mode */
    int width, height;
    vga_modeinfo *vgainfo;

    width = (opt_doublex ? 2 * image_width : image_width);
    height = (opt_doubley ? 2 * image_height : image_height);

    mode = best_mode(width, height, components);

    vgainfo = vga_getmodeinfo(mode);

    bytesperpixel = vgainfo->bytesperpixel;
    mode_linewidth = vgainfo->linewidth;
  
    logical_byte_width = MAX(mode_linewidth, width * bytesperpixel);
    if (logical_byte_width % PIX_ALIGN != 0) {
	logical_byte_width += PIX_ALIGN - logical_byte_width % PIX_ALIGN;
    }

    /*
     * I don't really understand why we need this.
     * I wonder if it's documented somewhere...
     * 
     * (Michael, Mach32): well some cards want multiples of 8pixels in a row..
     * it is that easy.. 
     */
    if (logical_byte_width % (bytesperpixel * PIX_ALIGN) != 0) {
	logical_byte_width += (bytesperpixel * PIX_ALIGN)
	    - logical_byte_width % (bytesperpixel * PIX_ALIGN);
    }

    while (logical_byte_width / bytesperpixel
	   > vgainfo->maxpixels / mode_height) {
	logical_byte_width -= bytesperpixel * PIX_ALIGN;
    }

    logical_width = logical_byte_width / bytesperpixel;
    logical_height =
	MIN(vgainfo->maxpixels * bytesperpixel / logical_byte_width, height);

    if (opt_verbose) {
	printf("Image  : %dx%dx%d\n", image_width, image_height,
	       1 << (8 * components));
	printf("Mode   : %dx%dx%d\n", mode_width, mode_height, mode_depth);
	printf("Logical: %dx%d\n", logical_width, logical_height);
    }

    if (vga_getcurrentmode() != mode) {
	vga_setmode(mode);
    }
    vga_setdisplaystart(0);
    vga_setlogicalwidth(logical_byte_width);

    start_row = 0;

    clear_screen(mode_width, mode_height, logical_width, logical_height);
}


void
display_set_palette(int num_colors, JSAMPARRAY colormap, int components)
{
  int i;

  for (i = 0; i < num_colors; i++) {
    /* we should put these in an array so there's only one syscall */
    if (components == 1) {
      vga_setpalette(i, GETJSAMPLE(colormap[0][i]) >> 2,
		     GETJSAMPLE(colormap[0][i]) >> 2,
		     GETJSAMPLE(colormap[0][i]) >> 2);
    } else if (components == 3) {
      vga_setpalette(i, GETJSAMPLE(colormap[0][i]) >> 2,
		     GETJSAMPLE(colormap[1][i]) >> 2,
		     GETJSAMPLE(colormap[2][i]) >> 2);
    } else {
      error_exit("Cannot cope with number of colour components");
    }
  }
}


void
display_set_greyscale_palette()
{
  int i;

  for (i = 0; i < 256; i++) {
    vga_setpalette(i, i >> 2, i >> 2, i >> 2);
  }
}


void
display_rows(int num_rows, JSAMPIMAGE pixel_data, int image_width,
	     int components)
{
    JSAMPROW ptr0, ptr1, ptr2;
    int row, col;
    int width, height;
    int mode_row, mode_col;

    if (opt_doublex) {
	width = MIN(image_width, logical_width / 2);
    } else {
	width = MIN(image_width, logical_width);
    }

    if (start_row < logical_height) {
	if (logical_height < start_row + num_rows) {
	    height = logical_height - start_row;
	} else {
	    height = num_rows;
	}
    } else {
	start_row += num_rows;
	return;
    }
  
    if (components == 1) {
	for (row = 0; row < height; row++) {
	    mode_row = (((start_row + row) * logical_byte_width)
			/ mode_linewidth);
	    mode_col = ((((start_row + row) * logical_byte_width)
			 % mode_linewidth) / bytesperpixel);
	    ptr0 = pixel_data[0][row];
      
	    /*
	     * Duplicated code so we only do an unnecessary test per line
	     * rather than per pixel.
	     */
	    if (opt_doublex) {
		for (col = 0; col < width; col++) {
		    vga_setcolor(GETJSAMPLE(*ptr0++));
		    vga_drawpixel(mode_col++, mode_row);
		    vga_drawpixel(mode_col++, mode_row);
		    if (mode_col == mode_width) {
			mode_col = 0;
			mode_row++;
		    }
		}
	    } else {
		for (col = 0; col < width; col++) {
		    vga_setcolor(GETJSAMPLE(*ptr0++));
		    vga_drawpixel(mode_col++, mode_row);
		    if (mode_col == mode_width) {
			mode_col = 0;
			mode_row++;
		    }
		}
	    }
	}
    } else {
	for (row = 0; row < height; row++) {
	    mode_row = (((start_row + row) * logical_byte_width)
			/ mode_linewidth);
	    mode_col = ((((start_row + row) * logical_byte_width)
			 % mode_linewidth) / bytesperpixel);
	    ptr0 = pixel_data[0][row];
	    ptr1 = pixel_data[1][row];
	    ptr2 = pixel_data[2][row];

	    /*
	     * Duplicated code so we only do an unnecessary test per line
	     * rather than per pixel.
	     */
	    if (opt_doublex) {
		for (col = 0; col < width; col++) {
		    /* the slowest known solution... */
		    if (mode_depth == 32768) {
			vga_setcolor(((GETJSAMPLE(*ptr0++) & 0xf8) << 7)
				     | ((GETJSAMPLE(*ptr1++) & 0xf8) << 2)
				     | (GETJSAMPLE(*ptr2++) >> 3));
		    } else {
			vga_setcolor((GETJSAMPLE(*ptr0++) << 16)
				     | (GETJSAMPLE(*ptr1++) << 8)
				     | GETJSAMPLE(*ptr2++));
		    }
		    vga_drawpixel(mode_col++, mode_row);
		    vga_drawpixel(mode_col++, mode_row);
		    if (mode_col == mode_width) {
			mode_col = 0;
			mode_row++;
		    }
		}
	    } else {
		for (col = 0; col < width; col++) {
		    /* the slowest known solution... */
		    if (mode_depth == 32768) {
			vga_setcolor(((GETJSAMPLE(*ptr0++) & 0xf8) << 7)
				     | ((GETJSAMPLE(*ptr1++) & 0xf8) << 2)
				     | (GETJSAMPLE(*ptr2++) >> 3));
		    } else {
			vga_setcolor((GETJSAMPLE(*ptr0++) << 16)
				     | (GETJSAMPLE(*ptr1++) << 8)
				     | GETJSAMPLE(*ptr2++));
		    }
		    vga_drawpixel(mode_col++, mode_row);
		    if (mode_col == mode_width) {
			mode_col = 0;
			mode_row++;
		    }
		}
	    }
	}
    }
    start_row += num_rows;
}


#define OFFSET_ROWS 8
#define OFFSET_COLS PIX_ALIGN	/* a high PIX_ALIGN is a pain here */

void
scroll_until_end()
{
    int c, done = 0, offset = 0;

    /*
     * If this is a slideshow we don't allow scrolling, we just sleep
     * for the given time.
     */
    if (opt_slideshow >= 0) {
	if (opt_slideshow > 0) {
	    sleep(opt_slideshow);
	}
	done = 1;
    }
    
    while (!done) {
	c = vga_getch();
	switch (c) {
	  case 'q':
	    opt_cycle = 0;
	    done = 1;
	    break;
	    
	  case 0x1b:		/* ESC */
	    c = vga_getch();
	    if (c == '[') {
		c = vga_getch();
		switch (c) {
		  case 'A':	/* up */
		    if (offset > 0) {
			offset -= OFFSET_ROWS * logical_byte_width;
			while (offset < 0) {
			    offset += logical_byte_width;
			}
			vga_setdisplaystart(offset);
		    }
		    break;
		  case 'B':	/* down */
		    if (offset / logical_byte_width
			< logical_height - mode_height) {
			offset += MIN(OFFSET_ROWS,
				      logical_height - mode_height
				      - offset / logical_byte_width)
			    * logical_byte_width;
			vga_setdisplaystart(offset);
		    }
		    break;
		  case 'C':	/* right */
		    if ((offset % logical_byte_width) / bytesperpixel
			+ mode_width
			< logical_width) {
			offset += MIN(OFFSET_COLS * bytesperpixel,
				      (logical_width - mode_width)
				      * bytesperpixel
				      - offset % logical_byte_width);
			vga_setdisplaystart(offset);
		    }
		    break;
		  case 'D':	/* left */
		    if (offset % logical_byte_width > 0) {
			offset -= MIN(OFFSET_COLS * bytesperpixel,
				      offset % logical_byte_width);
			vga_setdisplaystart(offset);
		    }
		    break;
		  default:
		    done = 1;
		}
	    } else {
		done = 1;
	    }
	    break;

	  default:
	    done = 1;
	    break;
	}
    }
}


void
display_shutdown()
{
    vga_setmode(TEXT);
}
