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

oesfile.c (C-Munipack project)
OES Astro read functions
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 <stdint.h>

#include "comfun.h"
#include "cmpack_common.h"
#include "oesfile.h"

#define HEADSIZE sizeof(oesheader)

#define H_SIGN_SIGNED 1
#define H_SIGN_UNSIGNED 0

#define H_SUBTYPE_FIX 1
#define H_SUBTYPE_NORM 2
#define H_TYPE_BYTE 4
#define H_TYPE_FIXBYTE (H_TYPE_BYTE | H_SUBTYPE_FIX)
#define H_TYPE_WORD 8
#define H_TYPE_FIXWORD (H_TYPE_WORD | H_SUBTYPE_FIX)
#define H_TYPE_LONG 16
#define H_TYPE_FIXLONG (H_TYPE_LONG | H_SUBTYPE_FIX)
#define H_TYPE_FLOAT 32
#define H_TYPE_NFLOAT (H_TYPE_FLOAT | H_SUBTYPE_NORM)
#define H_TYPE_DOUBLE 64
#define H_TYPE_NDOUBLE (H_TYPE_DOUBLE | H_SUBTYPE_NORM)
#define H_TYPE_COMPLEX 128
#define H_TYPE_DCOMPLEX 192
#define H_TYPE_CUSTOM 256

#define H_STAT_UNDEF 0
#define H_STAT_TIME 1
#define H_STAT_IMAGE 2
#define H_STAT_COMMENT 4
#define H_STAT_DISPLAY 8
#define H_STAT_CAMERA 16

#define H_IMAG_ROTDEF 1
#define H_IMAG_PIXDEF 2
#define H_IMAG_IMGDEF 4
#define H_IMAG_LOCDEF 8
#define H_IMAG_EASTLEFT 16

#define H_DISP_HISTO 1

#define H_CAME_INTEGRATION 1
#define H_CAME_INTEST 2
#define H_CAME_TRANSEST 4
#define H_CAME_ABSTEMP 8
#define H_CAME_DARKCORR 16
#define H_CAME_FLATCORR 32
#define H_CAME_BINNING 64

#define PDAT_Y(n) ((int)(n)/16/32)
#define PDAT_M(n) ((int)(n)/32%16)
#define PDAT_D(n) ((int)(n)%32)

#define PDEG_D(n) ((int)(n)/3600)
#define PDEG_M(n) ((int)(n)/60%60)
#define PDEG_S(n) ((int)(n)%60)

#define PTIM_H(n) ((int)(n)/3600)
#define PTIM_M(n) ((int)(n)/60%60)
#define PTIM_S(n) ((int)(n)%60)

typedef struct _oesheader {
	/* Contents */
	int32_t dx, dy;         /* Picture dimensions */
	int32_t pxlen;          /* Bytes per pixel */
	int32_t sign, type;     /* Signed/unsigned, data type */
	int32_t status;         /* Which parts are present? */
	/* Time */
	float ymd, hms;      /* Date and time of observation */
	float stime, jd;     /* Star time and Julian date */
	/* Image */
	int32_t seqnum, frnum;  /* Number of sequence, frame */
	int32_t istatus;        /* Image status */
	float irot;          /* Image rotation */
	float pxwidth;       /* Pixel width */
	int32_t pxrationdividend, pxrationdivider; /* Aspect ratio */
	float ra, dec;       /* R.A., DEC. */
	float lon, lat;      /* Longitude, latitude */
	/* Display */
	int32_t dstatus;        /* Display status */
	float dmax, dmin;    /* Display params (White, Black) */
	int32_t xmax, ymax;
	int32_t xmin, ymin;     /* histogram display params */
	/* Camera */
	int32_t cstatus;        /* Camera status */
	int32_t camera;         /*  */
	float exposure;      /* duration of exposure */
	float transfer;      /* duration of transfer */
	float temp;          /* temperature */
	/* Reserved */
	int32_t res[27];        /* Future expansion */
	float fixoffset;     /* Minimum of fixed numbers */
	float fixdynamics;   /* Fixed numbers dynamics */
	uint32_t magic1, magic2; /* Magic numbers (endian type detection) */
	/* Comments */
	char comment[4][64]; /* Location, telescope */
} oesheader;

struct _oesfile {
	FILE *f;
	oesheader head;
};

/* Dictionary of parameter names */
const char *keys[] = {
	"dX", "dY", "PxLen", "Sign", "Type", "Status", "YeMoDa", "HoMiSe", "StarTime", "JulDat", "SeqNum",
	"FrameNum", "ImagStat", "ImagRot", "PxWidth", "PxRtDiv", "PxRtDis", "Rektas", "Deklin", "Longitude", "Latitude",
	"DispStat", "Maximum", "Minimum", "Xmax", "Ymax", "Xmin", "Ymin", "CamStat", "Camera", "Integration", "Transfer",
	"Temperature", "FixOffset", "FixDynamic", "Magic1", "Magic2", "Comment1", "Comment2", "Comment3", "Comment4"
};

/* Parameter comment strings */
const char *coms[] = {
	"image width in pixels", "image height in pixels", "number of bytes per pixel", NULL,
	NULL, "filled parts of header", "date of observation", "time of observation", "star time of observation",
	"Julian date of observation", "sequence number", "frame number in the sequence", "filled parts in the header",
	"rotation of the field", "pixel width in arc seconds", "pixel ratio (nominator)", "pixel ratio (denominator)",
	"object's right ascension", "object's declination", "longitude of observation place", "latitude of observation place",
	"display status", "white point in ADU", "black point in ADU", NULL, NULL, NULL, NULL, "camera status", "camera id",
	"integration duration", "transfer duration", "temperature", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
};

/* Opens a file and reads the header */
int oesopen(oesfile **poes, const char *filename)
{
	size_t headlen;
	oesfile *oes;

	*poes = NULL;

	/* Open file */
	oes = (oesfile*) cmpack_calloc(1, sizeof(oesfile));
	oes->f = fopen(filename,"rb");
	if (oes->f==NULL) {
		/* Cannot open file */
		fclose(oes->f);
		cmpack_free(oes);
		return CMPACK_ERR_OPEN_ERROR;
	}
	/* Read header of the file */
	headlen = fread(&oes->head,1,HEADSIZE,oes->f);
	if ((headlen!=HEADSIZE)
		|| ((oes->head.dx<=0)||(oes->head.dx>0x4000)||(oes->head.dy<=0)||(oes->head.dy>0x4000))
		|| (((oes->head.magic1!=H_MAG1_OWN)||(oes->head.magic2!=H_MAG2_OWN))
		&&((oes->head.magic1!=H_MAG1_INV)||(oes->head.magic2!=H_MAG2_INV)))) {
			fclose(oes->f);
			cmpack_free(oes);
			return CMPACK_ERR_READ_ERROR;
	}
	/* Normal return */
	*poes = oes;
  	return 0;
}

/* Free allocated memory */
void oesclos(oesfile *oes)
{
	if (oes) {
		if (oes->f)
			fclose(oes->f);
		cmpack_free(oes);
	}
}

int oesghpr(oesfile *oes, int *nx, int *ny)
{
	if (nx) *nx = oes->head.dx;
	if (ny) *ny = oes->head.dy;
	return 0;
}

int oesgimg(oesfile *oes, uint16_t *buf, int bufsize)
{
	int i, nx = oes->head.dx, ny = oes->head.dy;
	float *fbuf, *sptr;
	uint16_t *dptr;

	if (nx<=0 || nx>=0x4000 || ny<=0 || ny>=0x4000) {
		/* Invalid image size */
		return CMPACK_ERR_INVALID_SIZE;
	}
	if (bufsize < nx*ny) {
		/* Insufficient buffer */
		return CMPACK_ERR_BUFFER_TOO_SMALL;
	}
	/* Read data into the buffer */
	fbuf = (float*)cmpack_calloc(nx*ny, sizeof(float));
	if (fseek(oes->f, HEADSIZE, SEEK_SET)!=0 || fread(fbuf,4,nx*ny,oes->f)!=nx*ny) {
		cmpack_free(fbuf);
		return CMPACK_ERR_READ_ERROR;
	}
	/* Convert data to unsigned shorts */
	for (i=0, sptr=fbuf, dptr=buf; i<nx*ny; i++) {
		double val = (*sptr++)*4096.0;
		if (val <= 0)
			*dptr++ = 0;
		else if (val >= 4095.0)
			*dptr++ = 4095;
		else
			*dptr++ = (uint16_t)val;
	}
	/* Free buffer */
	cmpack_free(fbuf);
	return CMPACK_ERR_OK;
}

/* Get exposure length in seconds */
int oesgexp(oesfile *oes, double *exp)
{
	*exp = PTIM_H(oes->head.exposure)*3600.0+PTIM_M(oes->head.exposure)*60.0+PTIM_S(oes->head.exposure)*1.0;
	if ((*exp<=0)||(*exp>=99999.9)) {
		/* This is invalid exposure duration */
		*exp = -1.0;
		return CMPACK_ERR_READ_ERROR;
	}
	return 0;
}

/* Get exposure length in seconds */
int oesgtem(oesfile *oes, double *temp)
{
	*temp = oes->head.temp;
	if ((*temp<-99.9)||(*temp>99.9)) {
		/*  This is invalid temperature */
		*temp = INVALID_TEMP;                            
		return CMPACK_ERR_READ_ERROR;
	}
	return 0;
}

/* Reads date and time */
int oesgdat(oesfile *oes, int *yr, int *mon, int *day, int *hr, int *min, int *sec)
{
	*yr = PDAT_Y(oes->head.ymd); *mon = PDAT_M(oes->head.ymd); *day = PDAT_D(oes->head.ymd);
	*hr = PTIM_H(oes->head.hms); *min = PTIM_M(oes->head.hms), *sec = PTIM_S(oes->head.hms);
	if ((*yr>1900)&&(*yr<=9999)&&(*mon>=1)&&(*mon<=12)&&(*day>=1)&&(*day<=31)
		&&(*hr>=0)&&(*hr<=23)&&(*min>=0)&&(*min<=59)&&(*sec>=0)&&(*sec<=59)) {
			return 0;
	} else {
		*yr = *mon = *day = *hr = *min = *sec = 0;
		return CMPACK_ERR_INVALID_DATE;
	}
}

/* Returns value from the header as int */
int oesgkyi(oesfile *oes, int nkey, int *value)
{
	if ((nkey<0)||(nkey>=(int)(HEADSIZE/4))) 
		return CMPACK_ERR_KEY_NOT_FOUND;
	
	*value = ((int*)&oes->head)[nkey];
	return 0;
}

/* Returns value from the header as double */
int oesgkyd(oesfile *oes, int nkey, double *value)
{
	if ((nkey<0)||(nkey>=(int)(HEADSIZE/4))) 
		return CMPACK_ERR_KEY_NOT_FOUND;
	
	*value = ((float*)&oes->head)[nkey];
	return 0;
}

/* Returns value from the header as string */
int oesgkys(oesfile *oes, int nkey, char **value)
{
	if ((nkey<64)||(nkey>=128)||(nkey%16!=0)) 
		return CMPACK_ERR_KEY_NOT_FOUND;

	*value = (char*)cmpack_malloc(65);
	memcpy(*value, oes->head.comment[(nkey-64)/16], 64);
	(*value)[64] = '\0';
	return 0;
}

/* Sequential read of parameters */
int oesgkyn(oesfile *oes, int nkey, char **pkey, char **pval, char **pcom)
{
	int y, m, d, h, n, i;
	double f, s;
	char vbuf[256], cbuf[256];

	if (pkey)
		*pkey = NULL;
	if (pval)
		*pval = NULL;
	if (pcom)
		*pcom = NULL;

	if (nkey<0 || nkey>40)
		return CMPACK_ERR_KEY_NOT_FOUND;

	if (pkey) 
		*pkey = cmpack_strdup(keys[nkey]);

	switch(nkey)
	{
	case 0:	case 1:	case 2:	case 5:	case 10: case 11: case 12: case 15:
	case 16: case 21: case 24: case 25: case 26: case 27: case 28: case 29:
		oesgkyi(oes,nkey,&i);
		sprintf(vbuf, "%d", i);
		if (pval) 
			*pval = cmpack_strdup(vbuf);
		if (pcom) 
			*pcom = cmpack_strdup(coms[nkey]);
		break;

	case 3:
		oesgkyi(oes,3,&i);
		sprintf(vbuf, "%d", i);
		if (pval) 
			*pval = cmpack_strdup(vbuf);
		switch (i) {
			case 0: strcpy(cbuf, "unsigned data type"); break;
			case 1: strcpy(cbuf, "signed data type"); break;
			default: strcpy(cbuf, "invalid value"); break;
		}
		if (pcom)
			*pcom = cmpack_strdup(cbuf);
		break;

	case 4:
		oesgkyi(oes,4,&i);
		sprintf(vbuf, "%d", i);
		if (pval) 
			*pval = cmpack_strdup(vbuf);
		switch (i&0xFFFC) {
			case 4:   strcpy(cbuf, "8-bit integer"); break;
			case 8:   strcpy(cbuf, "16-bit integer"); break;
			case 16:  strcpy(cbuf, "32-bit integer"); break;
			case 32:  strcpy(cbuf, "32-bit float"); break;
			case 64:  strcpy(cbuf, "64-bit float"); break;
			case 128: strcpy(cbuf, "32-bit complex"); break;
			case 192: strcpy(cbuf, "64-bit complex"); break;
			case 256: strcpy(cbuf, "custom format"); break;
			default:  strcpy(cbuf, "invalid value"); break;
		}
		if ((i&1)!=0)
			strcat(cbuf, " with fixed offset");
		if ((i&2)!=0)
			strcat(cbuf, " normalized to 0..1");
		if (pcom)
			*pcom = cmpack_strdup(cbuf);
		break;

	case 6:
		oesgkyd(oes,6,&f);
		y=(int)(f/(16.0*32.0)); m=(int)((f-y*16.0*32.0)/32.0); d=(int)((f-y*16.0*32.0-m*32.0));
		sprintf(vbuf, "%04d-%02d-%02d", y, m, d);
		if (pval) 
			*pval = cmpack_strdup(vbuf);
		if (pcom) 
			*pcom = cmpack_strdup(coms[nkey]);
		break;

	case 7: case 8: case 13: case 17: case 18: case 19: case 20: case 30: case 31:
		oesgkyd(oes,nkey,&f);
		h=(int)(f/(60.0*60.0)); n=(int)((f-h*60.0*60.0)/60.0); s=(f-h*60.0*60.0-n*60.0);
		sprintf(vbuf, "%02d:%02d:%04.1f", h, n, s);
		if (pval) 
			*pval = cmpack_strdup(vbuf);
		if (pcom) 
			*pcom = cmpack_strdup(coms[nkey]);
		break;

	case 9:
		oesgkyd(oes,9,&f);
		sprintf(vbuf, "%.6f", f);
		if (pval) 
			*pval = cmpack_strdup(vbuf);
		if (pcom) 
			*pcom = cmpack_strdup(coms[nkey]);
		break;

	case 14: case 22: case 23: case 32:
		oesgkyd(oes,nkey,&f);
		sprintf(vbuf, "%.2f", f);
		if (pval) 
			*pval = cmpack_strdup(vbuf);
		if (pcom) 
			*pcom = cmpack_strdup(coms[nkey]);
		break;

	case 33: case 34:
		oesgkyd(oes,nkey+27,&f);
		sprintf(vbuf, "%.2f", f);
		if (pval) 
			*pval = cmpack_strdup(vbuf);
		if (pcom) 
			*pcom = cmpack_strdup(coms[nkey]);
		break;

	case 35:
		oesgkyi(oes,62,&i);
		sprintf(vbuf, "%08X", i);
		if (pval) 
			*pval = cmpack_strdup(vbuf);
		switch (i) {
			case 0x012AEE94: strcpy(cbuf, "Own magic number"); break;
			case 0x94EE2A01: strcpy(cbuf, "Swapped magic number"); break;
			default: strcpy(cbuf, "Invalid magic number");
		}
		if (pcom) 
			*pcom = cmpack_strdup(cbuf);
		break;

	case 36:
		oesgkyi(oes,63,&i);
		sprintf(vbuf, "%08X", i);
		if (pval) 
			*pval = cmpack_strdup(vbuf);
		switch (i) {
			case 0x012F3462: strcpy(cbuf, "Own magic number"); break;
			case 0x62342F01: strcpy(cbuf, "Swapped magic number"); break;
			default: strcpy(cbuf, "Invalid magic number");
		}
		if (pcom) 
			*pcom = cmpack_strdup(cbuf);
		break;

	case 37: case 38: case 39: case 40:
		if (pcom)
			oesgkys(oes, (nkey-37)*16+64, pcom);
		break;

	default:
		return CMPACK_ERR_KEY_NOT_FOUND;
	}
	return 0;
}
