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

frameset.c (C-Munipack project)
Frame set context
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 <time.h>
#include <assert.h>

#include "comfun.h"
#include "config.h"
#include "frameset.h"
#include "allimport.h"

#define ALLOC_BY 64

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

static void info_clear(CmpackFrameSetInfo *info);
static void info_copy(CmpackFrameSetInfo *dst, const CmpackFrameSetInfo *src);
static int aperture_add(ApertureList *list, unsigned mask, const CmpackPhtAperture *info);
static int aperture_find(ApertureList *list, int aperture_id);
static void apertures_copy(ApertureList *dst, const ApertureList *src);
static void apertures_clear(ApertureList *list);
static int object_add(ObjectList *list, unsigned mask, const CmpackCatObject *info);
static void object_remove(ObjectList *list, int index);
static int object_find(ObjectList *list, int object_id);
static void objects_copy(ObjectList *dst, const ObjectList *src);
static void objects_clear(ObjectList *list);
static void frames_clear(FrameList *list);
static void frames_copy(FrameList *dst, const FrameList *src);
static void frames_append(FrameList *list, FrameRec *rec);
static void frames_remove(FrameList *list, FrameRec *rec);
static FrameRec *new_frame(void);
static void frame_realloc(FrameRec *f, int nobjects, int napertures);
static void frame_copy(FrameRec *dst, const FrameRec *src);
static void destroy_frame(FrameRec *f);

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

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

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

/* Test if the file is a photometry file */
int cmpack_fset_test_buffer(const char *buffer, int buflen, int filesize)
{
	return memstr(buffer, "# JD, instrumental mags and standard deviations of all detected stars", buflen)!=NULL;
}

/* Create a read-all file context */
CmpackFrameSet *cmpack_fset_init(void)
{
	CmpackFrameSet *f = cmpack_calloc(1, sizeof(CmpackFrameSet));
	f->refcnt = 1;
	f->info.jd_prec = JD_PRECISION;
	return f;
}

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

/* Decrement reference counter / detroy the instance */
void cmpack_fset_destroy(CmpackFrameSet *ctx)
{
	if (ctx) {
		ctx->refcnt--;
		if (ctx->refcnt==0) {
			info_clear(&ctx->info);
			apertures_clear(&ctx->apertures);
			objects_clear(&ctx->objects);
			frames_clear(&ctx->frames);
			cmpack_free(ctx);
		}
	}
}

/* Load data from 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_fset_load(CmpackFrameSet **fset, const char *filename, int flags)
{
	int res = 0;
	size_t bytes;
	char line[MAXLINE];
	FILE *f;
	
	*fset = NULL;

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

	/* Autodetect format */
	bytes = fread(line, 1, MAXLINE-1, f); 
	line[bytes] = '\0';
	fseek(f, 0, SEEK_SET);
	if (strstr(line, "# JD, instrumental mags and standard deviations of all detected stars")==line) {
		/* Text photometry file */
		res = all_import(fset, f, flags);
	} else {
		/* Unknown format */
		res = CMPACK_ERR_UNKNOWN_FORMAT;
	}
	if (res!=0) {
		/* Failed to open the file */
		fclose(f);
		return res;
	}

	/* Normal return */
	fclose(f);
	return CMPACK_ERR_OK;
}

/* Save data to file in XML format */
int cmpack_fset_export(CmpackFrameSet *fset, const char *filename, int apindex)
{
	int j, jd_prec, aperture;
	double mag, err;
	FrameRec *ptr;
	CmpackFrameSetInfo info;
	CmpackPhtData *data;
	FILE *f;

	cmpack_fset_get_info(fset, CMPACK_FS_JD_PREC, &info);
	jd_prec = info.jd_prec;

	if (apindex<0 || apindex>=fset->apertures.count)
		return CMPACK_ERR_AP_NOT_FOUND;
	aperture = fset->apertures.items[apindex].id;
	
	f = fopen(filename, "w+");
	if (!f) 
		return CMPACK_ERR_OPEN_ERROR;

	fprintf(f, "# JD, instrumental mags and standard deviations of all detected stars\n");
	fprintf(f, "# Aperture: %d", aperture);
	if (fset->frames.first && fset->frames.first->info.filter && fset->frames.first->info.filter[0]!=0)
		fprintf(f, ", Filter: %s", fset->frames.first->info.filter);
	if (fset->info.jd_mode!=CMPACK_JD_UNKNOWN)
		fprintf(f, ", JD: %s", (fset->info.jd_mode==CMPACK_JD_GEOCENTRIC ? "geocentric" : "heliocentric"));
	fprintf(f, "\n");

	for (ptr=fset->frames.first; ptr!=NULL; ptr=ptr->next) {
		fprintf(f, "%.*f", jd_prec, ptr->info.juldat);
		for (j=0; j<ptr->nstar; j++) {
			data = ptr->data + (j*ptr->naper+apindex);
			if (data->mag_valid) {
				mag = data->magnitude;
				err = data->mag_error;
			} else {
				mag = INVALID_MAG;
				err = INVALID_MAGERR;
			}
			fprintf(f, " %.*f %.*f", MAG_PRECISION, mag, MAG_PRECISION, err);
		}
		fprintf(f, "\n"); 
	}

	fclose(f);
	return CMPACK_ERR_OK;
}

/* Copy frame set */
int cmpack_fset_copy(CmpackFrameSet *dst, const CmpackFrameSet *src)
{
	info_copy(&dst->info, &src->info);
	apertures_copy(&dst->apertures, &src->apertures);
	objects_copy(&dst->objects, &src->objects);
	frames_copy(&dst->frames, &src->frames);
	dst->current = NULL;
	return CMPACK_ERR_OK;
}

/* Clear data */
void cmpack_fset_clear(CmpackFrameSet *fset)
{
	info_clear(&fset->info);
	apertures_clear(&fset->apertures);
	objects_clear(&fset->objects);
	frames_clear(&fset->frames);
	fset->current = NULL;
}

int cmpack_fset_set_info(CmpackFrameSet *fset, unsigned mask, const CmpackFrameSetInfo *info)
{
	if (mask & CMPACK_FS_FRAME_SIZE) {
		fset->info.frame_width = info->frame_width;
		fset->info.frame_height = info->frame_height;
	}
	if (mask & CMPACK_FS_OBJECT) {
		cmpack_free(fset->info.objcoords.designation);
		fset->info.objcoords.designation = cmpack_strdup(info->objcoords.designation);
		fset->info.objcoords.ra_valid = info->objcoords.ra_valid;
		fset->info.objcoords.ra = info->objcoords.ra;
		fset->info.objcoords.dec_valid = info->objcoords.dec_valid;
		fset->info.objcoords.dec = info->objcoords.dec;
	}
	if (mask & CMPACK_FS_LOCATION) {
		cmpack_free(fset->info.location.designation);
		fset->info.location.designation = cmpack_strdup(info->location.designation);
		fset->info.location.lon_valid = info->location.lon_valid;
		fset->info.location.lon = info->location.lon;
		fset->info.location.lat_valid = info->location.lat_valid;
		fset->info.location.lat = info->location.lat;
	}
	if (mask & CMPACK_FS_JD_MODE) 
		fset->info.jd_mode = info->jd_mode;
	if (mask & CMPACK_FS_JD_PREC)
		fset->info.jd_prec = info->jd_prec;
	return 0;
}

int cmpack_fset_get_info(CmpackFrameSet *fset, unsigned mask, CmpackFrameSetInfo *info)
{
	if (mask & CMPACK_FS_FRAME_SIZE) {
		info->frame_width = fset->info.frame_width;
		info->frame_height = fset->info.frame_height;
	}
	if (mask & CMPACK_FS_OBJECT) 
		info->objcoords = fset->info.objcoords;
	if (mask & CMPACK_FS_LOCATION)
		info->location = fset->info.location;
	if (mask & CMPACK_FS_JD_MODE)
		info->jd_mode = fset->info.jd_mode;
	if (mask & CMPACK_FS_JD_PREC)
		info->jd_prec = fset->info.jd_prec;
	return 0;
}

/*************************   Frameset info   *******************************/

static void info_copy(CmpackFrameSetInfo *dst, const CmpackFrameSetInfo *src)
{
	cmpack_free(dst->objcoords.designation);
	cmpack_free(dst->location.designation);
	*dst = *src;
	if (dst->objcoords.designation)
		dst->objcoords.designation = cmpack_strdup(dst->objcoords.designation);
	if (dst->location.designation)
		dst->location.designation = cmpack_strdup(dst->location.designation);
}

static void info_clear(CmpackFrameSetInfo *info)
{
	cmpack_free(info->objcoords.designation);
	cmpack_free(info->location.designation);
	memset(info, 0, sizeof(CmpackFrameSetInfo));
}

/*************************   Frame management   *******************************/

/* Clear all frames */
static void frames_clear(FrameList *list)
{
	FrameRec *ptr, *next;

	ptr = list->first;
	while (ptr) {
		next = ptr->next;
		destroy_frame(ptr);
		ptr = next;
	}
	list->first = list->last = NULL;
}

/* Copy frames */
static void frames_copy(FrameList *dst, const FrameList *src)
{
	FrameRec *ptr, *item;

	frames_clear(dst);

	ptr = src->first;
	while (ptr) {
		item = new_frame();
		frame_copy(item, ptr);
		frames_append(dst, item);
		ptr = ptr->next;
	}
}

/* Append a new frame */
static void frames_append(FrameList *list, FrameRec *frame)
{
	frame->prev = list->last;
	if (list->last) 
		list->last->next = frame;
	else
		list->first = frame;
	list->last = frame;
}

/* Remove frame from the list */
static void frames_remove(FrameList *list, FrameRec *frame)
{
	if (frame->prev)
		frame->prev->next = frame->next;
	else
		list->first = frame->next;
	if (frame->next)
		frame->next->prev = frame->prev;
	else
		list->last = frame->prev;
}

/* Create frame */
static FrameRec *new_frame(void)
{
	return cmpack_calloc(1, sizeof(FrameRec));
}

static void frame_realloc(FrameRec *f, int nobjects, int napertures)
{
	int i,j;

	if (nobjects!=f->nstar || napertures!=f->naper) {
		if (nobjects>0) {
			FrameObjData *aux = cmpack_calloc(nobjects, sizeof(FrameObjData));
			for (i=0; i<f->nstar; i++) 
				aux[i] = f->objs[i];
			cmpack_free(f->objs);
			f->objs = aux;
		} else {
			cmpack_free(f->objs);
			f->objs = NULL;
		}
		if (nobjects>0 && napertures>0) {
			CmpackPhtData *aux = cmpack_calloc(nobjects*napertures, sizeof(CmpackPhtData));
			for (i=0; i<f->nstar; i++) {
				for (j=0; j<f->naper; j++)
					aux[i*napertures+j] = f->data[i*f->naper+j];
			}
			cmpack_free(f->data);
			f->data = aux;
		} else {
			cmpack_free(f->data);
			f->data = NULL;
		}
		f->nstar = nobjects;
		f->naper = napertures;
	} 
}

/* Copy frame data */
static void frame_copy(FrameRec *dst, const FrameRec *src)
{
	cmpack_free(dst->info.filter);
	cmpack_free(dst->info.filename);
	dst->info = src->info;
	if (dst->info.filter)
		dst->info.filter = cmpack_strdup(dst->info.filter);
	if (dst->info.filename)
		dst->info.filename = cmpack_strdup(dst->info.filename);

	cmpack_free(dst->data);
	cmpack_free(dst->objs);
	if (src->nstar>0 && src->naper>0) {
		dst->nstar = src->nstar;
		dst->naper = src->naper;
		dst->data = cmpack_calloc(dst->nstar*dst->naper, sizeof(CmpackPhtData));
		memcpy(dst->data, src->data, dst->nstar*dst->naper*sizeof(CmpackPhtData));
		dst->objs = cmpack_calloc(dst->nstar, sizeof(FrameObjData));
		memcpy(dst->objs, src->objs, dst->nstar*sizeof(FrameObjData));
	} else {
		dst->nstar = dst->naper = 0;
		dst->data = NULL;
		dst->objs = NULL;
	}
}

/* Destroy frame */
static void destroy_frame(FrameRec *frame)
{
	if (frame) {
		cmpack_free(frame->info.filter);
		cmpack_free(frame->info.filename);
		cmpack_free(frame->data);
		cmpack_free(frame->objs);
		cmpack_free(frame);
	}
}

/* Get number of frames stored in a set */
int cmpack_fset_frame_count(CmpackFrameSet *fset)
{
	int count;
	FrameRec *ptr;

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

/* Append a new row to the table */
int cmpack_fset_rewind(CmpackFrameSet *fset)
{
	fset->current = fset->frames.first;
	return (fset->current!=NULL ? CMPACK_ERR_OK : CMPACK_ERR_OUT_OF_RANGE);
}

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

/* Go to record by index */
int cmpack_fset_setpos(CmpackFrameSet *fset, int row)
{
	FrameRec *ptr;

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

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

/* Add a frame to the file */
int cmpack_fset_append(CmpackFrameSet *fset, unsigned mask, const CmpackFrameInfo *info)
{
	FrameRec *item = new_frame();
	frames_append(&fset->frames, item);
	fset->current = item;
	cmpack_fset_set_frame(fset, mask, info);
	return CMPACK_ERR_OK;
}

/* Add a frame to the file */
int cmpack_fset_append_frame(CmpackFrameSet *fset, CmpackPhtFile *file, int frame_id,
	const char *filename)
{
	int i, j, ap_index, obj_index, nstars, napertures;
	CmpackPhtInfo info;
	CmpackPhtObject star;
	CmpackPhtAperture aper;
	CmpackCatObject obj;
	FrameRec *item;

	memset(&obj, 0, sizeof(CmpackCatObject));

	/* Create a new frame */
	item = new_frame();
	frames_append(&fset->frames, item);
	fset->current = item;

	/* Frame parameters */
	cmpack_pht_get_info(file, CMPACK_PI_FRAME_PARAMS | CMPACK_PI_MATCH_PARAMS, &info);
	item->info.frame_id = frame_id;
	item->info.juldat = info.jd;
	item->info.filter = cmpack_strdup(info.filter);
	item->info.ccdtemp = info.ccdtemp;
	item->info.exptime = info.exptime;
	item->info.valid_offset = info.matched;
	item->info.filename = cmpack_strdup(filename);
	memcpy(item->info.offset, info.offset, 2*sizeof(double));

	/* Frame data */
	napertures = cmpack_pht_aperture_count(file);
	nstars = cmpack_pht_object_count(file);
	if (nstars>0 && napertures>0) {
		/* Apertures */
		int *apid = (int*)cmpack_malloc(napertures*sizeof(int));
		for (j=0; j<napertures; j++) {
			cmpack_pht_get_aperture(file, j, CMPACK_PA_ID, &aper);
			apid[j] = aper.id;
		}
		/* Stars */
		frame_realloc(item, fset->objects.count, fset->apertures.count);
		for (i=0; i<nstars; i++) {
			int star_valid = cmpack_pht_get_object(file, i, CMPACK_PO_ID | CMPACK_PO_REF_ID | CMPACK_PO_CENTER | CMPACK_PO_SKY | CMPACK_PO_FWHM, &star) == 0;
			if (star_valid && star.ref_id>0) {
				obj_index = object_find(&fset->objects, star.ref_id);
				if (obj_index>=0 && obj_index<item->nstar) {
					FrameObjData *obj = item->objs + obj_index;
					obj->valid = 1;
					memcpy(&obj->data, &star, sizeof(CmpackPhtObject));
					for (j=0; j<napertures; j++) {
						if (apid[j]>0) {
							ap_index = aperture_find(&fset->apertures, apid[j]);
							if (ap_index>=0 && ap_index<item->naper) {
								CmpackPhtData *data = item->data + (obj_index*item->naper + ap_index);
								if (cmpack_pht_get_data(file, i, j, data)!=0) {
									data->mag_valid = 0;
									data->magnitude = INVALID_MAG;
									data->mag_error = INVALID_MAGERR;
								}
							}
						}
					}
				}
			}
		}	
		cmpack_free(apid);
	}

	return CMPACK_ERR_OK;
}

/* Add a frame to the file */
int cmpack_fset_append_file(CmpackFrameSet *fset, const char *filepath, int frame_id)
{
	int res;
	CmpackPhtFile *file;

	res = cmpack_pht_open(&file, filepath, CMPACK_OPEN_READONLY, 0);
	if (res==0) {
		res = cmpack_fset_append_frame(fset, file, frame_id, filepath);
		cmpack_pht_destroy(file);
	}
	return res;
}

int cmpack_fset_set_frame(CmpackFrameSet *fset, unsigned mask, const CmpackFrameInfo *info)
{
	CmpackFrameInfo *data;

	if (!fset->current)
		return CMPACK_ERR_OUT_OF_RANGE;

	data = &fset->current->info;
	if (mask & CMPACK_FI_ID)
		data->frame_id = info->frame_id;
	if (mask & CMPACK_FI_JULDAT) 
		data->juldat = info->juldat;
	if (mask & CMPACK_FI_AIRMASS_ALT) {
		data->airmass = info->airmass;
		data->altitude = info->altitude;
	}
	if (mask & CMPACK_FI_HELCOR) {
		data->valid_helcor = info->valid_helcor;
		data->helcor = info->helcor;
	}
	if (mask & CMPACK_FI_CCDTEMP)
		data->ccdtemp = info->ccdtemp;
	if (mask & CMPACK_FI_EXPTIME)
		data->exptime = info->exptime;
	if (mask & CMPACK_FI_FILTER) {
		cmpack_free(data->filter);
		data->filter = cmpack_strdup(info->filter);
	}
	if (mask & CMPACK_FI_FILENAME) {
		cmpack_free(data->filename);
		data->filename = cmpack_strdup(info->filename);
	}
	if (mask & CMPACK_FI_OFFSET) {
		data->valid_offset = info->valid_offset;
		data->offset[0] = info->offset[0];
		data->offset[1] = info->offset[1];
	}
	return 0;
}

/* Get information about a frame */
int cmpack_fset_get_frame(CmpackFrameSet *fset, unsigned mask, CmpackFrameInfo *info)
{
	CmpackFrameInfo *data;

	if (!fset->current)
		return CMPACK_ERR_OUT_OF_RANGE;

	data = &fset->current->info;
	if (mask & CMPACK_FI_ID)
		info->frame_id = data->frame_id;
	if (mask & CMPACK_FI_JULDAT) 
		info->juldat = data->juldat;
	if (mask & CMPACK_FI_AIRMASS_ALT) {
		info->airmass = data->airmass;
		info->altitude = data->altitude;
	}
	if (mask & CMPACK_FI_HELCOR) {
		info->valid_helcor = data->valid_helcor;
		info->helcor = data->helcor;
	}
	if (mask & CMPACK_FI_CCDTEMP)
		info->ccdtemp = data->ccdtemp;
	if (mask & CMPACK_FI_EXPTIME)
		info->exptime = data->exptime;
	if (mask & CMPACK_FI_FILTER) 
		info->filter = data->filter;
	if (mask & CMPACK_FI_FILENAME) 
		info->filename = data->filename;
	if (mask & CMPACK_FI_OFFSET) {
		info->valid_offset = data->valid_offset;
		info->offset[0] = data->offset[0];
		info->offset[1] = data->offset[1];
	}
	return 0;
}

/* Remove a frame from the set */
void cmpack_fset_delete_frame(CmpackFrameSet *fset)
{
	FrameRec *next;

	if (fset->current) {
		next = fset->current->next;
		frames_remove(&fset->frames, fset->current);
		destroy_frame(fset->current);
		fset->current = next;
	}
}

/* Find frame by its identifier */
int cmpack_fset_find_frame(CmpackFrameSet *fset, int frame_id)
{
	FrameRec *ptr;

	for (ptr=fset->frames.first; ptr!=NULL; ptr=ptr->next) {
		if (ptr->info.frame_id == frame_id) {
			fset->current = ptr;
			return 1;
		}
	}
	fset->current = NULL;
	return 0;
}

/****************************   Apertures   *******************************/

/* Clear apertures */
static void apertures_clear(ApertureList *list)
{
	cmpack_free(list->items);
	list->items = NULL;
	list->count = list->capacity = 0;
}

/* Clear apertures */
static void apertures_copy(ApertureList *dst, const ApertureList *src)
{
	cmpack_free(dst->items);
	if (src->count>0) {
		dst->count = dst->capacity = src->count;
		dst->items = (CmpackPhtAperture*)cmpack_malloc(dst->capacity*sizeof(CmpackPhtAperture));
		memcpy(dst->items, src->items, dst->count*sizeof(CmpackPhtAperture));
	} else {
		dst->items = NULL;
		dst->count = dst->capacity = 0;
	}
}

/* Add aperture to the list */
static int aperture_add(ApertureList *list, unsigned mask, const CmpackPhtAperture *info)
{
	int index = list->count;
	if (list->count >= list->capacity) {
		list->capacity += ALLOC_BY;
		list->items = (CmpackPhtAperture*)cmpack_realloc(list->items, list->capacity*sizeof(CmpackPhtAperture));
	}
	memset(&list->items[index], 0, sizeof(CmpackPhtAperture));
	list->items[index].id = info->id;
	if (mask & CMPACK_PA_RADIUS)
		list->items[index].radius = info->radius;
	list->count++;
	return index;
}

/* Get aperture context by the identifier */
static int aperture_find(ApertureList *list, int aper_id)
{
	int i;

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

/* Get number of apertures stored in a frame set */
int cmpack_fset_aperture_count(CmpackFrameSet *fset)
{
	return fset->apertures.count;
}

/* Add new object */
int cmpack_fset_add_aperture(CmpackFrameSet *fset, unsigned mask, const CmpackPhtAperture *info)
{
	if (info->id>=0 && aperture_find(&fset->apertures, info->id)<0)
		return aperture_add(&fset->apertures, mask, info);
	return -1;
}

/* Get aperture context by the identifier */
int cmpack_fset_find_aperture(CmpackFrameSet *fset, int aper_id)
{
	return aperture_find(&fset->apertures, aper_id);
}

/* Get aperture data */
int cmpack_fset_get_aperture(CmpackFrameSet *fset, int index, unsigned mask, CmpackPhtAperture *info)
{
	CmpackPhtAperture *aper;

	if (index<0 || index>=fset->apertures.count)
		return CMPACK_ERR_OUT_OF_RANGE;

	aper = &fset->apertures.items[index];
	if (mask & CMPACK_PA_ID)
		info->id = aper->id;
	if (mask & CMPACK_PA_RADIUS)
		info->radius = aper->radius;
	return CMPACK_ERR_OK;
}

/* Set aperture data */
int cmpack_fset_set_aperture(CmpackFrameSet *fset, int index, unsigned mask, const CmpackPhtAperture *info)
{
	CmpackPhtAperture *aper;

	if (index<0 || index>=fset->apertures.count)
		return CMPACK_ERR_OUT_OF_RANGE;

	aper = &fset->apertures.items[index];
	if (mask & CMPACK_PA_RADIUS)
		aper->radius = info->radius;
	return CMPACK_ERR_OK;
}

/*************************  Reference frame   *****************************/

/* Clear the list of objects */
static void objects_clear(ObjectList *list)
{
	cmpack_free(list->items);
	list->items = NULL;
	list->count = list->capacity = 0;
}	

/* Clear the list of objects */
static void objects_copy(ObjectList *dst, const ObjectList *src)
{
	cmpack_free(dst->items);
	if (src->count>0) {
		dst->count = dst->capacity = src->count;
		dst->items = (CmpackCatObject*)cmpack_malloc(dst->capacity*sizeof(CmpackCatObject));
		memcpy(dst->items, src->items, dst->count*sizeof(CmpackCatObject));
	} else {
		dst->items = NULL;
		dst->count = dst->capacity = 0;
	}
}

/* Add object to the list */
static int object_add(ObjectList *list, unsigned mask, const CmpackCatObject *info)
{
	int index = list->count;
	if (list->count >= list->capacity) {
		list->capacity += ALLOC_BY;
		list->items = (CmpackCatObject*)cmpack_realloc(list->items, list->capacity*sizeof(CmpackCatObject));
	}
	memset(&list->items[index], 0, sizeof(CmpackCatObject));
	list->items[index].id = info->id;
	if (mask & CMPACK_OM_CENTER) {
		list->items[index].center_x = info->center_x;
		list->items[index].center_y = info->center_y;
	}
	if (mask & CMPACK_OM_MAGNITUDE) {
		list->items[index].refmag_valid = info->refmag_valid;
		list->items[index].refmagnitude = info->refmagnitude;
	}
	list->count++;
	return index;
}

/* Remove object from the list */
static void object_remove(ObjectList *list, int index)
{
	if (index>=0 && index < list->count-1) 
		memmove(list->items+index, list->items+(index+1), (list->count-index-1)*sizeof(CmpackCatObject));
	list->count--;
}

/* Get aperture context by the identifier */
static int object_find(ObjectList *list, int object_id)
{
	int i;

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

/* Get number of stars */
int cmpack_fset_object_count(CmpackFrameSet *fset)
{
	return fset->objects.count;
}

/* Add new object */
int cmpack_fset_add_object(CmpackFrameSet *fset, unsigned mask, const CmpackCatObject *info)
{
	if (info->id>=0 && object_find(&fset->objects, info->id)<0)
		return object_add(&fset->objects, mask, info);
	return -1;
}

/* Get measurement by index */
int cmpack_fset_find_object(CmpackFrameSet *fset, int object_id)
{
	if (object_id>=0)
		return object_find(&fset->objects, object_id);
	return -1;
}

/* Get measurement by index */
int cmpack_fset_get_object(CmpackFrameSet *fset, int index, unsigned mask, CmpackCatObject *info)
{
	CmpackCatObject *obj;

	if (index<0 || index>fset->objects.count)
		return CMPACK_ERR_OUT_OF_RANGE;

	obj = &fset->objects.items[index];
	if (mask & CMPACK_OM_ID)
		info->id = obj->id;
	if (mask & CMPACK_OM_CENTER) {
		info->center_x = obj->center_x;
		info->center_y = obj->center_y;
	}
	if (mask & CMPACK_OM_MAGNITUDE) {
		info->refmag_valid = obj->refmag_valid;
		info->refmagnitude = obj->refmagnitude;
	}
	return CMPACK_ERR_OK;
}

int cmpack_fset_set_object(CmpackFrameSet *fset, int index, unsigned mask, const CmpackCatObject *info)
{
	CmpackCatObject *obj;

	if (index<0 || index>fset->objects.count)
		return CMPACK_ERR_OUT_OF_RANGE;

	obj = &fset->objects.items[index];
	if (mask & CMPACK_OM_CENTER) {
		obj->center_x = info->center_x;
		obj->center_y = info->center_y;
	}
	if (mask & CMPACK_OM_MAGNITUDE) {
		obj->refmag_valid = info->refmag_valid;
		obj->refmagnitude = info->refmagnitude;
	}
	return CMPACK_ERR_OK;
}

/* Remove object from the frame set */
void cmpack_fset_remove_object(CmpackFrameSet *fset, int object)
{
	FrameRec *ptr;

	if (object>=0) {
		object_remove(&fset->objects, object);
		for (ptr=fset->frames.first; ptr!=NULL; ptr=ptr->next) {
			if (object < ptr->nstar) {
				if (object < ptr->nstar-1) {
					memmove(ptr->data + object*ptr->naper, ptr->data + (object+1)*ptr->naper,
						(ptr->nstar-object-1)*ptr->naper*sizeof(CmpackPhtData));
				}
				ptr->nstar--;
			}
		}
	}
}

/*********************  Low-level data access   ************************/

int cmpack_fset_get_data(CmpackFrameSet *fset, int object, int aperture, CmpackPhtData *data)
{
	CmpackPhtData *d;

	if (!fset->current)
		return CMPACK_ERR_OUT_OF_RANGE;
	if (object<0 || object>=fset->objects.count)
		return CMPACK_ERR_STAR_NOT_FOUND;
	if (aperture<0 || aperture>=fset->apertures.count)
		return CMPACK_ERR_AP_NOT_FOUND;
	if (object>=fset->current->nstar || aperture>=fset->current->naper)
		return CMPACK_ERR_UNDEF_VALUE;
	
	d = fset->current->data + (object*fset->current->naper + aperture);
	if (d->mag_valid) {
		*data = *d;
		return CMPACK_ERR_OK;
	}
	return CMPACK_ERR_UNDEF_VALUE;
}

int cmpack_fset_set_data(CmpackFrameSet *fset, int object, int aperture, const CmpackPhtData *data)
{
	if (!fset->current)
		return CMPACK_ERR_OUT_OF_RANGE;
	if (object<0 || object>=fset->objects.count)
		return CMPACK_ERR_STAR_NOT_FOUND;
	if (aperture<0 || aperture>=fset->apertures.count)
		return CMPACK_ERR_AP_NOT_FOUND;

	if (object>=fset->current->nstar || aperture>=fset->current->naper) 
		frame_realloc(fset->current, fset->objects.count, fset->apertures.count);
	fset->current->data[object*fset->apertures.count + aperture] = *data;
	return CMPACK_ERR_OK;
}

int cmpack_fset_get_frame_object(CmpackFrameSet *fset, int object, unsigned mask, 
	CmpackPhtObject *data)
{
	if (!fset->current)
		return CMPACK_ERR_OUT_OF_RANGE;
	if (object<0 || object>=fset->objects.count)
		return CMPACK_ERR_STAR_NOT_FOUND;
	if (object>=fset->current->nstar)
		return CMPACK_ERR_UNDEF_VALUE;
	if (!fset->current->objs[object].valid)
		return CMPACK_ERR_UNDEF_VALUE;
	
	*data = (fset->current->objs + object)->data;
	return CMPACK_ERR_OK;
}
