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

konvfits.c (C-Munipack project)
Conversion functions for FITS files
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 <string.h>
#include <time.h>
#include <stdint.h>
#include <float.h>
#include <fitsio.h>

#include "comfun.h"
#include "console.h"
#include "trajd.h"
#include "config.h"
#include "image.h"
#include "cmpack_common.h"
#include "konvfits.h"
#include "wcsobj.h"

struct _CmpackFitsFile
{
	fitsfile *fits;
	int status;
	char *buffer;
	size_t bufsize;
	CmpackBitpix bitpix;
};
typedef struct _CmpackFitsFile CmpackFitsFile;

/* Realloc procedure */
static void *realloc_proc(void *ptr, size_t len)
{
	return cmpack_realloc(ptr, len);
}

/* Reads date */
static int getdatetime(fitsfile *fits, CmpackDateTime *dt, int *end, int *center)
{
	int x, status, date_ok, time_ok;
	char datestr[FLEN_VALUE], timestr[FLEN_VALUE], val[FLEN_VALUE], com[FLEN_COMMENT], *ptr;

	memset(dt, 0, sizeof(CmpackDateTime));
	*end = *center = 0;

	status = 0;
	ffgkys(fits,"DATE-OBS",datestr,NULL,&status);
    if (status!=0) { strcpy(datestr,""); status=0; }
	if (*datestr == '\0') {
		/* D02E-OBS= 'dd/mm/yy' */
		ffgkys(fits, "D02E-OBS", datestr, NULL, &status);
		if (status != 0) { strcpy(datestr, ""); status = 0; }
	}
	if (*datestr == '\0') {
		/* DATE-BEG= '2005-08-24T20:19:03' */
		ffgkys(fits, "DATE-BEG", datestr, NULL, &status);
		if (status != 0) { strcpy(datestr, ""); status = 0; }
	}
	if (*datestr == '\0') {
		/* DATE_OBS= '2005-08-24T20:19:03' */
		ffgkys(fits, "DATE_OBS", datestr, NULL, &status);
		if (status != 0) { strcpy(datestr, ""); status = 0; }
	}
	if (*datestr == '\0') {
		/* DATE-LOC= '2005-08-24T20:19:03.0' */
		ffgkys(fits, "DATE-LOC", datestr, NULL, &status);
		if (status != 0) { strcpy(datestr, ""); status = 0; }
	}
	if (*datestr == '\0') {
		/* BJD     =     2454358.79535969 / Beginning Julian date  */
		double jd = 0;
		ffgkyd(fits, "BJD", &jd, NULL, &status);
		if (status != 0) { strcpy(datestr, ""); status = 0; } 
		else if (cmpack_decodejd(jd, dt) == 0) 
			return 1;
	}
	if (*datestr == '\0') {
		/* DATE    = '30.12.2015' */
		/* According to the FITS standard, this field indicates the date and time
		   of the file creating. QHY9 and QHY8L cameras uses this field to indicate date of observation.
		   Use the 'DATE' field here only if the CAMERA is QHY9 or QHY8L */
		ffgkys(fits, "DATE", datestr, com, &status);
		if (status == 0)
			ffgkys(fits, "CAMERA", val, NULL, &status);
		if (status != 0 || strcmp(com, "Capture Date") != 0 || (strcmp(val, "QHY9") != 0 && strcmp(val, "QHY8L") != 0)) { strcpy(datestr, ""); status = 0; }
	}
	if (*datestr == '\0') {
		/* CTRLTIME= '2017-02-28 06:15:40.003439' */
		ffgkys(fits, "CTRLTIME", datestr, NULL, &status);
		if (status != 0) { strcpy(datestr, ""); status = 0; }
	}

	ptr = datestr;
	while (*ptr==' ')
		ptr++;
    
    date_ok = 0;
	if ((strlen(ptr) == 8) && (ptr[2] == '/') && (ptr[5] == '/')) {
		/* dd/mm/yy */
		dt->date.year = 2000 + atoi(ptr + 6);
		dt->date.month = atoi(ptr + 3);
		dt->date.day = atoi(ptr);
		date_ok = 1;
	}
	else if (strlen(ptr) == 10 && ptr[2] == '/' && ptr[5] == '/') {
		/* dd/mm/yyyy */
		dt->date.year = atoi(ptr + 6);
		dt->date.month = atoi(ptr + 3);
		dt->date.day = atoi(ptr);
		date_ok = 1;
	}
	else if (strlen(ptr) >= 10 && ptr[4] == '-' && ptr[7] == '-') {
		/* yyyy-mm-dd */
		dt->date.year = atoi(ptr);
		dt->date.month = atoi(ptr + 5);
		dt->date.day = atoi(ptr + 8);
		date_ok = 1;
	}
	else if (strlen(ptr) >= 10 && ptr[4] == '/' && ptr[7] == '/') {
		/* yyyy/mm/dd */
		dt->date.year = atoi(ptr);
		dt->date.month = atoi(ptr + 5);
		dt->date.day = atoi(ptr + 8);
		date_ok = 1;
	}
	else if (strlen(ptr) == 8 && ptr[1] == '.' && ptr[3] == '.') {
		/* d.m.yyyy */
		dt->date.day = atoi(ptr);
		dt->date.month = atoi(ptr + 2);
		dt->date.year = atoi(ptr + 4);
		date_ok = 1;
	}
	else if (strlen(ptr) == 9 && ptr[2] == '.' && ptr[4] == '.') {
		/* dd.m.yyyy */
		dt->date.day = atoi(ptr);
		dt->date.month = atoi(ptr + 3);
		dt->date.year = atoi(ptr + 5);
		date_ok = 1;
	}
	else if (strlen(ptr) == 9 && ptr[1] == '.' && ptr[4] == '.') {
		/* d.mm.yyyy */
		dt->date.day = atoi(ptr);
		dt->date.month = atoi(ptr + 2);
		dt->date.year = atoi(ptr + 5);
		date_ok = 1;
	}
	else if (strlen(ptr) == 10 && ptr[2] == '.' && ptr[5] == '.') {
		/* dd.mm.yyyy */
		dt->date.day = atoi(ptr);
		dt->date.month = atoi(ptr + 3);
		dt->date.year = atoi(ptr + 6);
		date_ok = 1;
	}
	else if (strlen(ptr) >= 14 && ptr[3] == ' ' && ptr[8] == '-' && ptr[11] == '-') {
		/* DDD yyyy-mm-dd */
		dt->date.year = atoi(ptr + 4);
		dt->date.month = atoi(ptr + 9);
		dt->date.day = atoi(ptr + 12);
		date_ok = 1;
	}

	status = 0;
	ffgkys(fits,"TIME-OBS",timestr,NULL,&status);
	if (status!=0) { strcpy(timestr,""); status=0; }
	if (*timestr == '\0') {
		/* DATE-BEG= '2005-08-24T20:19:03' */
		ffgkys(fits, "DATE-BEG", datestr, NULL, &status);
		if (status != 0) { strcpy(datestr, ""); status = 0; }
		if (strlen(datestr) >= 23 && datestr[3] == ' ' && datestr[8] == '-' && datestr[11] == '-' && datestr[14] == ' ') {
			/* DDD yyyy-mm-dd hh:mm:ss.sss */
			strcpy(timestr, datestr + 15);
		}
		else {
			ptr = strchr(datestr, 'T');
			if (ptr) {
				/* yyyy-mm-ddThh:mm:ss.sss */
				strcpy(timestr, ptr + 1);
			}
		}
	}
	if (*timestr == '\0') {
		/* DATE-OBS= '2004-03-20T22:55:28.014' / GMT START OF EXPOSURE */
		ffgkys(fits, "DATE-OBS", datestr, NULL, &status);
		if (status != 0) { strcpy(datestr, ""); status = 0; }
		if (strlen(datestr) >= 23 && datestr[3] == ' ' && datestr[8] == '-' && datestr[11] == '-' && datestr[14] == ' ') {
			/* DDD yyyy-mm-dd hh:mm:ss.sss */
			strcpy(timestr, datestr + 15);
		}
		else {
			ptr = strchr(datestr, 'T');
			if (ptr) {
				/* yyyy-mm-ddThh:mm:ss.sss */
				strcpy(timestr, ptr + 1);
			}
		}
	}
	if (*timestr == '\0') {
		/* DATE_OBS= ' 2004-03-20T22:55:28.014' / GMT START OF EXPOSURE */
		ffgkys(fits, "DATE_OBS", datestr, NULL, &status);
		if (status != 0) { strcpy(datestr, ""); status = 0; }
		if (strlen(datestr) >= 23 && datestr[3] == ' ' && datestr[8] == '-' && datestr[11] == '-' && datestr[14] == ' ') {
			/* DDD yyyy-mm-dd hh:mm:ss.sss */
			strcpy(timestr, datestr + 15);
		}
		else {
			ptr = strchr(datestr, 'T');
			if (ptr) {
				/* yyyy-mm-ddThh:mm:ss.sss */
				strcpy(timestr, ptr + 1);
			}
		}
	}
	if (*timestr == '\0') {
		/* DATE-LOC= ' 2004-03-20T22:55:28.0' / GMT START OF EXPOSURE */
		ffgkys(fits, "DATE-LOC", datestr, NULL, &status);
		if (status != 0) { strcpy(datestr, ""); status = 0; }
		if (strlen(datestr) >= 23 && datestr[3] == ' ' && datestr[8] == '-' && datestr[11] == '-' && datestr[14] == ' ') {
			/* DDD yyyy-mm-dd hh:mm:ss.sss */
			strcpy(timestr, datestr + 15);
		}
		else {
			ptr = strchr(datestr, 'T');
			if (ptr) {
				/* yyyy-mm-ddThh:mm:ss.sss */
				strcpy(timestr, ptr + 1);
			}
		}
	}
	if (*timestr == '\0') {
		/* TM-START=  1.3079446944444E+01 / [Corrected] UT obs'n start, real hours */
		ffgkys(fits, "TM-START", timestr, NULL, &status);
		if (status != 0) { strcpy(timestr, ""); status = 0; }
	}
	if (*timestr == '\0') {
		/* REC-STRT= '13:04:46'           / Recorded UT time of observation start */
		ffgkys(fits, "REC-STRT", timestr, NULL, &status);
		if (status != 0) { strcpy(timestr, ""); status = 0; }
	}
	if (*timestr == '\0') {
		/* CTRLTIME= '2017-02-28 06:15:40.003439' */
		ffgkys(fits, "CTRLTIME", datestr, NULL, &status);
		if (status != 0) { strcpy(datestr, ""); status = 0; }
		ptr = strchr(datestr, ' ');
		if (ptr) {
			strcpy(timestr, ptr + 1);
			*end = 1;
		}
	}
	if (*timestr == '\0') {
		/* UT      = '00:22:10.913' */
		ffgkys(fits, "UT", timestr, com, &status);
		if (status != 0) { strcpy(timestr, ""); strcpy(com, ""); status = 0; }
		if (!strchr(timestr, '.') && !strchr(timestr, ':') && !strchr(timestr, ' ') && !strstr(com, "hours") && strlen(com) >= 8 && com[2] == '/' && com[5] == '/') {
			/* UT      =                74680 / 20/44/40           UT start time           */
			char* endptr = NULL;
			unsigned sec = strtoul(timestr, &endptr, 10);
			if (*endptr == '\0')
				sprintf(timestr, "%.8f", sec / 3600.);
		}
	}
	if (*timestr == '\0') {
		/* UT-OBS   = '00:22:10.913' */
		ffgkys(fits, "UT-OBS", timestr, NULL, &status);
		if (status != 0) { strcpy(timestr, ""); status = 0; }
	}
	if (*timestr == '\0') {
		/* UT_END  = '19:33:59'				/ PMIS Image Processing Software */
		ffgkys(fits, "UT_END", timestr, NULL, &status);
		if (status != 0) { strcpy(timestr, ""); status = 0; }
		else *end = 1;
	}
	if (*timestr == '\0') {
		/* UT_START = '22:10:19.12' */
		ffgkys(fits, "UT-START", timestr, NULL, &status);
		if (status != 0) { strcpy(timestr, ""); status = 0; }
	}
	if (*timestr == '\0') {
		/* UTSTART = '22:10:19.12' */
		ffgkys(fits, "UTSTART", timestr, NULL, &status);
		if (status != 0) { strcpy(timestr, ""); status = 0; }
	}
	if (*timestr == '\0') {
		/* UTSTART = '22:10:19.12' */
		ffgkys(fits, "UTMIDDLE", timestr, NULL, &status);
		if (status != 0) { strcpy(timestr, ""); status = 0; }
		else *center = 1;
	}
	if (*timestr == '\0') {
		/* UTC     = '01:57:57'           / Exposure start time */
		ffgkys(fits, "UTC", timestr, NULL, &status);
		if (status != 0) { strcpy(timestr, ""); status = 0; }
	}
	if (*timestr == '\0') {
		/* TIME    = '22:10:19' */
		/* QHY9 camera uses this field to indicate date of observation together with
		   the 'DATE' field. This is against FITS standard. Use the 'TIME' field here only if the CAMERA is QHY9 */
		ffgkys(fits, "TIME", timestr, com, &status);
		if (status == 0)
			ffgkys(fits, "CAMERA", val, NULL, &status);
		if (status != 0 || strcmp(com, "Begin captuer time") != 0 || (strcmp(val, "QHY9") != 0 && strcmp(val, "QHY8L") != 0)) { strcpy(timestr, ""); status = 0; }
	}

    time_ok = 0;
	if ((strlen(timestr) >= 8) && (((timestr[2] == ':') && (timestr[5] == ':')) || ((timestr[2] == '.') && (timestr[5] == '.')))) {
		/* HH:MM:SS.SSSS or HH.MM.SS.SSSS */
		dt->time.hour = atol(timestr);
		dt->time.minute = atol(timestr + 3);
		x = (int)(1000.0*atof(timestr + 6) + 0.5);
		dt->time.second = x / 1000;
		dt->time.milisecond = x % 1000;
		time_ok = 1;
	}
	if (!time_ok && (strlen(timestr) >= 7) && (((timestr[1] == ':') && (timestr[4] == ':')) || ((timestr[1] == '.') && (timestr[4] == '.')))) {
		/* H:MM:SS.SSSS or H.MM.SS.SSSS*/
		dt->time.hour = atol(timestr);
		dt->time.minute = atol(timestr + 2);
		x = (int)(1000.0*atof(timestr + 5) + 0.5);
		dt->time.second = x / 1000;
		dt->time.milisecond = x % 1000;
		time_ok = 1;
	}
	if (!time_ok && !strchr(timestr, ':') && !strchr(timestr, ' ') && strchr(timestr, '.')) {
		/* real value in days */
		double val = strtod(timestr, &ptr);
		if (ptr != timestr) {
			int msec = (int)(val*3600000.0 + 0.5);
			dt->time.hour = (msec / 3600000);
			dt->time.minute = (msec / 60000) % 60;
			dt->time.second = (msec / 1000) % 60;
			dt->time.milisecond = msec % 1000;
			time_ok = 1;
		}
	}
	
	return (date_ok && time_ok);
}

static int getexptime(fitsfile *fits, double *value)
{
	char buf[FLEN_VALUE], com[FLEN_COMMENT];
	int stat;

	*value=0.0;

	stat = 0;
	if (ffgkyd(fits, "EXPTIME", value, com, &stat)==0) {
		if (strstr(com, "[ms]"))
			*value = 0.001 * (*value);
		return 1;
	}
	stat = 0;
	if (ffgkys(fits, "EXPOSURE", buf, com, &stat)==0) { 
		*value = atof(buf); 
		if (strstr(com, "[ms]"))
			*value = 0.001 * (*value);
		else if (strstr(com, "millisecond"))
			*value = 0.001 * (*value);
		return 1; 
	}
	stat = 0;
	if (ffgkys(fits, "ITIME", buf, com, &stat)==0) { 
		*value = atof(buf); 
		return 1; 
	}

	return 0;
}

/* Unquote value and trim leading and trailing white spaces */
static char *unquote_and_trim(const char *str)
{
	static const char quote_char = '\'';

	int len = (int)strlen(str);
	if (len>=2) {
		if (str[0]==quote_char && str[len-1]==quote_char) {
			char *buf = (char*)cmpack_malloc((len-1)*sizeof(char));
			if (buf) {
				int state = 0;
				char *sptr = buf, *dptr = buf;
				memcpy(buf, str+1, len-2);
				buf[len-2] = '\0';
				while (*sptr!='\0' && state>=0) {
					char ch = *sptr;
					switch (state) 
					{
					case 0:
						/* Skip leading spaces */
						if (ch!=' ') {
							*dptr++ = ch;
							state = 1;
						}
						break;
					case 1:
						/* Two quote character are replaced by a single one */
						if (ch==quote_char)
							state = 2;
						else
							*dptr++ = ch;
						break;
					case 2:
						/* Second quote character */
						if (ch==quote_char) {
							*dptr++ = quote_char;
							state = 1;
						} else
							state = -1;
					}
					sptr++;
				}
				if (state>=0) {
					/* Trim trailing spaces */
					while (dptr>buf && *(dptr-1)==' ') 
						dptr--;
					*dptr = '\0';
				} else
					*buf = '\0';
			}
			return buf;
		}
	}
	return cmpack_strdup(str);
}

/* Returns nonzero if the file is of FITS format */
int fits_test(const char *block, size_t length, size_t filesize)
{
    return (filesize>2880 && memstr(block, "SIMPLE  =", length)==block);
}

/* Open a FITS file */
int fits_open(tHandle *handle, const char *filename, CmpackOpenMode flags)
{
	int readonly, imgtype;

	CmpackFitsFile *fs = (CmpackFitsFile*)cmpack_calloc(1, sizeof(CmpackFitsFile));
	if (flags == CMPACK_OPEN_CREATE) {
		remove(filename);
		ffdkinit(&fs->fits, filename, &fs->status);
	} else {
		readonly = (flags == CMPACK_OPEN_READONLY);
		ffdkopn(&fs->fits, filename, (readonly ? READONLY : READWRITE), &fs->status);
	}
	if (fs->status!=0) {
		cmpack_free(fs);
		*handle = NULL;
		return CMPACK_ERR_OPEN_ERROR;
	}

	if (flags != CMPACK_OPEN_CREATE) {
		ffgiet(fs->fits, &imgtype, &fs->status);
		switch (imgtype)
		{
		case BYTE_IMG:
		case USHORT_IMG:
			fs->bitpix = CMPACK_BITPIX_USHORT;
			break;
		case SHORT_IMG:
		case SBYTE_IMG:
			fs->bitpix = CMPACK_BITPIX_SSHORT;
			break;
		case LONG_IMG:
			fs->bitpix = CMPACK_BITPIX_SLONG;
			break;
		case ULONG_IMG:
			fs->bitpix = CMPACK_BITPIX_ULONG;
			break;
		case FLOAT_IMG:
			fs->bitpix = CMPACK_BITPIX_FLOAT;
			break;
		case DOUBLE_IMG:
			fs->bitpix = CMPACK_BITPIX_DOUBLE;
			break;
		}
		if (fs->bitpix==CMPACK_BITPIX_UNKNOWN) {
			ffclos(fs->fits, &fs->status);
			cmpack_free(fs);
			return CMPACK_ERR_READ_ERROR;
		}
	}

	*handle = fs;
	return 0;
}

/* Open a memory file */
tHandle fits_init(void)
{
	CmpackFitsFile *fs = (CmpackFitsFile*)cmpack_calloc(1, sizeof(CmpackFitsFile));
	if (ffimem(&fs->fits, (void**)&fs->buffer, &fs->bufsize, 0x4000, &realloc_proc, &fs->status)!=0) {
		cmpack_free(fs);
		return NULL;
	}
	return fs;
}

/* Close file and free allocated memory */
void fits_close(tHandle handle)
{
	CmpackFitsFile *fs = (CmpackFitsFile*)handle;
	if (fs) {
		if (fs->fits) 
			ffclos(fs->fits, &fs->status);
		cmpack_free(fs->buffer);
		cmpack_free(fs);
	}
}

/* Close file and free allocated memory */
fitsfile *fits_getfits(tHandle handle)
{
	return ((CmpackFitsFile*)handle)->fits;
}

/* Reads string value from header */
int fits_gkys(tHandle handle, const char *key, char **buf)
{
	CmpackFitsFile *fs = (CmpackFitsFile*)handle;
	char val[FLEN_VALUE];
	int status = 0;
	
	if (ffgkys(fs->fits, (char*) key, val, NULL, &status)==0) {
		if (buf) *buf = cmpack_strdup(val);
		return 0;
	} else {
		if (buf) *buf = NULL;
		return CMPACK_ERR_KEY_NOT_FOUND;
	}
}

/* Reads integer value from header */
int fits_gkyi(tHandle handle, const char *key, int *val)
{
	CmpackFitsFile *fs = (CmpackFitsFile*)handle;
	int status = 0;
	long value = 0;

	*val = 0;
	if (ffgkyj(fs->fits, (char*)key, &value, NULL, &status)) {
		*val = (int)value;
		return 0;
	}
	return CMPACK_ERR_KEY_NOT_FOUND;
}

/* Reads integer value from header */
int fits_gkyl(tHandle handle, const char *key, int *val)
{
	CmpackFitsFile *fs = (CmpackFitsFile*)handle;
	int status = 0;

	if (ffgkyl(fs->fits, (char*) key, val, NULL, &status)==0) {
		return 0;
	} else {
		*val = 0;
		return CMPACK_ERR_KEY_NOT_FOUND;
	}
}

int fits_getsize(tHandle handle, int *width, int *height)
{
	CmpackFitsFile *fs = (CmpackFitsFile*)handle;
	int status = 0, naxis = 0;
    long naxes[2];

	if (ffgidm(fs->fits, &naxis, &status)==0 && naxis==2 && ffgisz(fs->fits, 2, naxes, &status)==0) {
		if (width) *width = naxes[0];
		if (height) *height = naxes[1];
		return 0;
	} else {
		if (width) *width = 0;
		if (height) *height = 0;
		return CMPACK_ERR_INVALID_SIZE;
	}
}

/* Get image data format */
CmpackBitpix fits_getbitpix(tHandle handle)
{
	return ((CmpackFitsFile*)handle)->bitpix;
}

/* Is this standard FITS format? */
int fits_working_format(tHandle handle)
{
	int dt, et, status = 0;
	CmpackFitsFile *fs = (CmpackFitsFile*)handle;

	if (ffgidt(fs->fits, &dt, &status)==0 && ffgiet(fs->fits, &et, &status)==0) {
		switch (fs->bitpix)
		{
		case CMPACK_BITPIX_USHORT:
			return (dt==USHORT_IMG && et==USHORT_IMG);
		case CMPACK_BITPIX_SSHORT:
			return (dt==SHORT_IMG && et==SHORT_IMG);
		case CMPACK_BITPIX_SLONG:
			return (dt==LONG_IMG && et==LONG_IMG);
		case CMPACK_BITPIX_ULONG:
			return (dt==ULONG_IMG && et==ULONG_IMG);
		case CMPACK_BITPIX_FLOAT:
			return (dt==FLOAT_IMG && et==FLOAT_IMG);
		case CMPACK_BITPIX_DOUBLE:
			return (dt==DOUBLE_IMG && et==DOUBLE_IMG);
		default:
			return 0;
		}
	}
	return 0;
}

/* Get image data range*/
int fits_getrange(tHandle handle, double *minvalue, double *maxvalue)
{
	CmpackFitsFile *fs = (CmpackFitsFile*)handle;
	int status;
	long imgtype;
	double bscale, bzero, min_val, max_val;

	status = 0;
	if (ffgkyj(fs->fits, "BITPIX", &imgtype, NULL, &status)!=0)
		imgtype = 0;
    status = 0;
    if (ffgkyd(fs->fits, "BSCALE", &bscale, NULL, &status)!=0)
       bscale = 1.0;
    status = 0;
    if (ffgkyd(fs->fits, "BZERO", &bzero, NULL, &status)!=0)
       bzero = 0.0;

	switch (imgtype)
	{
	case BYTE_IMG:   
		min_val = 0.0;
		max_val = UINT8_MAX;
		break;
	case SHORT_IMG:
		min_val = INT16_MIN;
		max_val = INT16_MAX;
		break;
	case LONG_IMG:
		min_val = INT32_MIN;
		max_val = INT32_MAX;
		break;
	case FLOAT_IMG:
		min_val = -FLT_MAX;
		max_val = FLT_MAX;
		break;
	case DOUBLE_IMG:
		min_val = -DBL_MAX;
		max_val = DBL_MAX;
		break;
	default:
		min_val = 0.0;
		max_val = 0.0;
	}
	if (bscale >= 0.0) {
		if (minvalue)
			*minvalue = bzero + bscale * min_val;
		if (maxvalue)
			*maxvalue = bzero + bscale * max_val;
	} else {
		if (maxvalue)
			*maxvalue = bzero + bscale * min_val;
		if (minvalue)
			*minvalue = bzero + bscale * max_val;
	}
	return 0;
}

/* Get format identifier */
char *fits_getmagic(tHandle handle)
{
	return cmpack_strdup("FITS (Flexible Image Transport System)");
}

/* Get optical filter name */
char *fits_getfilter(tHandle handle, CmpackChannel channel)
{
	int status = 0;
	char value[FLEN_VALUE], buf[FLEN_VALUE*2+8];
	CmpackFitsFile *fs = (CmpackFitsFile*)handle;

	value[0] = '\0';
	if (ffgkys(fs->fits, "INSTRUME", value, NULL, &status)==0 && strcmp(value, "DFOSC_FASU")==0) {
		/* Frames from LaSilla by Milos Zejda */
		status = 0;
		if (ffgkys(fs->fits, "FILTA", value, NULL, &status)==0) {
			strcpy(buf, value);
			if (ffgkys(fs->fits, "FILTB", value, NULL, &status)==0) {
				strcat(buf, "+");
				strcat(buf, value);
			}
			return cmpack_strdup(buf);
		}
	}
	status = 0;
	if (ffgkys(fs->fits, "FILTER", value, NULL, &status)==0)
		return cmpack_strdup(value);
	status = 0;
	if (ffgkys(fs->fits, "FILTERS", value, NULL, &status)==0) 
		return cmpack_strdup(value);
	status = 0;
	
	return NULL;
}

/* Get object's designation */
char *fits_getobject(tHandle handle)
{
	int status;
	char object[FLEN_VALUE];
	CmpackFitsFile *fs = (CmpackFitsFile*)handle;

	status = 0;
	if (ffgkys(fs->fits, "OBJECT", object, NULL, &status)==0 && *object!='\0')
		return cmpack_strdup(object);
	status = 0;
	if (ffgkys(fs->fits, "NAME", object, NULL, &status)==0 && *object!='\0')
		return cmpack_strdup(object);

	return NULL;
}

/* Reads right ascension */
char *fits_getobjra(tHandle handle)
{
	int status, hours = 0;
	double val;
	char rastr[FLEN_VALUE], commstr[FLEN_COMMENT], buf[256], *endptr;
	CmpackFitsFile *fs = (CmpackFitsFile*)handle;

	status = 0;
	if (ffgkys(fs->fits, "RA", rastr, commstr, &status) != 0) {
		status = 0;
		if (ffgkys(fs->fits, "OBJCTRA", rastr, commstr, &status) != 0) {
			status = 0;
			if (ffgkys(fs->fits, "OBJRA", rastr, commstr, &status) != 0) {
				status = 0;
				ffgkys(fs->fits, "TEL-RA", rastr, commstr, &status);
			}
		}
	}
	if (status == 0 && *rastr != '\0') {
		/* If the CTRLTIME field is present, RA is in hours */
		char datestr[FLEN_VALUE];
		ffgkys(fs->fits, "CTRLTIME", datestr, NULL, &status);
		if (status == 0) hours = 1;
		else status = 0;
	}
	if (status == 0 && *rastr != '\0') {
		buf[0] = '\0';
		if (!strchr(rastr, ':') && !strchr(rastr, ' ') && strchr(rastr, '.')) {
			val = strtod(rastr, &endptr);
			if (!hours) {
				if (endptr != rastr && val >= 0.0 && val <= 360.0 && cmpack_ratostr(val / 15.0, buf, 256) == 0)
					return cmpack_strdup(buf);
			}
			else {
				if (endptr != rastr && val >= 0.0 && val <= 24.0 && cmpack_ratostr(val, buf, 256) == 0)
					return cmpack_strdup(buf);
			}
		}
		if (cmpack_strtora(rastr, &val) == 0 && val >= 0 && val <= 24.0 && cmpack_ratostr(val, buf, 256) == 0)
			return cmpack_strdup(buf);
	}
	return NULL;
}

/* Reads declination */
char *fits_getobjdec(tHandle handle)
{
	int status;
	double val;
	char decstr[FLEN_VALUE], buf[256], *endptr;
	CmpackFitsFile *fs = (CmpackFitsFile*)handle;

	status = 0;
	if (ffgkys(fs->fits, "DEC", decstr, NULL, &status) != 0) {
		status = 0;
		if (ffgkys(fs->fits, "OBJCTDEC", decstr, NULL, &status) != 0) {
			status = 0;
			if (ffgkys(fs->fits, "OBJDEC", decstr, NULL, &status) != 0) {
				status = 0;
				ffgkys(fs->fits, "TEL-DEC", decstr, NULL, &status);
			}
		}
	}
	if (status == 0 && *decstr != '\0') {
		buf[0] = '\0';
		if (!strchr(decstr, ':') && !strchr(decstr, ' ') && strchr(decstr, '.')) {
			val = strtod(decstr, &endptr);
			if (endptr != decstr && val >= -90 && val <= 90 && cmpack_dectostr(val, buf, 256) == 0)
				return cmpack_strdup(buf);
		}
		if (cmpack_strtodec(decstr, &val) == 0 && val >= -90 && val <= 90 && cmpack_dectostr(val, buf, 256) == 0)
			return cmpack_strdup(buf);
	}
	return NULL;
}

/* Get observer's name */
char *fits_getobserver(tHandle handle)
{
	int status;
	char value[FLEN_VALUE];
	CmpackFitsFile *fs = (CmpackFitsFile*)handle;

	status = 0;
	if (ffgkys(fs->fits, "OBSERVER", value, NULL, &status)==0 && *value!='\0')
		return cmpack_strdup(value);
	return NULL;
}

/* Get observer's name */
char *fits_getobservatory(tHandle handle)
{
	int status;
	char value[FLEN_VALUE];
	CmpackFitsFile *fs = (CmpackFitsFile*)handle;

	status = 0;
	if (ffgkys(fs->fits, "OBSERVAT", value, NULL, &status)==0 && *value!='\0')
		return cmpack_strdup(value);
	status = 0;
	if (ffgkys(fs->fits, "OBS", value, NULL, &status)==0 && *value!='\0')
		return cmpack_strdup(value);

	return NULL;
}

/* Get telescope designation */
char *fits_gettelescope(tHandle handle)
{
	int status;
	char value[FLEN_VALUE];
	CmpackFitsFile *fs = (CmpackFitsFile*)handle;

	status = 0;
	if (ffgkys(fs->fits, "TELESCOP", value, NULL, &status)==0 && *value!='\0')
		return cmpack_strdup(value);
	return NULL;
}

/* Get camera designation */
char *fits_getinstrument(tHandle handle)
{
	int status;
	char value[FLEN_VALUE];
	CmpackFitsFile *fs = (CmpackFitsFile*)handle;

	status = 0;
	if (ffgkys(fs->fits, "INSTRUME", value, NULL, &status)==0 && *value!='\0')
		return cmpack_strdup(value);
	return NULL;
}

/* Reads longitude */
char *fits_getobslon(tHandle handle)
{
	int status;
	double val;
	char lonstr[FLEN_VALUE], commstr[FLEN_COMMENT], buf[256];
	CmpackFitsFile *fs = (CmpackFitsFile*)handle;

	status = 0;
    if (ffgkys(fs->fits,"LONGITUD", lonstr, commstr, &status)!=0) {
	    status = 0;
		if (ffgkys(fs->fits, "GEOLON", lonstr, commstr, &status) != 0) {
			status = 0;
			ffgkys(fs->fits, "SITELONG", lonstr, commstr, &status);
		}
    }
	if (status==0 && *lonstr!='\0') {
		if (cmpack_strtolon(lonstr, &val) == 0) {
			if (strstr(commstr, "hours"))
				val *= 15.0;
			cmpack_lontostr(val, buf, 256);
			return cmpack_strdup(buf);
		}
	}
	return NULL;
}

/* Reads latitude */
char *fits_getobslat(tHandle handle)
{
	int status;
	double val;
	char latstr[FLEN_VALUE], buf[256];
	CmpackFitsFile *fs = (CmpackFitsFile*)handle;

	status = 0;
    if (ffgkys(fs->fits, "LATITUDE", latstr, NULL, &status)!=0) { 
	    status = 0;
		if (ffgkys(fs->fits, "GEOLAT", latstr, NULL, &status) != 0) {
			status = 0;
			ffgkys(fs->fits, "SITELAT", latstr, NULL, &status);
		}
    }
	if (status==0 && *latstr!='\0') {
		if (cmpack_strtolat(latstr, &val) == 0) {

			cmpack_lattostr(val, buf, 256);
			return cmpack_strdup(buf);
		}
	}
	return NULL;
}

/* Reads floating point value from header */
int fits_gkyd(tHandle handle, const char *key, double *val)
{
	CmpackFitsFile *fs = (CmpackFitsFile*)handle;
	int status = 0;
	
	if (ffgkyd(fs->fits, (char*) key, val, NULL, &status)==0)
		return 0;
	else
		return CMPACK_ERR_KEY_NOT_FOUND;
}

/* Get date and time of observation */
int fits_getdatetime(tHandle handle, CmpackDateTime *dt)
{
	double jd, exp;
	int end, center;
	CmpackDateTime tmp;
	CmpackFitsFile *fs = (CmpackFitsFile*)handle;

	if (dt)
		memset(dt, 0, sizeof(CmpackDateTime));

	memset(&tmp, 0, sizeof(CmpackDateTime));
	if (!getdatetime(fs->fits, &tmp, &end, &center))
		return CMPACK_ERR_KEY_NOT_FOUND;

	if (end) {
		/* Tmp contains end of exposure */
		jd = cmpack_encodejd(&tmp);
		if (jd>0 && getexptime(fs->fits, &exp)) {
			if (dt)
				cmpack_decodejd(jd - exp/86400.0, dt);
			return 0;
		}
	} else if (center) {
		/* Tmp contain center of exposure */
		jd = cmpack_encodejd(&tmp);
		if (jd>0 && getexptime(fs->fits, &exp)) {
			if (dt)
				cmpack_decodejd(jd - 0.5*exp/86400.0, dt);
			return 0;
		}
	} else {
		/* Tmp contain start of exposure */
		if (dt)
			*dt = tmp;
		return 0;
	}

	return CMPACK_ERR_KEY_NOT_FOUND;
}

/* Get exposure duration */
int fits_getexptime(tHandle handle, double *val)
{
	CmpackFitsFile *fs = (CmpackFitsFile*)handle;
	if (getexptime(fs->fits, val))
		return 0;
	else
		return CMPACK_ERR_KEY_NOT_FOUND;
}

/* Get CCD temperature */
int fits_getccdtemp(tHandle handle, double *temp)
{
	int status;
	double value;
	char buf[FLEN_VALUE];
	CmpackFitsFile *fs = (CmpackFitsFile*)handle;

	status = 0;
	if (ffgkyd(fs->fits, "CCD-TEMP", &value, NULL, &status)==0) {
		if (temp) 
			*temp = value;
		return 0;
	}
	status = 0;
	if (ffgkyd(fs->fits, "CCD_TEMP", &value, NULL, &status)==0) {
		if (temp)
			*temp = value;
		return 0;
	}
	status = 0;
	if (ffgkyd(fs->fits, "CCDTEMP", &value, NULL, &status)==0) {
		if (temp)
			*temp = value;
		return 0;
	}
	status = 0;
	if (ffgkyd(fs->fits, "TEMPERAT", &value, NULL, &status)==0) {
		if (temp)
			*temp = value;
		return 0;
	}
	status = 0;
	if (ffgkys(fs->fits, "TEMP", buf, NULL, &status)==0) { 
		if (temp) {
			char *ptr = strchr(buf, ',');
			if (ptr)
				*ptr = '.';
			*temp = atof(buf); 
		}
		return 0; 
	}
	status = 0;
	if (ffgkys(fs->fits, "Temperature", buf, NULL, &status)==0) { 
		if (temp) {
			char *ptr = strchr(buf, ',');
			if (ptr)
				*ptr = '.';
			*temp = atof(buf); 
		}
		return 0; 
	}
	status = 0;
	if (ffgkys(fs->fits, "CHIPTEMP", buf, NULL, &status)==0) {
		if (temp) {
			char *ptr = strchr(buf, ',');
			if (ptr)
				*ptr = '.';
			*temp = atof(buf); 
		}
		return 0;
	}
	return CMPACK_ERR_KEY_NOT_FOUND;
}

/* Get number of frames averaged or combined */
void fits_getframes(tHandle handle, CmpackChannel channel, int *avg_frames, int *sum_frames)
{
	int status;
	long value;
	CmpackFitsFile *fs = (CmpackFitsFile*)handle;

	status = 0;
	if (ffgkyj(fs->fits, "FR_AVG", &value, NULL, &status)==0 && value>1) {
		if (avg_frames)
			*avg_frames = value;
	}
	status = 0;
	if (ffgkyj(fs->fits, "FR_SUM", &value, NULL, &status)==0 && value>1) {
		if (sum_frames)
			*sum_frames = value;
	}
}

/* Random access reading of (key, value, comment) triples from hedader */
int fits_gkyn(tHandle handle, int index, char **key, char **val, char **com)
{
	CmpackFitsFile *fs = (CmpackFitsFile*)handle;
	int status = 0;
	char k[FLEN_KEYWORD], v[FLEN_VALUE], c[FLEN_COMMENT];

	if (ffgkyn(fs->fits, index+1, k, v, c, &status)==0) {
		if (key) *key = cmpack_strdup(k);
		if (val) *val = unquote_and_trim(v);
		if (com) *com = cmpack_strdup(c);
		return 0;
	} else {
		if (key) *key = NULL;
		if (val) *val = NULL;
		if (com) *com = NULL;
		return CMPACK_ERR_KEY_NOT_FOUND;
	}
}

/* Read image data */
int fits_getimage(tHandle handle, void *buf, int bufsize, CmpackChannel channel)
{
	int status = 0, anyf, imgsize;
	long naxes[2];
	CmpackFitsFile *fs = (CmpackFitsFile*)handle;

	if (ffgisz(fs->fits, 2, naxes, &status)!=0)
		return CMPACK_ERR_READ_ERROR;

	/* Check buffer size */
	imgsize = cmpack_image_size(naxes[0], naxes[1], fs->bitpix);
	if (bufsize < imgsize)
		return CMPACK_ERR_BUFFER_TOO_SMALL;

	switch (fs->bitpix)
	{
	case CMPACK_BITPIX_SSHORT:
		ffg2di(fs->fits, 1, 0, naxes[0], naxes[0], naxes[1], (short*)buf, &anyf, &status);
		return (status==0 ? 0 : CMPACK_ERR_READ_ERROR);
	case CMPACK_BITPIX_USHORT:
		ffg2dui(fs->fits, 1, 0, naxes[0], naxes[0], naxes[1], (unsigned short*)buf, &anyf, &status);
		return (status==0 ? 0 : CMPACK_ERR_READ_ERROR);
	case CMPACK_BITPIX_SLONG:
		ffg2dk(fs->fits, 1, 0, naxes[0], naxes[0], naxes[1], (int*)buf, &anyf, &status);
		return (status==0 ? 0 : CMPACK_ERR_READ_ERROR);
	case CMPACK_BITPIX_ULONG:
		ffg2duk(fs->fits, 1, 0, naxes[0], naxes[0], naxes[1], (unsigned*)buf, &anyf, &status);
		return (status==0 ? 0 : CMPACK_ERR_READ_ERROR);
	case CMPACK_BITPIX_FLOAT:
		ffg2de(fs->fits, 1, 0, naxes[0], naxes[0], naxes[1], (float*)buf, &anyf, &status);
		return (status==0 ? 0 : CMPACK_ERR_READ_ERROR);
	case CMPACK_BITPIX_DOUBLE:
		ffg2dd(fs->fits, 1, 0, naxes[0], naxes[0], naxes[1], (double*)buf, &anyf, &status);
		return (status==0 ? 0 : CMPACK_ERR_READ_ERROR);
	default:
		return CMPACK_ERR_READ_ERROR;
	}
}

/* Put image */
int fits_putimage(tHandle handle, const CmpackImage *data)
{
	int status = 0, width = cmpack_image_width(data), height = cmpack_image_height(data);
	const void *buf = cmpack_image_const_data(data);
	CmpackFitsFile *fs = (CmpackFitsFile*)handle;

	switch (cmpack_image_bitpix(data))
	{
	case CMPACK_BITPIX_SSHORT:
		ffp2di(fs->fits, 1, width, width, height, (short*)buf, &status);
		return (status==0 ? 0 : CMPACK_ERR_WRITE_ERROR);
	case CMPACK_BITPIX_USHORT:
		ffp2dui(fs->fits, 1, width, width, height, (unsigned short*)buf, &status);
		return (status==0 ? 0 : CMPACK_ERR_WRITE_ERROR);
	case CMPACK_BITPIX_SLONG:
		ffp2dk(fs->fits, 1, width, width, height, (int*)buf, &status);
		return (status==0 ? 0 : CMPACK_ERR_WRITE_ERROR);
	case CMPACK_BITPIX_ULONG:
		ffp2duk(fs->fits, 1, width, width, height, (unsigned*)buf, &status);
		return (status==0 ? 0 : CMPACK_ERR_WRITE_ERROR);
	case CMPACK_BITPIX_FLOAT:
		ffp2de(fs->fits, 1, width, width, height, (float*)buf, &status);
		return (status==0 ? 0 : CMPACK_ERR_WRITE_ERROR);
	case CMPACK_BITPIX_DOUBLE:
		ffp2dd(fs->fits, 1, width, width, height, (double*)buf, &status);
		return (status==0 ? 0 : CMPACK_ERR_WRITE_ERROR);
	default:
		return CMPACK_ERR_WRITE_ERROR;
	}
}

/* Add string to history */
int fits_puthistory(tHandle handle, const char *text)
{
	int status = 0;

	CmpackFitsFile *fs = (CmpackFitsFile*)handle;
	ffphis(fs->fits, text, &status);
	return (status==0 ? 0 : CMPACK_ERR_WRITE_ERROR);
}

/* Add string to history */
void fits_pkys(tHandle handle, const char *keyname, const char *value, const char *comment)
{
	CmpackFitsFile *fs = (CmpackFitsFile*)handle;
	ffukys(fs->fits, (char*)keyname, (char*)value, (char*)comment, &fs->status);
}
void fits_pkyi(tHandle handle, const char *keyname, int value, const char *comment)
{
	CmpackFitsFile *fs = (CmpackFitsFile*)handle;
	ffukyj(fs->fits, (char*)keyname, value, (char*)comment, &fs->status);
}
void fits_pkyl(tHandle handle, const char *keyname, int value, const char *comment)
{
	CmpackFitsFile *fs = (CmpackFitsFile*)handle;
	ffukyl(fs->fits, (char*)keyname, value, (char*)comment, &fs->status);
}
void fits_pkyd(tHandle handle, const char *keyname, double value, int prec, const char *comment)
{
	CmpackFitsFile *fs = (CmpackFitsFile*)handle;
	ffukyd(fs->fits, (char*)keyname, value, prec, (char*)comment, &fs->status);
}

/* Copy frame header, skip FITS specific keywords */
int fits_prepare(tHandle handle, int width, int height, CmpackBitpix bitpix)
{
	long naxes[2];
	CmpackFitsFile *fs = (CmpackFitsFile*)handle;

	naxes[0] = width;
	naxes[1] = height;
	if (ffcrim(fs->fits, bitpix, 2, naxes, &fs->status)==0)
		fs->bitpix = bitpix;
	return (fs->status==0 ? 0 : CMPACK_ERR_WRITE_ERROR);
}

void fits_setexptime(tHandle handle, double exptime)
{
	int stat = 0;
	CmpackFitsFile *fs = (CmpackFitsFile*)handle;

	ffukyd(fs->fits, "EXPTIME", exptime, 3, "EXPOSURE DURATION IN SECONDS", &stat);
}

/* Updates date and time in FITS file */
int fits_setdatetime(tHandle handle, const CmpackDateTime *dt)
{
	char val[FLEN_VALUE];
  	CmpackFitsFile *fs = (CmpackFitsFile*)handle;

	sprintf(val, "%04d-%02d-%02d", dt->date.year, dt->date.month, dt->date.day);
	ffukys(fs->fits,"DATE-OBS", val, "UT DATE OF START", &fs->status);

	sprintf(val, "%02d:%02d:%02d.%03d", dt->time.hour, dt->time.minute, dt->time.second, dt->time.milisecond);
	ffukys(fs->fits,"TIME-OBS", val, "UT TIME OF START", &fs->status);
	return (fs->status==0 ? 0 : CMPACK_ERR_WRITE_ERROR);
}

/* Write CCD temperature */
void fits_setccdtemp(tHandle handle, double ccdtemp)
{
	int stat = 0;
  	CmpackFitsFile *fs = (CmpackFitsFile*)handle;

	ffukyg(fs->fits, "CCD-TEMP", ccdtemp, 2, "AVERAGE CCD TEMPERATURE", &stat);
}

/* Write filter name */
void fits_setfilter(tHandle handle, const char *filter)
{
	int status = 0;
  	CmpackFitsFile *fs = (CmpackFitsFile*)handle;
	char *f;

	f = trim(filter);
	ffukys(fs->fits, "FILTER", (char*)f, "OPTICAL FILTER NAME", &status);
	cmpack_free(f);
}

/* Write object's name */
void fits_setobject(tHandle handle, const char *object)
{
	int status = 0;
  	CmpackFitsFile *fs = (CmpackFitsFile*)handle;
	char *f;

	f = trim(object);
	ffukys(fs->fits, "OBJECT", (char*)f, "OBJECT DESIGNATION", &status);
	cmpack_free(f);
}

/* Set number of frames averaged */
void fits_setframes(tHandle handle, int avg_frames, int sum_frames)
{
	int status = 0;
  	CmpackFitsFile *fs = (CmpackFitsFile*)handle;

	ffukyj(fs->fits, "FR_AVG", avg_frames, "NUMBER OF FRAMES AVERAGED", &status);
	ffukyj(fs->fits, "FR_SUM", sum_frames, "NUMBER OF FRAMES SUMMED", &status);
}

/* Copy frame header, skip FITS specific keywords */
static int copyheader(fitsfile *src, fitsfile *dst, CmpackConsole *con)
{
	int i, status = 0, dstat = 0;
	char key[FLEN_KEYWORD], val[FLEN_VALUE], com[FLEN_COMMENT];
	char card[FLEN_CARD], msg[256];

	for (i=1; ffgkyn(src, i, key, val, com, &status)==0; i++) {
		if (key[0]=='\0' || strcmp(key, "SIMPLE")==0 || strcmp(key, "BITPIX")==0 ||
			strstr(key, "NAXIS")==key || strcmp(key, "EXTEND")==0 ||
			strcmp(key, "BZERO")==0 || strcmp(key, "BSCALE")==0 ||
			strcmp(key, "END")==0 || (strcmp(key, "COMMENT")==0 && (
				strstr(com, "FITS (Flexible Image Transport System) format is defined in 'Astronomy") ||
				strstr(com, "and Astrophysics', volume 376, page 359; bibcode: 2001A&A...376..359H")))) {
				/* Skip this keyword */
					continue;
		}
		/* Read card */
		if (ffgrec(src, i, card, &status)!=0) {
			if (con) {
				sprintf(msg, "Warning: Failed to read field '%s':", key);
				printout(con, 0, msg);
				ffgerr(status, msg);
				printout(con, 0, msg);
			}
			status = 0;
			continue;
		}
		/* Write card */
		if (ffprec(dst, card, &dstat)!=0) {
			if (con) {
				sprintf(msg, "Warning: Failed to write field '%s':", key);
				printout(con, 0, msg);
				ffgerr(status, msg);
				printout(con, 0, msg);
			}
			dstat = 0;
			continue;
		}
	}
	return 0;
}

int fits_copyheader(tHandle handle, CmpackImageHeader *dst, CmpackChannel channel, CmpackConsole *con)
{
	CmpackFitsFile *fs = (CmpackFitsFile*)handle;
	return copyheader(fs->fits, (fitsfile*)dst->fits, con);
}

int fits_restoreheader(tHandle handle, CmpackImageHeader *src, CmpackConsole *con)
{
	CmpackFitsFile *fs = (CmpackFitsFile*)handle;
	return copyheader((fitsfile*)src->fits, fs->fits, con);
}

int fits_getwcs(tHandle handle, CmpackWcs **wcs)
{
	CmpackFitsFile *fs = (CmpackFitsFile*)handle;
	char *header;
	int nkeyrec;

	*wcs = NULL;
	if (ffhdr2str(fs->fits, 1, NULL, 0, &header, &nkeyrec, &fs->status)==0) {
		*wcs = cmpack_wcs_new_from_FITS_header(header, nkeyrec);
		fffree(header, &fs->status);
		return (*wcs) != NULL;
	}
	return 0;
}
