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

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 <limits.h>

#include "cmpack_common.h"
#include "tabparser.h"
#include "table.h"
#include "strutils.h"
#include "list.h"
#include "comfun.h"

#define BUFFSIZE 	8192

/***********************      Datatypes      ******************/

/* Context structure for configuration file parser */
struct _CmpackTabParser
{
	int typeonly;			/**< We need only type of the table */
	int headonly;			/**< Parse only columns and header */
	int st;					/**< Automaton state */
	int lastch;				/**< Previous character */
	CmpackString *key;		/**< String buffers for keyword */
	CmpackString *val;		/**< String buffers for value */
	CmpackList *cols;		/**< List of columns */
	CmpackTable *tab;		/**< Reference to table */
	int col_index;			/**< Index of current column */
};
typedef struct _CmpackTabParser CmpackTabParser;

/* Add column name into the list */
static void process_col(CmpackTabParser *p, CmpackString *name)
{
	const char *sname = cmpack_str_cstr(name);
	if (sname[0]!='#' && sname[0]!='\0')
		p->cols = list_append(p->cols, cmpack_strdup(sname));
}

/* End of line(file) found, process parsed data for current line. */
static void process_cols(CmpackTabParser *p)
{
	const char *name, *name1, *name2;
	CmpackList *ptr;

	int ncol = list_count(p->cols);
	if (ncol>0) {
		name = (char*)p->cols->ptr;
		/* Determine the type of the table */
		if (ncol>1 && (strcmp(name, "JD")==0 || strcmp(name, "HJD")==0 || 
			strcmp(name, "GJD")==0 || strcmp(name, "JDHEL")==0 || strcmp(name, "JDGEO")==0)) {
				name1 = (char*)p->cols->next->ptr;
				if (name1[0]=='V' || strcmp(name1, "MAG0")==0 || strstr(name1, "ID")==name1) {
					if (strstr(name1, "-C"))
						cmpack_tab_set_type(p->tab, CMPACK_TABLE_LCURVE_DIFF);
					else
						cmpack_tab_set_type(p->tab, CMPACK_TABLE_LCURVE_INST);
				} else
				if (strcmp(name1, "OFFSETX")==0) {
					cmpack_tab_set_type(p->tab, CMPACK_TABLE_TRACKLIST);
				} else
				if (strcmp(name1, "AIRMASS")==0 || strcmp(name1, "ALTITUDE")==0) {
					cmpack_tab_set_type(p->tab, CMPACK_TABLE_AIRMASS);
				} else
				if (strcmp(name1, "X")==0 || strcmp(name1, "CENTER_X")==0) {
					cmpack_tab_set_type(p->tab, CMPACK_TABLE_OBJ_PROP);
				} else
				if (strcmp(name1, "CCDTEMP")==0) {
					cmpack_tab_set_type(p->tab, CMPACK_TABLE_CCD_TEMP);
				}
		}
		if (ncol>2 && strcmp(name, "INDEX")==0) {
			name1 = (char*)p->cols->next->ptr;
			name2 = (char*)p->cols->next->next->ptr;
			if (strcmp(name1, "MEAN_MAG")==0 && strcmp(name2, "STDEV")==0)
				cmpack_tab_set_type(p->tab, CMPACK_TABLE_MAGDEV);
		} else
		if (ncol>1 && strcmp(name, "APERTURE")==0) {
			name1 = (char*)p->cols->next->ptr;
			if (strcmp(name1, "C-K1")==0)
				cmpack_tab_set_type(p->tab, CMPACK_TABLE_APERTURES);
		}
		if (!p->typeonly) {
			/* Populate table with columns */
			for (ptr=p->cols; ptr!=NULL; ptr=ptr->next) {
				name = (char*)ptr->ptr;
				if (strcmp(name, "JD")==0 || strcmp(name, "HJD")==0 || 
					strcmp(name, "GJD")==0 || strcmp(name, "JDHEL")==0 || strcmp(name, "JDGEO")==0) {
						cmpack_tab_add_column_dbl(p->tab, name, JD_PRECISION, 1e6, 1e99, INVALID_JD);
				} else 
				if (strcmp(name, "HELCOR")==0) {
					cmpack_tab_add_column_dbl(p->tab, name, JD_PRECISION, -0.1, 0.1, INVALID_HCORR);
				} else 
				if (strcmp(name, "AIRMASS")==0) {
					cmpack_tab_add_column_dbl(p->tab, name, AMASS_PRECISION, 0.0, 1e99, INVALID_AMASS);
				} else
				if (strcmp(name, "ALTITUDE")==0) {
					cmpack_tab_add_column_dbl(p->tab, name, ALT_PRECISION, -90.0, 90.0, INVALID_ALT);
				} else
				if (strcmp(name, "APERTURE")==0 || strcmp(name, "INDEX")==0 || strcmp(name, "FRAME")==0) {
					cmpack_tab_add_column_int(p->tab, name, 0, INT_MAX, -1);
				} else
				if (strcmp(name, "OFFSETX")==0 || strcmp(name, "OFFSETY")==0) {
					cmpack_tab_add_column_dbl(p->tab, name, POS_PRECISION, -1e99, 1e99, 0.0);
				} else 
				if (strcmp(name, "GOODPOINTS")==0) {
					cmpack_tab_add_column_int(p->tab, name, 0, INT_MAX, 0);
				} else 
				if (strcmp(name, "EXPOSURE")==0) {
					cmpack_tab_add_column_dbl(p->tab, name, EXP_PRECISION, 0.0, 1e99, INVALID_EXPTIME);
				} else 
				if (strcmp(name, "CCDTEMP")==0) {
					cmpack_tab_add_column_dbl(p->tab, name, TEMP_PRECISION, -999.0, 999.0, INVALID_TEMP);
				} else
				if (strcmp(name, "X")==0 || strcmp(name, "Y")==0 || strcmp(name, "CENTER_X")==0 || strcmp(name, "CENTER_Y")==0) {
					cmpack_tab_add_column_dbl(p->tab, name, POS_PRECISION, 0.0, 65536.0, INVALID_XY);
				} else 
				if (strcmp(name, "SKY")==0) {
					cmpack_tab_add_column_dbl(p->tab, name, SKY_PRECISION, -999.0, 1e99, INVALID_SKY);
				} else
				if (strcmp(name, "FWHM")==0) {
					cmpack_tab_add_column_dbl(p->tab, name, FWHM_PRECISION, 0.0, 1e99, INVALID_FWHM);
				} else
				if (strcmp(name, "FILENAME")==0) {
					cmpack_tab_add_column_str(p->tab, name);
				} else 
				if (name[0]=='V' || name[0]=='C' || name[0]=='K' ||strcmp(name, "MEAN_MAG")==0 || strstr(name, "MAG")==name || strstr(name, "ID")==name) {
					cmpack_tab_add_column_dbl(p->tab, name, MAG_PRECISION, -99.0, 99.0, INVALID_MAG);
				} else
				if (name[0]=='s' || strstr(name, "ERR")==name || strcmp(name, "STDEV")==0) {
					cmpack_tab_add_column_dbl(p->tab, name, MAG_PRECISION, 0.0, 9.0, INVALID_MAGERR);
				} else
					cmpack_tab_add_column_str(p->tab, name);
			}
		}
	}
	list_free_with_items(p->cols, cmpack_free);
	p->cols = NULL;
	cmpack_str_clear(p->key);
	cmpack_str_clear(p->val);
}	

/* Append parameter */
static void process_val(CmpackTabParser *p, CmpackString *key, CmpackString *val)
{
	cmpack_tab_pkys(p->tab, cmpack_str_cstr(key), cmpack_str_cstr(val));
}

/* Append new row */
static void start_record(CmpackTabParser *p)
{
	cmpack_tab_append(p->tab);
	p->col_index = 0;
}

/* Set table data */
static void process_data(CmpackTabParser *p, CmpackString *str)
{
	cmpack_tab_ptds(p->tab, p->col_index++, cmpack_str_cstr(str));
}

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

/* Init parser context */
void tab_parser_init(CmpackTabParser *p, CmpackTable *tab)
{
	memset(p, 0, sizeof(CmpackTabParser));
	p->tab = tab;
	p->key = cmpack_str_create();
	p->val = cmpack_str_create();
}

/* Free asociated memory in the context */
void tab_parser_clear(CmpackTabParser *p)
{
	list_free_with_items(p->cols, cmpack_free);
	p->cols = NULL;
	cmpack_str_free(p->key);	
	p->key = NULL;
	cmpack_str_free(p->val);	
	p->val = NULL;
	p->tab = NULL;
}
	
/* Parse buffer */
int tab_parser_parse(CmpackTabParser *p, const char *buf, size_t buflen, int eof, int *need_more)
{
	int ch;

	if (need_more)
		*need_more = 0;

	while (p->st>=0) {
		/* Take next character */
		if (p->lastch) {
			ch = p->lastch;
			p->lastch = 0;
		} else if (buflen) {
			ch = *((unsigned char*)buf);
			buf++;
			buflen--;
		} else 
			break;
		/* Process character */
		switch (p->st) 
		{
		case 0:
			/* Start of the first line (column names) */
			if ((ch>='A' && ch<='Z') || (ch>='a' && ch<='z')) {
				/* Start of a column name */
				cmpack_str_set_text(p->key, (char*)&ch, 1);
				p->st = 1;
			} else if (ch!=' ' && ch!='#') {
				/* Invalid character on the first line */
				return CMPACK_ERR_UNKNOWN_FORMAT;
			}
		 	break;
		 	
		case 1:
		 	/* Reading column name */
			if (ch>0x20 && ch<=0x7E) {
				/* Next character in column name */
			 	cmpack_str_add_text(p->key, (char*)&ch, 1);
			} else if (ch==' ' || ch=='\t' || ch==',') {
				/* End of colum name, wait for the next one */
				process_col(p, p->key);
				p->st = 2;
		 	} else if (ch==13 || ch==10) {
				/* End of last column name */
				process_col(p, p->key);
				process_cols(p);
				p->st = (!p->typeonly ? (ch==13 ? 9 : 10) : -1);
			} else {
				/* Invalid character on the first line */
				return CMPACK_ERR_UNKNOWN_FORMAT;
			}
			break;

		case 2:
			/* Waiting for the next column name */
			if ((ch>='A' && ch<='Z') || (ch>='a' && ch<='z')) {
				/* Start of next column name */
				cmpack_str_set_text(p->key, (char*)&ch, 1);
				p->st = 1;
			} else if (ch==13 || ch==10) {
				/* End of last column name */
				process_cols(p);
				p->st = (!p->typeonly ? (ch==13 ? 9 : 10) : -1);
			} else if (ch!=' ') {
				/* Invalid character on the first line */
				return CMPACK_ERR_UNKNOWN_FORMAT;
			}
			break;

		case 9:
			/* Consume optional LF after CR */
			if (ch!=10) 
				p->lastch = ch;
			p->st = 10;
			break;
			
		case 10:
			/* Second line with parameters */
		 	if ((ch>='A' && ch<='Z') || (ch>='a' && ch<='z')) {
				/* Start of new keyword */
				cmpack_str_set_text(p->key, (char*)&ch, 1);
				p->st = 11;
			} else 
			if ((ch>='0' && ch<='9') || (ch=='-')) {
				/* Start of a table data, no parameters */
				start_record(p);
				cmpack_str_set_text(p->val, (char*)&ch, 1);
				p->st = (!p->headonly ? 41 : -1);
			} else
			if (ch==13 || ch==10) {
				/* Empty second line, no parameter */
				p->st = (!p->headonly ? (ch==13 ? 39 : 40) : -1);
			} else if (ch!=' ' && ch!='#') {
				/* Invalid character in metadata */
				return CMPACK_ERR_UNKNOWN_FORMAT;
			}
		 	break;
		 	
		case 11:
		 	/* Reading keyword */
			if (ch==':') {
				/* End of keyword wait for value */
				cmpack_str_rtrim(p->key);
				p->st = 12;
			} else if (ch>0x20 && ch<=0x7E) {
				/* Next character in keyword */
				cmpack_str_add_text(p->key, (char*)&ch, 1);
			} else if (ch==13 || ch==10) {
				/* End of line */
				p->st = (!p->headonly ? (ch==13 ? 39 : 40) : -1);
			} else {
				/* Invalid character in metadata */
				return CMPACK_ERR_UNKNOWN_FORMAT;
			}
			break;

		case 12:
			/* Waiting for first character of value */
			if (ch>0x20 && ch<=0x7E) {
				cmpack_str_set_text(p->val, (char*)&ch, 1);
				p->st = 13;
			} else if (ch==10 || ch==13) {
				/* End of line */
				p->st = (!p->headonly ? (ch==13 ? 39 : 40) : -1);
			} else if (ch!=' ') {
				/* Invalid character in metadata */
				return CMPACK_ERR_UNKNOWN_FORMAT;
			}
		 	break;
		 	
		case 13: 
			/* Reading value */
			if (ch==',') {
				/* End of value */
				cmpack_str_rtrim(p->val);
				process_val(p, p->key, p->val);
				p->st = 14;
			} else if (ch>0x20 && ch<=0x7E) {
				/* Next character of value */
				cmpack_str_add_text(p->val, (char*)&ch, 1);
			} else if (ch==13 || ch==10) {
				/* End of line */
				cmpack_str_rtrim(p->val);
				process_val(p, p->key, p->val);
				p->st = (!p->headonly ? (ch==13 ? 39 : 40) : -1);
			} else {
				/* Invalid character in metadata */
				return CMPACK_ERR_UNKNOWN_FORMAT;
			}
		 	break;

		case 14:
			/* Start a new parameter */
		 	if ((ch>='A' && ch<='Z') || (ch>='a' && ch<='z')) {
				/* Start of new keyword */
				cmpack_str_set_text(p->key, (char*)&ch, 1);
				p->st = 11;
			} else 
			if (ch==13 || ch==10) {
				/* No additional parameter, end of header */
				p->st = (!p->headonly ? (ch==13 ? 39 : 40) : -1);
			} else if (ch!=' ') {
				/* Invalid character in metadata */
				return CMPACK_ERR_UNKNOWN_FORMAT;
			}
		 	break;

		case 39:
			/* Consume optional LF after CR */
			if (ch!=10) 
				p->lastch = ch;
			p->st = 40;
			break;

		case 40:
			/* Waiting for table data */
		 	if ((ch>='0' && ch<='9') || ch=='-' || ch=='+') {
				/* Start of a value */
				start_record(p);
				cmpack_str_set_text(p->val, (char*)&ch, 1);
				p->st = 41;
			} else if (ch==13 || ch==10) {
				/* Empty line, ignore */
				p->st = (ch==13 ? 39 : 40);
			} else if (ch!=' ') {
				/* Invalid character in table data */
				return CMPACK_ERR_UNKNOWN_FORMAT;
			}
		 	break;

		case 41:
			/* Reading value */
			if (ch==' ' || ch==',') {
				/* End of value */
				process_data(p, p->val);
				p->st = 42;
		 	} else if (ch>0x20 && ch<=0x7E) {
				/* Next character in value */
				cmpack_str_add_text(p->val, (char*)&ch, 1);
			} else if (ch==13 || ch==10) {
				/* End of line */
				process_data(p, p->val);
				p->st = (ch==13 ? 39 : 40);
			} else {
				/* Invalid character in table data */
				return CMPACK_ERR_UNKNOWN_FORMAT;
			}
			break;

		case 42:
			/* Waiting for next value */
		 	if (ch>0x20 && ch<=0x7E) {
				/* Start of a next value */
				cmpack_str_set_text(p->val, (char*)&ch, 1);
				p->st = 41;
			} else if (ch==13 || ch==10) {
				/* End of line */
				p->st = (ch==13 ? 39 : 40);
			} else if (ch!=' ') {
				/* Invalid character in table data */
				return CMPACK_ERR_UNKNOWN_FORMAT;
			}
			break;
		}
	}

	if (eof) {
		/* End of file, we may have some data in key or val */
		switch (p->st) 
		{
		case 1:
			process_col(p, p->key);
			process_cols(p);
			break;
		case 2:
			process_cols(p);
			break;
		case 13: 
			cmpack_str_rtrim(p->val);
			process_val(p, p->key, p->val);
		 	break;
		case 41:
			process_data(p, p->val);
			break;
		}
	}
	/* When we need more data? */
	if (need_more && !eof && p->st>=0)
		*need_more = 1;
	return 0;
}

/* Load table */
int tab_load(CmpackTable *tab, FILE *from, int head_only)
{
	size_t len;
	int res, done, more;
	char buff[BUFFSIZE];
	CmpackTabParser p;

	tab_parser_init(&p, tab);
	p.headonly = head_only;
	do {
		len = fread(buff, 1, BUFFSIZE, from);
		if (ferror(from)) {
			res = CMPACK_ERR_READ_ERROR;
			break;
		}
		done = feof(from);
		res = tab_parser_parse(&p, buff, len, done, &more);
	} while (!done && res==0 && more);
	tab_parser_clear(&p);
	return res;
}

/* Detect table type */
CmpackTableType tab_format(const char *header)
{
	CmpackTabParser p;
	CmpackTableType retval = CMPACK_TABLE_UNSPECIFIED;
	CmpackTable *tab = cmpack_tab_init(CMPACK_TABLE_UNSPECIFIED);
	tab_parser_init(&p, tab);
	p.typeonly = 1;
	if (tab_parser_parse(&p, header, strlen(header), 1, NULL)==0) 
		retval = cmpack_tab_get_type(tab);
	tab_parser_clear(&p);
	cmpack_tab_destroy(tab);
	return retval;
}
