/*
 *	Grammar for gdit
 *	File:	grammar
 *	Date:	30th March 1995
 *
 *	(C) 1995, DB Harvey-George.
 *	All rights reserved
 *	http://www.demon.co.uk/3Wiz/gdit/index.html
 *
 *	Permission is granted for individual none-commercial use.  Please
 *	see the documentation for licensing details.
 *
 *	This program is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 *	Provides a simple command line interpreter for the GD graphics
 *	library.  GD was written by Thomas Boutell is copyright 1994,
 *	Quest Protein Database Center, Cold Spring Harbor Labs.  gdit makes
 *	no modifications to and is based on release 1.1.1.  You must obtain
 *	gd separately from this program.  The Quest home page is on:
 *		 http://siva.cshl.org/index.html
 *
 *	gdit only supports the creation of one image at a time.  A typical
 *	sequence for producing an image would be:
 *
 *		new <width> <height>
 *		 ... drawing commands
 *		save <filename>
 *		quit
 *
 *	The standard set of MS-Windows colors are predefined and can be
 *	refered to by name in drawing directives without having first declared
 *	the color.  gdMaxColors custom colors can also be defined, this is
 *	the limit of the GIF color pallete.  Colors remain defined even after
 *	a delete image directive.
 *
 *	To Do.
 *	Allow an arbitary number of parameters.  This could be done by
 *	replacing array with link list.  But is this really worth it?
 *
 *	Allow colors to be redefined - could be SET <EXISTING COLOR> rgb
 */

%{
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <stdlib.h>
#include "dict.h"
#include "gd.h"
#include "gdfontl.h"
#include "gdfonts.h"
#include "gdfontg.h"
#include "gdfontmb.h"
#include "gdfontt.h"
#include "gdit.h"

#ifdef SUNOS
#define strerror perror
#endif /* SUNOS */


/*
 *	A list of predefined colors.  Text names can be used to refer to
 *	these colors.  Table is terminated by Null name.
 *
 *	ISOish.
 *	These names will need changing to the proper names, if they exist.
 */

struct	predefined_colors pct[] = {
	"salmon", 255, 128, 128,
	"red", 255, 0, 0,
	"brown", 128, 64, 64,
	"dark_red", 128, 0, 0,
	"red_black", 64, 0, 0,
	"black", 0, 0, 0,
	"wheat", 255, 255, 128,
	"yellow", 255, 255, 0,
	"orange", 255, 128, 64,
	"orange_red", 255, 128, 0,
	"red_brown", 128, 64, 0,
	"forest_green", 128, 128, 0,
	"green_yellow", 128, 255, 128,
	"medium_green", 128, 255, 0,
	"green", 0, 255, 0,
	"dark_green", 0, 128, 0,
	"green_black", 0, 64, 0,
	"sienna", 128, 128, 64,
	"spring_green", 128, 255, 0,
	"blue_green", 0, 255, 64,
	"gun_metal", 0, 128, 128,
	"malachite", 0, 128, 64,
	"gray", 128, 128, 128,
	"aquamarine", 128, 255, 255,
	"sky_blue", 0, 255, 255,
	"blue", 0, 0, 255,
	"purple", 0, 0, 128,
	"ocean", 64, 128, 128,
	"slate_blue", 0, 128, 255,
	"cadet_blue", 0, 128, 192,
	"navy_blue", 0, 0, 160,
	"blue_black", 0, 0, 64,
	"light_gray", 192, 192, 192,
	"mauve", 128, 0, 128,
	"violet", 255, 128, 255,
	"magenta", 255, 0, 255,
	"violet_red", 255, 0, 128,
	"royal_blue", 128, 0, 255,
	"white", 255, 255, 255,
	"", 0, 0, 0
};
	
/*
 *	GDIT's color map.
 */

struct color_map_table cmt;

/*
 *	Dictionary of line styles
 */

Dict_t StyleDict;

/*
 *	Parameter list.  Up to MAXPARAM parameters are allowed
 */
/*	allow MAXSTYLE color changes in a style */
char	colors[MAXSTYLE][NAMELEN];
int	param[MAXPARAM];
int	pindex;

/*
 *	Last word returned by lexer
 */
char	*word;

/*
 *	Some globals
 */
char		style_name[NAMELEN];
FILE		*fp;
gdImagePtr	image_out = 0;
gdImagePtr	brush = 0;
gdImagePtr	tile = 0;
int		Closest = 0;
gdFontPtr	font;
char		*comment = 0;

struct idlist {
	char *	name;
	int	id;
};
%}

%start list
%token DELETE GET LETTER NEW NUMBER SAVE STRING ALPHANUM SET QUIT
%token GIFFILE DEBUG_ON DEBUG_OFF XBMFILE COMMENT
%token ARC LINE PIXEL POLYGON RECTANGLE FILL
%token FILLED TILED STYLE TILE TRANSPARENT INTERLACED PIXEL BRUSH INFO
%token NEWLINE COLOR ALL CLOSEST EXACT PRINT HELP UP
%token BIG SMALL MB GIANT TINY


%% /* beginning of rules section */

list	:	/* empty */
	|	list NEWLINE
		{
			if (prompt) {
				printf("> ");
			}
		}
	|	list function NEWLINE
		{
			if (prompt) {
				printf("> ");
			}
		}
	|	list draw NEWLINE
		{
			if (prompt) {
				printf("> ");
			}
		}
	|	list QUIT NEWLINE
		{
			delete_all();
			fflush(stdout);
			fflush(stderr);
			return;
		}
	|	error NEWLINE
		{	yyerrok;
			if (!silent) {
		  		printf("reenter last line\n> ");
			}
		}
	;

function:	NEW NUMBER NUMBER /* white is default color */
		{
			new_image($2, $3, get_color("white"), 0);
		}
	|	NEW NUMBER NUMBER style_type
		{
			new_image($2, $3, get_color("white"), $4);
		}
	|	NEW NUMBER NUMBER style_type style_type
		{
			new_image($2, $3, get_color("white"), ($4 | $5));
		}
	|	NEW NUMBER NUMBER color
		{
			new_image($2, $3, $4, 0);
		}
	|	NEW NUMBER NUMBER color style_type
		{
			new_image($2, $3, $4, $5 );
		}
	|	NEW NUMBER NUMBER color style_type style_type
		{
			new_image($2, $3, $4, ($5 | $6));
		}
	|	NEW XBMFILE
		{
			int	i;

        		if (image_out != 0) {
				WARNING((stderr, "delete old image first!\n"));
			} else {
				image_out = open_as_xbm(word);
/*
 *				new image, colors will all need to
 *				be reallocated.
 */
				for(i=0; i < cmt.count; i++) {
					cmt.map[i].color = -1;
				}/* for */
			}
		}
	|	NEW GIFFILE
		{
			int	i;

        		if (image_out != 0) {
				WARNING((stderr, "delete old image first!\n"));
			} else {
				image_out = open_as_gif(word);
/*
 *				new image, colors will all need to
 *				be reallocated.
 */
				if (image_out != NULL) {
					for(i=0; i < cmt.count; i++) {
						cmt.map[i].color = -1;
					}/* for */
				}
			}
		}
	|	SAVE GIFFILE
		{
			save_as_gif(word);
		}
	|	SAVE
		{
			save_as_gif(NULL);
		}
	|	GET PIXEL NUMBER NUMBER
		{
			get_pixel($3, $4);
		}
	|	GET COLOR INFO
		{
			int	i;

			for (i=0; i < cmt.count; i++) {
				printf("%s:\tr=%d, g=%d, b=%d, ",
					cmt.map[i].name,
					cmt.map[i].v1,
					cmt.map[i].v2,
					cmt.map[i].v3);
				if (cmt.map[i].color == -1) {
					printf("not allocated\n");
				} else {
					printf("index=%d\n", cmt.map[i].color);
				}
			}/* for */
		}
	|	GET INFO
		{
			if (image_out == 0) {
				if (!silent) {
					fprintf(stderr, "no current image\n");
				}
			} else if (!silent) {
				int index;

				printf("The image is:");
				if (gdImageGetInterlaced(image_out)) {
					printf(" interlaced,\n");
				} else {
					printf(" non-interlaced,\n");
				}
				printf("width %d, height %d,\n",
					gdImageSX(image_out),
					gdImageSY(image_out));
				index = gdImageGetTransparent(image_out);
				if (index == -1) {
					printf("no transparent color is set,\n");
				} else {
					printf("transparent color index is %d,\n", index);
				}
				printf("there are %d colors in the image.\n",
					gdImageColorsTotal(image_out));
			}
		}
	|	DELETE
		{
			if (image_out != 0) {
				gdImageDestroy(image_out);
			}
			image_out = 0;
		}
	|	DELETE BRUSH
		{
			if (brush != 0) {
				gdImageDestroy(brush);
				brush = 0;
			}
		}
	|	DELETE TILE
		{
			if (tile != 0) {
				gdImageDestroy(tile);
				tile = 0;
			}
		}
	|	DELETE STYLE style_name
		{
			if (removeNode(&StyleDict, style_name) == 0) {
				WARNING((stderr, "style not found\n"));
			}
		}
	|	DELETE STYLE ALL
		{
			del_all_styles();
		}
	|	DELETE ALL
		{
			delete_all();
		}
	|	HELP ALPHANUM
		{
			help_topic(getfunc(word));
		}
	|	HELP
		{
			help_topic(HELP);
		}
	|	DEBUG_ON
	{
#if YYDEBUG != 0
		yydebug = 1;
#endif
	}
	|	DEBUG_OFF
	{
#if YYDEBUG != 0
		yydebug = 0;
#endif
	}
	;

draw:		SET PIXEL NUMBER NUMBER color
		{
			set_pixel($3, $4, $5);
		}
	|	SET STYLE style_name names /* grammar conflict between these 2 */
		{
			if (pindex == -1) {
				pindex=0;
			} else {
				new_line_style(style_name);
			}
		}
	|	SET style_type
		{
			if ($2 == INTERLACED) {
				gdImageInterlace(image_out, 1);
			}
			if ($2 == TRANSPARENT) {
				gdImageColorTransparent(image_out, -1);
			}
		}
	|	SET COLOR NUMBER NUMBER NUMBER ALPHANUM 
		{
			define_color($3, $4, $5, word, 0);
			
		}
	|	SET CLOSEST COLOR NUMBER NUMBER NUMBER ALPHANUM 
		{
			define_color($3, $4, $5, word, 1);
			
		}
	|	SET TRANSPARENT color
		{
			if ($3 != -1) {
				gdImageColorTransparent(image_out, $3);
			}
		}
	|	SET TRANSPARENT NUMBER
		{
			gdImageColorTransparent(image_out, $3);
		}
	|	SET BRUSH GIFFILE
		{
        		if (brush != 0) {
				WARNING((stderr, "delete old brush first\n"));
			} else if (image_out == 0) {
				WARNING((stderr, "no current image\n"));
			} else {
				if ((brush = open_as_gif(word)) != NULL) {
					gdImageSetBrush(image_out, brush);
				}
			}
		}
	|	SET TILE GIFFILE
		{
        		if (tile != 0) {
				WARNING((stderr, "delete old tile first\n"));
			} else if (image_out == 0) {
				WARNING((stderr, "no current image\n"));
			} else {
				if ((tile = open_as_gif(word)) != NULL) {
					gdImageSetTile(image_out, tile);
				}
			}
		}
	|	SET CLOSEST
		{
			Closest=0;
		}
	|	SET EXACT
		{
			Closest=1;
		}
	|	COMMENT string
		{
			if (comment == 0) {
				comment = (char *) malloc(strlen(word) + 1);
				strcpy(comment, word);
			} else {
   				comment = (char *) realloc(comment,
							strlen(word));
				strcpy(comment, word);
			}
		}
	|	PRINT NUMBER NUMBER color string
		{
			print_string(0, gdFontSmall, $2, $3, $4, word);
		}
	|	PRINT font NUMBER NUMBER color string
		{
			print_string(0, font, $3, $4, $5, word);
		}
	|	PRINT UP font NUMBER NUMBER color string
		{
			print_string(1, font, $4, $5, $6, word);
		}
	|	PRINT UP NUMBER NUMBER color string
		{
			print_string(1, gdFontSmall, $3, $4, $5, word);
		}
	;

draw: 		lines values BRUSH
		{
			draw_lines($1, gdBrushed);
		}
	|	lines values color
		{
			draw_lines($1, $3);
		}
	|	lines values STYLE style_name
		{
			if (set_line_style(style_name)) {
				draw_lines($1, gdStyled);
			}
		}
	|	lines values STYLE style_name BRUSH
		{
			if (set_line_style(style_name)) {
				draw_lines($1, gdStyledBrushed);
			}
		}
	;

draw: 		shapes values color
		{ 
			draw_shape($1, $3, 0);
		}
	|	shapes values BRUSH
		{ 
			draw_shape($1, gdBrushed, 0);
		}
	|	shapes values BRUSH FILLED
		{ 
			draw_shape($1, gdBrushed, 1);
		}
	|	shapes values TILED FILLED
		{ 
			draw_shape($1, gdTiled, 1);
		}
	|	shapes values color FILLED
		{ 
			draw_shape($1, $3, 1);
		}
	|	shapes values STYLE style_name
		{
			if (set_line_style(style_name)) {
				draw_shape($1, gdStyled, 0);
			}
		}
	|	shapes values STYLE style_name FILLED
		{
			if (set_line_style(style_name)) {
				draw_shape($1, gdStyled, 1);
			}
		}
	|	shapes values STYLE style_name BRUSH
		{
			if (set_line_style(style_name)) {
				draw_shape($1, gdStyledBrushed, 0);
			}
		}
	|	shapes values STYLE style_name BRUSH FILLED
		{
			if (set_line_style(style_name)) {
				draw_shape($1, gdStyledBrushed, 1);
			}
		}
	;


draw	: 	FILL NUMBER NUMBER color
		{
			image_fill(
				$2,
				$3,
				$4,
				-1);
		}
	|	FILL NUMBER NUMBER TILED
		{
			image_fill(
				$2,
				$3,
				gdTiled,
				-1);
		}
	|	FILL NUMBER NUMBER color color
		{
			image_fill(
				$2,
				$3,
				$4,
				$5);
		}
	|	FILL NUMBER NUMBER TILED color
		{
			image_fill(
				$2,
				$3,
				gdTiled,
				$5);
		}
	;


names:		/* empty */
	|	names style_color
		{
/*
 *			A Single color
 */
			if (pindex != -1) {
				param[pindex]=1;
				strncpy(colors[pindex++], word, NAMELEN);
			}
		}
	|	names NUMBER style_color
		{
			if (pindex != -1) {
				param[pindex]=$2;
				strncpy(colors[pindex++], word, NAMELEN);
			}
		}
	;

style_color:	ALPHANUM
	{
		if (pindex==MAXSTYLE) {
			WARNING((stderr,"too many color changes\n"));
			pindex=-1;	/* an error occured */
		} 
	}
	|	TRANSPARENT
	{
		if (pindex==MAXSTYLE) {
			WARNING((stderr,"too many color changes\n"));
			pindex=-1;	/* an error occured */
		} 
	}
	;


style_name:	|	ALPHANUM
	{
		strncpy(style_name, word, NAMELEN);
	}
	;

values:	 	/* empty */
	|	values number
	;

number:		NUMBER
		{
			if (pindex==MAXPARAM) {
				WARNING((stderr,"too many parameters\n"));
			} else if (pindex != -1) {
				param[pindex++]=$1;
			}
		}
	;

color:		ALPHANUM
		{
			$$ = get_color(word);
		}
	
	;

style_type:	TRANSPARENT
		{
			$$ = TRANSPARENT;
		}
	|	INTERLACED
		{
			$$ = INTERLACED;
		}
	;

font:		BIG
		{
			font = gdFontLarge;
		}
	|	SMALL
		{
			font = gdFontSmall;
		}
	|	GIANT	
		{
			font = gdFontGiant;
		}
	|	MB
		{
			font = gdFontMediumBold;
		}
	|	TINY
		{
			font = gdFontTiny;
		}
	;

string:		STRING
		{
			char	*s;

			word++;	/* step over leading quote */
			s = (char *) strchr(word, '"');
			*s = '\0';
		}
	;

lines:		LINE
		{
			$$ = LINE;
		}
	|	ARC
		{
			$$ = ARC;
		}
	;

shapes:		POLYGON
		{
			$$ = POLYGON;
		}
	|	RECTANGLE
		{
			$$ = RECTANGLE;
		}
	;


%% /* start of programs */

/*
 *	get_color
 *	Gets the cmt index for the color.  This is either:
 *	+	from the current color map table
 * 	+	a special color
 *	or
 *	+	from the list of predefined colors.
 *
 *	changed.
 *
 *	RETURNS
 *	color index
 *		or
 *	-1 - The color wasn't found.
 */

int
get_color(
	char	*name	)
{
	register i;

/*
 *	Transparent is a special case.
 */
	if (strcmp(name, "transparent") == 0) {
		return gdTransparent;	/* special color */
	}
	if (cmt.count > 0) {
/*
 *		check cmt for color
 */
		for (i=0; i < cmt.count; i++) {
			if (strncmp(cmt.map[i].name, name, NAMELEN) == 0) {
				return i;	/* got it! */
			}
		}/* for */
	}
/*
 *	See if it's a predefined color, if so copy the parameters to the cmt
 *	but punt on allocating an index until we come to use it.
 */
	if (image_out == 0 || gdImageColorsTotal(image_out) < gdMaxColors) {
/* 
 *		Either no image or enough room for more colors.
 */
		if (cmt.count < gdMaxColors) {
		/*
		 * Still room for more colors in table.  Check
	 	 * Predefined table.
	 	 */
			for (i=0; *(pct[i].name) != '\0'; i++) {
				if (strncmp(pct[i].name, name, NAMELEN) == 0) {
					break;	/* got it! */
				}
			}/* for */
/* DBHG why not check against size? */
			if (*(pct[i].name) != '\0') {
				int c = cmt.count;
				/*
			 	 * found predefined color, copy it over.
				 */
				strncpy(cmt.map[c].name, name, NAMELEN);
				cmt.map[c].color=-1; /* not yet allocated */
				cmt.map[c].v1=pct[i].v1;
				cmt.map[c].v2=pct[i].v2;
				cmt.map[c].v3=pct[i].v3;
				i = cmt.count++;
			} else {
				WARNING((stderr,
					"color: %s has not been defined\n",
					name));
				return -1;
			}
		}
	}

	return i;
}/* get_color */


/*
 *	define_color
 *	User defined colors override any predefined colors.  Note: if the gdit
 *	color table is already full specifying closest won't cause this new
 *	color to be defined.  An existing color name should be used.
 *
 *	r,g,b	Red, Green, Blue intesities
 *	name	Name of color
 *	closest	if 1 try for closest color match.
 *
 *	RETURNS
 *	color index
 *		or
 *	-1
 */

int
define_color(
	int	r,
	int	g,
	int	b,
	char	*name,
	int	closest	)
{
	register i;

	if (cmt.count > 0) {
/*
 *		check cmt for color
 */
		for (i=0; i < cmt.count; i++) {
			if (strcmp(cmt.map[i].name, name) == 0) {
				WARNING((stderr, "%s, is already defined as %d, %d, %d\n", name, cmt.map[i].v1, cmt.map[i].v2, cmt.map[i].v3));
				return -1;
			}
		}/* for */
	}
	if (image_out && gdImageColorsTotal(image_out) == gdMaxColors ||
		cmt.count == gdMaxColors) {
		WARNING((stderr, "color table is full, delete another color first\n"));
		return -1;
	}
	/*
	 * copy it over.
	 */
	strcpy(cmt.map[cmt.count].name, name);
	cmt.map[cmt.count].v1=r;
	cmt.map[cmt.count].v2=g;
	cmt.map[cmt.count].v3=b;
	if (image_out == 0) {
		cmt.map[cmt.count].color=-1; /* not yet allocated */
	} else {
/*
 *		need to see if we can slot it in, maybe the image has already
 *		defined 256 colors.
 */
		if ((i = alloc_color(&cmt.map[cmt.count], closest)) == -1) {
			return -1;
		}
		cmt.map[cmt.count].color = i;
	}

	return ++cmt.count;
}/* define_color */


/*
 *	new_line_style
 *
 *	Set up a new line style.  Styles are simple descriptions of how
 *	line's should be drawn.  A gd library style is an array of color
 *	indexes, a red dotted line could be drawn as:
 *		red, red, red, transparent, transparent, transparent blue
 *
 *	GDIT uses color names rather then indexes.  This has the advantage
 *	that names are more meaningful to users.  Also styles can be used
 *	over multiple images because we store the style rather than the index
 *	(the GIF index for red may vary).  Counts can also be specified:
 *
 *	The above line would be:
 *		3 red 3 transparent blue
 */

int
new_line_style(
	char *name	)
{
	El_t		*pEl;
	Entry_t		*pEntry;
	register int	i;

	if (pindex == 1) {
		WARNING((stderr, "style must contain different colors\n"));
		pindex=0;
		return 0;
	}
	if ((pEntry = (Entry_t *) malloc(sizeof(Entry_t))) == 0) {
		WARNING((stderr, "out of memory!\n"));
		return 0;
	}
	strncpy(pEntry->Name, name, NAMELEN);
	if (addNode(&StyleDict, (Node_t*) pEntry)) {
		WARNING((stderr, "duplicate style\n"));
		free(pEntry);
		return 0;
	}
/*
 *	Set up style.  The format is:
 *	count1 color1... countn colorn
 *
 *	We don't care if the colors are valid at this stage.
 */
	pEntry->count=pindex;
	pEntry->total=0;
	if ((pEntry->pStyle = (El_t *) malloc(sizeof(El_t) * pindex)) == 0){
		WARNING((stderr, "out of memory!\n"));
		removeNode(&StyleDict, pEntry->Name);
		free(pEntry);
		return 0;
	}
	pEl = pEntry->pStyle;
	for (i = 0; i < pindex; i++, pEl++) {
		pEl->count=param[i];
		pEntry->total+= pEl->count;
		strncpy(pEl->color, colors[i], NAMELEN);
	}/* for */

	pindex=0;
	return 1;
}/* new_line_style */


/*
 *	set_line_style
 *
 *	name	name of style to set.
 *
 *	RETURN
 *	0	- can't set style
 *	1	- success
 */

int
set_line_style(
	char	*name	)
{
	Entry_t		*pEntry;
	El_t		*pEl;
	register int	i;
	int		*style,
			*pstyle;

	if (image_out == 0) {
		WARNING((stderr, "Can't set style because no image\n"));
		return 0;
	}
	if ((pEntry = (Entry_t *) getNode(&StyleDict, name)) == 0) {
		WARNING((stderr, "can't find style\n"));
		return 0;
	}

	pEl = pEntry->pStyle;
	if ((style = (int *) malloc(sizeof(int) * pEntry->total)) == 0) {
		WARNING((stderr, "out of memory!\n"));
		return 0;
	}
	pstyle = style;
	for (pEl = pEntry->pStyle, i=0; i < pEntry->count; i++, pEl++) {
		int color;
		int index;

		index=get_color(pEl->color);
		color = cmt.map[index].color;
		if (color == -1) {
			color = alloc_color(&cmt.map[index], Closest);
			if (color == -1) {
				free(style);
				return 0;
			}
			cmt.map[index].color = color;
		}
		index = pEl->count;
		for (index = 0; index < pEl->count; index++) {
			*pstyle++ = color;
		}/* for */
	}/* for */
	gdImageSetStyle(image_out, style, pEntry->total);

	free(style);
	return 1;
}/* set_line_style */


/*
 *	del_all_styles
 *
 *	Delete all line styles.
 */

void
del_all_styles()
{
	Entry_t		*pEntry;

	while((pEntry = (Entry_t *) getFirstNode(&StyleDict)) != 0) {
		pEntry = (Entry_t *) removeNode(&StyleDict, pEntry->Name);
		free(pEntry->pStyle);
		free(pEntry);
	}/* while */

	return;
}/* del_all_styles */


/*
 *	delete_all
 *
 *	Delete everyting!
 */

void
delete_all()
{
	if (image_out != 0) {
		gdImageDestroy(image_out);
		image_out = 0;
	}
	if (brush != 0) {
		gdImageDestroy(brush);
		brush = 0;
	}
	if (tile != 0) {
		gdImageDestroy(tile);
		tile = 0;
	}
	del_all_styles();
}/* delete_all */


/*
 *	new_image
 *
 *	Creates a new image, any existing image must be deleted first.
 *	Color index and style must be legal values.
 *
 *	width, height	Image dimensions in pixels
 *	color_index	gdit color value
 *	style		Interlaced	- interlaced GIF89 image.
 *			Transparent	- make color_index transparent.
 *
 *	RETURNS	0	failure
 *		1	success
 */

int
new_image(
	int	height,
	int	width,
	int	color_index,
	int	style	)
{
	register int	i;

	if (color_index == -1) {
		return 0;
	}
        if (image_out != 0) {
		WARNING((stderr, "delete the old image first!\n"));
		return 0;
	}
/*
 *	new image, colors will all need to be reallocated.
 */
	for(i=0; i < cmt.count; i++) {
		cmt.map[i].color = -1;
	}/* for */
	image_out = gdImageCreate(height, width);
	if (image_out == 0) {
		WARNING((stderr, "cannot create image\n"));
		return 0;
	}
/*
 *	First color is background
 */
	cmt.map[color_index].color = gdImageColorAllocate(
		image_out,
		cmt.map[color_index].v1,
		cmt.map[color_index].v2,
		cmt.map[color_index].v3 );

	if (style & INTERLACED) {
		gdImageInterlace(image_out, 1);
	}
	if (style & TRANSPARENT) {
		gdImageColorTransparent(image_out, cmt.map[color_index].color);
	}

	return 1;
}/* new_image */


/*
 *	get_pixel
 *
 *	Prints the color index and componenents for the given pixel to
 *	the screen.
 *
 *	x,y	coordinates
 *
 *	RETURNS	0	failure
 *		1	success
 */

int
get_pixel(
	int	x,
	int	y	)
{
	int	index;
        if (image_out == 0) {
		WARNING((stderr, "no current image\n"));
		return 0;
	}
	index = gdImageGetPixel(image_out, x, y);
	if (index >= 0 && index < 255) {
/*
 *		valid index
 */
		printf("index %d; RGB values are %d,%d,%d\n",
			index,
			gdImageRed(image_out, index),
			gdImageGreen(image_out, index),
			gdImageBlue(image_out, index));
	} else {
		return 0;
	}

	return 1;
}/* get_pixel */


/*
 *	set_pixel
 *
 *	Set pixel to specified color.
 *
 *	x,y	coordinates
 *	int	color_index
 *
 *	RETURNS	0	failure
 *		1	success
 */

int
set_pixel(
	int	x,
	int	y,
	int	color_index)
{
	int	color;

	if (color_index == -1) {
		return 0;
	}
        if (image_out == 0) {
		WARNING((stderr, "no current image\n"));
		return 0;
	} else {
		if ((color = cmt.map[color_index].color) == -1) {

			color = alloc_color(&cmt.map[color_index], Closest);
			if (color == -1) {
				return 0;
			}
			cmt.map[color_index].color = color;
		}
		gdImageSetPixel(
			image_out,
			x,
			y,
			color	);
	}

	return 1;
}/* set_pixel */


/*
 *	draw_line
 *
 *	Draws a line from points x1,y1 to points x2,y2.  The coordinates are
 *	passed in the params array.  
 *
 *	color_index	Color
 *
 *	RETURNS	0	failure
 *		1	success
 */

int
draw_line(
	int	color_index	)
{
	int	color;

	if (pindex != 4) {
		WARNING((stderr, "A line needs 2 sets of coordinates\n"));
		return 0;
	}
	if (color_index == -1) {
		return 0;
	}
	if (image_out == 0) {
		WARNING((stderr, "no current image\n"));
		return 0;
	}

	switch (color_index) {
	case gdBrushed:
	case gdStyled:
	case gdStyledBrushed:
		color = color_index;
		break;
	case gdTiled:
		WARNING((stderr,"can't tile a line\n"));
		return 0;
	default:
		if ((color = cmt.map[color_index].color) == -1) {
			color = alloc_color(&cmt.map[color_index], Closest);
			if (color == -1) {
				return 0;
			}
			cmt.map[color_index].color = color;
		}
		break;
	}/* switch */

	gdImageLine(
		image_out,
		param[0],
		param[1],
		param[2],
		param[3],
		color	); 

	return 1;
}/* draw line */


/*
 *	draw_shape
 *
 *	type		- POLYGON or RECTANGLE
 *	color_index	 - line color 
 *
 *	RETURNS
 *	1	Success
 *	0	Failure
 */

int
draw_shape(
	int	shape,
	int	color_index,
	int	filled	)
{
	switch (shape) {
	case POLYGON:
		draw_polygon(color_index, filled);
		break;
	case RECTANGLE:
		draw_rectangle(color_index, filled);
		break;
	}/* switch */

	pindex=0;
}/* draw_shape */


/*
 *	draw_lines
 *
 *	type		- ARC or LINE
 *	color_index	 - line color 
 *
 *	RETURNS
 *	1	Success
 *	0	Failure
 */

int
draw_lines(
	int	type,
	int	color_index	)
{
	switch (type) {
	case LINE:
		draw_line(color_index);
		break;
	case ARC:
		draw_arc(color_index);
		break;
	}/* switch */

	pindex=0;
}/* draw_lines */


/*
 *	draw_rectangle
 *
 *	Draws a rectangle.  Corners are passed globally in param array.
 *
 *	color_index	Border color
 *	style		0	- none
 *			1	- Filled
 *
 *	RETURNS
 *	1	Success
 *	0	Failure
 */

int
draw_rectangle(
	int	color_index,
	int	style)
{
	int color;

	if (color_index == -1) {
		return 0;
	}
	if (image_out == 0) {
		WARNING((stderr, "no current image\n"));
		return 0;
	} 
	if (pindex != 4) {
		WARNING((stderr, "Rectangle needs 2 sets of coordinates\n"));
		return 0;
	}

	switch (color_index) {
	case gdBrushed:
	case gdStyled:
	case gdTiled:
	case gdStyledBrushed:
		color = color_index;
		break;
	default:
		if ((color = cmt.map[color_index].color) == -1) {
			color = alloc_color(&cmt.map[color_index], Closest);
			if (color == -1) {
				return 0;
			}
			cmt.map[color_index].color = color;
		}
		break;
	}/* switch */

	if (style) {
		gdImageFilledRectangle(
			image_out,
			param[0],
			param[1],
			param[2],
			param[3],
			color );
	} else {
		gdImageRectangle(
			image_out,
			param[0],
			param[1],
			param[2],
			param[3],
			color );
	}

	return 1;
}/* draw rectangle */


/*
 *	draw arc
 *
 *	Draws a partial ellipse centered on x,y with the specified height
 *	width.  The arc begins at degree start and ends at end.  The line
 *	is drawn in the specified color.
 *
 *	PARAMETERS
 *	x,y		center
 *	width,height	size
 *	start,end	length
 *	color_index	color
 *
 *	RETURNS
 *	1	Success
 *	0	Failure
 */

int
draw_arc(int	color_index	)

{
	int color;

	if (pindex != 6) {
		WARNING((stderr, "An arc needs 6 pieces of data\n"));
		return 0;
	}
	if (color_index == -1) {
		return 0;
	}
	if (image_out == 0) {
		WARNING((stderr, "no current image\n"));
		return 0;
	} 
	switch (color_index) {
	case gdBrushed:
	case gdStyled:
	case gdStyledBrushed:
		color = color_index;
		break;
	case gdTiled:
		WARNING((stderr,"can't tile a line\n"));
		return 0;
	default:
		if ((color = cmt.map[color_index].color) == -1) {
			color = alloc_color(&cmt.map[color_index], Closest);
			if (color == -1) {
				return 0;
			}
			cmt.map[color_index].color = color;
		}

		break;
	}/* switch */

	gdImageArc(
		image_out,
		param[0],
		param[1],
		param[2],
		param[3],
		param[4],
		param[5],
		color	);

	return 1;
}/* draw arc */


/*
/*
 *	image_fill
 *
 *	PARAMETERS
 *	x,y
 *	color_index	color | gdTiled
 *	border_index	color
 *
 *	RETURNS
 *	1	Success
 *	0	Failure
 */

int
image_fill(
	int	x,
	int	y,
	int	color_index,
	int	border_index	)

{
	int	color,
		border;

	if (color_index == -1) {
		return 0;
	}
	if (image_out == 0) {
		WARNING((stderr, "no current image\n"));
		return 0;
	} 

	switch (color_index) {
	case gdTiled:
		if (tile == 0) {
			WARNING((stderr, "no tile set\n"));
			return 0;
		}
		color = gdTiled;
		break;
	default:
		if ((color = cmt.map[color_index].color) == -1) {
			color = alloc_color(&cmt.map[color_index], Closest);
			if (color == -1) {
				return 0;
			}
			cmt.map[color_index].color = color;
		}
		break;
	}/* switch */

	if (border_index != -1) {
		if ((border = cmt.map[border_index].color) == -1) {
			border = alloc_color(&cmt.map[color_index], Closest);
			if (border == -1) {
				return 0;
			}
			cmt.map[color_index].color = border;
		}
	}
	if (border_index == -1) {
		gdImageFill(
			image_out,
			x,
			y,
			color);
	} else {
		gdImageFillToBorder(
			image_out,
			x,
			y,
			border,
			color);
	}

	return 1;
}/* image_fill */


/*
 *	draw_polygon
 *
 *	Draw a polygon.  That is, a random closed shape.
 *
 *	color_index	Border color
 *	style		0 - hollow
 *			1 - filled
 *
 *	RETURNS
 *	1	Success
 *	0	Failure
 */

int
draw_polygon(
	int	color_index,
	int	style)
{ 
	int	color;
	gdPoint *Points,
		*p;
	int	points;
	int	i;

	if (color_index == -1) {
		return 0;
	}
	if (image_out == 0) {
		WARNING((stderr, "no current image\n"));
		return 0;
	} 
	if ((pindex % 2) != 0) {
		WARNING((stderr, "unmatched coordinate set\n"));
	}

	Points = (gdPoint *) malloc(sizeof(gdPoint) * (pindex >> 1));

	p = Points;
	i=0;
	while (i < pindex) {
		p->x=param[i++];
		p++->y=param[i++];
	}/* for */

	switch (color_index) {
	case gdBrushed:
	case gdStyled:
	case gdTiled:
	case gdStyledBrushed:
		color = color_index;
		break;
	default:
		if ((color = cmt.map[color_index].color) == -1) {
			color = alloc_color(&cmt.map[color_index], Closest);
			if (color == -1) {
				return 0;
			}
			cmt.map[color_index].color = color;
		}
		break;
	}/* switch */

	if (style) {
		gdImageFilledPolygon(
			image_out,
			Points,
			pindex >> 1,
			color );
	} else {
		gdImagePolygon(
			image_out,
			Points,
			pindex >> 1,
			color );
	}

	free(Points);
}/* draw_polygon */


/*
 *	open_as_gif
 *
 *	RETURNS
 *	Image pointer or
 *	NULL	- failed to open file.
 */

gdImagePtr
open_as_gif(
	char *filename	)
{
	gdImagePtr	im;
	FILE		*fp;

	if ((fp = fopen(filename, "rb")) == NULL) {
		fprintf(stderr, "cannot open input file: %s. %s\n", filename, strerror(errno));
		return NULL;
	} else if ((im = gdImageCreateFromGif(fp)) == NULL) {
		fprintf(stderr, "cannot create image from giffile: %s, errno: %d\n", filename, strerror(errno));
	}
	fclose(fp);


	return im;
}/* open_as_gif */


/*
 *	open_as_xbm
 *
 *	RETURNS
 *	Image pointer or
 *	NULL	- failed to open file.
 */

gdImagePtr
open_as_xbm(
	char *filename	)
{
	gdImagePtr	im;
	FILE		*fp;

	if ((fp = fopen(filename, "rb")) == NULL) {
		fprintf(stderr, "cannot open input file: %s. %s\n", filename, strerror(errno));
		return NULL;
	} else if ((im = gdImageCreateFromXbm(fp)) == NULL) {
		fprintf(stderr, "cannot create image from giffile: %s, errno: %d\n", filename, strerror(errno));
	}
	fclose(fp);

	return im;
}/* open_as_xbm */


/*
 *	save_as_gif
 *
 *	Saves the current image into the file specified by filename.
 *
 *	filename	Gif file name.
 *
 *	RETURNS		1	success
 *			0	failure
 */

int
save_as_gif(
	char	*filename )
{
	FILE		*fp = NULL;

	if (image_out == 0) {
		fprintf(stderr, "cannot save, no current image\n");
		return 0;
	}
	if (filename == NULL) {
		gdImageGif(image_out, stdout);
	} else {
		if ((fp = fopen(filename, "wb")) == NULL) {
			WARNING((stderr, "can't open file %s. %s\n", filename, strerror(errno)));
			return 0;
		}
		/* Write GIF */
		gdImageGif(image_out, fp);
		if (comment) {
/*
 *			Write the comment trailer
 */
			unsigned char len;

			fseek(fp, -1, SEEK_CUR);
			fputs("\041\376",fp);
			len = strlen(comment);
			len = len<256?len:255;
			fwrite((char *) &len,1,1,fp);
			fwrite(comment,len+1,1,fp);
			fputc(';', fp);
		}
		fclose(fp);
	}

	return 1;
}/* save_as_gif */


/*
 *	print_string
 *
 *	RETURNS		1	success
 *			0	failure
 */

int
print_string(
	int	style,
	gdFontPtr	font,
	int	x,
	int	y,
	int	color_index,
	char	*string	)
{
	int color;

	if (image_out == 0) {
		WARNING((stderr, "no current image\n"));
		return 0;
	}

	if ((color = cmt.map[color_index].color) == -1) {
		color = alloc_color(&cmt.map[color_index], Closest);
		if (color == -1) {
			return 0;
		}
		cmt.map[color_index].color = color;
	}

	if (style == 1) {
		gdImageStringUp(
			image_out,
			font,
			x, y,
			string,
			color);
	} else {
		gdImageString(
			image_out,
			font,
			x, y,
			string,
			color);
	}

	return 1;
}/* print */


/*
 * alloc_color
 *
 *	Attempts to allocate the color from the current images color table,
 *	If this fails it allocates a new color.
 *
 *	mapp		gdit color map	
 *	closest		get closest color if map is full.
 *
 *	RETURNS
 *	GIF color index	between 0 and 255
 *		or
 *	-1
 */

int
alloc_color(
	struct color_map *mapp,
	int	closest)
{
	mapp->color = gdImageColorExact(
		image_out,
		mapp->v1,
		mapp->v2,
		mapp->v3 );
	if (mapp->color == -1) {
		mapp->color = gdImageColorAllocate(
			image_out,
			mapp->v1,
			mapp->v2,
			mapp->v3 );
	}
	if (mapp->color == -1 && closest) {
		mapp->color = gdImageColorClosest(
			image_out,
			mapp->v1,
			mapp->v2,
			mapp->v3 );
	}

	return mapp->color;
}/* alloc_color */


/*
 *	Dictionary Functions
 */

/*
 *	NameCmp
 *	Comparison functions for searches.
 *
 *	Returns
 *	0     - Equal
 *	-ve   - Less Than
 *	+ve   - Greater Than
 */


int NameCmp(
    Entry_t	*dictname,
    char	*name	)
{
	return strncmp(dictname->Name, name, NAMELEN);
}


void
init_gdit(int debug)
{
#if YYDEBUG != 0
	yydebug=debug;	/* parser debugging */
#endif
	pindex=0;
	image_out = 0;
	brush = 0;
	tile = 0;
	initDict(&StyleDict, NameCmp);
}/* init_gdit */

void
help_topic(
	int	topic	)
{
	switch(topic) {
	case COMMENT:
		printf("set GIF comment string, max 255 characters\n");
		break;	
	case NEW:
		printf("new width height [color][transparent][interlaced]\n");
		printf("new <filename>.gif\n");
		break;
	case SAVE:
		printf("save <filename>.gif\n");
		break;
	case DELETE:
		printf("delete\n");
		printf("delete brush\n");
		printf("delete tile\n");
		printf("delete style <name>\n");
		printf("delete style all\n");
		printf("delete all\n");
		break;
	case SET:
		printf("set pixel x y <color>\n");
		printf("set transparent [color|index]\n");
		printf("set [closest] <color> <r> <g> <b> <name>\n");
		printf("set closest\n");
		printf("set exact\n");
		printf("set style <name> [count] <color>... [countn] <colorn>\n");
		printf("set brush <filename>.gif\n");
		printf("set tile <filename>.gif\n");
		break;
	case LINE:
		printf("line x1 y1 x2 y2 <color>\n");
		printf("line x1 y1 x2 y2 brushed\n");
		printf("line x1 y1 x2 y2 style <name> [brushed]\n");
		break;
	case POLYGON:
		printf("polygon x1 y1... xn yn <color> [filled]\n");
		printf("polygon x1 y1... xn yn style <name> [brushed][filled]\n");
		printf("polygon x1 y1... xn yn tiled filled\n");
		break;
	case RECTANGLE:
		printf("rectangle x1 y1 x2 y2 <color> [filled]\n");
		printf("rectangle x1 y1 x2 y2 style <name> [brushed][filled]\n");
		printf("rectangle x1 y1 x2 y2 tiled filled\n");
		break;
	case ARC:
		printf("arc x y height width start end <color>\n");
		printf("arc x y height width start end brushed\n");
		printf("arc x y height width start end style name [brushed]\n");
		break;
	case FILL:
		printf("fill x y <color> [<color>]\n");
		break;
	case GET:
		printf("get pixel x y\n");
		printf("get information\n");
		break;
	case PRINT:
		printf("print [up][large|small|giant|medium|tiny] x y <color> \"string\"\n");
		break;
	case HELP:
		printf("help is available on these topics:\n");
		printf("comment\n");
		printf("new\n");
		printf("save\n");
		printf("delete\n");
		printf("set\n");
		printf("line\n");
		printf("polygon\n");
		printf("rectangle\n");
		printf("arc\n");
		printf("fill\n");
		printf("get\n");
		printf("print\n");
		printf("\nplease enter\nhelp <topic>\n");
		break;
	default:
		printf("sorry no help on this topic\n");
		printf("\n\nplease enter\nhelp <topic>\n");
		break;
	}/* switch */
}


int
getfunc(char *ident)
{
	struct idlist fdl[] = {
		"a",		ARC,
		"bye",		QUIT,
		"c",		COMMENT,
		"del",		DELETE,
		"debug_on",	DEBUG_ON,
		"debug_off",	DEBUG_OFF,
		"f",		FILL,
		"g",		GET,
		"h",		HELP,
		"l",		LINE,
		"n",		NEW,
		"po",		POLYGON,
		"pr",		PRINT,
		"q",		QUIT,
		"r",		RECTANGLE,
		"s",		SAVE,
		"se",		SET,
		"set",		SET
	};

	return getid(ident, fdl, sizeof(fdl)/(sizeof(char *)+sizeof(int)));
}

int
getparam(char *ident)
{
	struct idlist pdl[] = {
		"al",		ALL,
		"bru",		BRUSH,
		"clos",		CLOSEST,
		"col",		COLOR,
		"ex",		EXACT,
		"fille",	FILLED,
		"giant",	GIANT,
		"inf",		INFO,
		"inter", 	INTERLACED,
		"lar",		BIG,
		"med",		MB,
		"pix",		PIXEL,
		"sma",		SMALL,
		"sty",		STYLE,
		"tile",		TILE,
		"tiled",	TILED,
		"tiny",		TINY,
		"tran",		TRANSPARENT,
		"up",		UP
	};

	return getid(ident, pdl, sizeof(pdl)/(sizeof(char *)+sizeof(int)));
}

/*
 *	Lex is good at basic tokenisation but can get overloaded, hence this
 *	routine for sorting out identifiers.
 */

int
getid(char *ident, struct idlist idl[], int size)
{
	int	count,
		cmp,
		len,
		best,
		id;

/*
 *	could make this chop search for speed, list would need to be
 *	ordered.
 */
	id = -1;
	best = 0;
	for (count = 0; count < size; count++) {
		len = strlen(idl[count].name);
		cmp = strncmp(idl[count].name, ident, len);
		if (cmp == 0) {
/*
 *			Try for a longer match
 */
			if (id != -1) {
				if (best < len) {
					id = idl[count].id;
					best = len;
				}
			} else {	/* first match */
				id = idl[count].id;
				best = len;
			}
		} else if (id != -1) {
			break;
		}	
	}/* for */

	return id;
}
#include "lex.yy.c"
