/*
 *	mdl -- display a DL animation (monochrome ST only)
 *
 *	uses Setscreen flicker trick
 *
 *
 *
 *	derived from xdl by:
 *	      Jonas Yngvesson <jonas-y@isy.liu.se>
 *
 *	which was derived from dltogl.c by:
 *		George Phillips <phillips@cs.ubc.ca>
 *
 *	Support for user defined animation speed:
 *	      Per Beremark <per.beremark@telelogic.se>
 */

#include <stdio.h>
#include <stdlib.h>		/* for malloc */
#include <unistd.h>		/* for getopt */
#include <sys/types.h>
#include <sys/time.h>


#define isneg16(x)	((x) & 0x8000)
#define neg16(x)	((~(x) + 1) & 0x7fff)


typedef struct {
	int	version;
	int	format;
	int	images_per_screen;
	char	title[21];
	char	author[21];
	int	num_screen;
	int	num_command;
} DL_info;


int		__default_mode__ = _IOBIN;	/* force stdin binary (gcc) */

unsigned long	pixels[256];			/* for colormap conversion */
char	       *myname = "mdl";

void		usage (int);
void		colormap_setup (FILE *, int);
extern int	dl_flicker (unsigned char *, int, int, int, int, int, int, int, int *, int, int);

		/* pimg		-> raster image sequence (intensities) */
		/* nframes	number of frames in pimg */
		/* width	width, pixels */
		/* hight	height, pixels */
		/* beta		for laplace filter */
		/* maxrandom	for random noise */
		/* opt		0=no flicker, 1=flicker */
		/* delay	ms delay between images */
		/* cmd		-> commands (order to display frames) */
		/* numcmd	number of commands */
		/* show		print info to stdout if != 0 */


extern char    *optarg;				/* getopt stuff */
extern int	optind;



/*------------------------------*/
/*	main			*/
/*------------------------------*/
void main (int argc, char *argv[])
{
	DL_info		dlinfo;		/* dl file info */
	char	       *filename;
	FILE	       *fp;
	unsigned char  *image_data;	/* space for 1 screen from file */
	unsigned char  *img;		/* space for all frames */
	unsigned char  *pimg;		/* ptr to img */
	long		pimg_inc;	/* increment to next frame in img */
	int		nframes;	/* num of individual frames */
	int		ncommands;	/* num of frames (= num frames in loop)*/
	short		gray;
	int		width,
			height;
	int	       *cmd;		/* space for command list */
	int		labelpos;
	int		frame_freq;	/* milliseconds per frame */
	int		fps = 25;	/* frames per second */
	int		zoomflag = 0;
	int		ival = 0;
	int		errflg = 0;
	int		do_flick = 1;
	int		beta = 0;
	int		noise = 0;
	int		verbose = 0;
	int		i, j;



	/*
	 *   parse command line...
	 */
	while ((i = getopt(argc, argv, "vszhr:i:b:n:")) != -1)
	{
		switch (i)
		{
		case 'v':				/* verbose */
			verbose++;
			break;

		case 's':				/* single (no flicker)*/
			do_flick = 0;
			break;

		case 'b':				/* beta */
			beta = atoi(optarg);
			if (beta < 0) beta = 0;
			break;

		case 'n':				/* noise */
			noise = atoi(optarg);
			if (noise < 0)   noise = 0;
			if (noise > 255) noise = 255;
			break;

		case 'i':				/* intensity adjust */
			ival = atoi(optarg);
			if (ival < -254) ival = -254;
			if (ival >  254) ival = 254;
			break;

		case 'z':				/* zoom */
			zoomflag = 1;
			break;

		case 'h':				/* help */
			usage (0);
			break;	/*NOTREACHED*/

		case 'r':				/* rate (fps) */
			fps = atoi(optarg);
			if (fps < 1) fps = 1;
			if (fps > 100) fps = 100;
			break;

		case '?':
		default:
			errflg++;
		}
	}
	if (errflg)
	{
		usage(2);
	}



	/*
	 *   if no files, use stdin. otherwise open file
	 */
	if (argv[optind] == NULL)
	{
		fp       = stdin;
		filename = "stdin";
	}
	else if ((fp = fopen (argv[optind], "rb")) == NULL)
	{
		fprintf(stderr, "%s: can't open %s\n", myname, argv[1]);
		exit(1);
	}
	else
	{
		filename = argv[optind];
	}



	/*
	 *   Check the version number...
	 */
	if ((dlinfo.version = fgetc(fp)) != 1 && dlinfo.version != 2)
	{
		fprintf(stderr, "%s: This file is in an unknown format.\n",
			myname);
		fprintf(stderr, "can only do .DL version 1 and 2 (this is %d).\n",
			dlinfo.version);
		exit(1);
	}

    

	/*
	 *   ...and the format.
	 */
	if (dlinfo.version == 1)
		dlinfo.format = 1;
	else
		dlinfo.format = fgetc(fp);
    
	switch (dlinfo.format)
	{
	case 0:				/* large */
		width = 320;
		height = 200;
		dlinfo.images_per_screen = 1;
		zoomflag = 0;
		break;

	case 1:				/* medium */
		if (zoomflag)
		{
			width = 320;
			height = 200;
		}
		else
		{
			width = 160;
			height = 100;
		}
		dlinfo.images_per_screen = 4;
		break;

	default:
		fprintf(stderr,
			"%s: only large and medium formats are handled",
			myname);
		exit(1);
		break;
	}
    


	/*
	 *   Get title and author (if any). i don't know what this is used
	 *   for...
	 */
	dlinfo.title[20] = dlinfo.author[20] = 0;
	for (i = 0; i < 20; i++)
	{
		dlinfo.title[i] = fgetc(fp) ^ 255;
		if ((unsigned char)dlinfo.title[i] == 255)
			dlinfo.title[i] = 0;
	}
	for (i = 0; i < 20; i++)
	{
		if (dlinfo.version == 2)
		{
			dlinfo.author[i] = fgetc(fp) ^ 255;
			if ((unsigned char)dlinfo.author[i] == 255)
				dlinfo.author[i] = 0;
		}
		else
			dlinfo.author[i] = 0;
	}
    


	/*
	 *   Read number of screens and commands.
	 */
	dlinfo.num_screen = fgetc(fp);
	dlinfo.num_command = fgetc(fp);
    


	/*
	 *   Display what we know so far.
	 */
	nframes   = dlinfo.num_screen * dlinfo.images_per_screen;
	ncommands = dlinfo.num_command;
	if (verbose)
	{
		printf("%s is a %s sized version %d .DL file.\n",
			filename,
			(dlinfo.format == 0 ? "large" : "medium"), 
			dlinfo.version);
		printf("It contains %d images (num_screen=%d * images_per_screen=%d)\n",
			dlinfo.num_screen * dlinfo.images_per_screen,
			dlinfo.num_screen,
			dlinfo.images_per_screen);
		printf("in a %d frame (num_command) loop.\n",
			dlinfo.num_command);
		if (dlinfo.format == 1 && zoomflag)
		{
			puts("Zooming images to 320x200.");
		}
		printf("Displaying animation at %d frames per second.\n",fps);
	}



	/*
	 *   do the color map
	 */
	if (verbose)
		printf("Reading colormap...\n");
	colormap_setup(fp, dlinfo.version);



	/*
	 *   Allocate memory for the commands, the image data, etc
	 */
	if (verbose)
		printf ("Allocating memory:\n");

	/* command list: */
	if (verbose)
		printf ("   command list (%d bytes)\n",
			dlinfo.num_command * sizeof(int));
	if (!(cmd = (int *)malloc(dlinfo.num_command * sizeof(int))))
	{
		fprintf(stderr, "%s: out of memory (commands)", myname);
		exit(1);
	}

	/* one screen (file): */
	if (verbose)
		printf ("   single screen from file (64000 bytes)\n");
	if (NULL == (image_data = (unsigned char *)malloc(320 * 200)))
	{
		fprintf(stderr, "%s: not enough memory (image data).", myname);
		exit(1);
	}

	/* all frames: */
	if(dlinfo.format == 0)
	{
		/* large */
		if (verbose)
			printf ("   raster list, large (%ld bytes)\n",
				(long)(320L * 200L * dlinfo.num_screen));
		img = (unsigned char *)malloc(320 * 200 * dlinfo.num_screen);
		pimg_inc = 320 * 200;
	}
	else if (zoomflag)
	{
		/* medium->large */
		if (verbose)
			printf ("   raster list, zoom (%ld bytes)\n",
				(long)(320L * 200L * dlinfo.num_screen * dlinfo.images_per_screen));
		img = (unsigned char *)malloc(320 * 200 *
			dlinfo.num_screen * dlinfo.images_per_screen);
		pimg_inc = 320 * 200;
	}
	else
	{
		/* medium */
		if (verbose)
			printf ("   raster list, medium (%ld bytes)\n",
				(long)(160L * 100L * dlinfo.num_screen * dlinfo.images_per_screen));
		img = (unsigned char *)malloc(160 * 100 *
			dlinfo.num_screen * dlinfo.images_per_screen);
		pimg_inc = 160 * 100;
	}
	if (img == (unsigned char *) NULL)
	{
		fprintf(stderr, "%s: not enough memory (pimg).", myname);
		exit(1);
	}
	pimg = img;



	/*
	 *   Build the pixmaps for the animation.
	 */
	if (verbose)
	{
		printf("Building pixmaps, wait..."); fflush(stdout);
	}
	for (j = 0; j < dlinfo.num_screen; j++)
	{
		unsigned char  *src;
		int		row;
		int		col;


		/*
		 *   Read one screen of data.
		 */
		fread(image_data, 1, 320 * 200, fp);


		/*
		 *   Each screen can hold several images.
		 */
		for (i = 0; i < dlinfo.images_per_screen; i++)
		{
			/*
			 *   Get a pointer to the beginning of this image.
			 *   Put the pixels in the img raster. We do zooming
			 *   by reading the same data several times.
			 */
			src = image_data + (i % 2) * 160 + (i / 2) * 100 * 320;

			for (row = 0; row < height; row++)
			{
				for (col = 0; col < width; col++)
				{
					/*
					 *   map raw data to colormap
					 *   intensity
					 */
					gray = pixels[*src] + ival;
					if (gray > 255)	gray = 255;
					if (gray < 0)	gray = 0;

					/*
					 *   poke in our raster
					 */
					pimg[width * row + col] = gray;

					src += (zoomflag) ? (col & 1) : 1;
				}

				if (dlinfo.format)
				{
					if (zoomflag)
					{
						src += (row & 1) ? 160 : -160;
					}
					else
					{
						src += 160;
					}
				}
			}


			/*
			 *   advance image pointer to next frame
			 */
			pimg += pimg_inc;
		}
	}
	if (verbose)
		printf("done.\n"); fflush(stdout);



	/*
	 *   Read the commands.
	 */
	if (verbose)
		printf("Number of commands is %d\n", dlinfo.num_command);
	for (i = 0; i < dlinfo.num_command; i++)
	{
		if (dlinfo.version == 2)
		{
			j      = fgetc(fp);
			j     += fgetc(fp) << 8;
			cmd[i] = j;
		}
		else
		{
			j      = fgetc(fp);
			cmd[i] = (j % 10) - 1 + ((j / 10) - 1) * 4;
		}
		if (verbose)
			printf("command %3d: %d\n", i, cmd[i]);
	}
	if (verbose)
		fflush(stdout);


	/*
	 *   if last cmd is -ve, it contains the 'labelpos'. what is this?
	 */
	labelpos = 0;
	if (isneg16(cmd[dlinfo.num_command - 1]))
	{
		labelpos = (neg16(cmd[dlinfo.num_command - 1]) 
			+ 1);			/* Correct? Why add 1 ?? */
		dlinfo.num_command--;		/* ignore that last command */
	}



	/*
	 *   do it. first set delay per frame (milliseconds)
	 */
	frame_freq = 1000/fps;

	dl_flicker (img, nframes, width, height, beta, noise, do_flick,
		frame_freq, cmd, ncommands, verbose);


	exit (0);
}





/*------------------------------*/
/*	colormap_setup		*/
/*------------------------------*/
void colormap_setup (FILE *fp, int version)
{

/*
 *	Initialize the colormap. I use a private one for PseudoColor,
 *	I was too tired to fiddle with allocating shared colors.
 */

	unsigned char	pal[768];
	int		i;


	/*
	 *   Is this the border colour? ignore this anyway...
	 */
	if (version == 2)
	{
		for (i = 0; i < 3; i++)
			fgetc(fp);
	}
	else
		fgetc(fp);


    
	/*
	 *   Here comes the colormap. 3 bytes per each of 256 colors.
	 */
	fread(pal, 1, 768, fp);



	/*
	 *   Set up for grayscale conversion on a monochrome display (NTSC
	 *   weights). flicker interprets 0 as black and 255 as white.
	 *
	 *   TODO: consider histogram equalization here...
	 */
	for (i = 0; i < 256; i++)
	{
		pixels[i] = (((unsigned long)pal[3*i    ] << 2) * 300) / 1000
			  + (((unsigned long)pal[3*i + 1] << 2) * 590) / 1000
			  + (((unsigned long)pal[3*i + 2] << 2) * 110) / 1000;
	}
	return;
}



/*------------------------------*/
/*	usage			*/
/*------------------------------*/
void usage (int excode)
{
	fprintf(stderr,"usage: %s [options] [file.dl]\n", myname);
	fprintf(stderr,"options:\n");
	fprintf(stderr,"-v       verbose\n");
	fprintf(stderr,"-s       single image (no flicker)\n");
	fprintf(stderr,"-b beta  for laplace filter (0..10)\n");
	fprintf(stderr,"-n noise add random noise (0..255)\n");
	fprintf(stderr,"-z       zoom (enlarge to 320x200)\n");
	fprintf(stderr,"-h       help\n");
	fprintf(stderr,"-i val   add value to pixel intensity (-255..255)\n");
	fprintf(stderr,"-r fps   run at fps frames/second (default=25)\n");

	exit (excode);
}

