/**************************************************************

tabparser.c (C-Munipack project)
Copyright (C) 2004 David Motl, dmotl@volny.cz

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 2
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
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

**************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "allimport.h"
#include "strutils.h"
#include "comfun.h"
#include "cmpack_common.h"

#define ALLOC_BY 64
#define BUFFSIZE 	8192

/* Single measurement */
typedef struct _Data
{
	double mag, err;
	struct _Data *next;
} Data;

/* Frame data */
typedef struct _Frame
{
	double juldat;
	int nstars;
	Data *first, *last;
	struct _Frame *next;
} Frame;

/* Context structure for catalog file parser */
typedef struct _Parser
{
	int headonly;				/**< Read header only, skip frame data */
	int st;						/**< Automaton state */
	int row;					/**< Current position in file - row index */
	int col;					/**< Current position in file - column index */
	char lastch;				/**< Previous character */
	CmpackString *filter;		/**< Color filter name */
	CmpackString *key;			/**< String buffer for parameter name */
	CmpackString *val;			/**< String buffer for parameter value */
	CmpackString *jd;			/**< String buffer for Julian date */
	CmpackString *mag;			/**< String buffer for magnitude */
	CmpackString *err;			/**< String buffer for error value */
	int aperture_id;			/**< Aperture identifier */
	CmpackJDMode jd_mode;		/**< Julian date mode */
	Frame *first, *last;		/**< First frame, last frame */
	Frame *frame;				/**< Current frame */
} Parser;

/* End of line(file) found, process parsed data for current line. */
static void process_line(Parser *p)
{
	cmpack_str_clear(p->key);
	cmpack_str_clear(p->val);
	cmpack_str_clear(p->jd);
	cmpack_str_clear(p->mag);
	cmpack_str_clear(p->err);
	p->row++; 
	p->col = 1;
}	

/* Append column */
static void process_frame(Parser *p, CmpackString *jdstr)
{
	double jd;

	if (cmpack_str_dbl(jdstr, &jd) && jd>0) {
		Frame *f = cmpack_calloc(1, sizeof(Frame));
		f->juldat = jd;
		if (p->last) {
			p->last->next = f;
			p->last = f;
		} else {
			p->last = p->first = f;
		}
		p->frame = f;
	} else {
		p->frame = NULL;
	}
}

/* Append parameter */
static void process_val(Parser *p, CmpackString *key, CmpackString *val)
{
	int id;

	const char *skey = cmpack_str_cstr(key);
	if (skey && strcmp(skey, "Aperture")==0 && cmpack_str_int(val, &id)) 
		p->aperture_id = id;
	else if (skey && strcmp(skey, "Filter")==0 && cmpack_str_cstr(val)) 
		cmpack_str_set_string(p->filter, val);
	else if (skey && strcmp(skey, "JD")==0 && cmpack_str_cstr(val)) {
		if (strcmp(cmpack_str_cstr(val), "geocentric")==0)
			p->jd_mode = CMPACK_JD_GEOCENTRIC;
		else if (strcmp(cmpack_str_cstr(val), "heliocentric")==0)
			p->jd_mode = CMPACK_JD_HELIOCENTRIC;
	}
}

/* Append star */
static void process_mag(Parser *p, CmpackString *mag, CmpackString *err)
{
	double dmag, derr;

	if (p->frame) {
		if (cmpack_str_dbl(mag, &dmag) && cmpack_str_dbl(err, &derr)) {
			Data *d = cmpack_malloc(sizeof(Data));
			d->mag = dmag;
			d->err = derr;
			d->next = NULL;
			if (p->frame->last) {
				p->frame->last->next = d;
				p->frame->last = d;
			} else {
				p->frame->first = p->frame->last = d;
			}
			p->frame->nstars++;
		}
	}
}

/*********************   PUBLIC FUNCTIONS   ****************************/

/* Init parser context */
void all_parser_init(Parser *p, int flags)
{
	memset(p, 0, sizeof(Parser));
	p->headonly = (flags & CMPACK_LOAD_HEADONLY)!=0;
	p->key = cmpack_str_create();
	p->val = cmpack_str_create();
	p->jd = cmpack_str_create();
	p->mag = cmpack_str_create();
	p->err = cmpack_str_create();
	p->filter = cmpack_str_create();
	p->row = p->col = 1;
}

/* Free asociated memory in the context */
void all_parser_clear(Parser *p)
{
	Frame *f;
	Data *d;
	
	f=p->first; 
	while (f) {
		Frame *next = f->next;
		d = f->first;
		while (d) {
			Data *next = d->next;
			cmpack_free(d);
			d = next;
		}
		cmpack_free(f);
		f = next;
	}
	cmpack_str_free(p->key);	
	cmpack_str_free(p->val);	
	cmpack_str_free(p->jd);	
	cmpack_str_free(p->mag);	
	cmpack_str_free(p->err);	
	cmpack_str_free(p->filter);
}
	
/* Parse buffer */
int all_parser_parse(Parser *p, const char *buf, size_t buflen, int eof)
{
	size_t i;
	char ch;

	if (p==NULL || buf==NULL)
		return 0; 
	
	for (i=0; i<buflen; i++) {
		/* Take next character from the buffer */
		ch = buf[i]; 
		/* Finite automaton which processes the characters */
		switch (p->st) 
		{
		case 0:
			/* Skip first line (column names) */
			p->col = 1;	
			/* First character on the line */
			if (ch==13) {
				/* Skip empty line */
				process_line(p);
				p->st = 10;
			} else
			if (ch==10) {
				/* Skip empty line */
				if (p->lastch!=13) {
					process_line(p);
					p->st = 10;
				}
			}
		 	break;
		 	
		case 10:
			/* Start of new line */
			p->col = 1;	
			/* First character on the line */
		 	if ((ch>='A' && ch<='Z') || (ch>='a' && ch<='z') || (ch=='_')) {
				/* Start of a keyword */
				cmpack_str_set_text(p->key, &ch, 1);
				p->st = 12;
			} else
			if (ch==13) {
				/* Empty line ends the header */
				process_line(p);
				p->st = 40;
			} else
			if (ch==10) {
				if (p->lastch!=13) {
					process_line(p);
					p->st = 40;
				}
			} else {
				/* Write space */
				p->st = 11;
			}
		 	break;
		 	
		case 11: 
			/* Skip leading spaces */
		 	if ((ch>='A' && ch<='Z') || (ch>='a' && ch<='z') || (ch=='_')) {
				/* Start of a keyword */
				cmpack_str_set_text(p->key, &ch, 1);
				p->st = 12;
			} else
			if (ch==13 || ch==10) {
				/* Line with spaces only */
				process_line(p);
				p->st = 40;
			}
		 	break;

		case 12: 
		 	/* Reading keyword */
			if (ch==':') {
				/* End of keyword, old format, wait for value */
				cmpack_str_rtrim(p->key);
				p->st = 20;
		 	} else 
			if (ch==13 || ch==10) {
				/* End of line (ignore it) */
				process_line(p);
				p->st = 40;
			} else {
				/* Next character in keyword */
				cmpack_str_add_text(p->key, &ch, 1);
			}
			break;

		case 20:
			/* Old format */
			if (ch==13 || ch==10) {
				/* End of line */
				process_line(p);
				p->st = 40;
			} else
		 	if (ch!=' ' && ch!='\t') {
				/* Start of value (unquoted) */
				cmpack_str_set_text(p->val, &ch, 1); 
				p->st = 21;
			}
		 	break;
		 	
		case 21: 
			/* Reading value */
			if (ch==',') {
				/* End of value */
				cmpack_str_rtrim(p->val);
				process_val(p, p->key, p->val);
				p->st = 22;
			} else
			if (ch==13 || ch==10) {
				/* End of line */
				cmpack_str_rtrim(p->val);
				process_val(p, p->key, p->val);
				process_line(p);
				p->st = 40;
			} else {
				cmpack_str_add_text(p->val, &ch, 1);
			}
		 	break;

		case 22:
			/* Waiting for next keyword */
		 	if ((ch>='A' && ch<='Z') || (ch>='a' && ch<='z') || (ch=='_')) {
				/* Start of a keyword */
				cmpack_str_set_text(p->key, &ch, 1);
				p->st = 12;
			} else
			if (ch==13 || ch==10) {
				/* Line with spaces only */
				process_line(p);
				p->st = 40;
			}
			break;

		case 40:
			/* Waiting for table data */
			p->col = 1;	
			/* First character on the line */
		 	if (ch>='0' && ch<='9') {
				/* Start of a value */
				cmpack_str_set_text(p->jd, &ch, 1);
				p->st = 42;
			} else
			if (ch==13) {
				/* Empty line ends the header */
				process_line(p);
			} else
			if (ch==10) {
				if (p->lastch!=13) {
					process_line(p);
				}
			} else {
				/* White space */
				p->st = 41;
			}
		 	break;

		case 41:
			/* Waiting for table data */
		 	if (ch>='0' && ch<='9') {
				/* Start of a keyword */
				cmpack_str_set_text(p->jd, &ch, 1);
				p->st = 42;
			} else
			if (ch==13 || ch==10) {
				/* Line with spaces only */
				process_line(p);
				p->st = 40;
			}
		 	break;

		case 42:
			/* Reading value */
			if (ch==' ') {
				/* End of value */
				if (!p->headonly)
					process_frame(p, p->jd);
				p->st = 43;
		 	} else 
			if (ch==13 || ch==10) {
				/* End of value */
				if (!p->headonly)
					process_frame(p, p->jd);
				process_line(p);
				p->st = 40;
			} else {
				/* Next character in keyword */
				cmpack_str_add_text(p->jd, &ch, 1);
			}
			break;

		case 43:
			/* Waiting for next keyword */
		 	if ((ch>='0' && ch<='9') || (ch=='+') || (ch=='-')) {
				/* Start of a keyword */
				cmpack_str_set_text(p->mag, &ch, 1);
				p->st = 44;
			} else
			if (ch==13 || ch==10) {
				/* Line with spaces only */
				process_line(p);
				p->st = 40;
			}
			break;

		case 44:
			/* Reading next values */
			if (ch==' ') {
				/* End of value */
				p->st = 45;
		 	} else 
			if (ch==13 || ch==10) {
				/* End of value */
				process_line(p);
				p->st = 40;
			} else {
				/* Next character in keyword */
				cmpack_str_add_text(p->mag, &ch, 1);
			}
			break;

		case 45:
			/* Waiting for next keyword */
		 	if ((ch>='0' && ch<='9') || (ch=='+') || (ch=='-')) {
				/* Start of a keyword */
				cmpack_str_set_text(p->err, &ch, 1);
				p->st = 46;
			} else
			if (ch==13 || ch==10) {
				/* Line with spaces only */
				process_line(p);
				p->st = 40;
			}
			break;

		case 46:
			/* Reading next values */
			if (ch==' ') {
				/* End of value */
				process_mag(p, p->mag, p->err);
				p->st = 43;
		 	} else 
			if (ch==13 || ch==10) {
				/* End of value */
				process_mag(p, p->mag, p->err);
				process_line(p);
				p->st = 40;
			} else {
				/* Next character in keyword */
				cmpack_str_add_text(p->err, &ch, 1);
			}
			break;
		}
		/* Remember last character */
		p->lastch = ch;
	}

	if (eof && p->st!=0) {
		/* Last line, which doesn't end with line delimiter */
		p->st = 0;
	}
	return 0;
}

/* Loads a XML document. Returns zero on success or one of MPK_xxx error codes.
 * Uses Expat library. Parsing context is held in internal file structure,
 * passed as user data (xml_xxx fields).
 */
int all_import(CmpackFrameSet **fset, FILE *from, int flags)
{
	size_t len;
	int i, done, res;
	char Buff[BUFFSIZE];
	Parser p;
	Frame *f;
	Data *d;
	CmpackFrameSetInfo sinfo;
	CmpackFrameInfo finfo;
	CmpackCatObject obj;
	CmpackPhtAperture aper;
	CmpackPhtData data;

	*fset = NULL;

	all_parser_init(&p, flags);
	/* Parse source file */
	res = 0;
	do {
		len = fread(Buff, 1, BUFFSIZE, from);
		if (ferror(from)) {
			res = CMPACK_ERR_READ_ERROR;
			break;
		}
		done = feof(from);
		if (all_parser_parse(&p, Buff, (int)len, done)!=0) {
   			res = CMPACK_ERR_READ_ERROR;
			break;
		}
	} while (!done);
	/* Create an fill a frame set */
	if (res==0) {
		int nobjects = 0, frame_id = 1;
		for (f=p.first; f!=NULL; f=f->next) {
			if (f->nstars > nobjects)
				nobjects = f->nstars;
		}
		*fset = cmpack_fset_init();
		sinfo.jd_mode = p.jd_mode;
		cmpack_fset_set_info(*fset, CMPACK_FS_JD_MODE, &sinfo);
		aper.id = p.aperture_id;
		cmpack_fset_add_aperture(*fset, CMPACK_PA_ID, &aper);
		for (i=0; i<nobjects; i++) {
			obj.id = i+1;
			cmpack_fset_add_object(*fset, CMPACK_OM_ID, &obj);
		}
		for (f=p.first; f!=NULL; f=f->next) {
			finfo.frame_id = frame_id++;
			finfo.juldat = f->juldat;
			finfo.filter = (char*)cmpack_str_cstr(p.filter);
			cmpack_fset_append(*fset, CMPACK_FI_ID | CMPACK_FI_JULDAT | CMPACK_FI_FILTER, &finfo);
			for (d=f->first, i=0; d!=NULL; d=d->next, i++) {
				if (d->mag<99.9) {
					data.mag_valid = 1;
					data.magnitude = d->mag;
					data.mag_error = d->err;
					cmpack_fset_set_data(*fset, i, 0, &data);
				}
			}
		}
	}
	all_parser_clear(&p);

	return res;
}
