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

table.c (C-Munipack project)
Tables -- public routines
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 <float.h>
#include <limits.h>

#include "comfun.h"
#include "console.h"
#include "hash.h"
#include "header.h"
#include "cmpack_common.h"
#include "cmpack_table.h"
#include "tabparser.h"
#include "table.h"

/******************* Constants and definitions **********************/

#define ALLOC_BY 	64

/********************** Local functions ****************************/

static void table_clear(CmpackTable *tab);
static void columns_clear(TabColumns *cols);
static void columns_copy(TabColumns *dst, const TabColumns *src);
static void cell_copy(TabCell *dst, TabColumn *dst_col, const TabCell *src, const TabColumn *src_col);
static int check_value(const TabValue *item, const TabColumn *col);
static void record_free(CmpackTable *tab, TabRecord *rec);

/* Init table context */
static CmpackTable *table_create(CmpackTableType type)
{
	CmpackTable *f = (CmpackTable*)cmpack_calloc(1, sizeof(CmpackTable));
	f->refcnt = 1;
	f->type = type;
	header_init(&f->head);
	return f;
}

/* Clear content of the file */
static void table_clear(CmpackTable *t)
{
	TabRecord *ptr, *next;

	ptr = t->first;
	while (ptr) {
		next = ptr->next;
		record_free(t, ptr);
		ptr = next;
	}
	t->first = t->last = t->current = NULL;

	t->type = CMPACK_TABLE_UNSPECIFIED;
	header_clear(&t->head);
	columns_clear(&t->cols);
}

/* Write table to file */
static void table_write(CmpackTable *t, FILE *f, int flags, const int *cols, int ncols)
{
	int i, j, *c, first;
	const TabRecord *ptr;
	const CmpackHeadItem *hit;

	/* Which columns shall be exported? */
	c = (int*)cmpack_calloc(t->cols.count, sizeof(int));
	if (cols && ncols>0) {
		for (i=0; i<ncols; i++) {
			j = cols[i];
			if (j>=0 && j<t->cols.count && !c[j])
				c[j] = 1;
		}
	} else {
		for (j=0; j<t->cols.count; j++)
			c[j] = 1;
	}
	
	/* Table header */
	header_normalize(&t->head);
	if ((flags & CMPACK_SAVE_NO_HEADER) == 0) {
		first = 1;
		for (j = 0; j < t->cols.count; j++) {
			if (c[j]) {
				const TabColumn* col = &t->cols.list[j];
				if (!first)
					fprintf(f, " ");
				fprintf(f, "%s", (col->name ? col->name : ""));
				first = 0;
			}
		}
		fprintf(f, "\n");
		first = 1;
		for (j = 0; j < t->head.count; j++) {
			hit = t->head.list[j];
			if (hit->key && hit->val) {
				if (!first)
					fprintf(f, ", ");
				fprintf(f, "%s: %s", hit->key, hit->val);
				first = 0;
			}
		}
		fprintf(f, "\n");
	}

	/* Table data */
	for (ptr=t->first; ptr!=NULL; ptr=ptr->next) {
		int first = 1;
		for (j=0; j<t->cols.count; j++) {
			if (c[j]) {
				const TabColumn *col = &t->cols.list[j];
				if (!first)
					fprintf(f, " ");
				if (ptr->data[j].assigned && check_value(&ptr->data[j].data, col)) {
					const TabValue *val = &ptr->data[j].data;
					switch (col->dtype)
					{
					case CMPACK_TYPE_STR:
						fprintf(f, "%s", val->sValue);
						break;
					case CMPACK_TYPE_INT:
						fprintf(f, "%d", val->iValue);
						break;
					case CMPACK_TYPE_DBL:
						fprintf(f, "%.*f", col->prec, val->dValue);
						break;
					default:
						break;
					}
				} else {
					switch (col->dtype)
					{
					case CMPACK_TYPE_STR:
						break;
					case CMPACK_TYPE_INT:
						fprintf(f, "%d", col->iNulVal);
						break;
					case CMPACK_TYPE_DBL:
						fprintf(f, "%.*f", col->prec, col->dNulVal);
						break;
					default:
						break;
					}
				}
				first = 0;
			}
		}
		fprintf(f, "\n");
	}
	cmpack_free(c);
}

/*********************   Public functions    ***********************/

/* Create a table */
CmpackTable *cmpack_tab_init(CmpackTableType type)
{
	return table_create(type);
}

/* Increment the reference counter */
CmpackTable *cmpack_tab_reference(CmpackTable *ctx)
{
	ctx->refcnt++;
	return ctx;
}

/* Decrement reference counter / detroy the instance */
void cmpack_tab_destroy(CmpackTable *ctx)
{
	if (ctx) {
		ctx->refcnt--;
		if (ctx->refcnt==0) {
			table_clear(ctx);
			cmpack_free(ctx);
		}
	}
}

/* Test the file */
int cmpack_tab_test(const char *filename)
{
	int bytes, filesize;
	char buffer[2048];

	if (filename) {
		FILE *f = fopen(filename, "r");
		if (f) {
			fseek(f, 0, SEEK_END);
			filesize = ftell(f);
			fseek(f, 0, SEEK_SET);
			bytes = (int)fread(buffer, 1, 2048, f);
			fclose(f);
			return cmpack_tab_test_buffer(buffer, bytes, filesize);
		}
	}
	return 0;
}

/* Test if the file is a photometry file */
int cmpack_tab_test_buffer(const char *buffer, int buflen, int filesize)
{
	int res = 0;
	char *aux;

	if (buffer && buflen>16) {
		/* Convert buffer to a null-terminated string */
		aux = (char*)cmpack_malloc((buflen+1)*sizeof(char));
		memcpy(aux, buffer, buflen);
		aux[buflen] = '\0';
		res = (tab_format(aux)!=CMPACK_TABLE_UNSPECIFIED);
		cmpack_free(aux);
	}
	return res;
}

/* Opens file. You can open the file for reading only or in 
 * read-write mode. Returns pointer to internal file structure 
 * handle or NULL if failed.
 */
int cmpack_tab_load(CmpackTable **out, const char *filename, int flags)
{
	int res;
	FILE *f;
	CmpackTable *tab;

	f = fopen(filename, "r");
	if (!f) {
		*out = NULL;
		return CMPACK_ERR_CANT_OPEN_SRC;
	}
	tab = cmpack_tab_init(CMPACK_TABLE_UNSPECIFIED);
	res = tab_load(tab, f, flags);
	if (res!=0) {
		*out = NULL;
		fclose(f);
		return res;
	}
	header_normalize(&tab->head);
	fclose(f);
	*out = tab;
	return CMPACK_ERR_OK;
}

/* Destroy a table */
int cmpack_tab_save(CmpackTable *tab, const char *filename, int flags,
	const int *columns, int ncolumns)
{
	FILE *f;

	if (!tab)
		return CMPACK_ERR_INVALID_CONTEXT;

	f = fopen(filename, "w+");
	if (!f) 
		return CMPACK_ERR_OPEN_ERROR;

	table_write(tab, f, flags, columns, ncolumns);
	fclose(f);
	return CMPACK_ERR_OK;
}

/* Clear table content */
void cmpack_tab_clear(CmpackTable *tab)
{
	table_clear(tab);
}

/* Copy a table */
int cmpack_tab_copy(CmpackTable *dsttab, const CmpackTable *srctab)
{
	int i;
	const TabRecord *sptr;
	TabRecord *dptr;

	table_clear(dsttab);
	dsttab->type = srctab->type;
	header_copy(&dsttab->head, &srctab->head);
	columns_copy(&dsttab->cols, &srctab->cols);

	for (sptr = srctab->first; sptr!=NULL; sptr=sptr->next) {
		dptr = (TabRecord*)cmpack_calloc(1, sizeof(TabRecord));
		dptr->ndata = sptr->ndata;
		dptr->data = (TabCell*)cmpack_calloc(dptr->ndata, sizeof(TabCell));
		for (i=0; i<dptr->ndata; i++) 
			cell_copy(dptr->data+i, &dsttab->cols.list[i], &sptr->data[i], &srctab->cols.list[i]);
		dptr->prev = dsttab->last;
		if (dsttab->last) 
			dsttab->last->next = dptr;
		else
			dsttab->first = dptr;
		dsttab->last = dptr;
	}
	dsttab->current = dsttab->first;

	return CMPACK_ERR_OK;
}

/* Set table type */
void cmpack_tab_set_type(CmpackTable *tab, CmpackTableType type)
{
	tab->type = type;
}

/* Get table type */
CmpackTableType cmpack_tab_get_type(CmpackTable *tab)
{
	return tab->type;
}

/*********************   Table header     ***********************/

/* Set value of parameter in table header */
void cmpack_tab_pkys(CmpackTable *f, const char *key, const char *val)
{
	header_pkys(&f->head, key, val, NULL);
}

/* Set value of parameter in table header */
void cmpack_tab_pkyi(CmpackTable *f, const char *key, int val)
{
	header_pkyi(&f->head, key, val, NULL);
}

/* Set value of parameter in table header */
void cmpack_tab_pkyd(CmpackTable *f, const char *key, double val, int prec)
{
	header_pkyf(&f->head, key, val, prec, NULL);
}

/* Get value of parameter in table header */
const char *cmpack_tab_gkys(CmpackTable *f, const char *key)
{
	return header_gkys(&f->head, key);
}

/* Get value of parameter in table header */
int cmpack_tab_gkyi(CmpackTable *f, const char *key, int *value)
{
	return header_gkyi(&f->head, key, value);
}

/* Get value of parameter in table header */
int cmpack_tab_gkyd(CmpackTable *f, const char *key, double *value)
{
	return header_gkyd(&f->head, key, value);
}

/* Return number of parameters stored in table header */
int cmpack_tab_nkey(CmpackTable *tab)
{
	return tab->head.count; 
}

/* Get the (key, value, comment) tripplets */
int cmpack_tab_gkyn(CmpackTable *f, int index, const char **key, const char **val)
{
	return header_gkyn(&f->head, index, key, val, NULL);
}

/* Delete specified parameter */
void cmpack_tab_dkey(CmpackTable *f, const char *key)
{
	header_dkey(&f->head, key);
}

/*********************   Table structure     ***********************/

/* Default constructor */
static int columns_add(TabColumns *cols, unsigned mask, const CmpackTabColumn *col)
{
	int i = cols->count;
	if (cols->count>=cols->capacity) {
		cols->capacity += ALLOC_BY;
		cols->list = (TabColumn*)cmpack_realloc(cols->list, cols->capacity*sizeof(TabColumn));
	}
	memset(&cols->list[i], 0, sizeof(TabColumn));
	cols->list[i].name = cmpack_strdup(col->name);
	cols->list[i].dtype = col->dtype;
	if (cols->list[i].dtype == CMPACK_TYPE_DBL)
		cols->list[i].prec = col->prec;
	if (mask & CMPACK_TM_LIMITS) {
		if (cols->list[i].dtype == CMPACK_TYPE_INT) {
			cols->list[i].iMin = (int)col->limit_min;
			cols->list[i].iMax = (int)col->limit_max;
		} 
		if (cols->list[i].dtype == CMPACK_TYPE_DBL) {
			cols->list[i].dMin = col->limit_min;
			cols->list[i].dMax = col->limit_max;
		} 
	}
	if (mask & CMPACK_TM_NULVAL) {
		if (cols->list[i].dtype == CMPACK_TYPE_INT) 
			cols->list[i].iNulVal = (int)col->nul_value;
		if (cols->list[i].dtype == CMPACK_TYPE_DBL) 
			cols->list[i].dNulVal = col->nul_value;
	}
	cols->count++;
	return i;	
}

/* Make copy */
static void columns_copy(TabColumns *dst, const TabColumns *src)
{
	int j;

	columns_clear(dst);
	dst->count = src->count;
	if (src->count>0) {
		dst->capacity = dst->count;
		dst->list = (TabColumn*)cmpack_malloc(dst->count*sizeof(TabColumn));
		for (j=0; j<dst->count; j++) {
			dst->list[j] = src->list[j];
			if (src->list[j].name)
				dst->list[j].name = cmpack_strdup(src->list[j].name);
			dst->list[j].needs_update = 1;
		}
	}
}

static void columns_clear(TabColumns *cols)
{
	int i;

	for (i=0; i<cols->count; i++)
		cmpack_free(cols->list[i].name);
	cmpack_free(cols->list);
	cols->list = NULL;
	cols->count = cols->capacity = 0;
}

static void update_range(CmpackTable *tab, TabColumn *col, int index)
{
	int ok, iMin, iMax;
	double dMin, dMax;
	TabRecord *ptr;

	col->valid_range = 0;
	col->min = col->max = 0;
	switch (col->dtype)
	{
	case CMPACK_TYPE_INT:
		ok = 0;
		iMin = iMax = 0;
		for (ptr=tab->first; ptr!=NULL; ptr=ptr->next) {
			if (index<ptr->ndata) {
				const TabValue *val = &ptr->data[index].data;
				if (ptr->data[index].assigned && check_value(val, col)) {
					if (!ok) {
						iMin = iMax = val->iValue;
						ok = 1;
					} else {
						if (val->iValue > iMax)
							iMax = val->iValue;
						if (val->iValue < iMin)
							iMin = val->iValue;
					}
				}
			}
		}
		col->valid_range = ok;
		col->min = iMin;
		col->max = iMax;
		break;

	case CMPACK_TYPE_DBL:
		ok = 0;
		dMin = dMax = 0;
		for (ptr=tab->first; ptr!=NULL; ptr=ptr->next) {
			if (index<ptr->ndata) {
				const TabValue *val = &ptr->data[index].data;
				if (ptr->data[index].assigned && check_value(val, col)) {
					if (!ok) {
						dMin = dMax = val->dValue;
						ok = 1;
					} else {
						if (val->dValue > dMax)
							dMax = val->dValue;
						if (val->dValue < dMin)
							dMin = val->dValue;
					}
				}
			}
		}
		col->valid_range = ok;
		col->min = dMin;
		col->max = dMax;
		break;

	default:
		break;
	}
	col->needs_update = 0;
}

/* Returns number of columns */
int cmpack_tab_ncolumns(CmpackTable *tab)
{
	return tab->cols.count;
}

/* Append column */
int cmpack_tab_add_column(CmpackTable *tab, unsigned mask, CmpackTabColumn *info)
{
	return columns_add(&tab->cols, mask, info);
}

/* Append column (integer number) */
int cmpack_tab_add_column_int(CmpackTable *tab, const char *name, 
	int min, int max, int nulval)
{
	static const unsigned mask = CMPACK_TM_LIMITS | CMPACK_TM_NULVAL;
	CmpackTabColumn col;

	col.name = (char*)name;
	col.dtype = CMPACK_TYPE_INT;
	col.limit_min = min;
	col.limit_max = max;
	col.nul_value = nulval;
	return cmpack_tab_add_column(tab, mask, &col);
}

/* Append column (real number) */
int cmpack_tab_add_column_dbl(CmpackTable *tab, const char *name, int prec,
	double min, double max, double nulval)
{
	static const unsigned mask = CMPACK_TM_LIMITS | CMPACK_TM_NULVAL;
	CmpackTabColumn col;

	col.name = (char*)name;
	col.dtype = CMPACK_TYPE_DBL;
	col.prec = prec;
	col.limit_min = min;
	col.limit_max = max;
	col.nul_value = nulval;
	return cmpack_tab_add_column(tab, mask, &col);
}

/* Append column (string) */
int cmpack_tab_add_column_str(CmpackTable *tab, const char *name)
{
	CmpackTabColumn col;

	col.name = (char*)name;
	col.dtype = CMPACK_TYPE_STR;
	return cmpack_tab_add_column(tab, 0, &col);
}

/* Get column by its index */
int cmpack_tab_get_column(CmpackTable *tab, int index, unsigned mask, CmpackTabColumn *info)
{
	TabColumn *col;

	if (index<0 || index>=tab->cols.count)
		return CMPACK_ERR_UNDEF_VALUE;

	col = &tab->cols.list[index];
	if (mask & CMPACK_TM_NAME) 
		info->name = col->name;
	if (mask & CMPACK_TM_TYPE_PREC) {
		info->dtype = col->dtype;
		info->prec = col->prec;
	}
	if (mask & CMPACK_TM_LIMITS) {
		if (col->dtype==CMPACK_TYPE_INT) {
			info->limit_min = col->iMin;
			info->limit_max = col->iMax;
		}
		if (col->dtype==CMPACK_TYPE_DBL) {
			info->limit_min = col->dMin;
			info->limit_max = col->dMax;
		}
	}
	if (mask & CMPACK_TM_NULVAL) {
		if (col->dtype==CMPACK_TYPE_INT) 
			info->nul_value = col->iNulVal;
		if (col->dtype==CMPACK_TYPE_DBL)
			info->nul_value = col->dNulVal;
	}
	if (mask & CMPACK_TM_RANGE) {
		if (col->needs_update)
			update_range(tab, col, index);
		info->range_valid = col->valid_range;
		info->range_min = col->min;
		info->range_max = col->max;
	}
	return CMPACK_ERR_OK;
}

/* Set column by its index */
void cmpack_tab_set_column(CmpackTable *tab, int index, unsigned mask, const CmpackTabColumn *info)
{
	TabColumn *col;

	if (index<0 || index>=tab->cols.count)
		return;

	col = &tab->cols.list[index];
	if (mask & CMPACK_TM_NAME) {
		cmpack_free(col->name);
		col->name = cmpack_strdup(info->name);
	}
	if (mask & CMPACK_TM_TYPE_PREC) {
		col->dtype = info->dtype;
		col->prec = info->prec;
	}
	if (mask & CMPACK_TM_LIMITS) {
		if (col->dtype==CMPACK_TYPE_INT) {
			col->iMin = (int)info->limit_min;
			col->iMax = (int)info->limit_max;
		}
		if (col->dtype==CMPACK_TYPE_DBL) {
			col->dMin = info->limit_min;
			col->dMax = info->limit_max;
		}
	}
	if (mask & CMPACK_TM_NULVAL) {
		if (col->dtype==CMPACK_TYPE_INT) 
			col->iNulVal = (int)info->nul_value;
		if (col->dtype==CMPACK_TYPE_DBL)
			col->dNulVal = info->nul_value;
	}
}

/* Get column by its name */
int cmpack_tab_find_column(CmpackTable *tab, const char *name)
{
	int i;

	for (i=0; i<tab->cols.count; i++) {
		if (strcmp(tab->cols.list[i].name, name)==0)
			return i;
	}
	return -1;
}

/*********************    Table data    ***********************/

static void cell_set(TabCell *to, TabColumn *col, const TabValue *val, CmpackType type)
{
	char buf[128], *endptr;

	to->assigned = 0;
	if (col->dtype==CMPACK_TYPE_STR && to->data.sValue!=NULL) {
		cmpack_free(to->data.sValue);
		to->data.sValue = NULL;
	}
	if (val) {
		switch (col->dtype) 
		{
		case CMPACK_TYPE_STR:
			switch (type)
			{
			case CMPACK_TYPE_STR:
				to->data.sValue = cmpack_strdup(val->sValue);
				to->assigned = 1;
				break;
			case CMPACK_TYPE_INT:
				sprintf(buf, "%d", val->iValue);
				to->data.sValue = cmpack_strdup(buf);
				to->assigned = 1;
				break;
			case CMPACK_TYPE_DBL:
				sprintf(buf, "%.12g", val->dValue);
				to->data.sValue = cmpack_strdup(buf);
				to->assigned = 1;
				break;
			default:
				break;
			}
			break;

		case CMPACK_TYPE_INT:
			switch (type)
			{
			case CMPACK_TYPE_STR:
				to->data.iValue = strtol(val->sValue, &endptr, 10);
				to->assigned = (endptr!=val->sValue);
				break;
			case CMPACK_TYPE_INT:
				to->data.iValue = val->iValue;
				to->assigned = 1;
				break;
			case CMPACK_TYPE_DBL:
				if (val->dValue>=INT_MIN && val->dValue<=INT_MAX) {
					to->data.iValue = (int)val->dValue;
					to->assigned = 1;
				}
				break;
			default:
				break;
			}
			break;

		case CMPACK_TYPE_DBL:
			switch (type)
			{
			case CMPACK_TYPE_STR:
				to->data.dValue = strtod(val->sValue, &endptr);
				to->assigned = (endptr!=val->sValue);
				break;
			case CMPACK_TYPE_INT:
				to->data.dValue = val->iValue;
				to->assigned = 1;
				break;
			case CMPACK_TYPE_DBL:
				to->data.dValue = val->dValue;
				to->assigned = 1;
				break;
			default:
				break;
			}
			break;

		default:
			break;
		}
	}
}

static void cell_unset(TabCell *to, TabColumn *col)
{
	to->assigned = 0;
	if (col->dtype==CMPACK_TYPE_STR && to->data.sValue!=NULL) {
		cmpack_free(to->data.sValue);
		to->data.sValue = NULL;
	}
}

static int cell_get(const TabCell *from, const TabColumn *col, TabValue *to, CmpackType type)
{
	char buf[128], *endptr;

	if (from->assigned && check_value(&from->data, col)) {
		switch (col->dtype)
		{
		case CMPACK_TYPE_STR:
			switch (type)
			{
			case CMPACK_TYPE_STR:
				to->sValue = cmpack_strdup(from->data.sValue);
				return 1;
			case CMPACK_TYPE_INT:
				to->iValue = strtol(from->data.sValue, &endptr, 10);
				return (endptr!=from->data.sValue);
			case CMPACK_TYPE_DBL:
				to->dValue = strtod(from->data.sValue, &endptr);
				return (endptr!=from->data.sValue);
			default:
				break;
			}
			break;

		case CMPACK_TYPE_INT:
			switch (type)
			{
			case CMPACK_TYPE_STR:
				sprintf(buf, "%d", from->data.iValue);
				to->sValue = cmpack_strdup(buf);
				return 1;
			case CMPACK_TYPE_INT:
				to->iValue = from->data.iValue;
				return 1;
			case CMPACK_TYPE_DBL:
				to->dValue = from->data.iValue;
				return 1;
			default:
				break;
			}
			break;

		case CMPACK_TYPE_DBL:
			switch (type)
			{
			case CMPACK_TYPE_STR:
				sprintf(buf, "%.*f", col->prec, from->data.dValue);
				to->sValue = cmpack_strdup(buf);
				return 1;
			case CMPACK_TYPE_INT:
				if (from->data.dValue>=INT_MIN && from->data.dValue<=INT_MAX) {
					to->iValue = (int)from->data.dValue;
					return 1;
				}
				break;
			case CMPACK_TYPE_DBL:
				to->dValue = from->data.dValue;
				return 1;
			default:
				break;
			}
			break;

		default:
			break;
		}
	}
	return 0;
}

static void cell_copy(TabCell *dst, TabColumn *dst_col, const TabCell *src, const TabColumn *src_col)
{
	if (src->assigned)
		cell_set(dst, dst_col, &src->data, src_col->dtype);
	else
		cell_unset(dst, dst_col);
}

/* Is value valid? */
static int check_value(const TabValue *val, const TabColumn *col)
{
	switch (col->dtype) 
	{
	case CMPACK_TYPE_STR:
		return val->sValue!=NULL;
	case CMPACK_TYPE_INT:
		return (val->iValue >= col->iMin && val->iValue <= col->iMax);
	case CMPACK_TYPE_DBL:
		return (val->dValue >= col->dMin && val->dValue <= col->dMax);
	default:
		break;
	}
	return 0;
}

static TabRecord *record_new(int ndata)
{
	TabRecord *ptr = (TabRecord*)cmpack_calloc(1, sizeof(TabRecord));
	ptr->ndata = ndata;
	ptr->data = (TabCell*)cmpack_calloc(ndata, sizeof(TabCell));
	return ptr;
}

static void record_resize(TabRecord *rec, int size)
{
	if (size > rec->ndata) {
		rec->data = (TabCell*)cmpack_realloc(rec->data, size*sizeof(TabCell));
		memset(rec->data+rec->ndata, 0, (size-rec->ndata)*sizeof(TabCell));
		rec->ndata = size;
	}
}

static void record_free(CmpackTable *t, TabRecord *rec)
{
	int i;

	if (rec) {
		for (i=0; i<rec->ndata; i++) {
			if (t->cols.list[i].dtype==CMPACK_TYPE_STR)
				cmpack_free(rec->data[i].data.sValue);
		}
		cmpack_free(rec->data);
		cmpack_free(rec);
	}
}

/* Returns number of rows */
int cmpack_tab_nrows(CmpackTable *tab)
{
	int count;
	TabRecord *ptr;

	count = 0;
	for (ptr=tab->first; ptr!=NULL; ptr=ptr->next)
		count++;
	return count;
}

/* Go to the first frame */
int cmpack_tab_rewind(CmpackTable *tab)
{
	tab->current = tab->first;
	return (tab->current!=NULL ? CMPACK_ERR_OK : CMPACK_ERR_OUT_OF_RANGE);
}

/* Go to the next record */
int cmpack_tab_next(CmpackTable *tab)
{
	if (tab->current)
		tab->current = tab->current->next;
	return (tab->current!=NULL ? CMPACK_ERR_OK : CMPACK_ERR_OUT_OF_RANGE);
}

/* Go to record by index */
int cmpack_tab_setpos(CmpackTable *tab, int row)
{
	TabRecord *ptr;

	ptr = tab->first;
	while (ptr!=NULL && row>0) {
		ptr=ptr->next;
		row--;
	}
	tab->current = ptr;
	return (tab->current!=NULL ? CMPACK_ERR_OK : CMPACK_ERR_OUT_OF_RANGE);
}

/* Returns true if the pointer is at the end of table */
int cmpack_tab_eof(CmpackTable *tab)
{
	return tab->current==NULL;
}

/* Append empty rows to the end of a table */
void cmpack_tab_append(CmpackTable *tab)
{
	TabRecord *ptr = record_new(tab->cols.count);
	ptr->prev = tab->last;
	if (tab->last) 
		tab->last->next = ptr;
	else
		tab->first = ptr;
	tab->last = ptr;
	tab->current = ptr;
}

/* Delete a row */
int cmpack_tab_delete(CmpackTable *tab)
{
	int i;
	TabRecord *ptr;

	if (!tab->current)
		return CMPACK_ERR_OUT_OF_RANGE;
	ptr = tab->current;
	if (ptr->prev) 
		ptr->prev->next = ptr->next;
	else
		tab->first = ptr->next;
	if (ptr->next) {
		ptr->next->prev = ptr->prev;
		tab->current = ptr->next;
	} else {
		tab->last = ptr->prev;
		tab->current = NULL;
	}
	for (i=0; i<tab->cols.count; i++) {
		if (ptr->data[i].assigned)
			tab->cols.list[i].needs_update = 1;
	}
	record_free(tab, ptr);
	return CMPACK_ERR_OK;
}

/* Write table data */
void cmpack_tab_ptds(CmpackTable *tab, int index, const char *value)
{
	TabValue val;

	if (tab->current && index>=0 && index<tab->cols.count) {
		TabColumn *col = &tab->cols.list[index];
		if (index>=tab->current->ndata)
			record_resize(tab->current, index+1);
		if (index < tab->current->ndata) {
			TabCell *dst = &tab->current->data[index];
			if (value) {
				val.sValue = (char*)value;
				cell_set(dst, col, &val, CMPACK_TYPE_STR);
			} else {
				cell_unset(dst, col);
			}
			col->needs_update = 1;
		}
	}
}

/* Write table data */
void cmpack_tab_ptdi(CmpackTable *tab, int index, int value)
{
	TabValue val;
	
	if (tab->current && index>=0 && index<tab->cols.count) {
		TabColumn *col = &tab->cols.list[index];
		if (index>=tab->current->ndata)
			record_resize(tab->current, index+1);
		if (index < tab->current->ndata) {
			TabCell *dst = &tab->current->data[index];
			if (value >= col->iMin && value <= col->iMax) {
				val.iValue = value;
				cell_set(dst, col, &val, CMPACK_TYPE_INT);
			} else {
				cell_unset(dst, col);
			}
			col->needs_update = 1;
		}
	}
}

/* Write table data */
void cmpack_tab_ptdd(CmpackTable *tab, int index, double value)
{
	TabValue val;

	if (tab->current && index>=0 && index<tab->cols.count) {
		TabColumn *col = &tab->cols.list[index];
		if (index>=tab->current->ndata)
			record_resize(tab->current, index+1);
		if (index<tab->current->ndata) {
			TabCell *dst = &tab->current->data[index];
			if (value >= col->dMin && value <= col->dMax) {
				val.dValue = value;
				cell_set(dst, col, &val, CMPACK_TYPE_DBL);
			} else {
				cell_unset(dst, col);
			}
			col->needs_update = 1;
		}
	}
}

/* Read table data */
int cmpack_tab_gtds(CmpackTable *tab, int index, char **value)
{
	TabValue val;

	if (tab->current && index>=0 && index<tab->cols.count) {
		TabColumn *col = &tab->cols.list[index];
		if (index < tab->current->ndata) {
			TabCell *src = &tab->current->data[index];
			if (cell_get(src, col, &val, CMPACK_TYPE_STR)) {
				if (value)
					*value = val.sValue;
				else
					cmpack_free(val.sValue);
				return CMPACK_ERR_OK;
			}
		}
		return CMPACK_ERR_UNDEF_VALUE;
	}
	return CMPACK_ERR_OUT_OF_RANGE;
}

/* Read table data */
int cmpack_tab_gtdi(CmpackTable *tab, int index, int *value)
{
	TabValue val;

	if (tab->current && index>=0 && index<tab->cols.count) {
		TabColumn *col = &tab->cols.list[index];
		if (index < tab->current->ndata) {
			TabCell *src = &tab->current->data[index];
			if (cell_get(src, col, &val, CMPACK_TYPE_INT)) {
				if (value)
					*value = val.iValue;
				return CMPACK_ERR_OK;
			}
		}
		return CMPACK_ERR_UNDEF_VALUE;
	}
	return CMPACK_ERR_OUT_OF_RANGE;
}

/* Read table data */
int cmpack_tab_gtdd(CmpackTable *tab, int index, double *value)
{
	TabValue val;

	if (tab->current && index>=0 && index<tab->cols.count) {
		TabColumn *col = &tab->cols.list[index];
		if (index < tab->current->ndata) {
			TabCell *src = &tab->current->data[index];
			if (cell_get(src, col, &val, CMPACK_TYPE_DBL)) {
				if (value)
					*value = val.dValue;
				return CMPACK_ERR_OK;
			}
		}
		return CMPACK_ERR_UNDEF_VALUE;
	}
	return CMPACK_ERR_OUT_OF_RANGE;
}

/* Find row by value */
int cmpack_tab_ftdi(CmpackTable *tab, int index, int value)
{
	TabValue val;

	if (index>=0 && index<tab->cols.count) {
		while (tab->current) {
			TabCell *src = &tab->current->data[index];
			TabColumn *col = &tab->cols.list[index];
			if (cell_get(src, col, &val, CMPACK_TYPE_INT) && val.iValue==value) 
				return 1;
			tab->current = tab->current->next;
		}
	}
	return 0;
}
