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

wcs.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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <fitsio.h>

#include "config.h"

#ifdef CMUNIPACK_HAVE_WCSLIB
#include "wcslib.h"
#endif

#include "wcsobj.h"
#include "comfun.h"

/* Mutex */
static CmpackMutex mutex;

/* Create the mutex */
void cmpack_wcs_init(void)
{
	cmpack_mutex_init(&mutex);
}

/* Destroy the mutex */
void cmpack_wcs_clean(void)
{
	cmpack_mutex_destroy(&mutex);
}

/* Acquire the mutex */
#define cmpack_wcs_lock() { cmpack_mutex_lock(&mutex); }

/* Release the mutex */
#define cmpack_wcs_unlock() { cmpack_mutex_unlock(&mutex); }

/* Returns nonzero if the key is a valid WCS keyword */
int is_wcs_keyword(const char *key)
{
	char buf[FLEN_KEYWORD];

	memset(buf, 0, FLEN_KEYWORD);
	strcpy(buf, key);
	
	if (memcmp(buf, "RADECSYS", 8)==0)
		return 1;

	if ((memcmp(buf, "WCSAXES", 7)==0 || memcmp(buf, "WCSNAME", 7)==0 || 
		memcmp(buf, "LONPOLE", 7)==0 || memcmp(buf, "LATPOLE", 7)==0 ||
		memcmp(buf, "EQUINOX", 7)==0 || memcmp(buf, "RADESYS", 7)==0)
		&& (buf[7]==' ' || buf[7]=='\0' || (buf[7]>='A' && buf[7]<='Z')))
		return 1;

	if ((memcmp(buf, "CTYPE", 5)==0 || memcmp(buf, "CUNIT", 5)==0 || 
		memcmp(buf, "CRVAL", 5)==0 || memcmp(buf, "CDELT", 5)==0 || 
		memcmp(buf, "CRPIX", 5)==0 || memcmp(buf, "CROTA", 5)==0 ||
		memcmp(buf, "CNAME", 5)==0 || memcmp(buf, "CRDER", 5)==0 ||
		memcmp(buf, "CSYER", 5)==0) 
		&& (buf[5]>='0' && buf[5]<='9')
		&& (buf[6]==' ' || buf[6]=='\0' || (buf[6]>='A' && buf[6]<='Z')))
		return 1;

	if ((memcmp(buf, "PC", 2)==0 || memcmp(buf, "CD", 2)==0 || 
		memcmp(buf, "PV", 2)==0 || memcmp(buf, "PS", 2)==0)
		&& (buf[2]>='0' && buf[2]<='9')
		&& (buf[3]=='_')
		&& (buf[4]>='0' && buf[4]<='9')
		&& (buf[5]==' ' || buf[5]=='\0' || (buf[5]>='A' && buf[5]<='Z')))
		return 1;
		
	return 0;
}

void cmpack_wcs_update_header(const CmpackImageHeader *sfile, CmpackImageHeader *dfile, int action)
{
	int i, status = 0, dstat = 0;
	char key[FLEN_KEYWORD], val[FLEN_VALUE], com[FLEN_COMMENT], card[FLEN_CARD];
	fitsfile *src = (fitsfile*)sfile->fits, *dst = (fitsfile*)dfile->fits;
	
	for (i=1; ffgkyn(src, i, key, val, com, &status)==0; i++) {
		/* Skip the following keywords: */
		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) 
				continue;
		/* Skip FITS default comments */
		if (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")))
				continue;
		/* Action: 1 = copy WCS data only, 2 = skip WCS data */
		if ((action==1 && !is_wcs_keyword(key)) || (action==2 && is_wcs_keyword(key))) {
			if (ffgrec(src, i, card, &status)==0)
				ffprec(dst, card, &dstat);
		}
	}
}

#ifdef CMUNIPACK_HAVE_WCSLIB

/* Wcs data */
struct _CmpackWcs
{
	int				refcnt;		/**< Reference counter */
	struct wcsprm	data;		/**< Wcs data */
};

/* Parse FITS header and create a wcs object */
CmpackWcs *cmpack_wcs_new_from_FITS_header(char *header, int nkeyrec)
{
	int i, wcs_status = 0, nwcs = 0, nreject = 0;
	struct wcsprm *data = NULL;
	CmpackWcs *retval = NULL;

	assert(header != NULL && nkeyrec > 0);

	cmpack_wcs_lock();
	wcs_status = wcspih(header, nkeyrec, 0, 0, &nreject, &nwcs, &data);
	if (wcs_status==0) {
		for (i=0; i<nwcs; i++) {
			/* Find the master celestial WCS coordinates */
			struct wcsprm *prm = data+i;
			wcsset(prm);
			if (prm->naxis==2 && prm->lng>=0 && prm->lat>=0 && (prm->alt[0]=='\0' || prm->alt[0]==' ')) {
				CmpackWcs *wcs = (CmpackWcs*)cmpack_calloc(1, sizeof(CmpackWcs));
				wcs->refcnt = 1;
				wcs->data.flag = -1;
				if (wcssub(1, prm, 0, 0, &wcs->data)==0) {
					retval = wcs;
					break;
				}
				cmpack_free(wcs);
			}
		}
	}
	cmpack_wcs_unlock();
	return retval;
}

/* Parse FITS header and create a wcs object */
int cmpack_wcs_to_FITS_header(CmpackWcs *wcs, char **buf, int *nkeyrec)
{
	int retval = CMPACK_ERR_UNDEF_VALUE;

	*buf = NULL;
	*nkeyrec = 0;

	if (wcs) {
		char *aux = NULL;
		cmpack_wcs_lock();
		if (wcshdo(0, &wcs->data, nkeyrec, &aux) == 0 && (*nkeyrec) > 0 && aux) {
			*buf = (char*)cmpack_malloc((*nkeyrec) * 80);
			if (*buf) {
				memcpy(*buf, aux, (*nkeyrec) * 80);
				retval = CMPACK_ERR_OK;
			}
			else
				retval = CMPACK_ERR_MEMORY;
		}
		else
			retval = CMPACK_ERR_INVALID_PAR;
#ifdef HAVE_WCSDEALLOC
		wcsdealloc(aux);
#else
	/*************************************************************************
	* wcslib below 6.3 does not provide any deallocating function
	* so it will leave memory leaks. Compile agains wcslib 6.3+ if possible 
	**************************************************************************/
#endif
		cmpack_wcs_unlock();
	}
	return retval;
}

CmpackWcs *cmpack_wcs_reference(CmpackWcs *wcs)
{
	assert(wcs != NULL);

	wcs->refcnt++;
	return wcs;
}

/* Frees all allocated memory in internal structures */
void cmpack_wcs_destroy(CmpackWcs *wcs)
{
	if (wcs) {
		wcs->refcnt--;
		if (wcs->refcnt==0) {
			cmpack_wcs_lock();
			wcsfree(&wcs->data);
			cmpack_wcs_unlock();
			cmpack_free(wcs);
		}
	}
}

/* Parse FITS header and create a wcs object */
CmpackWcs *cmpack_wcs_copy(const CmpackWcs *src)
{
	CmpackWcs *retval = NULL;
	if (src) {
		CmpackWcs *wcs = (CmpackWcs*)cmpack_calloc(1, sizeof(CmpackWcs));
		wcs->refcnt = 1;
		wcs->data.flag = -1;
		cmpack_wcs_lock();
		if (wcssub(1, &src->data, 0, 0, &wcs->data)==0)
			retval = wcs;
		else
			cmpack_free(wcs);
		cmpack_wcs_unlock();
	}
	return retval;
}

int cmpack_wcs_print(CmpackWcs *wcs, char **buf, int *len)
{
	const char *aux;

	assert(buf!=NULL && len!=NULL);

	*buf = NULL;
	*len = 0;
	
	if (!wcs)
		return CMPACK_ERR_INVALID_CONTEXT;

	cmpack_wcs_lock();
	wcsprintf_set(NULL);
	wcsset(&wcs->data);
	wcsprt(&wcs->data);
	aux = wcsprintf_buf();
	*len = (int)strlen(aux);
	*buf = (char*)cmpack_malloc((*len)+1);
	memcpy(*buf, aux, (*len));
	(*buf)[*len] = '\0';
	cmpack_wcs_unlock();

	return CMPACK_ERR_OK;
}

const char *cmpack_wcs_get_name(CmpackWcs *wcs)
{
	int status;

	if (!wcs)
		return NULL;

	cmpack_wcs_lock();
	status = wcsset(&wcs->data);
	cmpack_wcs_unlock();
	if (status != 0)
		return NULL;

	return wcs->data.wcsname;
}

int cmpack_wcs_get_axis_params(CmpackWcs *wcs, int axis, unsigned mask, CmpackWcsAxisParams *params)
{
	int status;

	if (!wcs)
		return CMPACK_ERR_INVALID_CONTEXT;

	cmpack_wcs_lock();
	status = wcsset(&wcs->data);
	cmpack_wcs_unlock();
	if (status != 0)
		return CMPACK_ERR_INVALID_CONTEXT;

	if (mask & CMPACK_AP_STYP) {
		if (axis==0) 
			strcpy(params->styp, wcs->data.lngtyp);
		else if (axis==1)
			strcpy(params->styp, wcs->data.lattyp);
		else
			return CMPACK_ERR_OUT_OF_RANGE;
		rtrim(params->styp);
	}
	if (mask & CMPACK_AP_NAME) {
		if (axis==0) {
			if (wcs->data.cname[wcs->data.lng][0]!='\0')
				strcpy(params->name, wcs->data.cname[wcs->data.lng]);
			else
				strcpy(params->name, wcs->data.lngtyp);
		} else if (axis==1) {
			if (wcs->data.cname[wcs->data.lat][0]!='\0')
				strcpy(params->name, wcs->data.cname[wcs->data.lat]);
			else
				strcpy(params->name, wcs->data.lattyp);
		} else
			return CMPACK_ERR_OUT_OF_RANGE;
		rtrim(params->name);
	} 
	if (mask & CMPACK_AP_UNIT) {
		if (axis==0)
			strcpy(params->unit, wcs->data.cunit[wcs->data.lng]);
		else if (axis==1)
			strcpy(params->unit, wcs->data.cunit[wcs->data.lat]);
		else
			return CMPACK_ERR_OUT_OF_RANGE;
		rtrim(params->unit);
	}
	return CMPACK_ERR_OK;
}

int cmpack_wcs_p2w(CmpackWcs *wcs, double x, double y, double *r, double *d)
{
	int status, stat[NWCSFIX];
	double imgcrd[2], phi, pixcrd[2], theta, world[2]; 

	if (!wcs)
		return CMPACK_ERR_INVALID_CONTEXT;

	pixcrd[0] = x;
	pixcrd[1] = y;
	cmpack_wcs_lock();
	status = wcsp2s(&wcs->data, 1, 2, pixcrd, imgcrd, &phi, &theta, world, stat); 
	cmpack_wcs_unlock();
	if (status != 0) 
		return CMPACK_ERR_UNDEF_VALUE;
	*r = world[0];
	*d = world[1];
	return CMPACK_ERR_OK;
}

int cmpack_wcs_w2p(CmpackWcs *wcs, double r, double d, double *x, double *y)
{
	int status, stat[NWCSFIX];
	double imgcrd[2], phi, pixcrd[2], theta, world[2]; 

	if (!wcs)
		return CMPACK_ERR_INVALID_CONTEXT;

	world[0] = r;
	world[1] = d;
	cmpack_wcs_lock();
	status = wcss2p(&wcs->data, 1, 2, world, &phi, &theta, imgcrd, pixcrd, stat); 
	cmpack_wcs_unlock();
	if (status != 0) 
		return CMPACK_ERR_UNDEF_VALUE;
	*x = pixcrd[0];
	*y = pixcrd[1];
	return CMPACK_ERR_OK;
}

static int has_string_value(const char *buf)
{
	if (memcmp(buf, "RADECSYS", 8)==0 || memcmp(buf, "DATE-OBS", 8)==0 || memcmp(buf, "DATE-AVG", 8)==0)
		return 1;

	if ((memcmp(buf, "WCSNAME", 7)==0 || memcmp(buf, "RADESYS", 7)==0 || memcmp(buf, "SPECSYS", 7)==0)
		&& (buf[7]==' ' || buf[7]=='\0' || (buf[7]>='A' && buf[7]<='Z')))
		return 1;

	if ((memcmp(buf, "CTYPE", 5)==0 || memcmp(buf, "CUNIT", 5)==0 || memcmp(buf, "CNAME", 5)==0) 
		&& (buf[5]>='0' && buf[5]<='9')
		&& (buf[6]==' ' || buf[6]=='\0' || (buf[6]>='A' && buf[6]<='Z')))
		return 1;

	if ((memcmp(buf, "SPECSYS", 7)==0 || memcmp(buf, "SSYSOBS", 7)==0 || memcmp(buf, "SSYSSRC", 7)==0)
		&& (buf[7]==' ' || buf[7]=='\0' || (buf[7]>='A' && buf[7]<='Z')))
		return 1;

	return 0;
}

static void make_fits_card(char *buf, const char *keyword, const char *keyvalue)
{
	if (has_string_value(keyword)) {
		/* Double-up single-quotes in the keyvalue. */
		const char *sptr;
		char aux[FLEN_CARD], *dptr;
		aux[0] = '\'';
		sptr = keyvalue;
		dptr = aux+1;
		for (sptr=keyvalue; *sptr!='\0'; sptr++) {
			if (*sptr == '\'') {
				*dptr++ = '\'';
				*dptr++ = '\'';
			} else
				*dptr++ = *sptr;
		}
		*dptr++ = '\'';
		*dptr = '\0';
		sprintf(buf, "%-8.8s= %s", keyword, aux);
	} else 
		sprintf(buf, "%-8.8s= %20s", keyword, keyvalue); 
}

/* Load WCS data from a XML node */
CmpackWcs *cmpack_wcs_new_from_XML_node(CmpackElement *node)
{
	int i, nkeyrec, klen, wcs_status = 0, nwcs = 0, nreject = 0;
	char *header;
	const char *keyword, *keyvalue;
	struct wcsprm *data = NULL;
	CmpackWcs *retval = NULL;
	CmpackElement *rec;

	if (node) {
		/* Get number of header records */
		nkeyrec = 0;
		rec = cmpack_xml_element_first_element(node, "wcsitem");
		while (rec) {
			nkeyrec++;
			rec = cmpack_xml_element_next_element(rec);
		}
		header = (char*)cmpack_malloc((nkeyrec+1)*80);
		if (header) {
			char *ptr = header;
			memset(header, ' ', (nkeyrec+1)*80);
			/* Copy data from xml elements */
			rec = cmpack_xml_element_first_element(node, "wcsitem");
			while (rec) {
				keyword = cmpack_xml_attr_s(rec, "key", NULL);
				if (keyword && *keyword!='\0') {
					klen = (int)strlen(keyword);
					if (klen<=8) {
						keyvalue = cmpack_xml_value(rec, NULL);
						if (keyvalue) {
							char aux[FLEN_CARD];
							make_fits_card(aux, keyword, keyvalue);
							memcpy(ptr, aux, strlen(aux));
						}
						ptr += 80;
					}
				}
				rec = cmpack_xml_element_next_element(rec);
			}
			/* Append END header */
			memcpy(ptr, "END", 3);
			cmpack_wcs_lock();
			wcs_status = wcspih(header, nkeyrec, 0, 0, &nreject, &nwcs, &data);
			if (wcs_status==0) {
				for (i=0; i<nwcs; i++) {
					/* Find the master celestial WCS coordinates */
					struct wcsprm *prm = data+i;
					wcsset(prm);
					if (prm->naxis==2 && prm->lng>=0 && prm->lat>=0 && (prm->alt[0]=='\0' || prm->alt[0]==' ')) {
						CmpackWcs *wcs = (CmpackWcs*)cmpack_calloc(1, sizeof(CmpackWcs));
						wcs->refcnt = 1;
						wcs->data.flag = -1;
						if (wcssub(1, prm, 0, 0, &wcs->data)==0) {
							retval = wcs;
							break;
						}
						cmpack_free(wcs);
					}
				}
			}
			cmpack_wcs_unlock();
			return retval;
		}
	}
	return NULL;
}

/* Save WCS data to a file in XML format */
void cmpack_wcs_write_XML(CmpackWcs *wcs, FILE *to)
{
	int i, nkeyrec, namelen, status = 0;
	char *aux = NULL, dtype;

	if (wcs) {
		cmpack_wcs_lock();
		if (wcshdo(0, &wcs->data, &nkeyrec, &aux) == 0 && nkeyrec > 0 && aux) {
			for (i = 0; i < nkeyrec; i++) {
				char crd[FLEN_CARD], name[FLEN_KEYWORD], value[FLEN_VALUE], comment[FLEN_COMMENT];
				memcpy(crd, aux + i * 80, 80);
				crd[80] = '\0';
				ffgknm(crd, name, &namelen, &status);
				ffpsvc(crd, value, comment, &status);
				fprintf(to, "\t<wcsitem key=\"%s\"", name);
				if (strlen(value) > 0) {
					/* Keyword with value */
					fprintf(to, ">");
					if (ffdtyp(value, &dtype, &status) == 0 && dtype == 'C') {
						char *aux = xml_encode_string(fits_unquote(value));
						fprintf(to, "%s", aux);
						cmpack_free(aux);
					}
					else {
						fprintf(to, "%s", value);
					}
					/* Keyword with value */
					fprintf(to, "</wcsitem>");
				}
				else {
					/* Keyword only */
					fprintf(to, "/>");
				}
				/* Comment */
				if (strlen(comment) > 0) {
					fprintf(to, "\t<!-- %s -->", comment);
				}
				fprintf(to, "\n");
			}
		}
#ifdef HAVE_WCSDEALLOC
		wcsdealloc(aux);
#else
		/*************************************************************************
		* wcslib below 6.3 does not provide any deallocating function
		* so it will leave memory leaks. Compile agains wcslib 6.3+ if possible
		**************************************************************************/
#endif
		cmpack_wcs_unlock();
	}
}

const char *cmpack_wcs_version(void)
{
	return wcslib_version(NULL);
}

#else

/* Wcs data */
struct _CmpackWcs
{
	int				refcnt;		/**< Reference counter */
};

/* Parse FITS header and create a wcs object */
CmpackWcs *cmpack_wcs_new_from_FITS_header(char *header, int nkeyrec)
{
	return NULL;
}

/* Parse FITS header and create a wcs object */
int cmpack_wcs_to_FITS_header(CmpackWcs *wcs, char **buf, int *nkeyrec)
{
	return CMPACK_ERR_NOT_IMPLEMENTED;
}

CmpackWcs *cmpack_wcs_reference(CmpackWcs *wcs)
{
	assert(wcs != NULL);

	wcs->refcnt++;
	return wcs;
}

/* Frees all allocated memory in internal structures */
void cmpack_wcs_destroy(CmpackWcs *wcs)
{
	if (wcs) {
		wcs->refcnt--;
		if (wcs->refcnt==0) 
			cmpack_free(wcs);
	}
}

/* Parse FITS header and create a wcs object */
CmpackWcs *cmpack_wcs_copy(const CmpackWcs *src)
{
	CmpackWcs *retval = NULL;
	if (src) {
		CmpackWcs *wcs = (CmpackWcs*)cmpack_calloc(1, sizeof(CmpackWcs));
		wcs->refcnt = 1;
	}
	return retval;
}

int cmpack_wcs_print(CmpackWcs *wcs, char **buf, int *len)
{
	return CMPACK_ERR_NOT_IMPLEMENTED;
}

const char *cmpack_wcs_get_name(CmpackWcs *wcs)
{
	return NULL;
}

int cmpack_wcs_get_axis_params(CmpackWcs *wcs, int axis, unsigned mask, CmpackWcsAxisParams *params)
{
	return CMPACK_ERR_NOT_IMPLEMENTED;
}

int cmpack_wcs_p2w(CmpackWcs *wcs, double x, double y, double *r, double *d)
{
	return CMPACK_ERR_NOT_IMPLEMENTED;
}

int cmpack_wcs_w2p(CmpackWcs *wcs, double r, double d, double *x, double *y)
{
	return CMPACK_ERR_NOT_IMPLEMENTED;
}

/* Load WCS data from a XML node */
CmpackWcs *cmpack_wcs_new_from_XML_node(CmpackElement *node)
{
	return NULL;
}

/* Save WCS data to a file in XML format */
void cmpack_wcs_write_XML(CmpackWcs *wcs, FILE *to)
{
}

const char *cmpack_wcs_version(void)
{
	return NULL;
}

#endif
