/*
 * MultiGIF  V1.0  28-Jun-96  Andy Wardley
 *
 * (C) 1996 Andy Wardley <abw@peritas.com>.  All rights reserved.
 *
 * parse.c
 *
 * Module containing functions to parse the command line parameters.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "parse.h"
#include "multigif.h"


/*
 * global configuration data
 */

int gpalframe;                    /* global palette frame    */
int bgcol;                        /* background colour index */
int nsloop;                       /* netscape loop number    */
int verbose;                      /* verbose mode            */
int nframes;                      /* number of frames        */
char *comment;                    /* comment string          */
char *outfile;                    /* output filename         */
frame *frameset;                  /* linked list of frames   */



/*
 * void usage(void)
 *
 * Print help message detailing command line options.
 */

void usage(void)
{
	(void) printf("\n");
	(void) printf("%s V%s.%s  %s\n\n", 
			PROGNAME, VERSION, INCREMENT, DATE);
	(void) printf("(C) Copyright %s %s <%s>.  All Rights Reserved\n\n",
			CRYEAR, AUTHOR, EMAIL);

	(void) printf("usage: %s [options] files  (or %s @paramfile)\n\n", PROGNAME, PROGNAME);
	(void) printf("  -b colour       Background colour index (default: 0)\n");
	(void) printf("  -c \"comment\"    Add comment to output GIF file\n");
	(void) printf("  -g frame        Use palette from <frame> as a global palette (default: 1)\n");
	(void) printf("  -n count        Add a Netscape loop of <count> iterations\n");
	(void) printf("  -o file         Output file name (default: %s)\n", OUTFILE);
	(void) printf("  -d \"n:n:n...\"   Delays (1/100s) for each frame.  Frame repetions may be\n");
	(void) printf("                  specified as \"d*r\" - delay <d> for <r> frames\n");
	(void) printf("  -r \"n:n:n...\"   Replace modes for each frame, specified as above\n");
	(void) printf("                  0/1 - None, 2 - Background, 3 - Restore\n");
	(void) printf("  -x \"n:n:n...\"   X position of frame (default: 0), specified as above\n");
	(void) printf("  -y \"n:n:n...\"   Y position of frame (default: 0), specified as above\n");
	(void) printf("  -l \"f,f,f...\"   List of frames to have a local palette.  Range: \"a-b\"\n");
	(void) printf("  -u \"f,f,f...\"   List of frames to require user input \n");
	(void) printf("  -v              Verbose mode\n");
	(void) printf("  -h              This help\n\n");

	(void) printf("The Graphics Interchange Format(c) is the Copyright property of CompuServe\n");
	(void) printf("Incorporated. GIF(sm) is a Service Mark property of CompuServe Incorporated.\n");
}



/*
 * char *next_arg(int argc, char **argv)
 *
 * Returns the next argument from the argv array.  Maintains a static 
 * variable recording which arguments have been read.  If an argument
 * begins with the '@' character, it assumes this is a parameter file.
 * It silently opens the file and starts reading tokens.  Each invocation
 * of next_arg will return the next token in the file.  When the file 
 * has been read, it will continue returning any tokens left in the 
 * argv array.  Returns a pointer to the argument or NULL when no more
 * arguments remain.
 */
 
char *next_arg(int argc, char **argv)
{
	static int arg = 0;
	static FILE *fp = NULL;
	static char buffer[PARSEBUFSIZ];
	static char *bufptr = NULL;
	static char *ptr;
	int inquotes;
	char *zap;


	/* loop around until we've read an argument */
	for(;;)
	{
		/* are we reading from a file? */
		if (fp != NULL)
		{
			/* read a line if we don't have one */
			while (*buffer == NULLTERM)
			{
				/* read line */
				if (fgets(buffer, PARSEBUFSIZ, fp) == NULL)
				{
					/* end of file */
					(void) fclose(fp);
					fp = NULL;
					*buffer = NULLTERM;

					/* break out of file read loop */
					break;
				}

				/* ensure NULL termination */
				buffer[PARSEBUFSIZ - 1] = NULLTERM;

				/* clobber CR/LF */
				if ((zap = strchr(buffer, '\n')) != NULL)	
					*zap = NULLTERM;

				/* point to start of buffer */
				bufptr = buffer;
			}

			/* loop around if file has closed */
			if (fp == NULL)
				continue;

			/* point to start of next token, ignoring leading whitespace */
			while (*bufptr && (*bufptr == SPACE || *bufptr == TAB))
				bufptr++;
			ptr = bufptr;

			inquotes = 0;

			/* terminate buffer after next token */
			while (*bufptr)
			{
				/* look for a quote character */
				if (*bufptr == QUOTE)
					inquotes = !inquotes;

				/* return on unquoted whitespace */
				if (!inquotes && (*bufptr == SPACE || *bufptr == TAB))
				{
					*bufptr++ = NULLTERM;
					break;
				}

				bufptr++;
			}

			/* if ptr is NULL, there was no token */
			if (*ptr != NULLTERM)
			{
				/* zap leading and trailing quotes */
				if (*ptr == QUOTE)
					ptr++;
					
				if ((zap = strrchr(ptr, QUOTE)) != NULL)
					*zap = NULLTERM;
					
				return ptr;
			}

			/* clear buffer to request another file read */
			*buffer = NULLTERM;
			continue;
		}

		/* check next arg */
		if (arg >= argc - 1)
			return (char *) NULL;
		else if (*argv[arg + 1] == '@')
		{
			if ((fp = fopen(argv[++arg] + 1, "r")) == NULL)
			{
				perror(argv[arg] + 1);
				exit(1);
			}

			/* zero out buffer */
			*buffer = '\0';
		}
		else
			/* return next arg */
			return (argv[++arg]);
	}
}



/*
 * frame *add_frame(frame **frameset)
 *
 * Malloc's a new frame struture and adds it to the end of the frame linked
 * list pointed to by framset.  Returns a pointer to the new frame or NULL
 * on failure.
 */

frame *add_frame(frame **frameset)
{
	static frame *newframe;
	frame *f;

	if ((newframe = (frame *) malloc(sizeof(frame))) != NULL)
	{
		/* init frame */
		newframe->filename = (char *) NULL;
		newframe->delay = 0;
		newframe->user = 0;
		newframe->replace = 0;
		newframe->xpos = 0;
		newframe->ypos = 0;
		newframe->locpal = 0;
		newframe->next = (frame *) NULL;

		/* add frame to frameset list */
		if (*frameset == NULL)
			*frameset = newframe;
		else
		{
			/* traverse frameset list */
			f = *frameset;
			while (f->next)
				f = f->next;

			/* add frame to end of list */
			f->next = newframe;
		}
	}

	return newframe;
}
	


/*
 * void expand_frame_mask(frame **frameset, char option, char *mask)
 *
 * Expands a mask (of the form "n1:n2:n3") into the individual colon-
 * seperated values.  These are then used to set variables (indicated
 * by option: 'd' = delay, 'r' = replace mode, 'x' = xpos, 'y' = ypos)
 * in the frames in the list pointed to by frameset.  Repetitions may
 * by indicated by the value "n*r" - value 'n' for next 'r' frames.
 */

void expand_frame_mask(frame **frameset, char option, char *mask)
{
	frame *f = *frameset;
	char *valptr, *repptr;
	int value, reps;
	
	/* get first token */
	valptr = strtok(mask, ":,");
	
	while (valptr)
	{
		reps = 1;
		
		/* look for a '*' repetition */
		if ((repptr = strchr(valptr, '*')) != NULL)
		{
			/* terminate value at '*' and convert reps counter */
			*repptr++ = NULLTERM;
			reps = atoi(repptr);
		}

		/* convert value */
		value = atoi(valptr);

		while (reps--)
		{
			/* create a frame if we need to */
			if (f == NULL)
			{
				if ((f = add_frame(frameset)) == NULL)
				{
					(void) fprintf(stderr, "Memory allocation failure\n");
					exit(1);
				}
			}

			switch (option)
			{
			case 'd':
				f->delay = value;
				break;
				
			case 'r':
				f->replace = value;
				break;
				
			case 'x':
				f->xpos = value;
				break;
				
			case 'y':
				f->ypos = value;
				break;
				
			default:
				(void) fprintf(stderr, "Unknown frame mask option: %c\n", 
						option);
				break;
			}

			/* get next frame */
			f = f->next;
		}
			
		/* get next token */
		valptr = strtok((char *) NULL, ":,");
	}
}



/*
 * void expand_frame_list(frame **frameset, char option, char *mask)
 *
 * Expands a mask (of the form "f1:f2:f3") into the individual colon-
 * seperated values.  These represent individual frame numbers for which
 * a flag should be set.  The flag in question is indicated by option:
 * 'l' - local palette, 'u' - user intervention.  A range of frame
 * numbers may be specified by the element "n-m".
 */

void expand_frame_list(frame **frameset, char option, char *mask)
{
	frame *f = *frameset;
	char *valptr, *endptr;
	int value, endval;
	int frameno, swapbuf, step;
	
	/* get first token */
	valptr = strtok(mask, ":,");
	
	while (valptr)
	{
		endval = -1;
		
		/* look for a '-' range marker */
		if ((endptr = strchr(valptr, '-')) != NULL)
		{
			/* terminate value at '-' and convert range end */
			*endptr++ = NULLTERM;
		}

		/* convert range start value */
		value = atoi(valptr);

		/* read range end value or set to range start if not used */
		if (endptr != NULL)
			endval = atoi(endptr);
		else
			endval = value;

		/* swap start and end ranges if necessary */
		if (value > endval)
		{
			swapbuf = endval;
			endval = value;
			value = swapbuf;
		}

		for(frameno = value; frameno <= endval; frameno++)
		{
			/* step through the frame list until we find frame i */
			step = frameno;
			f = *frameset;
			
			while (--step)
			{
				/* create a frame if we need to */
				if (f == NULL)
				{
					if ((f = add_frame(frameset)) == NULL)
					{
						(void) fprintf(stderr, "Memory allocation failure\n");
						exit(1);
					}
				}

				/* get next frame */
				f = f->next;
			}

			/* create a frame if we need to */
			if (f == NULL)
			{
				if ((f = add_frame(frameset)) == NULL)
				{
					(void) fprintf(stderr, "Memory allocation failure\n");
					exit(1);
				}
			}

			switch (option)
			{
			case 'u':
				f->user = 1;
				break;
				
			case 'l':
				f->locpal = 1;
				break;

			default:
				(void) fprintf(stderr, "Unknown frame list option: %c\n", 
						option);
				break;
			}

			/* get next frame */
			f = f->next;
		}
			
		/* get next token */
		valptr = strtok((char *) NULL, ":,");
	}
}



/*
 * int parse_cmd_line(int argc, char **argv)
 *
 * Reads and attempts to intepret the command line paramaters.  Sets the
 * global configuration items.  Returns 0 on success, -1 if an error
 * occurred.
 */

int parse_cmd_line(int argc, char **argv)
{
	char *parg, *narg;
	frame *f, *lastf, *nextf;
	int frameno, badframes;
	
	/* check usage */
	if (argc < 2)
	{
		(void) printf("usage: %s [options] files  (%s -h for help)\n", 
				PROGNAME, PROGNAME);
 		return -1;
	}

	/* initialise configuration set */
	gpalframe = 1;
	bgcol = 0;
	nsloop = -1;
	verbose = 0;
	comment = NULL;
	outfile = NULL;
	frameset = NULL;
		
	/* read each argument */
	while ((parg = next_arg(argc, argv)) != NULL)
	{
		/* option arguments start with a '-' */
		if (*parg == '-')
		{
			parg++;
			
			/* some options expect another parameter */
			if (strchr("bcdglnoruxy", *parg) != NULL)
			{
				/* next arg may follow directly after option letter... */
				if (*(parg + 1) != '\0')
				{
					narg = parg + 1;

					/* ignore leading quotes */
					while (*narg == QUOTE)
						narg++;
				}
				/* ...or as the next parameter */
				else if ((narg = next_arg(argc, argv)) == NULL || *narg == '-')
				{
					(void) fprintf(stderr, "-%c option expects an argument\n",			
							*parg);
					return -1;
				}
			}

					
			switch (*parg)
			{
			case 'b':  /* background colour */
				bgcol = atoi(narg);
				break;

			case 'c':  /* comment string */
				/* free any existing comment */
				if (comment != NULL)
					free(comment);

				/* malloc memory for comment */
				if ((comment = (char *) malloc(strlen(narg) + 1)) == NULL)
				{
					(void) fprintf(stderr, "Memory allocation failure\n");
					return -1;
				}
				
				(void) strcpy(comment, narg);
				break;

			case 'g':  /* use palette from frame ? as global */
				gpalframe = atoi(narg);
				break;

			case 'n':  /* netscape loop */
				nsloop = atoi(narg);
				break;

			case 'o':  /* output file */
				/* free any existing memory */
				if (outfile != NULL)
					free(outfile);

				/* malloc memory for outfile name */
				if ((outfile = (char *) malloc(strlen(narg) + 1)) == NULL)
				{
					(void) fprintf(stderr, "Memory allocation failure\n");
					return -1;
				}
				(void) strcpy(outfile, narg);
				break;
				
			case 'v':  /* verbose mode */
				verbose++;
				break;

			case 'h':  /* help */
				usage();
				return -1;

			/* 
			 * options having a parameter which lists values for
			 * each frame.  
			 */
			case 'd':  /* frame delays  */
			case 'r':  /* replace modes */
			case 'x':  /* x position    */
			case 'y':  /* y position    */
				expand_frame_mask(&frameset, *parg, narg);
				break;

			/*
			 * options having a parameter which indicates a list
			 * of one or more frames
			 */
			case 'u':  /* frames requiring user input  */
			case 'l':  /* frames using a local palette */
				expand_frame_list(&frameset, *parg, narg);
				break;

			default:
				(void) fprintf(stderr, "invalid option: -%c\n", *parg);
				break;
				
			} /* switch */
		} 
		else /* non '-' option */
		{
			/* 
			 * search through list for a frame without a name
			 * and create one at the end of the list if needs be
			 */
			f = frameset;
			 
			while (f != NULL && f->filename != NULL)
				f = f->next;

			if (f == NULL)
			{
				if ((f = add_frame(&frameset)) == NULL)
				{
					(void) fprintf(stderr, "Memory allocation failure\n");
					return -1;
				}
			}

			/* malloc filename memory */
			if ((f->filename = (char *) malloc(strlen(parg) + 1)) == NULL)
			{
				(void) fprintf(stderr, "Memory allocation failure\n");
				return -1;
			}

			(void) strcpy(f->filename, parg);
		}
	}

	/* set default output file name */
	if (outfile == NULL)
		outfile = OUTFILE;

	/* rebuild list exluding frames without filenames */
	nframes = badframes = 0;
	f = frameset;
	frameset = lastf = NULL;
	
	/* trawl list */
	while (f)
	{
		/* add frames with filenames */
		if (f->filename != NULL)
		{
			if (frameset == NULL)
				/* start new list */
				frameset = lastf = f;
			else
			{
				/* add to end of new list */
				lastf->next = f;
				lastf = lastf->next;
			}

			/* get next node in old list */
			f = f->next;

			/* break link to old list */
			lastf->next = NULL;
			
			nframes++;
		}
		else
		{
			/* free bad frame */
			nextf = f->next;
			free(f);

			badframes++;
			f = nextf;
		}
	}

	/* warn about ignored frames */
	if (badframes != 0)
		(void) fprintf(stderr, "WARNING: ignored %d %s without filenames\n", 
				badframes, badframes == 1 ? "frame" : "frames");

	/* barf if no frames have been specified */
	if (nframes == 0)
	{
		(void) fprintf(stderr, "ERROR: No source frames specified\n");
		return -1;
	}

	/* warn about global palette frame out of range */
	if (gpalframe > nframes)
	{
		(void) fprintf(stderr, "WARNING: Global Palette - frame %d does not exist\n",
				gpalframe);
		gpalframe = 1;
	}


	/* print action summary */
	if (verbose)
	{
		(void) printf("         Output file: %s\n", outfile);
		(void) printf("   Background colour: %d\n", bgcol);
		(void) printf("Global palette frame: %d\n", gpalframe);
		if (nsloop >= 0)
			(void) printf("       Netscape loop: %d\n", nsloop);
		if (comment != NULL)
			(void) printf("             Comment: %s\n", comment);

		(void) printf("            # frames: %d\n", nframes);
			
		(void) printf("\n");

		/* summarise frames */
		(void) printf("FRAME  DELAY  U/INPUT  REPLACE  XPOS  YPOS  PALETTE  FILENAME\n");
		
		frameno = 1;
		f = frameset;

		/* each frame */
		while (f)
		{
			(void) printf("  %3d  %5d  %7s  %7s  %4d  %4d  %7s  %s\n", 
					frameno++, f->delay, f->user ? "on" : "off",
					f->replace == 0 ? "O/Write" : 
					f->replace == 1 ? "None" :
					f->replace == 2 ? "Restore" :
					f->replace == 3 ? "Bckgrnd" : "???",
					f->xpos, f->ypos,
					f->locpal ? "local" : "global",
					f->filename);

			f = f->next;
		}
	}		


	return 0;
}

	
