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

ccdfile.c (C-Munipack project)
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 <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <fitsio.h>

#include "comfun.h"
#include "console.h"
#include "trajd.h"
#include "image.h"
#include "cmpack_common.h"
#include "cmpack_ccdfile.h"
#include "cmpack_wcs.h"
#include "ccdfile.h"
#include "konvfits.h"
#include "konvsbig.h"
#include "konvoes.h"
#include "konvcrw.h"
#include "konvnef.h"
#include "konvmrw.h"
#include "konvcr3.h"
#include "config.h"
#include "wcsobj.h"

#define BLOCK_SIZE 4096

/************************   Table of supported formats   ***************************/

/* Registration of known image formats */
typedef struct _CmpackCcdFmt
{
	CmpackFormat format;									/**< Format identifier */
	int (*test)(const char *, size_t, size_t);				/**< Test image format */
	int (*open)(tHandle*, const char*, CmpackOpenMode);		/**< Open a file */
	tHandle (*init)(void);									/**< Create a new file */
	void (*close)(tHandle);									/**< Close a file */
	char* (*get_magic)(tHandle);							/**< Get magic string */
	int (*get_size)(tHandle, int*, int *);					/**< Get image size */
	CmpackBitpix (*get_bitpix)(tHandle);					/**< Get image data type */
	int (*working_format)(tHandle);							/**< Standard FITS format? */
	int (*get_range)(tHandle, double*, double*);			/**< Get special pixel values */
	int (*get_image)(tHandle, void*, int, CmpackChannel);	/**< Get image data */
	int (*get_datetime)(tHandle, CmpackDateTime*);			/**< Get date of observation */
	int (*get_exptime)(tHandle, double*);					/**< Get exposure duration */
	int (*get_ccdtemp)(tHandle, double*);					/**< Get CCD temperature */
	char* (*get_filter)(tHandle, CmpackChannel);			/**< Get optical filter name */
	char* (*get_object)(tHandle);							/**< Get object's designation */
	char* (*get_objra)(tHandle);							/**< Get object's right ascension */
	char* (*get_objdec)(tHandle);							/**< Get object's declination */
	char* (*get_observer)(tHandle);							/**< Get observer's name */
	char* (*get_observatory)(tHandle);						/**< Get observatory name */
	char* (*get_telescope)(tHandle);						/**< Get telescope designation */
	char* (*get_instrument)(tHandle);						/**< Get camera designation */
	char* (*get_obslon)(tHandle);							/**< Get observer's longitude */
	char* (*get_obslat)(tHandle);							/**< Get observer's latitude */
	void (*get_frames)(tHandle, CmpackChannel, int*, int*);	/**< Get number of combined frames */
	int (*get_wcs)(tHandle, CmpackWcs**);					/**> Get WCS data */
	int (*get_str)(tHandle, const char*, char**);			/**< Get a string value */
	int (*get_int)(tHandle, const char*, int*);				/**< Get a integer value */
	int (*get_dbl)(tHandle, const char*, double*);			/**< Get a real value */
	int (*get_bool)(tHandle, const char*, int*);			/**< Get a logic value */
	int (*get_param)(tHandle, int, char**, char**, char**);	/**< Parameter enumeration */
	int (*copy_header)(tHandle, CmpackImageHeader*, CmpackChannel, CmpackConsole*);	/**< Save header to FITS file */
	int (*set_datetime)(tHandle, const CmpackDateTime*);	/**< Set date of observation */
	void (*set_exptime)(tHandle, double);					/**< Set exposure duration */
	void (*set_ccdtemp)(tHandle, double);					/**< Set CCD temperature */
	void (*set_frames)(tHandle, int, int);					/**< Set number of combined frames */
	void (*set_object)(tHandle, const char*);				/**< Set object's designtion */
	void (*set_str)(tHandle, const char*, const char*, const char*);	/**< Set a string value */
	void (*set_int)(tHandle, const char*, int, const char*);	/**< Set a string value */
	void (*set_dbl)(tHandle, const char*, double, int, const char*);	/**< Set a string value */
	void (*set_bool)(tHandle, const char*, int, const char*);			/**< Set a logic value */
	int (*put_history)(tHandle, const char*);				/**< Parameter enumeration */
	int (*prepare)(tHandle, int, int, CmpackBitpix);		/**< Set image parameters */
	int (*put_image)(tHandle, const CmpackImage*);			/**< Set image data */
	int (*restore_header)(tHandle, CmpackImageHeader*, CmpackConsole*);	/**< Restore header from FITS file */
} CmpackCcdFmt;

static const CmpackCcdFmt flist[] = {
	{ CMPACK_FORMAT_FITS, fits_test, fits_open, fits_init, fits_close, fits_getmagic,
		fits_getsize, fits_getbitpix, fits_working_format, fits_getrange, fits_getimage, fits_getdatetime, 
		fits_getexptime, fits_getccdtemp, fits_getfilter, fits_getobject, 
		fits_getobjra, fits_getobjdec, fits_getobserver, fits_getobservatory, fits_gettelescope, fits_getinstrument,
		fits_getobslon, fits_getobslat, fits_getframes, fits_getwcs, fits_gkys, fits_gkyi, 
		fits_gkyd, fits_gkyl, fits_gkyn, fits_copyheader, fits_setdatetime, 
		fits_setexptime, fits_setccdtemp, fits_setframes, fits_setobject, fits_pkys, 
		fits_pkyi, fits_pkyd, fits_pkyl, fits_puthistory, fits_prepare, fits_putimage,
		fits_restoreheader },
	{ CMPACK_FORMAT_SBIG, sbig_test, sbig_open, NULL, sbig_close, sbig_getmagic, 
		sbig_getsize, sbig_getbitpix, NULL, sbig_getrange, sbig_getimage, sbig_getdatetime, 
		sbig_getexptime, sbig_getccdtemp, sbig_getfilter, NULL, NULL, NULL, 
		sbig_getobserver, NULL, NULL, NULL, NULL, NULL, NULL, NULL, sbig_gets, sbig_geti, sbig_getd, 
		NULL, sbig_gkyn, sbig_copyheader },
	{ CMPACK_FORMAT_OES, oes_test, oes_open, NULL, oes_close, oes_getmagic, 
		oes_getsize, oes_getbitpix, NULL, oes_getrange, oes_getimage, oes_getdatetime, 
		oes_getexptime, oes_getccdtemp, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
		NULL, NULL, NULL, NULL, oes_gets, oes_geti, oes_getd, NULL, oes_gkyn, 
		oes_copyheader },
	{ CMPACK_FORMAT_CRW, crw_test, crw_open, NULL, crw_close, crw_getmagic, 
		crw_getsize, crw_getbitpix, NULL, crw_getrange, crw_getimage, crw_getdatetime, 
		crw_getexptime, crw_getccdtemp, crw_getfilter, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
		NULL, NULL, crw_getframes, NULL, NULL, NULL, NULL, NULL, NULL, crw_copyheader },
	{ CMPACK_FORMAT_NEF, nef_test, nef_open, NULL, nef_close, nef_getmagic, 
		nef_getsize, nef_getbitpix, NULL, nef_getrange, nef_getimage, nef_getdatetime, 
		nef_getexptime, NULL, nef_getfilter, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
		NULL, NULL, nef_getframes, NULL, NULL, NULL, NULL, NULL, NULL, nef_copyheader },
	{ CMPACK_FORMAT_MRW, mrw_test, mrw_open, NULL, mrw_close, mrw_getmagic,
		mrw_getsize, mrw_getbitpix, NULL, mrw_getrange, mrw_getimage, mrw_getdatetime,
		mrw_getexptime, NULL, mrw_getfilter, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
		NULL, NULL, mrw_getframes, NULL, NULL, NULL, NULL, NULL, NULL, mrw_copyheader },
	{ CMPACK_FORMAT_CR3, konv_cr3_test, konv_cr3_open, NULL, konv_cr3_close, konv_cr3_getmagic,
		konv_cr3_getsize, konv_cr3_getbitpix, NULL, konv_cr3_getrange, konv_cr3_getimage, konv_cr3_getdatetime,
		konv_cr3_getexptime, konv_cr3_getccdtemp, konv_cr3_getfilter, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
		NULL, NULL, konv_cr3_getframes, NULL, NULL, NULL, NULL, NULL, NULL, konv_cr3_copyheader },
	{ 0 }
};

/************************   Private data types   ***************************/

/* Cache flags */
#define CACHE_MAGIC			(1<<0)
#define CACHE_FILTER		(1<<1)
#define CACHE_OBJECT		(1<<2)
#define CACHE_OBSERVER		(1<<3)
#define CACHE_LOCATION		(1<<4)
#define CACHE_TELESCOPE		(1<<5)
#define CACHE_INSTRUMENT	(1<<6)
#define CACHE_WCS			(1<<7)

/* Image file context */
struct _CmpackCcdFile
{
	int refcnt;							/**< Reference counter */
	const CmpackCcdFmt *fmt;			/**< Access routines */
	tHandle handle;						/**< Format specific handle */
	int cache_flags;
	char *magic, *filter, *observer;
	char *telescope, *instrument;
	CmpackObjCoords obj;
	CmpackLocation loc;
	CmpackChannel channel;
	CmpackWcs *wcs;
};

/************************   Local data and functions   ****************************/

/* Create a new CCD file context */
static CmpackCcdFile *file_create(void)
{
	CmpackCcdFile *f = (CmpackCcdFile*)cmpack_calloc(1, sizeof(CmpackCcdFile));
	f->refcnt = 1;
	return f;
}

/* Destroy a CCD file context */
static int file_close(CmpackCcdFile *f)
{
	if (f->fmt) {
		f->fmt->close(f->handle);
		f->fmt = NULL;
		f->handle = NULL;
	}
	cmpack_free(f->magic);
	f->magic = NULL;
	cmpack_free(f->filter);
	f->filter = NULL;
	cmpack_free(f->obj.designation);
	f->obj.designation = NULL;
	cmpack_free(f->observer);
	f->observer = NULL;
	cmpack_free(f->loc.designation);
	f->loc.designation = NULL;
	cmpack_free(f->telescope);
	f->telescope = NULL;
	cmpack_free(f->instrument);
	f->instrument = NULL;
	if (f->wcs) {
		cmpack_wcs_destroy(f->wcs);
		f->wcs = NULL;
	}
	return CMPACK_ERR_OK;
}

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

/* Determines file format. */
int cmpack_ccd_test(const char *filename)
{
	char buf[BLOCK_SIZE];
	int res = 0, filesize, bytes;
	const CmpackCcdFmt *fmt;

	/* Test if source file is present and accessible */
	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(buf, 1, BLOCK_SIZE, f);
			for (fmt=flist; fmt->format!=0; fmt++) {
				if (fmt->test) {
					if (fmt->test(buf, bytes, filesize)) {
						res = 1;
						break;
					}
				}
			}
			fclose(f);
		}
	}
	return res;
}

/* Determines file format. Return 0 if successful, or error code if not. */
int cmpack_ccd_test_buffer(const char *buffer, int buflen, int filesize)
{
	const CmpackCcdFmt *fmt;

	for (fmt=flist; fmt->test!=NULL; fmt++) {
		if (fmt->test(buffer, buflen, filesize))
			return 1;
	}
	return 0;
}

/* Create a temporary file */
CmpackCcdFile *cmpack_ccd_new(void)
{
	CmpackCcdFile *fs;
	const CmpackCcdFmt *fmt;

	for (fmt=flist; fmt->test!=NULL; fmt++) {
		if (fmt->format == CMPACK_FORMAT_FITS) {
			fs = file_create();
			fs->fmt = fmt;
			fs->handle = fmt->init();
			return fs;
		}
	}
	return NULL;
}

/* Frees all allocated memory in internal structures */
CmpackCcdFile *cmpack_ccd_reference(CmpackCcdFile* file)
{
	file->refcnt++;
	return file;
}

/* Select color channel */
void cmpack_ccd_set_channel(CmpackCcdFile* file, CmpackChannel channel)
{
	file->channel = channel;
}

/* Frees all allocated memory in internal structures */
void cmpack_ccd_destroy(CmpackCcdFile *file)
{
	if (file) {
		file->refcnt--;
		if (file->refcnt==0) {
			if (file->fmt) 
				file_close(file);
			cmpack_free(file);
		}
	}
}

/* Open a source file (format is specified) */
int cmpack_ccd_open(CmpackCcdFile **fc, const char *filename, CmpackOpenMode mode, unsigned flags)
{
	int res;
	char buf[BLOCK_SIZE];
	CmpackCcdFile *fs;
	size_t filesize, bytes;
	FILE *f;
	const CmpackCcdFmt *fmt, *format;

	*fc = NULL;

	/* Check source file */
	if (!filename || *filename=='\0') 
		return CMPACK_ERR_INVALID_PAR;

	format = NULL;
	if (mode != CMPACK_OPEN_CREATE) {
		/* Determine file format */
		if (mode == CMPACK_OPEN_READONLY)
			f = fopen(filename, "rb");
		else
			f = fopen(filename, "rb+");
		if (!f)
			return CMPACK_ERR_CANT_OPEN_SRC;
		fseek(f, 0, SEEK_END);
		filesize = ftell(f);
		fseek(f, 0, SEEK_SET);
		bytes = (int)fread(buf, 1, BLOCK_SIZE, f);
		for (fmt=flist; fmt->format!=0; fmt++) {
			if (fmt->test) {
				if (fmt->test(buf, bytes, filesize)) {
					format = fmt;
					break;
				}
			}
		}
		fclose(f);
	} else {
		/* New file is always FITS */
		for (fmt=flist; fmt->format!=0; fmt++) {
			if (fmt->format == CMPACK_FORMAT_FITS)
				format = fmt;
		}
	}
	if (!format)
		return CMPACK_ERR_UNKNOWN_FORMAT;

	/* Create context */
	fs = file_create();
	res = format->open(&fs->handle, filename, mode);
	if (res!=0) {
		cmpack_ccd_destroy(fs);
		return res;
	}

	/* Normal return */
	fs->fmt = format;
	*fc = fs;
	return 0;
}

/* Save data to a file */
int cmpack_ccd_close(CmpackCcdFile *file)
{
	int res = file_close(file);
	if (res==0)
		cmpack_ccd_destroy(file);
	return res;
}

/* Read string parameter from file header */
int cmpack_ccd_gkys(CmpackCcdFile *fs, const char *key, char **val)
{
	if (!fs->fmt)
		return CMPACK_ERR_CLOSED_FILE;
	else if (!fs->fmt->get_str)
		return CMPACK_ERR_NOT_IMPLEMENTED;
	else
		return fs->fmt->get_str(fs, key, val);
}

/* Read double parameter from file header */
int cmpack_ccd_gkyd(CmpackCcdFile *fs, const char *key, double *val)
{
	if (!fs->fmt)
		return CMPACK_ERR_CLOSED_FILE;
	else if (!fs->fmt->get_dbl)
		return CMPACK_ERR_NOT_IMPLEMENTED;
	else
		return fs->fmt->get_dbl(fs, key, val);
}

/* Read integer parameter from file header */
int cmpack_ccd_gkyi(CmpackCcdFile *fs, const char *key, int *val)
{
	if (!fs->fmt)
		return CMPACK_ERR_CLOSED_FILE;
	else if (!fs->fmt->get_int)
		return CMPACK_ERR_NOT_IMPLEMENTED;
	else
		return fs->fmt->get_int(fs, key, val);
}

/* Read integer parameter from file header */
int cmpack_ccd_gkyl(CmpackCcdFile *fs, const char *key, int *val)
{
	if (!fs->fmt)
		return CMPACK_ERR_CLOSED_FILE;
	else if (!fs->fmt->get_bool)
		return CMPACK_ERR_NOT_IMPLEMENTED;
	else
		return fs->fmt->get_bool(fs->handle, key, val);
}

/* Read parameter by its index */
int cmpack_ccd_get_param(CmpackCcdFile *fs, int index, char **key, char **val, char **com)
{
	if (!fs->fmt)
		return CMPACK_ERR_CLOSED_FILE;
	else if (!fs->fmt->get_param)
		return CMPACK_ERR_NOT_IMPLEMENTED;
	else 
		return fs->fmt->get_param(fs->handle, index, key, val, com);
}

/* Get image width */
int cmpack_ccd_width(CmpackCcdFile *fs)
{
	int w, h;

	if (fs && fs->fmt && fs->fmt->get_size && fs->fmt->get_size(fs->handle, &w, &h)==0)
		return w;
	return 0;
}

/* Get image height */
int cmpack_ccd_height(CmpackCcdFile *fs)
{
	int w, h;

	if (fs && fs->fmt && fs->fmt->get_size && fs->fmt->get_size(fs->handle, &w, &h)==0)
		return h;
	return 0;
}

/* Get image format */
CmpackBitpix cmpack_ccd_bitpix(CmpackCcdFile *fs)
{
	if (fs && fs->fmt && fs->fmt->get_bitpix)
		return fs->fmt->get_bitpix(fs->handle);
	return CMPACK_BITPIX_UNKNOWN;
}

/* Read image data from a file */
int cmpack_ccd_to_image(CmpackCcdFile *fs, CmpackBitpix dst_format, CmpackImage **image)
{
	int res, width = 0, height = 0;
	CmpackImage *dst;
	CmpackBitpix src_format;
	
	/* Check parameters */
	if (!fs->fmt)
		return CMPACK_ERR_CLOSED_FILE;
	if (!fs->fmt->get_size || !fs->fmt->get_bitpix || !fs->fmt->get_image)
		return CMPACK_ERR_NOT_IMPLEMENTED;
	res = fs->fmt->get_size(fs->handle, &width, &height);
	if (res!=0 || width<=0 || height<=0 || width>=65536 || height>=65536)
		return CMPACK_ERR_INVALID_SIZE;
	src_format = fs->fmt->get_bitpix(fs->handle);
	if (src_format == CMPACK_BITPIX_UNKNOWN)
		return CMPACK_ERR_INVALID_BITPIX;

	dst = cmpack_image_new(width, height, src_format);
	res = fs->fmt->get_image(fs->handle, cmpack_image_data(dst), 
			cmpack_image_size(width, height, src_format), fs->channel);
	if (res!=0) {
		cmpack_image_destroy(dst);
		return res;
	}
	if (dst_format != CMPACK_BITPIX_UNKNOWN && src_format != dst_format) {
		CmpackImage *tmp = cmpack_image_convert(dst, dst_format);
		cmpack_image_destroy(dst);
		dst = tmp;
	}

	*image = dst;
	return 0;
}

/* Set image data */
int cmpack_ccd_set_image(CmpackCcdFile *fd, const CmpackImage *img)
{
	int width, height;

	if (!fd->fmt)
		return CMPACK_ERR_CLOSED_FILE;
	if (!fd->fmt->get_size || !fd->fmt->put_image)
		return CMPACK_ERR_NOT_IMPLEMENTED;

	if (fd->fmt->get_size(fd->handle, &width, &height)!=0) {
		ccd_prepare(fd, cmpack_image_width(img), cmpack_image_height(img),
			cmpack_image_bitpix(img));
	}

	/* Init output image */
	return ccd_write_image(fd, img);
}

/* Copy entire file */
int cmpack_ccd_copy(CmpackCcdFile *fd, CmpackCcdFile *fs, CmpackConsole *con)
{
	int res;
	CmpackImage *img;
	
	if (!fs->fmt || !fd->fmt)
		return CMPACK_ERR_CLOSED_FILE;
	if (!fs->fmt->get_size || !fs->fmt->get_bitpix || !fs->fmt->get_image ||
		!fd->fmt->restore_header || !fd->fmt->put_image)
		return CMPACK_ERR_NOT_IMPLEMENTED;

	/* Init output image */
	res = ccd_prepare(fd, cmpack_ccd_width(fs), cmpack_ccd_height(fs), cmpack_ccd_bitpix(fs));
	if (res!=0) 
		return res;

	/* Copy image header */
	res = ccd_copy_header(fd, fs, con, 0);
	if (res!=0)
		return res;

	/* Copy image data */
	res = cmpack_ccd_to_image(fs, CMPACK_BITPIX_AUTO, &img);
	if (res!=0)
		return res;

	res = ccd_write_image(fd, img);
	cmpack_image_destroy(img);
	return res;
}

/* Save image header */
int ccd_save_header(CmpackCcdFile *src, CmpackImageHeader *dst, CmpackConsole *con)
{
	if (!src->fmt)
		return CMPACK_ERR_CLOSED_FILE;
	if (!src->fmt->copy_header)
		return CMPACK_ERR_NOT_IMPLEMENTED;

	return src->fmt->copy_header(src->handle, dst, src->channel, con);
}

/* Save image header */
int ccd_restore_header(CmpackCcdFile *dst, CmpackImageHeader *src, CmpackConsole *con)
{
	if (!dst->fmt)
		return CMPACK_ERR_CLOSED_FILE;
	if (!dst->fmt->restore_header)
		return CMPACK_ERR_NOT_IMPLEMENTED;

	return dst->fmt->restore_header(dst->handle, src, con);
}

/* Copy entire file */
int ccd_copy_header(CmpackCcdFile *fd, CmpackCcdFile *fs, CmpackConsole *con, int wcs)
{
	int res = 0;

	if (!fs->fmt)
		return CMPACK_ERR_CLOSED_FILE;

	/* Copy image header */
	if (fs->fmt->copy_header && fd->fmt->restore_header) {
		CmpackImageHeader hdr;
		cmpack_image_header_init(&hdr);
		res = fs->fmt->copy_header(fs->handle, &hdr, fs->channel, con);
		if (wcs!=0) {
			CmpackImageHeader tmp;
			cmpack_image_header_init(&tmp);
			cmpack_wcs_update_header(&hdr, &tmp, wcs);
			cmpack_image_header_copy(&tmp, &hdr);
			cmpack_image_header_destroy(&tmp);
		}
		if (res==0)
			res = fd->fmt->restore_header(fd->handle, &hdr, con);
		cmpack_image_header_destroy(&hdr);
	}

	return res;
}

/* Append text to the history */
void ccd_update_history(CmpackCcdFile *fs, const char *text)
{
	if (fs->fmt && fs->fmt->put_history)
		fs->fmt->put_history(fs->handle, text);
}

/* Set image parameters */
int ccd_prepare(CmpackCcdFile *fs, int nx, int ny, CmpackBitpix bitpix)
{
	if (!fs->fmt)
		return CMPACK_ERR_CLOSED_FILE;
	else if (!fs->fmt->prepare)
		return CMPACK_ERR_NOT_IMPLEMENTED;
	else if (nx<=0 || ny<=0 || nx>=65536 || ny>=65536)
		return CMPACK_ERR_INVALID_SIZE;
	else if (pixformat(bitpix)==NULL)
		return CMPACK_ERR_INVALID_BITPIX;
	else
		return fs->fmt->prepare(fs->handle, nx, ny, bitpix);
}

/* Set image data */
int ccd_write_image(CmpackCcdFile *fs, const CmpackImage *image)
{
	if (!fs->fmt)
		return CMPACK_ERR_CLOSED_FILE;
	else if (!fs->fmt->put_image)
		return CMPACK_ERR_NOT_IMPLEMENTED;

	return fs->fmt->put_image(fs->handle, image);
}

/* Set parameter (string value) */
void ccd_set_str(CmpackCcdFile *fs, const char *key, const char *value, const char *comment)
{
	if (fs->fmt && fs->fmt->set_str)
		fs->fmt->set_str(fs->handle, key, value, comment);
}

/* Set parameter (integer value) */
void ccd_set_int(CmpackCcdFile *fs, const char *key, int value, const char *comment)
{
	if (fs->fmt && fs->fmt->set_int)
		fs->fmt->set_int(fs->handle, key, value, comment);
}

/* Set parameter (real value) */
void ccd_set_dbl(CmpackCcdFile *fs, const char *key, double value, int prec, const char *comment)
{
	if (fs->fmt && fs->fmt->set_dbl)
		fs->fmt->set_dbl(fs->handle, key, value, prec, comment);
}

/* Set parameter (logic value) */
void ccd_set_bool(CmpackCcdFile *fs, const char *key, int value, const char *comment)
{
	if (fs->fmt && fs->fmt->set_bool)
		fs->fmt->set_bool(fs->handle, key, value, comment);
}

/* Set CCD frame parameters */
int cmpack_ccd_get_params(CmpackCcdFile *fs, unsigned mask, CmpackCcdParams *params)
{
	double val;
	int w, h;
	CmpackDateTime dt;

	if (mask & CMPACK_CM_FORMAT) {
		if (!(fs->cache_flags & CACHE_MAGIC) && fs->fmt->get_magic) {
			fs->magic = fs->fmt->get_magic(fs->handle);
			fs->cache_flags |= CACHE_MAGIC;
		}
		params->format_id = fs->fmt->format;
		params->format_name = fs->magic;
	}
	if (mask & CMPACK_CM_IMAGE) {
		/* Image dimensions */
		if (fs->fmt->get_size && fs->fmt->get_size(fs->handle, &w, &h)==0) {
			params->image_width = w;
			params->image_height = h;
		} else {
			params->image_width = params->image_height = 0;
		}
		/* Native image data format */
		if (fs->fmt->get_bitpix) 
			params->image_format = fs->fmt->get_bitpix(fs->handle);
		else 
			params->image_format = CMPACK_BITPIX_UNKNOWN;
	}
	if (mask & CMPACK_CM_WORKFORMAT) {
		/* Standard FITS format? */
		if (fs->fmt->working_format)
			params->working_format = fs->fmt->working_format(fs->handle);
		else
			params->working_format = 0;
	}
	if (mask & (CMPACK_CM_DATETIME | CMPACK_CM_JD)) {
		memset(&dt, 0, sizeof(CmpackDateTime));
		if (fs->fmt->get_datetime && fs->fmt->get_datetime(fs->handle, &dt)==0) {
			if (mask & CMPACK_CM_DATETIME)
				params->date_time = dt;
			if (mask & CMPACK_CM_JD)
				params->jd = cmpack_encodejd(&dt);
		} else {
			params->date_time = dt;
			params->jd = 0.0;
		}
	}
	if (mask & CMPACK_CM_EXPOSURE) {
		if (fs->fmt->get_exptime && fs->fmt->get_exptime(fs->handle, &val)==0) 
			params->exposure = val;
		else
			params->exposure = 0;
	}
	if (mask & CMPACK_CM_CCDTEMP) {
		if (fs->fmt->get_ccdtemp && fs->fmt->get_ccdtemp(fs->handle, &val)==0)
			params->ccdtemp = val;
		else
			params->ccdtemp = INVALID_TEMP;
	}
	if (mask & CMPACK_CM_FILTER) {
		if (!(fs->cache_flags & CACHE_FILTER) && fs->fmt->get_filter) {
			fs->filter = fs->fmt->get_filter(fs->handle, fs->channel);
			cmpack_str_trim(fs->filter);
			fs->cache_flags |= CACHE_FILTER;
		}
		params->filter = fs->filter;
	}
	if (mask & CMPACK_CM_OBSERVER) {
		if (!(fs->cache_flags & CACHE_OBSERVER) && fs->fmt->get_observer) {
			fs->observer = fs->fmt->get_observer(fs->handle);
			cmpack_str_trim(fs->observer);
			fs->cache_flags |= CACHE_OBSERVER;
		}
		params->observer = fs->observer;
	}
	if (mask & CMPACK_CM_TELESCOPE) {
		if (!(fs->cache_flags & CACHE_TELESCOPE) && fs->fmt->get_telescope) {
			fs->telescope = fs->fmt->get_telescope(fs->handle);
			cmpack_str_trim(fs->telescope);
			fs->cache_flags |= CACHE_TELESCOPE;
		}
		params->telescope = fs->telescope;
	}
	if (mask & CMPACK_CM_INSTRUMENT) {
		if (!(fs->cache_flags & CMPACK_CM_INSTRUMENT) && fs->fmt->get_instrument) {
			fs->instrument = fs->fmt->get_instrument(fs->handle);
			cmpack_str_trim(fs->instrument);
			fs->cache_flags |= CACHE_INSTRUMENT;
		}
		params->instrument = fs->instrument;
	}
	if (mask & CMPACK_CM_OBJECT) {
		if (!(fs->cache_flags & CACHE_OBJECT)) {
			if (fs->fmt->get_object) {
				fs->obj.designation = fs->fmt->get_object(fs->handle);
				cmpack_str_trim(fs->obj.designation);
			}
			if (fs->fmt->get_objra) {
				char *ra = fs->fmt->get_objra(fs->handle);
				fs->obj.ra_valid = cmpack_strtora(ra, &fs->obj.ra)==0;
				cmpack_free(ra);
			}
			if (fs->fmt->get_objdec) {
				char *dec = fs->fmt->get_objdec(fs->handle);
				fs->obj.dec_valid = cmpack_strtodec(dec, &fs->obj.dec)==0;
				cmpack_free(dec);
			}
			fs->cache_flags |= CACHE_OBJECT;
		}
		params->object = fs->obj;
	}
	if (mask & CMPACK_CM_LOCATION) {
		if (!(fs->cache_flags & CACHE_LOCATION)) {
			if (fs->fmt->get_observatory) {
				fs->loc.designation = fs->fmt->get_observatory(fs->handle);
				cmpack_str_trim(fs->loc.designation);
			}
			if (fs->fmt->get_obslon) {
				char *lon = fs->fmt->get_obslon(fs->handle);
				fs->loc.lon_valid = cmpack_strtolon(lon, &fs->loc.lon)==0;
				cmpack_free(lon);
			}
			if (fs->fmt->get_obslat) {
				char *lat = fs->fmt->get_obslat(fs->handle);
				fs->loc.lat_valid = cmpack_strtolat(lat, &fs->loc.lat)==0;
				cmpack_free(lat);
			}
			fs->cache_flags |= CACHE_LOCATION;
		}
		params->location = fs->loc;
	}
	if (mask & CMPACK_CM_SUBFRAMES) {
		params->subframes_avg = params->subframes_sum = 1;
		if (fs->fmt->get_frames)
			fs->fmt->get_frames(fs->handle, fs->channel, &params->subframes_avg, &params->subframes_sum);
	}
	return CMPACK_ERR_OK;
}

/* Set CCD frame parameters */
int cmpack_ccd_set_params(CmpackCcdFile *fs, unsigned mask, const CmpackCcdParams *params)
{
	int res = 0;
	CmpackDateTime dt;

	if (res==0 && (mask & (CMPACK_CM_DATETIME | CMPACK_CM_JD))) {
		if (fs->fmt->set_datetime) {
			if (mask & CMPACK_CM_DATETIME) 
				dt = params->date_time;
			else
				res = cmpack_decodejd(params->jd, &dt);
			if (res==0)
				res = fs->fmt->set_datetime(fs->handle, &dt);
		} else 
			res = CMPACK_ERR_NOT_IMPLEMENTED;
	}
	if (res==0 && (mask & CMPACK_CM_EXPOSURE)) {
		if (fs->fmt->set_exptime)
			fs->fmt->set_exptime(fs->handle, params->exposure);
		else
			res = CMPACK_ERR_NOT_IMPLEMENTED;
	}
	if (res==0 && (mask & CMPACK_CM_CCDTEMP)) {
		if (fs->fmt->set_ccdtemp)
			fs->fmt->set_ccdtemp(fs->handle, params->ccdtemp);
		else
			res = CMPACK_ERR_NOT_IMPLEMENTED;
	}
	if (res==0 && (mask & CMPACK_CM_FILTER)) 
		res = CMPACK_ERR_NOT_IMPLEMENTED;
	if (res==0 && (mask & CMPACK_CM_OBSERVER))
		res = CMPACK_ERR_NOT_IMPLEMENTED;
	if (res==0 && (mask & CMPACK_CM_TELESCOPE))
		res = CMPACK_ERR_NOT_IMPLEMENTED;
	if (res==0 && (mask & CMPACK_CM_INSTRUMENT))
		res = CMPACK_ERR_NOT_IMPLEMENTED;
	if (res==0 && (mask & CMPACK_CM_OBJECT))
		res = CMPACK_ERR_NOT_IMPLEMENTED;
	if (res==0 && (mask & CMPACK_CM_LOCATION)) 
		res = CMPACK_ERR_NOT_IMPLEMENTED;
	if (res==0 && (mask & CMPACK_CM_SUBFRAMES)) {
		if (fs->fmt->set_frames)
			fs->fmt->set_frames(fs->handle, params->subframes_avg, params->subframes_sum);
		else
			res = CMPACK_ERR_NOT_IMPLEMENTED;
	}
	return res;
}

/* Write package name and version to file header */
void ccd_set_origin(CmpackCcdFile *fs)
{
	char val[FLEN_VALUE];

	sprintf(val, "%s %s", CMAKE_PROJECT_NAME, VERSION);
	ccd_set_str(fs, "ORIGIN", val, "CREATOR NAME AND VERSION");
}

/* Write date of creation */
void ccd_set_pcdate(CmpackCcdFile *fs)
{
	time_t tsec;
	struct tm *actim;
	char val[FLEN_VALUE];

	tsec = time(&tsec);
	actim = localtime(&tsec);
	sprintf(val,"%04d-%02d-%02d",actim->tm_year+1900,actim->tm_mon+1,actim->tm_mday);
 	ccd_set_str(fs, "DATE", val, "DATE OF CREATION");
}

/* Read World Coordinate System (WCS) data from the file */
int cmpack_ccd_get_wcs(CmpackCcdFile *fs, CmpackWcs **wcs)
{
	if (!(fs->cache_flags & CACHE_WCS) && fs->fmt->get_wcs) {
		if (fs->wcs) {
			cmpack_wcs_destroy(fs->wcs);
			fs->wcs = NULL;
		}
		fs->fmt->get_wcs(fs->handle, &fs->wcs);
		fs->cache_flags |= CACHE_WCS;
	}
	if (fs->wcs) {
		*wcs = fs->wcs;
		return CMPACK_ERR_OK;
	} else {
		*wcs = NULL;
		return CMPACK_ERR_UNDEF_VALUE;
	}
}
