/*
 *  acm : an aerial combat simulator for X
 *  Copyright (C) 1991-1998  Riley Rainey
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  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.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software Foundaation,
 *  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 */

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

extern void VComputeObjectExtent();

static VPoint scale =
{1.0, 1.0, 1.0};

void
VSetReadObjectScale(VPoint * p)
{
	scale = *p;
}

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;

#ifndef linux
	extern double atof();

#endif

	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);
		tmp_points[i].x *= scale.x;
		tmp_points[i].y *= scale.y;
		tmp_points[i].z *= scale.z;
	}

/*
 *  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;
}
