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

CmpackCatFile.c (C-Munipack project)
Photometry file read/write routines
Copyright (C) 2003 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 "comfun.h"
#include "catfile.h"
#include "xmldom.h"
#include "wcsobj.h"
#include "cmpack_catfile.h"
#include "console.h"

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

#define BUFFSIZE 			8192
#define ALLOC_BY			64

static const char *TypePrefix[CMPACK_SELECT_COUNT] = {
	NULL, "var", "comp", "chk"
};

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

static int file_close(CmpackCatFile *file);
static void stars_load(CmpackObjTab *tab, CmpackElement *node);
static void stars_write(CmpackObjTab *tab, FILE *to);
static void stars_clear(CmpackObjTab *tab);
static void stars_copy(CmpackObjTab *dsttab, const CmpackObjTab *srcttab);
static int stars_add(CmpackObjTab *tab, unsigned mask, const CmpackCatObject *obj);
static int stars_find(CmpackObjTab *tab, int obj_id);
static void select_clear(CmpackSelection *tab);
static void select_copy(CmpackSelection *dsttab, const CmpackSelection *srcttab);
static void selection_load(CmpackSelectionList *list, CmpackElement *node);
static void selection_write(CmpackSelectionSet *set, FILE *to);
static void tags_load(CmpackTagList *tab, CmpackElement *node);
static void tags_write(CmpackTagList *tab, FILE *to);
static void tags_clear(CmpackTagList *tab);
static void tags_copy(CmpackTagList *dsttab, const CmpackTagList *srcttab);

/* Alocates memory for photometry file and clears all fields. */
static CmpackCatFile *file_create(void)
{
	CmpackCatFile *f = (CmpackCatFile*)cmpack_calloc(1, sizeof(CmpackCatFile));
	f->refcnt = 1;
	header_init(&f->head);
	return f;
}

static void file_clear(CmpackCatFile *f)
{
	header_clear(&f->head);
	stars_clear(&f->stars);
	select_clear(&f->sel);
	tags_clear(&f->tags);
	if (f->wcs) {
		cmpack_wcs_destroy(f->wcs);
		f->wcs = NULL;
	}
}

static int file_load(CmpackCatFile *f, FILE *from)
{
	CmpackElement *root, *head, *selection, *stars, *taglist, *wcsdata;

	CmpackDocument *doc = cmpack_xml_doc_from_file(from);
	if (!doc)
		return CMPACK_ERR_UNKNOWN_FORMAT;

	root = cmpack_xml_doc_get_root(doc);
	if (!root || strcmp(root->node.nodeName, "cat_file")!=0) {
		cmpack_xml_doc_free(doc);
		return CMPACK_ERR_UNKNOWN_FORMAT;
	}

	head = cmpack_xml_element_first_element(root, "info");
	if (head) 
		header_load_xml(&f->head, head);

	stars = cmpack_xml_element_first_element(root, "stars");
	if (stars) 
		stars_load(&f->stars, stars);

	selection = cmpack_xml_element_first_element(root, "selection");
	while (selection) {
		CmpackSelectionSet *set = (CmpackSelectionSet*)cmpack_calloc(1, sizeof(CmpackSelectionSet));
		const char *name = cmpack_xml_attr_s(selection, "name", NULL);
		if (name && name[0]!='\0')
			set->name = cmpack_strdup(name);
		selection_load(&set->list, selection);
		if (!f->sel.last)
			f->sel.first = set;
		else
			f->sel.last->next = set;
		f->sel.last = set;
		selection = cmpack_xml_element_next_element(selection);
	}
	
	taglist = cmpack_xml_element_first_element(root, "taglist");
	if (taglist) 
		tags_load(&f->tags, taglist);

	wcsdata = cmpack_xml_element_first_element(root, "wcsdata");
	if (wcsdata) 
		f->wcs = cmpack_wcs_new_from_XML_node(wcsdata);

	cmpack_xml_doc_free(doc);
	return CMPACK_ERR_OK;
}

static int file_save(CmpackCatFile *f, FILE *to)
{
	CmpackSelectionSet *set;

	/* Prolog */
	fprintf(to, "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
	fprintf(to, "<cat_file>\n");
	/* Write header */
	if (f->head.count>0) {
		fprintf(to, "<info>\n");
		header_write_xml(&f->head, to);
		fprintf(to, "</info>\n");
	}
	/* Write selection */
	set = f->sel.first;
	while (set) {
		selection_write(set, to);
		set = set->next;
	}
	/* Write tags */
	tags_write(&f->tags, to);
	/* Write stars */
	stars_write(&f->stars, to);
	/* Write WCS data */
	if (f->wcs) {
		fprintf(to, "<wcsdata>\n");
		cmpack_wcs_write_XML(f->wcs, to);
		fprintf(to, "</wcsdata>\n");
	}
	/* Epilog */
	fprintf(to, "</cat_file>\n");
	return 0;
}

static int file_close(CmpackCatFile *file)
{
	int res = CMPACK_ERR_OK;

	if (file->f && !file->readonly && file->changed) {
		rewind(file->f);
		res = file_save(file, file->f);
	}
	if (res==0) {
		if (file->f) {
			fclose(file->f);
			file->f = NULL;
		}
		file->readonly = 1;
	}
	return res;
}

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

/* Creates a memory-only catalog file context */
CmpackCatFile *cmpack_cat_new(void)
{
	return file_create();
}

/* Increment the reference counter */
CmpackCatFile *cmpack_cat_reference(CmpackCatFile *file)
{
	file->refcnt++;
	return file;
}

/* Decrement reference counter / detroy the instance */
void cmpack_cat_destroy(CmpackCatFile *file)
{
	if (file) {
		file->refcnt--;
		if (file->refcnt==0) {
			if (file->f) 
				file_close(file);
			file_clear(file);
			cmpack_free(file);
		}
	}
}

/* Load catalog file */
int cmpack_cat_open(CmpackCatFile **pfile, const char *filename, CmpackOpenMode mode, unsigned flags)
{
	int res = 0;
	char line[MAXLINE];
	FILE *f = NULL;
	CmpackCatFile *file;

	*pfile = NULL;
	if (mode == CMPACK_OPEN_READWRITE) 
		f = fopen(filename, "r+");
	else if (mode == CMPACK_OPEN_CREATE) 
		f = fopen(filename, "w+");
	else
		f = fopen(filename, "r");
	if (!f) 
		return CMPACK_ERR_OPEN_ERROR;

	file = file_create();
	if (mode != CMPACK_OPEN_CREATE) {
		/* Autodetect format */
		size_t bytes = fread(line, 1, MAXLINE, f); 
		fseek(f, 0, SEEK_SET);
		if (memstr(line, "<?xml", bytes) && memstr(line, "<cat_file", bytes)) {
			/* XML catalogue file */
			res = file_load(file, f);
		} else {
			/* Unknown format */
			res = CMPACK_ERR_UNKNOWN_FORMAT;
		}
		if (res!=0) {
			/* Failed to load the file */
			fclose(f);
			cmpack_cat_destroy(file);
			return res;
		}
	}
		
	/* Normal return */
	file->f = f;
	file->changed = (mode == CMPACK_OPEN_CREATE);
	file->readonly = (mode == CMPACK_OPEN_READONLY);
	file->flags = flags;
	*pfile = file;
	return CMPACK_ERR_OK;
}

/* Decrement reference counter / detroy the instance */
int cmpack_cat_close(CmpackCatFile *file)
{
	int res = file_close(file);
	if (res==0)
		cmpack_cat_destroy(file);
	return res;
}

/* Makes copy of the file */
void cmpack_cat_clear(CmpackCatFile *f)
{
	if (!f->readonly) {
		file_clear(f);
		f->changed = 1;
	}
}

/* Makes copy of the file */
int cmpack_cat_copy(CmpackCatFile *fd, const CmpackCatFile *fs)
{
	if (fd->readonly)
		return CMPACK_ERR_READ_ONLY;

	cmpack_cat_clear(fd);
	header_copy(&fd->head, &fs->head);
	stars_copy(&fd->stars, &fs->stars);
	select_copy(&fd->sel, &fs->sel);
	tags_copy(&fd->tags, &fs->tags);
	if (fs->wcs)
		fd->wcs = cmpack_wcs_copy(fs->wcs);
	fd->changed = 1;
	return CMPACK_ERR_OK;
}

/* Make catalog file from a photometry file */
int cmpack_cat_make(CmpackCatFile *fd, CmpackPhtFile *fs, int aperture)
{
	int i, nstar;
	CmpackPhtInfo info;
	CmpackPhtObject star;
	CmpackPhtData mag;
	CmpackCatObject obj;
	CmpackWcs *wcs;

	if (!fs || !fd || fd->readonly)
		return CMPACK_ERR_INVALID_PAR;
	
	cmpack_cat_clear(fd);

	/* Frame parameters */
	cmpack_pht_get_info(fs, CMPACK_PI_FRAME_PARAMS, &info);
	header_pkyf(&fd->head, "jd", info.jd, JD_PRECISION, NULL);
	header_pkyf(&fd->head, "exptime", info.exptime, EXP_PRECISION, NULL);
	header_pkys(&fd->head, "filter", info.filter, NULL);

	/* WCS data */
	if (cmpack_pht_get_wcs(fs, &wcs)==0) 
		fd->wcs = cmpack_wcs_copy(wcs);

	/* For all stars */
	nstar = cmpack_pht_object_count(fs);
	fd->stars.nx = info.width;
	fd->stars.ny = info.height;
	fd->stars.capacity = nstar;
	fd->stars.list = (CmpackCatObject*)cmpack_malloc(fd->stars.capacity*sizeof(CmpackCatObject));
	for (i=0; i<nstar; i++) {
		cmpack_pht_get_object(fs, i, CMPACK_PO_REF_ID | CMPACK_PO_CENTER, &star);
		if (star.ref_id>=0) {
			unsigned mask = CMPACK_OM_ID | CMPACK_OM_CENTER | CMPACK_OM_MAGNITUDE;
			obj.id = star.ref_id;
			obj.center_x = star.x;
			obj.center_y = star.y;
			if (cmpack_pht_get_data(fs, i, aperture, &mag)==0 && mag.mag_valid) {
				obj.refmag_valid = 1;
				obj.refmagnitude = mag.magnitude;
			} else {
				obj.refmag_valid = 0;
				obj.refmagnitude = INVALID_MAG;
			}
			stars_add(&fd->stars, mask, &obj);
		}
	}
	fd->changed = 1;
	return 0;
}

/* Test if the file is a photometry file */
int cmpack_cat_test(const char *filename)
{
	int bytes, filesize;
	char buffer[2048];

	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_cat_test_buffer(buffer, bytes, filesize);
	}
	return 0;
}

/* Test if the file is a catalogue file */
int cmpack_cat_test_buffer(const char *buffer, int buflen, int filesize)
{
	return memstr(buffer, "<?xml", buflen) && memstr(buffer, "<cat_file", buflen);
}

/********************   Header manipulation    ***********************/

/* Sets value and comment of specified key */
void cmpack_cat_pkys(CmpackCatFile *f, const char *key, const char *val, const char *com)
{
	if (!f->readonly) {
		header_pkys(&f->head, key, val, com);
		f->changed = 1;
	}
}

/* Sets value and comment of specified key */
void cmpack_cat_pkyi(CmpackCatFile *f, const char *key, int val, const char *com)
{
	if (!f->readonly) {
		header_pkyi(&f->head, key, val, com);
		f->changed = 1;
	}
}

/* Sets value and comment of specified key */
void cmpack_cat_pkyd(CmpackCatFile *f, const char *key, double val, int prec, const char *com)
{
	if (!f->readonly) {
		header_pkyf(&f->head, key, val, prec, com);
		f->changed = 1;
	}
}

/* Gets value and comment of specified key */
const char *cmpack_cat_gkys(CmpackCatFile *f, const char *key)
{
	return header_gkys(&f->head, key);
}

/* Gets value and comment of specified key */
int cmpack_cat_gkyi(CmpackCatFile *f, const char *key, int *value)
{
	return header_gkyi(&f->head, key, value);
}

/* Gets value and comment of specified key */
int cmpack_cat_gkyd(CmpackCatFile *f, const char *key, double *value)
{
	return header_gkyd(&f->head, key, value);
}

/* Returns number of keys in active section */
int cmpack_cat_nkey(CmpackCatFile *f)
{
	return f->head.count;
}

/* Get key, value and comment triplet by its index. The first record has index 0. */
int cmpack_cat_gkyn(CmpackCatFile *f, int index, const char **key, const char **val, const char **com)
{
	return header_gkyn(&f->head, index, key, val, com);
}

/* Deletes specified key */
void cmpack_cat_dkey(CmpackCatFile *f, const char *key)
{
	if (!f->readonly) {
		header_dkey(&f->head, key);
		f->changed = 1;
	}
}

/********************   Table of stars   ***********************/

static int stars_add(CmpackObjTab *tab, unsigned mask, const CmpackCatObject *obj)
{
	int index;

	if (tab->count>=tab->capacity) {
		tab->capacity += ALLOC_BY;
		tab->list = (CmpackCatObject*)cmpack_realloc(tab->list, tab->capacity*sizeof(CmpackCatObject));
	}
	index = tab->count++;
	tab->list[index].id = obj->id;
	if (mask & CMPACK_OM_MAGNITUDE) {
		tab->list[index].refmag_valid = obj->refmag_valid;
		tab->list[index].refmagnitude = obj->refmagnitude;
	}
	if (mask & CMPACK_OM_CENTER) {
		tab->list[index].center_x = obj->center_x;
		tab->list[index].center_y = obj->center_y;
	}
	return index;
}

static int stars_find(CmpackObjTab *tab, int star_id)
{
	int i;

	for (i=0; i<tab->count; i++) {
		if (tab->list[i].id == star_id)
			return i;
	}
	return -1;
}

static void stars_load(CmpackObjTab *tab, CmpackElement *node)
{
	CmpackCatObject obj;
	CmpackElement *star;

	tab->nx = cmpack_xml_attr_i(node, "width", 0);
	tab->ny = cmpack_xml_attr_i(node, "height", 0);
	star = cmpack_xml_element_first_element(node, "s");
	while (star) {
		obj.id = cmpack_xml_attr_i(star, "id", -1);
		if (obj.id>=0) {
			obj.center_x = cmpack_xml_attr_d(star, "x", 0);
			obj.center_y = cmpack_xml_attr_d(star, "y", 0);
			obj.refmag_valid = cmpack_xml_element_has_attribute(star, "m");
			obj.refmagnitude = cmpack_xml_attr_d(star, "m", 0);
			stars_add(tab, CMPACK_OM_CENTER | CMPACK_OM_MAGNITUDE, &obj);
		}
		star = cmpack_xml_element_next_element(star);
	}
}

/* Write all stars to file */
static void stars_write(CmpackObjTab *tab, FILE *to)
{
	int i;
	
	if (tab->count>0) {
		fprintf(to, "<stars width=\"%d\" height=\"%d\">\n", tab->nx, tab->ny);
		for (i=0; i<tab->count; i++) {
			CmpackCatObject *star = &tab->list[i];
			fprintf(to, "<s id=\"%d\"", star->id);
			fprintf(to, " x=\"%.*f\" y=\"%.*f\"", POS_PRECISION, star->center_x, 
				POS_PRECISION, star->center_y);
			if (star->refmag_valid)
				fprintf(to, " m=\"%.*f\"", MAG_PRECISION, star->refmagnitude);
			fprintf(to, "/>\n");
		}
		fprintf(to, "</stars>\n");
	}
}

/* Make copy of a table of stars */
static void stars_copy(CmpackObjTab *dsttab, const CmpackObjTab *srctab)
{
	stars_clear(dsttab);
	dsttab->nx = srctab->nx;
	dsttab->ny = srctab->ny;
	if (srctab->list && srctab->count>0) {
		dsttab->count = dsttab->capacity = srctab->count;
		dsttab->list = (CmpackCatObject*)cmpack_malloc(dsttab->capacity*sizeof(CmpackCatObject));
		memcpy(dsttab->list, srctab->list, dsttab->count*sizeof(CmpackCatObject));
	}
}

/* Clear table of stars */
static void stars_clear(CmpackObjTab *tab)
{
	tab->nx = tab->ny = 0;
	cmpack_free(tab->list);
	tab->list = NULL;
	tab->count = tab->capacity = 0;
}

/* Returns number of stars defined */
int cmpack_cat_nstar(CmpackCatFile *f)
{
	return f->stars.count;
}

/* Get chart size */
void cmpack_cat_set_size(CmpackCatFile *f, int width, int height)
{
	if (!f->readonly) {
		f->stars.nx = width;
		f->stars.ny = height;
		f->changed = 1;
	}
}

/* Get chart size */
int cmpack_cat_get_width(CmpackCatFile *f)
{
	return f->stars.nx;
}

/* Get chart size */
int cmpack_cat_get_height(CmpackCatFile *f)
{
	return f->stars.ny;
}

/* Find section by name */
int cmpack_cat_find_star(CmpackCatFile *f, int star_id)
{
	return stars_find(&f->stars, star_id);
}

/* Append new star */
int cmpack_cat_add_star(CmpackCatFile *f, unsigned mask, const CmpackCatObject *obj)
{
	if (!f->readonly && obj->id>=0 && stars_find(&f->stars, obj->id)<0) {
		int index = stars_add(&f->stars, mask, obj);
		if (index>=0)
			f->changed = 1;
		return index;
	}
	return -1;
}

/* Set magnitude for active star and active aperture */
int cmpack_cat_get_star(CmpackCatFile *f, int index, unsigned mask, CmpackCatObject *obj)
{
	if (index<0 || index>=f->stars.count)
		return CMPACK_ERR_OUT_OF_RANGE;

	if (mask & CMPACK_OM_ID)
		obj->id = f->stars.list[index].id;
	if (mask & CMPACK_OM_MAGNITUDE) {
		obj->refmag_valid = f->stars.list[index].refmag_valid;
		obj->refmagnitude = f->stars.list[index].refmagnitude;
	}
	if (mask & CMPACK_OM_CENTER) {
		obj->center_x = f->stars.list[index].center_x;
		obj->center_y = f->stars.list[index].center_y;
	}
	return CMPACK_ERR_OK;
}

/* Set magnitude for active star and active aperture */
int cmpack_cat_set_star(CmpackCatFile *f, int index, unsigned mask, const CmpackCatObject *obj)
{
	if (f->readonly)
		return CMPACK_ERR_READ_ONLY;
	if (index<0 || index>=f->stars.count)
		return CMPACK_ERR_OUT_OF_RANGE;

	if (mask & CMPACK_OM_ID)
		f->stars.list[index].id = obj->id;
	if (mask & CMPACK_OM_MAGNITUDE) {
		f->stars.list[index].refmag_valid = obj->refmag_valid;
		f->stars.list[index].refmagnitude = obj->refmagnitude;
	}
	if (mask & CMPACK_OM_CENTER) {
		f->stars.list[index].center_x = obj->center_x;
		f->stars.list[index].center_y = obj->center_y;
	}
	return CMPACK_ERR_OK;
}

/********************   Selection   ***********************/

/* Add an object to the selection list */
static void selection_add(CmpackSelectionList *list, int select_id, CmpackSelectionType type)
{
	CmpackSelectionItem *item = (CmpackSelectionItem*)cmpack_calloc(1, sizeof(CmpackSelectionItem));
	item->star_id = select_id;
	item->type = type;
	if (!list->last)
		list->first = item;
	else 
		list->last->next = item;
	list->last = item;
}

/* Clear table of stars */
static void selection_clear(CmpackSelectionList *list)
{
	CmpackSelectionItem *ptr = list->first;
	while (ptr) {
		CmpackSelectionItem *next = ptr->next;
		cmpack_free(ptr);
		ptr = next;
	}
	list->first = list->last = NULL;
}

/* Clear table of stars */
static void select_clear(CmpackSelection *sel)
{
	CmpackSelectionSet *ptr = sel->first;
	while (ptr) {
		CmpackSelectionSet *next = ptr->next;
		selection_clear(&ptr->list);
		cmpack_free(ptr->name);
		cmpack_free(ptr);
		ptr = next;
	}
	sel->first = sel->last = sel->current = NULL;
}

/* Make copy of a table of stars */
static void selection_copy(CmpackSelectionList *dsttab, const CmpackSelectionList *srctab)
{
	CmpackSelectionItem *ptr;

	selection_clear(dsttab);

	ptr = srctab->first;
	while (ptr) {
		CmpackSelectionItem *item = (CmpackSelectionItem*)cmpack_calloc(1, sizeof(CmpackSelectionItem));
		item->star_id = ptr->star_id;
		item->type = ptr->type;
		if (!dsttab->last)
			dsttab->first = item;
		else
			dsttab->last->next = item;
		dsttab->last = item;
		ptr = ptr->next;
	}
}

/* Make copy of a table of stars */
static void select_copy(CmpackSelection *dsttab, const CmpackSelection *srctab)
{
	CmpackSelectionSet *ptr;

	select_clear(dsttab);

	ptr = srctab->first;
	while (ptr) {
		CmpackSelectionSet *item = (CmpackSelectionSet*)cmpack_calloc(1, sizeof(CmpackSelectionSet));
		selection_copy(&item->list, &ptr->list);
		if (ptr->name)
			item->name = cmpack_strdup(ptr->name);
		if (!dsttab->last)
			dsttab->first = item;
		else
			dsttab->last->next = item;
		dsttab->last = item;
		ptr = ptr->next;
	}
}

/* Load tags from XML file */
static void selection_load(CmpackSelectionList *tab, CmpackElement *node)
{
	int i, id;
	CmpackElement *select;
	const char *label;

	select = cmpack_xml_element_first_element(node, "select");
	while (select) {
		id = cmpack_xml_attr_i(select, "id", -1);
		label = cmpack_xml_attr_s(select, "label", NULL);
		if (!label)
			label = cmpack_xml_attr_s(select, "value", NULL);
		if (id>=0 && label) {
			for (i=1; i<CMPACK_SELECT_COUNT; i++) {
				if (strstr(label, TypePrefix[i])==label) {
					selection_add(tab, id, (CmpackSelectionType)i);
					break;
				}
			}
		}
		select = cmpack_xml_element_next_element(select);
	}
}

/* Save star to file */
static void selection_write(CmpackSelectionSet *tab, FILE *to)
{	
	int index[CMPACK_SELECT_COUNT];

	memset(index, 0, CMPACK_SELECT_COUNT*sizeof(int));

	fprintf(to, "<selection");
	if (tab->name && tab->name[0]!='\0')
		fprintf(to, " name=\"%s\"", tab->name);
	if (tab->list.first) {
		CmpackSelectionItem *ptr = tab->list.first;
		fprintf(to, ">\n");
		while (ptr) {
			if (ptr->star_id>=0 && ptr->type!=CMPACK_SELECT_NONE) {
				int j = ptr->type;
				if (j>=0 && j<CMPACK_SELECT_COUNT) {
					char label[32];
					if (index[j]>0)
						sprintf(label, "%s%d", TypePrefix[j], index[j]+1);
					else
						sprintf(label, "%s", TypePrefix[j]);
					fprintf(to, "<select id=\"%d\" label=\"%s\"/>\n", ptr->star_id, label);
					index[j]++;
				}
			}
			ptr = ptr->next;
		}
		fprintf(to, "</selection>\n");
	} else {
		/* An empty tag */
		fprintf(to, "/>\n");
	}
}

/* Set selection type of all stars to CMPACK_SELECT_NONE */
int cmpack_cat_clear_selection(CmpackCatFile *f)
{
	if (f->readonly)
		return CMPACK_ERR_READ_ONLY;
	if (!f->sel.current)
		return CMPACK_ERR_INVALID_CONTEXT;

	selection_clear(&f->sel.current->list);
	f->changed = 1;
	return CMPACK_ERR_OK;
}

/* Set selection type by star identifier */
int cmpack_cat_update_selection(CmpackCatFile *f, int star_id, CmpackSelectionType type)
{
	if (f->readonly)
		return CMPACK_ERR_READ_ONLY;
	if (!f->sel.current)
		return CMPACK_ERR_INVALID_CONTEXT;

	if (type!=CMPACK_SELECT_NONE) {
		CmpackSelectionItem *ptr = f->sel.current->list.first;
		while (ptr) {
			if (ptr->star_id == star_id) {
				if (ptr->type!=type) {
					/* Update existing record */
					ptr->type = type;
					f->changed = 1;
				}
				break;
			}
			ptr = ptr->next;
		}
		if (!ptr) {
			/* Append a new record */
			selection_add(&f->sel.current->list, star_id, type);
			f->changed = 1;
		}
	} else {
		CmpackSelectionItem *ptr = f->sel.current->list.first, *prev = NULL;
		while (ptr) {
			if (ptr->star_id == star_id) {
				/* Remove the record */
				if (prev) 
					prev->next = ptr->next;
				else 
					f->sel.current->list.first = ptr->next;
				if (f->sel.current->list.last == ptr)
					f->sel.current->list.last = prev;
				cmpack_free(ptr);
				break;
			}
			prev = ptr;
			ptr = ptr->next;
		}
	}
	return CMPACK_ERR_OK;
}

/* Get number of selected objects */
int cmpack_cat_get_selection_count(CmpackCatFile *file)
{
	if (file->sel.current) {
		int count = 0;
		CmpackSelectionItem *ptr = file->sel.current->list.first;
		while (ptr) {
			count++;
			ptr = ptr->next;
		}
		return count;
	}
	return 0;
}

/* Get selection record by index */
int cmpack_cat_get_selection(CmpackCatFile *f, int rec_index, int *star_id, CmpackSelectionType *type)
{
	int i;
	CmpackSelectionItem *ptr;

	if (!f->sel.current)
		return CMPACK_ERR_INVALID_CONTEXT;


	i = 0;
	ptr = f->sel.current->list.first;
	while (ptr && i<rec_index) {
		ptr = ptr->next;
		i++;
	}
	if (!ptr) 
		return CMPACK_ERR_OUT_OF_RANGE;

	if (star_id)
		*star_id = ptr->star_id;
	if (type)
		*type = ptr->type;
	return 0;
}

/* Find selection record by object identifier */
int cmpack_cat_find_selection(CmpackCatFile *f, int star_id)
{
	if (f->sel.current) {
		int i = 0;
		CmpackSelectionItem *ptr = f->sel.current->list.first;
		while (ptr) {
			if (ptr->star_id == star_id)
				return i;
			ptr = ptr->next;
			i++;
		}
	}
	return -1;
}

/* Get name of the current selection set */
const char *cmpack_cat_get_selection_set_name(CmpackCatFile *f)
{
	if (f->sel.current) 
		return f->sel.current->name;
	return NULL;
}

int cmpack_cat_set_selection_set_name(CmpackCatFile *f, const char *name)
{
	if (f->readonly)
		return CMPACK_ERR_READ_ONLY;

	if (!f->sel.current)
		return CMPACK_ERR_INVALID_CONTEXT;

	cmpack_free(f->sel.current->name);
	if (name)
		f->sel.current->name = cmpack_strdup(name);
	else
		f->sel.current->name = NULL;
	f->changed = 1;
	return CMPACK_ERR_OK;
}

/* Get number of selection sets */
int cmpack_cat_selection_set_count(CmpackCatFile *f)
{
	int count = 0;
	CmpackSelectionSet *ptr = f->sel.first;
	while (ptr) {
		count++;
		ptr = ptr->next;
	}
	return count;
}

/* Clear all selection sets */
int cmpack_cat_clear_all_selections(CmpackCatFile *f)
{
	if (f->readonly)
		return CMPACK_ERR_READ_ONLY;

	if (f->sel.first) {
		select_clear(&f->sel);
		f->sel.first = f->sel.last = f->sel.current = NULL;
		f->changed = 1;
	}
	return CMPACK_ERR_OK;
}

/* Create a new selection set */
int cmpack_cat_selection_set_new(CmpackCatFile *f, const char *name, int position)
{
	CmpackSelectionSet *set;

	if (f->readonly)
		return CMPACK_ERR_READ_ONLY;

	set = (CmpackSelectionSet*)cmpack_calloc(1, sizeof(CmpackSelectionSet));
	if (name && name[0]!='\0')
		set->name = cmpack_strdup(name);
	if (position<0 || !f->sel.last) {
		/* Append to the end of the table */
		if (!f->sel.last)
			f->sel.first = set;
		else
			f->sel.last->next = set;
		f->sel.last = set;
	} else {
		/* Insert to specified position */
		CmpackSelectionSet *ptr = f->sel.first, *prev = NULL;
		int index = 0;
		while (ptr && index<position) {
			index++;
			prev = ptr;
			ptr = ptr->next;
		}
		set->next = ptr;
		if (!ptr) {
			/* Insert as a last item */
			f->sel.last = set;
		}
		if (prev) {
			/* Insert after prev */
			prev->next = set;
		} else {
			/* Insert as a first item */
			f->sel.first = set;
		}
	}
	f->sel.current = set;
	f->changed = 1;
	return CMPACK_ERR_OK;
}

int cmpack_cat_selection_set_remove(CmpackCatFile *f, int index)
{
	int i = 0;
	CmpackSelectionSet *ptr = f->sel.first, *prev = NULL;

	if (f->readonly)
		return CMPACK_ERR_READ_ONLY;

	while (ptr) {
		if (i == index) {
			/* Remove the record */
			if (prev) 
				prev->next = ptr->next;
			else 
				f->sel.first = ptr->next;
			if (f->sel.last == ptr)
				f->sel.last = prev;
			cmpack_free(ptr->name);
			cmpack_free(ptr);
			f->changed = 1;
			return CMPACK_ERR_OK;
		}
		prev = ptr;
		ptr = ptr->next;
		i++;
	}
	return CMPACK_ERR_OUT_OF_RANGE;
}

int cmpack_cat_set_current_selection_set(CmpackCatFile *f, int index)
{
	int i = 0;
	CmpackSelectionSet *ptr = f->sel.first;
	while (ptr) {
		if (i == index) {
			f->sel.current = ptr;
			return CMPACK_ERR_OK;
		}
		ptr = ptr->next;
		i++;
	}
	return CMPACK_ERR_OUT_OF_RANGE;
}

/********************   Tags   ***********************/

/* Append tag to the table (makes copy of given buffer) */
static void tags_add(CmpackTagList *tab, int star_id, const char *value)
{
	CmpackTag *tag = (CmpackTag*)cmpack_calloc(1, sizeof(CmpackTag));
	tag->star_id = star_id;
	tag->tag = cmpack_strdup(value);
	if (!tab->last)
		tab->first = tag;
	else
		tab->last->next = tag;
	tab->last = tag;
}

/* Clear table of stars */
static void tags_clear(CmpackTagList *tab)
{
	CmpackTag *ptr = tab->first;
	while (ptr) {
		CmpackTag *next = ptr->next;
		cmpack_free(ptr->tag);
		cmpack_free(ptr);
		ptr = next;
	}
	tab->first = tab->last = NULL;
}

/* Make copy of a table of stars */
static void tags_copy(CmpackTagList *dsttab, const CmpackTagList *srctab)
{
	CmpackTag *ptr;

	tags_clear(dsttab);

	ptr = srctab->first;
	while (ptr) {
		CmpackTag *item = (CmpackTag*)cmpack_calloc(1, sizeof(CmpackTag));
		item->star_id = ptr->star_id;
		item->tag = cmpack_strdup(ptr->tag);
		if (!dsttab->last)
			dsttab->first = item;
		else
			dsttab->last->next = item;
		dsttab->last = item;
		ptr = ptr->next;
	}
}

/* Load tags from the XML file */
static void tags_load(CmpackTagList *tab, CmpackElement *node)
{
	int id;
	CmpackElement *tag;
	const char *value;

	tag = cmpack_xml_element_first_element(node, "tag");
	while (tag) {
		id = cmpack_xml_attr_i(tag, "id", -1);
		value = cmpack_xml_attr_s(tag, "value", NULL);
		if (!value)
			value = cmpack_xml_attr_s(tag, "label", NULL);
		if (id>=0 && value) 
			tags_add(tab, id, value);
		tag = cmpack_xml_element_next_element(tag);
	}
}

/* Save star to file */
static void tags_write(CmpackTagList *tab, FILE *to)
{	
	CmpackTag *ptr;

	if (tab->first) {
		fprintf(to, "<taglist>\n");
		ptr = tab->first;
		while (ptr) {
			if (ptr->star_id>=0 && ptr->tag) 
				fprintf(to, "<tag id=\"%d\" value=\"%s\"/>\n", ptr->star_id, ptr->tag);
			ptr = ptr->next;
		}
		fprintf(to, "</taglist>\n");
	}
}

/* Clear all tags */
void cmpack_cat_clear_tags(CmpackCatFile *f)
{
	if (!f->readonly && f->tags.first) {
		tags_clear(&f->tags);
		f->changed = 1;
	}
}

/* Set selection type by star identifier */
int cmpack_cat_update_tag(CmpackCatFile *f, int star_id, const char *value)
{
	if (f->readonly)
		return CMPACK_ERR_READ_ONLY;

	if (value) {
		CmpackTag *tag = f->tags.first;
		while (tag) {
			if (tag->star_id == star_id) {
				if (!tag->tag || strcmp(tag->tag, value)) {
					/* Update existing record */
					cmpack_free(tag->tag);
					tag->tag = cmpack_strdup(value);
					f->changed = 1;
				}
				break;
			}
			tag = tag->next;
		}
		if (!tag) {
			/* Append a new record */
			tags_add(&f->tags, star_id, value);
			f->changed = 1;
		}
	} else {
		/* Setting value to NULL removes the record */
		CmpackTag *tag = f->tags.first, *prev = NULL;
		while (tag) {
			if (tag->star_id == star_id) {
				if (prev) 
					prev->next = tag->next;
				else 
					f->tags.first = tag->next;
				if (f->tags.last == tag)
					f->tags.last = prev;
				cmpack_free(tag->tag);
				cmpack_free(tag);
				break;
			}
			prev = tag;
			tag = tag->next;
		}
	}
	return CMPACK_ERR_OK;
}

/* Remove a tag from an object */
int cmpack_cat_remove_tag(CmpackCatFile *f, int star_id)
{
	CmpackTag *tag, *prev = NULL;

	if (f->readonly)
		return CMPACK_ERR_READ_ONLY;

	tag = f->tags.first;
	while (tag) {
		if (tag->star_id == star_id) {
			if (prev) 
				prev->next = tag->next;
			else 
				f->tags.first = tag->next;
			if (f->tags.last == tag)
				f->tags.last = prev;
			cmpack_free(tag->tag);
			cmpack_free(tag);
			break;
		}
		tag = tag->next;
	}
	return CMPACK_ERR_OK;
}

/* Get number of selected objects */
int cmpack_cat_get_tag_count(CmpackCatFile *f)
{
	int count = 0;
	CmpackTag *ptr = f->tags.first;
	while (ptr) {
		count++;
		ptr = ptr->next;
	}
	return count;
}

/* Get selection record by index */
int cmpack_cat_get_tag(CmpackCatFile *f, int rec_index, int *star_id, const char **value)
{
	int i = 0;
	CmpackTag *ptr = f->tags.first;
	while (ptr) {
		if (i == rec_index) 
			break;
		i++;
		ptr = ptr->next;
	}
	if (!ptr) 
		return CMPACK_ERR_OUT_OF_RANGE;

	if (star_id)
		*star_id = ptr->star_id;
	if (value)
		*value = ptr->tag;
	return 0;
}

/* Find selection record by object identifier */
int cmpack_cat_find_tag(CmpackCatFile *f, int star_id)
{
	int index = 0;
	CmpackTag *ptr = f->tags.first;
	while (ptr) {
		if (ptr->star_id == star_id)
			return index;
		index++;
		ptr = ptr->next;
	}
	return -1;
}

/************************   WCS data   ****************************/

/* Get World Coordinate System (WCS) data */
int cmpack_cat_get_wcs(CmpackCatFile *fc, CmpackWcs **wcs)
{
	if (fc->wcs) {
		*wcs = fc->wcs;
		return CMPACK_ERR_OK;
	} else {
		*wcs = NULL;
		return CMPACK_ERR_UNDEF_VALUE;
	}
}

/* Set World Coordinate System (WCS) data */
int cmpack_cat_set_wcs(CmpackCatFile *fc, const CmpackWcs *wcs)
{
	if (fc->readonly)
		return CMPACK_ERR_READ_ONLY;
	if (fc->wcs) {
		cmpack_wcs_destroy(fc->wcs);
		fc->wcs = NULL;
	}
	if (wcs)
		fc->wcs = cmpack_wcs_copy(wcs);
	return CMPACK_ERR_OK;
}

/* Clear World Coordinate System (WCS) data */
void cmpack_cat_clear_wcs(CmpackCatFile *fc)
{
	if (!fc->readonly && fc->wcs) {
		cmpack_wcs_destroy(fc->wcs);
		fc->wcs = NULL;
	}
}

/************************   Miscellaneous   ****************************/

int cmpack_cat_dump(CmpackCatFile *fs, CmpackTable **ptable, CmpackConsole *con, unsigned flags)
{
	int		i, pos_column, id_column, mag_column;
	char	msg[MAXLINE];
	CmpackTable *table;

	*ptable = NULL;
	
	pos_column = id_column = mag_column = -1;

	/* Create table */
	table = cmpack_tab_init(CMPACK_TABLE_UNSPECIFIED);

	/* Columns */
	id_column = cmpack_tab_add_column_int(table, "OBJ_ID", 0, INT_MAX, -1);
	pos_column = cmpack_tab_add_column_dbl(table, "CENTER_X", POS_PRECISION, 0, INT_MAX, INVALID_XY);
	cmpack_tab_add_column_dbl(table, "CENTER_Y", POS_PRECISION, 0, INT_MAX, INVALID_XY);
	mag_column = cmpack_tab_add_column_dbl(table, "MAG", MAG_PRECISION, -99.0, 99.0, INVALID_MAG);

	/* Table header */
	if (fs->stars.nx>0 && fs->stars.ny>0) {
		cmpack_tab_pkyi(table, "Width", fs->stars.nx);
		cmpack_tab_pkyi(table, "Height", fs->stars.ny);
	}

	/* Process frames */
	for (i=0; i<fs->stars.count; i++) {
		CmpackCatObject *obj = fs->stars.list+i;

		cmpack_tab_append(table);
		if (id_column>=0) 
			cmpack_tab_ptdi(table, id_column, obj->id);
		if (pos_column>=0) {
			cmpack_tab_ptdd(table, pos_column, obj->center_x);
			cmpack_tab_ptdd(table, pos_column+1, obj->center_y);
		}
		if (mag_column>=0 && obj->refmag_valid) 
			cmpack_tab_ptdd(table, mag_column, obj->refmagnitude);

		/* Debug printout */
		if (is_debug(con)) {
			sprintf(msg, "OBJECT-ID: %d", obj->id);
			printout(con, 1, msg);
			if (pos_column>=0) {
				sprintf(msg, "CENTER: %.*f, %.*f pxl", POS_PRECISION, obj->center_x, POS_PRECISION, obj->center_y); 
				printout(con, 1, msg);
			}
			if (mag_column>=0 && obj->refmag_valid) {
				sprintf(msg, "MAG: %.*f mag", MAG_PRECISION, obj->refmagnitude); 
				printout(con, 1, msg);
			}
		}
	}

	*ptable = table;
	return CMPACK_ERR_OK;
}
