/* $Header$ */
/*
 * read.c: Dataset read code
 *
 * Routines:
 *	int ReadData();
 *
 * $Log$
 * Revision 1.1  2001-10-08 05:53:13  bap
 * Initial revision
 *
 * Revision 1.2  1999/12/03 23:17:45  heideman
 * apply xgraph_no_animation.patch
 *
 * Revision 1.1.1.1  1999/12/03 23:15:53  heideman
 * xgraph-12.0
 *
 */

#include "copyright.h"
#include <stdio.h>
#include <math.h>
#include <pwd.h>
#include <ctype.h>
#include "xgraph.h"
#include "xtb.h"
#include "hard_devices.h"
#include "params.h"
/*
 * New dataset reading code
 */

static int setNumber = 0;
static PointList **curSpot = (PointList **) 0;
static PointList *curList = (PointList *) 0;
static int newGroup = 0;
static int redundant_set = 0;

#ifdef DO_DER
extern void Der1();
#endif

static int
rdSet(fn)
char   *fn;			/* Reading from file `fn' */

/*
 * Set up new dataset.  Will return zero if there are too many data sets.
 */
{
    char    setname[100];

    if (!redundant_set) {
	if (setNumber < MAXSETS) {
	    (void) sprintf(setname, "Set %d", setNumber);
	    if ((strcmp(PlotData[setNumber].setName, setname) == 0) && fn) {
		PlotData[setNumber].setName = fn;
	    }
	    curSpot = &(PlotData[setNumber].list);
	    PlotData[setNumber].list = (PointList *) 0;
	    newGroup = 1;
	    setNumber++;
	    redundant_set = 1;
	    return 1;
	}
	else {
	    return 0;
	}
    }
    else {
	return 1;
    }
}

static void
rdSetName(name)
char   *name;			/* New set name */

/*
 * Sets the name of a data set.  Automatically makes a copy.
 */
{
    PlotData[setNumber - 1].setName = STRDUP(name);
}

static void
rdGroup()
/*
 * Set up for reading new group of points within a dataset.
 */
{
    newGroup = 1;
}

static void
rdPoint(xval, yval)
double  xval,
        yval;			/* New point         */

/*
 * Adds a new point to the current group of the current
 * data set.
 */
{
    if (newGroup) {
	*curSpot = (PointList *) Malloc(sizeof(PointList));
	curList = *curSpot;
	curSpot = &(curList->next);
	curList->numPoints = 0;
	curList->allocSize = INITSIZE;
	curList->xvec = (double *) Malloc((unsigned)
					  (INITSIZE * sizeof(double)));
	curList->yvec = (double *) Malloc((unsigned)
					  (INITSIZE * sizeof(double)));
	curList->next = (PointList *) 0;
	newGroup = 0;
    }
    if (curList->numPoints >= curList->allocSize) {
	curList->allocSize *= 2;
	curList->xvec = (double *) Realloc((char *) curList->xvec,
					   (unsigned) (curList->allocSize *
						       sizeof(double)));
	curList->yvec = (double *) Realloc((char *) curList->yvec,
					   (unsigned) (curList->allocSize *
						       sizeof(double)));
    }

    curList->xvec[curList->numPoints] = xval;
    curList->yvec[curList->numPoints] = yval;

    (curList->numPoints)++;
    redundant_set = 0;
}

static int
rdFindMax()
/*
 * Returns the maximum number of items in any one group of any
 * data set.
 */
{
    int     i;
    PointList *list;
    int     max = -1;

    for (i = 0; i < setNumber; i++) {
	for (list = PlotData[i].list; list; list = list->next) {
	    if (list->numPoints > max)
		max = list->numPoints;
	}
    }
    return max;
}


typedef enum line_type {
    EMPTY, COMMENT, SETNAME, DRAWPNT, MOVEPNT, SETPARAM, ERROR
}       LineType;

typedef struct point_defn {
    double  xval,
            yval;
}       Point;

typedef struct parmval_defn {
    char   *name,
           *value;
}       ParmVals;

typedef struct line_info {
    LineType type;
    union val_defn {
	char   *str;		/* SETNAME, ERROR   */
	Point   pnt;		/* DRAWPNT, MOVEPNT */
	ParmVals parm;		/* SETPARAM         */
    }       val;
}       LineInfo;

static  LineType
parse_line(line, result)
char   *line;			/* Line to parse   */
LineInfo *result;		/* Returned result */

/*
 * Parses `line' into one of the types given in the definition
 * of LineInfo.  The appropriate values are filled into `result'.
 * Below are the current formats for each type:
 *   EMPTY:	All white space
 *   COMMENT:	Starts with "#"
 *   SETNAME:	A name enclosed in double quotes
 *   DRAWPNT:	Two numbers optionally preceded by keyword "draw"
 *   MOVEPNT:	Two numbers preceded by keyword "move"
 *   SETPARAM:  Two non-null strings separated by ":"
 *   ERROR:	Not any of the above (an error message is returned)
 * Note that often the values are pointers into the line itself
 * and should be copied if they are to be used over a long period.
 */
{
    char   *first;

    /* Find first non-space character */
    while (*line && isspace(*line))
	line++;
    if (*line) {
	if (*line == '#') {
	    /* comment */
	    result->type = COMMENT;
	}
	else if (*line == '"') {
	    /* setname */
	    result->type = SETNAME;
	    line++;
	    result->val.str = line;
	    while (*line && (*line != '\n') && (*line != '"'))
		line++;
	    if (*line)
		*line = '\0';
	}
	else {
	    first = line;
	    while (*line && !isspace(*line))
		line++;
	    if (*line) {
		*line = '\0';
		if (stricmp(first, "move") == 0) {
		    /* MOVEPNT */
		    if (sscanf(line + 1, "%lf %lf",
			       &result->val.pnt.xval,
			       &result->val.pnt.yval) == 2) {
			result->type = MOVEPNT;
		    }
		    else {
			result->type = ERROR;
			result->val.str = "Cannot read move coordinates";
		    }
		}
		else if (stricmp(first, "draw") == 0) {
		    /* DRAWPNT */
		    if (sscanf(line + 1, "%lf %lf",
			       &result->val.pnt.xval,
			       &result->val.pnt.yval) == 2) {
			result->type = DRAWPNT;
		    }
		    else {
			result->type = ERROR;
			result->val.str = "Cannot read draw coordinates";
		    }
		}
		else if (first[strlen(first) - 1] == ':') {
		    /* SETPARAM */
		    first[strlen(first) - 1] = '\0';
		    result->val.parm.name = first;
		    line++;
		    while (*line && isspace(*line))
			line++;
		    /* may be a \n at end of it */
		    if (line[strlen(line) - 1] == '\n') {
			line[strlen(line) - 1] = '\0';
		    }
		    result->val.parm.value = line;
		    result->type = SETPARAM;
		}
		else if (sscanf(first, "%lf", &result->val.pnt.xval) == 1) {
		    /* DRAWPNT */
		    if (sscanf(line + 1, "%lf", &result->val.pnt.yval) == 1) {
			result->type = DRAWPNT;
		    }
		    else {
			result->type = ERROR;
			result->val.str = "Cannot read second coordinate";
		    }
		}
		else {
		    /* ERROR */
		    result->type = ERROR;
		    result->val.str = "Unknown line type";
		}
	    }
	    else {
		/* ERROR */
		result->type = ERROR;
		result->val.str = "Premature end of line";
	    }
	}
    }
    else {
	/* empty */
	result->type = EMPTY;
    }
    return result->type;
}


int
ReadData(stream, filename)
FILE   *stream;
char   *filename;

/*
 * Reads in the data sets from the supplied stream.  If the format
 * is correct,  it returns the current maximum number of points across
 * all data sets.  If there is an error,  it returns -1.
 */
{
    char    buffer[MAXBUFSIZE];
    LineInfo info;
    int     line_count = 0;
    int     errors = 0;

    if (!rdSet(filename)) {
	(void) fprintf(stderr, "Error in file `%s' at line %d:\n  %s\n",
		       filename, line_count,
		       "Too many data sets - extra data ignored");
	return -1;
    }
    while (fgets(buffer, MAXBUFSIZE, stream)) {
	line_count++;
	switch (parse_line(buffer, &info)) {
	case EMPTY:
	    if (!rdSet(filename)) {
		(void) fprintf(stderr, "Error in file `%s' at line %d:\n  %s\n",
			       filename, line_count,
			       "Too many data sets - extra data ignored");
		return -1;
	    }
	    break;
	case COMMENT:
	    /* nothing */
	    break;
	case SETNAME:
	    rdSetName(info.val.str);
	    break;
	case DRAWPNT:
	    rdPoint(info.val.pnt.xval, info.val.pnt.yval);
	    break;
	case MOVEPNT:
	    rdGroup();
	    rdPoint(info.val.pnt.xval, info.val.pnt.yval);
	    break;
	case SETPARAM:
	    param_reset(info.val.parm.name, info.val.parm.value);
	    break;
	default:
	    if (filename) {
		(void) fprintf(stderr, "Error in file `%s' at line %d:\n  %s\n",
			       filename, line_count, info.val.str);
		errors++;
	    }
	    break;
	}
    }
#ifdef DO_DER
    Der1();
#endif
    if (errors)
	return -1;
    else
	return rdFindMax();
}


