/*********************************************************************
 * EDUSHOW	Copyright 1990 Laurence Vanhelsuwé
 * -------
 * Specialised interactive slideshow program for educational purposes 
 * or giving presentations to groups of people.
 *
 * Written by L.Vanhelsuwé	© 1990
 *
 * - started/finished on 12-Mar-1990
 * - quick check before releasing to Fish PD 22-May-1994
 *********************************************************************/


#include	<exec/types.h>
#include	<intuition/intuition.h>

#include	<clib/exec_protos.h>			// ANSI function prototypes
#include	<clib/alib_protos.h>
#include	<clib/dos_protos.h>
#include	<clib/graphics_protos.h>
#include	<clib/intuition_protos.h>

#define		FIRST_ARG		argv[1] 
#define		LF				(0x0a)
#define		MAX_COLOR		16	/** IN HIRES SCREEN			**/
#define		COLOR			15	/** MASK FOR HARDWARE COLOR PRIMARY	**/
#define		CHUNK_ID		long
#define		CHUNK_LEN		long

#define		COLORS			16

#define		NEXT_COLOR		' '
#define		PREV_COLOR		'\b'
#define		NEXT_PICTURE	'n'
#define		PREV_PICTURE	'p'
#define		QUIT			'q'

#define		IN				1		/** COLOR FADE DIRECTIONS **/
#define		OUT				0

#define		MAX_ALLOCS	40			// maximum objects allocated 

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

char ver[] = "$VER: EDUSHOW v0.9 (12-MAR-90) by Laurence Vanhelsuwé";

char screentitle[] = 
"WICPUG 68000 Assembly Language Course -Educational Slideshow 0.9 (C) LVA 1990";

int count_lines(char *text);		/** AND ZERO TERMINATE LINES !! **/
int cache_all_pictures(char * list_of_pictures, int number_of_pics);

		/****** non "int" functions ******/
		
char  * load_file(char *name);		/** CACHES ANY FILE IN MEMORY	**/
char  * next_string(char*n);		/** NEXT START OF LINE		**/
char  * expand_line(char*a,char*b);	/** UN-COMPRESS RUN-LENGTH LINE **/
char  * find_chunk(char*s,char*n);	/** FIND START OF ANY iff CHUNK **/
char	get_input(void);			/** USER INPUT ROUTINE		**/

short	scale_color(short,short);	/** FADE COLOR n BY FACTOR x	**/

void	fade_color(int p, int d);	/** FADES PEN n IN or OUT	**/
void	fade_screen(int dir);
void	decode_color_maps(struct mem_iff_pic *mip_array,int n);	/** UNPACKS iff CMAP INTO INTERNAL FORM **/
void	decompress_pic(void);		/** PUT COMPRESSED PICTURE ON SCREEN **/
void	open_screen(void);			/** OPEN AN Intuition SCREEN	**/
void	lights_out(void);			/** ALL PENS ARE SET TO BLACK	**/
void	close_everything(void);		/** DEALLOCATE,FREE,CLOSE ALL	**/
void	next_color(void);			/** FADE IN NEXT COLOR IN SLIDE **/
void	prev_color(void);			/** FADE OUT CURRENT COLOR IN SLIDE **/
void	next_picture(void);				/** FADE OUT THIS SLIDE, SHOW NEXT **/
void	prev_picture(void);			/** FADE OUT THIS SLIDE, SHOW PREV **/

struct Screen * OpenScreen();
struct Window * OpenWindow();

		/****** global variables ******/
struct IntuitionBase *IntuitionBase;
struct GfxBase		*GfxBase;

struct Screen *screen;
struct Window *window;
struct ViewPort *vport;

int current_pic,current_color,lessons;

int alloc_num=0;

		/****** global structures *****/
struct iff_header {			/** THIS DEFINES THE iff HEADER **/
	CHUNK_ID	form_id;
	CHUNK_LEN	form_len;
	CHUNK_ID	form_type;
	CHUNK_ID	bmhdr_id;
	CHUNK_LEN	bmhdr_len;
	short		width;
	short		height;
	long		unknown;
	BYTE		planes;
	BYTE		pad0;
	BYTE		compression;
	BYTE		pad1;
	long		pad2,pad3;
	CHUNK_ID	cmap_id;
	CHUNK_LEN	cmap_len;
	char		cmap[16*3];
	CHUNK_ID	body_id;
	CHUNK_LEN	body_len;
	char		body;
};	

struct mem_iff_pic {
	struct iff_header *file_buffer;	/** PTR TO COMPRESSED IFF PIC **/
	char	*body_ptr;		/** PTR TO BODY CHUNK IFF **/
	short cmap [COLORS][3];		/** EXPANDED COLOR PALETTE */
};

struct mem_iff_pic *lesson_pics;

struct mem_block {			/** define a struct for every object **/
	char	*ptr;			/** that we've allocated			 **/
	int		size;
} allocations[MAX_ALLOCS];

/*************************************************************************/
/*************************** START OF MAIN *******************************/
/*************************************************************************/

main(argc,argv)			/** TAKES A SCRIPT FILE AS ARGUMENT **/
int argc;
char *argv[];
{
	char *script_buffer;
	char user_input;
	int	script_size;
	int	success;
	
	script_size = 0;
	
//	printf("Filename = %s # of args = %d\n",FIRST_ARG,argc);
	if (argc != 2) exit (100);

	printf("EDUcational slideSHOW V0.9                   (C) 1990 L. Vanhelsuwé\n");
	printf("--------------------------                   -------------------------\n");
	printf("\n");
	printf("Use the following keys to control the presentation:\n");
	printf("\n");
	printf("SPACE...... fade in next image element\n");
	printf("BACKSPACE.. fade out current image element\n");
	printf("'N'........ fade in next slide\n");
	printf("'P'........ fade in previous slide\n\n");
	printf("'Q'........ to quit\n\n");

	printf("HIT <ENTER> to start presentation."); getch();
	printf("\n");
	
	GfxBase		  = (struct GfxBase *) OpenLibrary("graphics.library",0);
	IntuitionBase = (struct IntuitionBase*) OpenLibrary("intuition.library",0);
	
	script_buffer = load_file(FIRST_ARG);		/** LOAD SCRIPT **/
		if (script_buffer == NULL) exit(100);

	lessons = count_lines(script_buffer);		/** HOWMANY PICS **/
	printf("Number of Slides in this presentation = %d \n",lessons);

	lesson_pics = (struct mem_iff_pic*)
		 AllocMem(lessons*sizeof (struct mem_iff_pic),0); 

							/** LOAD ALL PICS **/
	success = cache_all_pictures(script_buffer,lessons);
	if (success == FALSE)
	{
		printf("Cache technique failed this time....\n");
		exit(100);
	}

	decode_color_maps(lesson_pics,lessons);

	open_screen();			/** OPEN SLIDESHOW SCREEN	**/
	current_pic = -1;		/** START SHOW OFF ....		**/
	next_picture();			
	
	while ( (user_input = get_input()) != QUIT)
	{
		switch (user_input)
		{
			case 13:
			case NEXT_COLOR:	next_color(); break;
			case PREV_COLOR:	prev_color(); break;
			case NEXT_PICTURE:	next_picture(); break;
			case PREV_PICTURE:	prev_picture(); break;

			default:
				printf("Don't know event %d !\n",(int) user_input);
		}
	}

	close_everything();		/** RELEASE SCREEN,CACHE ETC..**/

	CloseLibrary((struct Library*)GfxBase);
	CloseLibrary((struct Library*)IntuitionBase);

	printf ("\n\nHave a nice day..\n");
}
	
/***********************************************************************/
// Turn screen dark, decompress next slide, fade it in.
/***********************************************************************/
void next_picture()
{
	if (current_pic != (lessons -1) )
	{
		lights_out();
		current_pic++;
		decompress_pic();
		
		current_color = 1; fade_color(current_color,IN);
	}
}
/***********************************************************************/
// Fade next "subject" in (mapped to a unique pen color)
// Goto next slide if no more subjects.
/***********************************************************************/
void next_color()
{
	current_color++;
	if (current_color < MAX_COLOR)
	{
		fade_color(current_color,IN);
	}
	else
	{
		next_picture();
	}
}	
/***********************************************************************/
// Do opposite of next_color()
/***********************************************************************/
void prev_color()
{
	if (current_color != 1)
	{
		fade_color( current_color, OUT );
		current_color--;
	}
	else
	{
		prev_picture();
	}
}
/***********************************************************************/
// Backtrack by going back to previous slide
/***********************************************************************/
void prev_picture()
{
	if (current_pic != 0)
	{
		lights_out();
		current_pic--;
		decompress_pic();
		fade_screen(IN);
		current_color = 1;
	}
}
/***********************************************************************/
char get_input()		/** WAIT FOR ANY ASCII KEY PRESS **/
{
	int type = -1;
	char key;
	struct IntuiMessage * msg;

	while (type != VANILLAKEY)
	{
		Wait(1 << window->UserPort->mp_SigBit);

		while (msg = (struct IntuiMessage *) GetMsg(window->UserPort))
		{
			type = msg->Class;
			key = (char) msg->Code;
			ReplyMsg((struct Message*)msg);
		}
	}

	return key;
}
/***********************************************************************/
// Cache a picture file in its entirety.
/***********************************************************************/

char * load_file(name)
char * name;
{
	int file_handle;
	int file_size;
	
	char * buff_ptr;
	

	file_handle = Open(name,MODE_OLDFILE);
	if (file_handle) {
		Seek(file_handle,0L,OFFSET_END);
		file_size = Seek(file_handle,0L,OFFSET_BEGINNING);
		
		buff_ptr = (char *) AllocMem(file_size +1,0); /* ANY MEM */
		if (buff_ptr == NULL) 
			return NULL;		/* ALLOC FAILED */
		
		allocations[alloc_num].ptr  = buff_ptr;			// Track allocations
		allocations[alloc_num].size = file_size+1;
		alloc_num++;

		Read(file_handle,buff_ptr,file_size);
		Close(file_handle);
		
		*(buff_ptr+file_size) = '\0';	/** ADD END TAG **/
		return buff_ptr;
	} else {
		printf("OPEN FAILED : file %s \n",name);
		exit(100);
	}
}

/***********************************************************************/
// Go through script counting # of lines (== number of slides)
/***********************************************************************/
int count_lines(text)		/** AND ZERO TERMINATE LINES !! **/
char * text;
{
	int lines=0;
	register char *text_ptr=text;
	register char ch;
	
	while (ch = *text_ptr++)	/** UNTIL WE HIT END OF TEXT **/
	{
		if (ch == LF) 		/** COUNT LINE FEEDS **/
		{
			lines++;
			*(text_ptr-1) = '\0';	/* CHANGE INTO C-STRING **/
		}
	}
	
	return lines;
}

/***********************************************************************/
int cache_all_pictures(list_of_pictures,number_of_pics)
char * list_of_pictures;
int number_of_pics;
{
	int i;
	struct mem_iff_pic *mip;
	char *ptr, *fname = list_of_pictures;
	
	mip = lesson_pics;
	
	for (i=0; i<number_of_pics; i++,mip++) {

//		printf("FNAME #%d -> %s \n",i,fname);

		mip->file_buffer = (struct iff_header *) load_file(fname);

//		printf("Mmmmm... cached at %x \n", (int) mip->file_buffer);

		if (mip->file_buffer == NULL) {
			printf("Madonna !*&#$% NO MORE MEMORY !\n");
			return FALSE;
		}
	
		ptr = (char *) &mip->file_buffer->bmhdr_id;
		
		mip->body_ptr = find_chunk(ptr,"BODY");
		
		fname = next_string(fname);
	}
	return TRUE;
}
/***********************************************************************/
char * next_string(text)
char * text;
{
	register char *ptr=text;
	
	while (*ptr++)
		; 
	
	return ptr;
}
/***********************************************************************/
char * find_chunk(start,name)
char *start,*name;
{
	long *len;
	
	while (	(start[0] != name[0]) || (start[1] != name[1]) ||
		(start[2] != name[2]) || (start[3] != name[3])    )
	{
		len = (long *) start +1;
		start += (*len)+8;
	}
	
	return start+8;
}
/***********************************************************************/
void decode_color_maps(mip_array,entries)
struct mem_iff_pic *mip_array;
int entries;
{
	int c,i;
	struct mem_iff_pic *mip=mip_array;
	struct iff_header  *ptr;
	char   *gun_ptr;
	
	for(i=0; i<entries; i++) {
		ptr = (struct iff_header*) mip->file_buffer;

//		printf("Planes = %d ",(int) ptr->planes);
//		printf("Width = %d Height = %d ",
//			(int) ptr->width, (int) ptr->height);
//		printf("Compressed = %s",ptr->compression?"YES":"NO");
//		printf("\n");
				
		gun_ptr = &ptr->cmap[0];
		for(c=0; c<16; c++)
		{
			mip->cmap[c][0] = (short) (*(gun_ptr++)>>4) &COLOR;
			mip->cmap[c][1] = (short) (*(gun_ptr++)>>4) &COLOR;
			mip->cmap[c][2] = (short) (*(gun_ptr++)>>4) &COLOR;
		}

		mip++;
	}
}
/***********************************************************************/
void lights_out()
{
	int color;
	
	for (color=0; color<16; color++)
	{
		SetRGB4(vport,color,0,0,0);
	}
}
/***********************************************************************/
// Fade a CLUT from dark to normal or vice versa.
/***********************************************************************/
void fade_screen(direction)
int direction;
{
	short i,pen,min,max,incr;
	short red,green,blue;

	struct mem_iff_pic *mip;
	
	mip = lesson_pics + current_pic;
	
	if (direction == IN) {
		min = 0; max = MAX_COLOR; incr = 1;
	} else {
		min = MAX_COLOR; max = 0; incr = -1;
	}

	for (i=min; i != max ; i = i+incr)	/** INTENSITY 0..16 **/
	{
		for (pen=0; pen < MAX_COLOR; pen++)	/** PEN 0..15 **/
		{
			red   = scale_color(mip->cmap[pen][0],i);
			green = scale_color(mip->cmap[pen][1],i);
			blue  = scale_color(mip->cmap[pen][2],i);
			SetRGB4(vport,pen,red,green,blue);
		}

		Delay(1);
	}
}
/***********************************************************************/
// Core routine for fade:  "scale" an RGB triplet by a brightness factor.
/***********************************************************************/
void fade_color(pen,direction)
int pen,direction;
{
	short i,min,max,incr;
	short red,green,blue;
	struct mem_iff_pic *mip;
	
	mip = lesson_pics + current_pic;
	
	if (direction == IN) {
		min = 0; max = MAX_COLOR; incr = 1;
	} else {
		min = MAX_COLOR; max = 0; incr = -1;
	}

	for (i=min; i != max ; i = i+incr) {
		red   = scale_color(mip->cmap[pen][0],i);
		green = scale_color(mip->cmap[pen][1],i);
		blue  = scale_color(mip->cmap[pen][2],i);
		
		SetRGB4(vport,pen,red,green,blue);
		Delay(1);
	}
}
/***********************************************************************/
short int scale_color(primary,intens)
short primary;
short intens;
{
	primary = (primary*intens)>>4;
	return primary;
}
/***********************************************************************/
// Expand cached IFF picture to currently darkened screen.
/***********************************************************************/
void decompress_pic()
{
	short scan_line;		/** SCREEN LINE COUNTER **/
	char *p1,*p2,*p3,*p4,*data;	/** PTRS TO IFF & PLANES **/
	struct mem_iff_pic *mip;	/** PTR TO PICTURE DESCRIPTOR **/
	
	mip = lesson_pics + current_pic;
	data = mip->body_ptr;
	
	p1 = screen->BitMap.Planes[0];	/** FIND BITPLANES OF SCREEN **/
	p2 = screen->BitMap.Planes[1];
	p3 = screen->BitMap.Planes[2];
	p4 = screen->BitMap.Planes[3];
	
	for(scan_line=0; scan_line<200; scan_line++) {
		data = expand_line(data,p1);	/** DECODE ONE LINE PER */
		data = expand_line(data,p2);	/** PLANE AT A TIME */
		data = expand_line(data,p3);
		data = expand_line(data,p4);
		
		p1 += 80; p2 += 80; p3 += 80; p4 += 80;	/** NEXT LINE.. **/
	}
}
/***********************************************************************/
char * expand_line(data,plane_ptr)	/** RUN-LENGTH DECOMPRESSOR **/
char *data,*plane_ptr;
{
	char code,pattern,*eol;
	
	eol = plane_ptr +80;		/** CALC END OF LINE **/
	
	while (plane_ptr != eol) {
		code = *data++;		/** GET ENCRYPTION TYPE && 'N' **/
		
		if (code < 0)	/** NEGATIVE : REPLICATE NEXT BYTE **/
		{
			code = (-code) +1;
			pattern = *data++;
			while (code--)
			{
				*plane_ptr++ = pattern;
			}
		} else {		/** POSITIVE : COPY LITERAL RUN **/
			code++;
			while (code--)
			{
				*plane_ptr++ = *data++;
			}
		}
	}
	
	return data;		/** RETURN UPDATED SOURCE PTR **/
}
/***********************************************************************/
void open_screen() {

static struct NewScreen myscreen =	/** INTUITION SCREEN **/
{					/** PARAMETER BLOCK **/
	0,0,
	640,200,
	4,
	1,0,
	HIRES,
	CUSTOMSCREEN,
	NULL,screentitle,
	NULL,NULL
};

struct NewWindow mywindow =		/** INTUITION WINDOW	**/
{					/** PARAMETER BLOCK	**/
	0,0,
	640,200,
	255,255,
	VANILLAKEY,			/** WANT KEY PRESSES	**/
	BACKDROP+ACTIVATE,
	NULL,NULL,
	NULL,				/* NO TITLE */
	NULL,NULL,
	0,0,640,200,
	CUSTOMSCREEN
};
	
	screen = OpenScreen(&myscreen);			/** MAGIC ! **/
	if (screen == NULL) {
		printf("Damn You : NO SCREEN !\n");
		exit(100);
	}
	
	vport = &screen->ViewPort;	/** HANDLE FOR COLOR CHANGES **/

	mywindow.Screen = screen;
	window = OpenWindow(&mywindow);
}
/***********************************************************************/
void close_everything()
{
	int i;

	CloseWindow(window);
	CloseScreen(screen);

	for (i=0; i< alloc_num; i++) {
		FreeMem(allocations[i].ptr, allocations[i].size);
	}

	FreeMem((char*) lesson_pics, lessons * sizeof(struct mem_iff_pic));
	
	/* return cached file buffers & descriptor array */
}
/***********************************************************************/
