#include "Vlib.h"
#include <string.h>
#include <stdio.h>
#include <ctype.h>

extern void VComputeObjectExtent();

typedef enum {
	Nil,
	EndOfFile,
	TOKEN_RGB_VALUE,
	TOKEN_LPAREN,
	TOKEN_RPAREN,
	TOKEN_COMMA,
	TOKEN_STRING,
	TOKEN_CLIP,
	TOKEN_DOUBLE,
	TOKEN_LONG,
	} field_id;

static field_id NextToken();

struct keyword_info {
	char	*word;
	field_id id;
	};

static struct keyword_info keywords[] = {
	{ "clip",		TOKEN_CLIP },
	{ (char *) NULL,	Nil },
	};

static char token[256];
static int token_length = 0;

typedef union {
	struct keyword_info *kw;
	double		double_value;
	char		*string_value;
	long		long_value;
	VPoint		point;
	VPolygon	*poly;
	} lex_val;

static lex_val lex_value;

struct lex_record {
	char	*name;
	FILE	*f;
	int	lineno;
	int	lookahead_valid;
	int	lookahead;
	int	stack_top;
	lex_val	value_stack[4];
	};

#define push_value(p, type, val) \
	p->value_stack[p->stack_top++].type = val

#define pop_value(p, type) (p->value_stack[--p->stack_top].type)

#define input(p)	(p->lookahead_valid \
				? (p->lookahead_valid = 0, p->lookahead) \
				: (((p->lookahead = getc(p->f)) == '\n') \
					? (p->lineno++, p->lookahead) \
					: p->lookahead))

#define unput(p, c)	{ p->lookahead = c; p->lookahead_valid = 1; }

#define STATE_INITIAL	0
#define STATE_WORD	1
#define STATE_NUMBER	2
#define STATE_STRING	3
#define STATE_RGB	4

static void
ParseError (p, s)
struct	lex_record *p;
char	*s;
{
	fprintf (stderr, "error in file %s (line %d):\n\t%s\n", p->name,
		p->lineno, s);
}

/*
 *  Skip to the specified token, if token is Nil, then skip to the end of the
 *  current line.
 */

static void
Resync (p, token)
struct lex_record *p;
field_id	token;
{
	field_id	t;
	int		c;

	if (token == Nil) {
		while ((c = input(p)) != EOF) {
			if (c == '\n')
				break;
		}
	}
	else {
		while ((t = NextToken(p)) != EndOfFile) {
			if (t == token)
				break;
		}
	}

}

static field_id
NextTokenx (p)
struct	lex_record *p;
{
	register int	c, state = STATE_INITIAL, seen_dot = 0;
	register struct keyword_info	*q;
	extern double atof();

	token_length = 0;

	while ((c = input(p)) != EOF) {

	    switch (state) {

	    case STATE_INITIAL:

		if (isalpha (c)) {
			token[token_length++] = c;
			state = STATE_WORD;
		}
		else if (isspace (c)) {
			continue;
		}
		else if (isdigit(c) || c == '-' || c == '+' || c == '.') {
			if (c == '.')
				seen_dot = 1;
			token[token_length++] = c;
			state = STATE_NUMBER;
		}
		else if (c == '"') {
			state = STATE_STRING;
		}
		else if (c == '#') {
			token[token_length++] = c;
			state = STATE_RGB;
		}		
		else {
			token[0] = c;
			token[1] = '\0';
#ifdef DEBUG
			printf ("other %s\n", token);
#endif
			switch (c) {
			case ',':
				return TOKEN_COMMA;
			case '(':
				return TOKEN_LPAREN;
			case ')':
				return TOKEN_RPAREN;
			default:
				ParseError (p, "invalid character");
				state = STATE_INITIAL;
			}
		}
		break;

	    case STATE_WORD:
	    case STATE_NUMBER:
		if (isspace (c) || c == ',' || c == '(' || c == ')') {
			token[token_length] = '\0';
			unput (p, c);
			if (state == STATE_WORD) {
				for (q=keywords; q->word; ++q) {
					if (strcmp (q->word, token) == 0) {
						lex_value.kw = q;
						return q->id;
					}
				}
				return TOKEN_STRING;
			}
			else {
			    if (seen_dot) {
				lex_value.double_value = atof (token);
				return TOKEN_DOUBLE;
			    }
			    else {
				lex_value.long_value = atoi (token);
				return TOKEN_LONG;
			    }
			}
		}
		else {
			if (c == '.' || c == 'e' || c == 'E') {
				seen_dot = 1;
			}
			token[token_length++] = c;
		}
		break;

	    case STATE_STRING:

		switch (c) {

		case '"':
			token[token_length] = '\0';
			return TOKEN_STRING;

		case '\n':
			ParseError (p, "strings cannot span a line");
			unput (p, c);
			state = STATE_INITIAL;
			break;

		case '\\':

			switch (c = input(p)) {

			case EOF:
				ParseError (p, "Premature End-of-file");
				break;

			case 'n':
				token[token_length++] = '\n';
				break;

			case 't':
				token[token_length++] = '\t';
				break;

			default:
				token[token_length++] = c;
				break;
			}

		default:
			token[token_length++] = c;
			break;
		}
		break;

	    case STATE_RGB:
		while (isxdigit(c)) {
			token[token_length++] = c;
			c = input (p);
		}
		unput (p, c);
		token[token_length] = '\0';
		state = STATE_INITIAL;
		return TOKEN_RGB_VALUE;
/*NOTREACHED*/	break;
			

	    }
	}

	return EndOfFile;	
}

static field_id
NextToken (p)
struct lex_record *p;
{
	field_id t;

	t = NextTokenx(p);

#ifdef DEBUG
	printf ("token %s\n", token);
#endif
	return t;
}

/*
 *  Parse a polygon's color information
 *
 *  color_information:	foreground_color
 *		|	'(' foreground_color [ background_color ] [ 'clip' ]  ')'
 *		;
 */

static int
ParseColorInfo (p, flag)
struct lex_record *p;
int	flag;
{
	field_id	t;
	static VPolygon	template;
	int		count = 0, done = 0;

	template.color = template.backColor = (VColor *) NULL;
	template.flags = 0;
	template.assignedDepth = -1;

	t = NextToken (p);

	if (t == TOKEN_RGB_VALUE || t == TOKEN_STRING) {
		template.color = VAllocDepthCueuedColor (token, flag);
	}
	else if (t == TOKEN_LPAREN) {
		template.flags = 0;

		while (!done) {

			t = NextToken (p);

			switch (t) {

			case TOKEN_RGB_VALUE:
			case TOKEN_STRING:
				if (count++ == 0) {
				    template.color =
				    	VAllocDepthCueuedColor (token, flag);
				}
				else {
				    template.backColor =
				    	VAllocDepthCueuedColor (token, flag);
				}
				break;

			case TOKEN_COMMA:
				break;

			case TOKEN_CLIP:
				template.flags |= PolyClipBackface;
				break;

			case TOKEN_RPAREN:
				done = 1;
				break;

			default:
				break;
			}
		}
				
	}
	else {
		Resync (p, Nil);
		return 1;
	}

	push_value (p, poly, &template);
	return 0;
}

static int
ParseVertex (p)
struct lex_record *p;
{
	field_id	t;
	VPoint		pt;

	if (NextToken(p) != TOKEN_LONG) {
	}

	t = NextToken(p);

	if (t == TOKEN_DOUBLE) {
		pt.x = lex_value.double_value;
	}
	else if (t == TOKEN_LONG) {
		pt.x = lex_value.long_value;
	}
	else {
		return 1;
	}

	t = NextToken(p);

	if (t == TOKEN_DOUBLE) {
		pt.y = lex_value.double_value;
	}
	else if (t == TOKEN_LONG) {
		pt.y = lex_value.long_value;
	}
	else {
		return 1;
	}

	t = NextToken(p);

	if (t == TOKEN_DOUBLE) {
		pt.z = lex_value.double_value;
	}
	else if (t == TOKEN_LONG) {
		pt.z = lex_value.long_value;
	}
	else {
		return 1;
	}

	push_value (p, point, pt);
	return 0;
}

/*
 *  Parse a polygon description.  tmp is the vector containing the points list
 *  (all points vertices used in all polgons in this object). pts is a
 *  scratch point list used to build this polygon;  it is pre-allocated
 *  by the caller.
 */

static int
ParsePolygon (p, tmp, pts, flag)
struct lex_record *p;
VPoint	*tmp;
VPoint	*pts;
int	flag;
{
	int		num_points, i, id;
	VPolygon	template;

	if (ParseColorInfo(p, flag) != 0) {
		ParseError (p, "invalid color specification");
		return 1;
	}

	template = *(pop_value(p, poly));

	if (NextToken(p) != TOKEN_LONG) {
		ParseError (p, "invalid polygon vertex count");
		return 1;
	}

	num_points = lex_value.long_value;

	for (i=0; i<num_points; ++i) {
		if (NextToken(p) != TOKEN_LONG) {
			ParseError (p, "invalid polygon vertex");
			return 1;
		}
		id = lex_value.long_value;
		pts[i] = tmp[id - 1];
	}

	push_value(p, poly, VCreatePolygonFromTemplate (num_points, pts,
		&template));
	return 0;
}

VObject *
VReadObject(f)
FILE	*f;
{
	return VReadDepthCueuedObject(f, 0);
}

VObject *
VReadDepthCueuedObject(f, flag)
FILE	*f;
int	flag;
{

	char		*name;
	int		num_points, num_polys, i;
	VPoint		*tmp_points, *tmp_points1;
	VPolygon	**polygons;
	VObject		*object;
	struct lex_record lr, *p;
	char		line[256];

	p = &lr;
	p->f = f;
	p->lineno = 1;
	p->lookahead_valid = 0;
	p->stack_top = 0;

	fgets (line, sizeof(line), f);
	p->lineno = 2;
	p->name = line;

	name = strdup(line);

	if (NextToken(p) != TOKEN_LONG) {
		return (VObject *) NULL;
	}

	num_points = lex_value.long_value;

	if (NextToken(p) != TOKEN_LONG) {
		return (VObject *) NULL;
	}

	num_polys = lex_value.long_value;

/*
 *  Allocate temporary storage for the polygon vertices.  tmp_points1 is
 *  a vector of points used to build each polygon structure.  Also, allocate
 *  storage for the object's polygon vector.
 */

	tmp_points = (VPoint *) Vmalloc (num_points * 2 * sizeof(VPoint));
	tmp_points1 = &tmp_points[num_points];
	polygons = (VPolygon **) Vmalloc (num_polys * sizeof(VPolygon *));

/*
 *  Get the vertices
 */

	for (i=0; i<num_points; ++i) {
		if (ParseVertex(p) != 0) {
			return (VObject *) NULL;
		}
		tmp_points[i] = pop_value(p, point);
	}

/*
 *  Now get the polgon descriptions
 */

	for (i=0; i<num_polys; ++i) {
		if (ParsePolygon(p, tmp_points, tmp_points1, flag) !=0) {
			ParseError (p, "invalid polygon specification");
			return (VObject *) NULL;
		}
		polygons[i] = pop_value(p, poly);
	}

/*
 *  Build the object structure
 */

	object = (VObject *) Vmalloc (sizeof(VObject));
	object->name = name;
	object->numPolys = num_polys;
	object->polygon = polygons;
	object->order = (unsigned short *) NULL;
	VComputeObjectExtent (object);

	if (VObjectNeedsOrdering (object)) {
		VComputePolygonOrdering (object);
	}

	free ((char *) tmp_points);
	return object;
}
