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

konvraw.c (C-Munipack project)
Conversion functions for OES Astro 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 <stdio.h>
#include <string.h>
#include <time.h>
#include <stdint.h>
#include <fitsio.h>

#include "config.h"
#include "konvcrw.h"
#include "comfun.h"
#include "console.h"

#define INVALID_CAMERA_TEMPERATURE -128

/*--------------   LOSSLESS JPEG DECODER   ---------------------------------*/

/*
   Not a full implementation of Lossless JPEG, just
   enough to decode Canon, Kodak and Adobe DNG images.
 */
typedef struct _jhead {
	int		bits, high, wide, clrs, sraw, psv, restart, vpred[6];
	uint16_t	*huff[6], *free[4], *row;

	unsigned bitbuf;
	int vbits, reset;

	int zero_after_ff;
	int error;
} jhead;

static uint16_t *make_decoder_ref (const uint8_t **source)
{
	int max, len, h, i, j;
	const uint8_t *count;
	uint16_t *huff;

	count = (*source)-1;
	*source += 16;	
	for (max=16; max>=1; max--) {
		if (count[max]!=0)
			break;
	}
	huff = (uint16_t*)cmpack_calloc (1+(1<<max), sizeof(uint16_t));
	huff[0] = max;
	h = 1;
	for (len=1; len<=max; len++) {
		for (i=0; i < count[len]; i++) {
			for (j=0; j < 1 << (max-len); j++) {
				if (h <= 1 << max)
					huff[h++] = len << 8 | **source;
			}
			(*source)++;
		}
	}
	return huff;
}

static int ljpeg_start (FILE *ifp, jhead *jh)
{
	int c, tag, len;
	uint8_t* data = malloc(0x10000);
	const uint8_t *dp;

	memset (jh, 0, sizeof(jhead));
	jh->restart = 0x7FFFFFFF;
	jh->zero_after_ff = 1;
	fread (data, 2, 1, ifp);
	if (data[1] != 0xd8) {
		free(data);
		return 0;
	}
	do {
		fread (data, 2, 2, ifp);
		tag =  data[0] << 8 | data[1];
		len = (data[2] << 8 | data[3]) - 2;
		if (tag <= 0xff00) {
			free(data);
			return 0;
		}
		fread (data, 1, len, ifp);
		switch (tag) 
		{
		case 0xffc3:
			/* SOF 3 marker */
			jh->sraw = ((data[7] >> 4) * (data[7] & 15) - 1) & 3;
		case 0xffc0:
			/* SOF marker */
			jh->bits = data[0];
			jh->high = data[1] << 8 | data[2];
			jh->wide = data[3] << 8 | data[4];
			jh->clrs = data[5] + jh->sraw;
			if (len == 9) 
				(void)getc(ifp);
			break;
		case 0xffc4:
			/* DHT marker */
			dp = data;
			while (dp < data+len) {
				c = *dp++;
				if (c>=4)
					break;
				jh->free[c] = jh->huff[c] = make_decoder_ref (&dp);
			}
			break;
		case 0xffda:
			/* Start of scan */
			jh->psv = data[1+data[0]*2];
			jh->bits -= data[3+data[0]*2] & 15;
			break;
		case 0xffdd:
			/* Define Restart Interval */
			jh->restart = data[0] << 8 | data[1];
		}
	} while (tag != 0xffda);
	for (c=0; c < 5; c++) {
		if (!jh->huff[c+1]) 
			jh->huff[c+1] = jh->huff[c];
	}
	if (jh->sraw) {
		for (c=0; c < 4; c++)
			jh->huff[2+c] = jh->huff[1];
		for (c=0; c < jh->sraw; c++)
			jh->huff[1+c] = jh->huff[0];
	}
	jh->row = (uint16_t *)cmpack_calloc (jh->wide*jh->clrs, 4);
	free(data);
	return 1;
}

/*
   getbits(n) where 0 <= n <= 25 returns an n-bit integer
 */
static unsigned getbithuff(jhead *jh, FILE *ifp, int nbits, uint16_t *huff)
{
	unsigned c;

	if (nbits == 0 || jh->vbits < 0) 
		return 0;

	while (!jh->reset && jh->vbits < nbits && (c = fgetc(ifp)) != EOF &&
		!(jh->reset = jh->zero_after_ff && c == 0xff && fgetc(ifp))) {
			jh->bitbuf = (jh->bitbuf << 8) + (uint8_t) c;
			jh->vbits += 8;
	}

	c = jh->bitbuf << (32-jh->vbits) >> (32-nbits);
	if (huff) {
		jh->vbits -= huff[c] >> 8;
		c = (uint8_t) huff[c];
	} else
		jh->vbits -= nbits;
	if (jh->vbits < 0) 
		jh->error = 1;
	return c;
}

static int ljpeg_diff (jhead *jh, FILE *ifp, uint16_t *huff)
{
	int len, diff;

	len = getbithuff(jh, ifp, *huff, huff+1);
	if (len == 16)
		return -32768;

	diff = getbithuff(jh, ifp, len, 0);
	if ((diff & (1 << (len-1))) == 0)
		diff -= (1 << len) - 1;
	return diff;
}

static uint16_t *ljpeg_row(FILE *ifp, int jrow, jhead *jh)
{
	int col, c, diff, pred, spred=0;
	uint16_t mark=0, *row[3];

	if (jrow * jh->wide % jh->restart == 0) {
		for (c=0; c < 6; c++)
			jh->vpred[c] = 1 << (jh->bits-1);
		if (jrow) {
			fseek (ifp, -2, SEEK_CUR);
			do {
				mark = (mark << 8) + (c = fgetc(ifp));
			} while (c != EOF && mark >> 4 != 0xffd);
		}
		jh->bitbuf = jh->vbits = jh->reset = 0;
	}
	for (c=0; c < 3; c++) 
		row[c] = jh->row + jh->wide*jh->clrs*((jrow+c) & 1);

	for (col=0; col < jh->wide; col++) {
		for (c=0; c < jh->clrs; c++) {
			diff = ljpeg_diff (jh, ifp, jh->huff[c]);
			if (jh->sraw && c <= jh->sraw && (col | c))
				pred = spred;
			else if (col) 
				pred = row[0][-jh->clrs];
			else	    
				pred = (jh->vpred[c] += diff) - diff;
			if (jrow && col) {
				switch (jh->psv) 
				{
				case 1:	
					break;
				case 2: 
					pred = row[1][0];
					break;
				case 3: 
					pred = row[1][-jh->clrs];				
					break;
				case 4: 
					pred = pred +   row[1][0] - row[1][-jh->clrs];		
					break;
				case 5: 
					pred = pred + ((row[1][0] - row[1][-jh->clrs]) >> 1);	
					break;
				case 6: 
					pred = row[1][0] + ((pred - row[1][-jh->clrs]) >> 1);	
					break;
				case 7: 
					pred = (pred + row[1][0]) >> 1;				
					break;
				default: 
					pred = 0;
				}
			}
			if ((**row = pred + diff) >> jh->bits)
				jh->error = 1;
			if (c <= jh->sraw) 
				spred = **row;
			row[0]++; row[1]++;
		}
	}
	return row[2];
}

static void ljpeg_end(jhead *jh)
{
	int c;
	
	for (c=0; c < 4; c++) 
		cmpack_free(jh->free[c]);
	cmpack_free(jh->row);
}

static void lossless_jpeg_load_raw(FILE *ifp, const uint16_t *cr2_slice, int raw_width, 
	int raw_height, int top_margin, int left_margin, int iwidth, int iheight, unsigned chmask, 
	int *image)
{
	int jwide, jrow, jcol, row=0, col=0;
	jhead jh;

	memset(image, 0, iwidth*iheight*sizeof(int));

	if (!ljpeg_start(ifp, &jh)) 
		return;

	jwide = jh.wide * jh.clrs;
	for (jrow=0; jrow < jh.high; jrow++) {
		uint16_t *rp = ljpeg_row (ifp, jrow, &jh);
		for (jcol=0; jcol < jwide; jcol++) {
			int val = *rp++;
			if (jh.bits <= 12)
				val = (val & 0xfff);
			if (cr2_slice[0]) {
				int jidx = jrow*jwide + jcol;
				int i = jidx / (cr2_slice[1]*raw_height);
				if (i >= cr2_slice[0]) {
					i = cr2_slice[0];
					jidx -= i * (cr2_slice[1]*raw_height);
					row = jidx / cr2_slice[2];
					col = jidx % cr2_slice[2] + i*cr2_slice[1];
				} else {
					jidx -= i * (cr2_slice[1]*raw_height);
					row = jidx / cr2_slice[1];
					col = jidx % cr2_slice[1] + i*cr2_slice[1];
				}
			}
			if (raw_width == 3984) {
				col -= 2;
				if (col < 0) {
					row--;
					col += raw_width;
				}
			}
			if ((row < raw_height) && ((1<<((col%2)+2*(row%2))) & chmask)) {
				int x = (col-left_margin)/2-1, y = (row-top_margin)/2-1;
				if (x>=0 && x<iwidth && y>=0 && y<iheight) 
					image[x + y*iwidth] += val;
			}
			col++;
			if (col >= raw_width) {
				row++;
				col = 0;
			}
		}
	}
	ljpeg_end (&jh);
}

/*-------------------------------   CANON CRW DECODER   --------------------------*/

/* File descriptor */
typedef struct _crwfile
{
	FILE		*ifd;						/* Source file */
	char		*camera_name;				/* Camera type */
	char		*date_time;					/* Date of observation */
	uint32_t	model_id;					/* Camera model id */
	uint32_t	strip_offset;				/* Position of the image data */
	uint32_t	strip_bytes;				/* Size of image data */
	uint32_t	exif_offset;				/* Position of exif header */
	uint32_t	makernote_offset;			/* Position of the maker note tag */
	uint32_t	makernote_size;				/* Size of the maker note in bytes */
	uint32_t	sensorinfo_offset;			/* Position of the sensor info tag */
	uint32_t	sensorinfo_size;			/* Size of the sensor info tag in words! */
	uint32_t	camerainfo_offset;			/* Position of the camera info tag */
	uint32_t	camerainfo_size;			/* Size of the camera info tag in bytes */
	uint16_t	cr2_slice[3];				/* Slices */ 
	int			crw_width, crw_height;		/* Original image size */
	int			left, top, right, bottom;	/* Margins */
	double		exposure;					/* Exposure duration in seconds */
	int			ccd_temp;					/* Camera temperature (-128 = not available) */
	int			img_width, img_height;		/* Decoded image size */
} crwfile;

/* Returns nonzero if the file is in raw format */
int crw_test(const char *block, size_t length, size_t filesize)
{
	uint32_t buf[16];

	if (filesize>=64 && length>=64) {
		memcpy(buf, block, 16*sizeof(uint32_t));
		return (buf[0]==0x2A4949) && (buf[2]==0x025243);
	}
	return 0;
}

/* Read parameters from SBIG file */
int crw_open(tHandle *handle, const char *filename, CmpackOpenMode mode, unsigned flags)
{
	int res = 0;
	crwfile *raw;
	FILE *tif;
	char header[8], cr2hdr[8], ifdentry[12];
	uint32_t image_ifd, current_ifd, next_ifd, tagcount, tagvalue;
	uint16_t i, ifdsize, tagid, tagtype;

	*handle = NULL;

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

	/* TIFF header */
	if (fread(header, 8, 1, tif)!=1)
		res = CMPACK_ERR_UNKNOWN_FORMAT;
	if (header[0]!='I' || header[1]!='I' || header[2]!='*' || header[3]!='\0')
		res = CMPACK_ERR_UNKNOWN_FORMAT;
	if (fread(cr2hdr, 8, 1 , tif)!=1)
		res = CMPACK_ERR_UNKNOWN_FORMAT;
	if (cr2hdr[0]!='C' || cr2hdr[1]!='R' || cr2hdr[2]!=2 || cr2hdr[3]!=0)
		res = CMPACK_ERR_UNKNOWN_FORMAT;
	if (res!=0) {
		fclose(tif);
		return res;
	}
	
	raw = (crwfile*)cmpack_calloc(1, sizeof(crwfile));
	raw->ifd = tif;
	raw->ccd_temp = INVALID_CAMERA_TEMPERATURE;
	raw->right = raw->bottom = -1;

	memcpy(&image_ifd, cr2hdr+4, sizeof(int32_t));
	memcpy(&next_ifd, header+4, sizeof(int32_t));
	while (next_ifd) {
		current_ifd = next_ifd;
		if (fseek(tif, next_ifd, SEEK_SET)!=0) {
			res = CMPACK_ERR_READ_ERROR;
			break;
		}
		if (fread(&ifdsize, 2, 1, tif)!=1) {
			res = CMPACK_ERR_READ_ERROR;
			break;
		}
		for (i=0; i<ifdsize; i++) {
			if (fread(&ifdentry, 12, 1, tif)!=1) {
				res = CMPACK_ERR_READ_ERROR;
				break;
			}
			memcpy(&tagid, ifdentry, sizeof(int16_t));
			memcpy(&tagtype, ifdentry+2, sizeof(int16_t));
			memcpy(&tagcount, ifdentry+4, sizeof(int32_t));
			memcpy(&tagvalue, ifdentry+8, sizeof(int32_t));
			if (tagid==0x0111 && current_ifd==image_ifd)
				raw->strip_offset = tagvalue;
			if (tagid==0x0117 && current_ifd==image_ifd)
				raw->strip_bytes = tagvalue;
			if (tagid==0x8769) 
				raw->exif_offset = tagvalue;
			if (tagid==0x0110 && tagtype==2 && tagcount>0) {
				long oldpos = ftell(tif);
				fseek(tif, tagvalue, SEEK_SET);
				raw->camera_name = (char*)cmpack_malloc(tagcount);
				fread(raw->camera_name, 1, tagcount, tif);
				fseek(tif, oldpos, SEEK_SET);
			}
			if (tagid==0x0132 && tagtype==2 && tagcount>0) {
				long oldpos = ftell(tif);
				fseek(tif, tagvalue, SEEK_SET);
				raw->date_time = (char*)cmpack_malloc(tagcount);
				fread(raw->date_time, 1, tagcount, tif);
				fseek(tif, oldpos, SEEK_SET);
			}
			if (tagid==0xc640 && tagtype==3 && tagcount==3) {
				long oldpos = ftell(tif);
				fseek(tif, tagvalue, SEEK_SET);
				fread(raw->cr2_slice, 2, 3, tif);
				fseek(tif, oldpos, SEEK_SET);
			}
		}
		if (res==0 && fread(&next_ifd, 4, 1, tif)!=1) {
			res = CMPACK_ERR_READ_ERROR;
			break;
		}
	}

	/* Process EXIF data */
	if (res==0 && raw->exif_offset) {
		if (fseek(tif, raw->exif_offset, SEEK_SET)!=0) {
			res = CMPACK_ERR_READ_ERROR;
		} else
		if (fread(&ifdsize, 2, 1, tif)!=1) {
			res = CMPACK_ERR_READ_ERROR;
		} else {
			for (i=0; i<ifdsize; i++) {
				if (fread(&ifdentry, 12, 1, tif)!=1) {
					res = CMPACK_ERR_READ_ERROR;
					break;
				}
				memcpy(&tagid, ifdentry, sizeof(int16_t));
				memcpy(&tagtype, ifdentry+2, sizeof(int16_t));
				memcpy(&tagcount, ifdentry+4, sizeof(int32_t));
				memcpy(&tagvalue, ifdentry+8, sizeof(int32_t));
				if (tagid == 0x927c && tagtype==7) {
					/* Offset to the makernote */
					raw->makernote_offset = tagvalue;
					raw->makernote_size = tagcount;
				} else
				if (tagid == 0x829a && tagtype==5 && tagcount==1) {
					/* Exposure duration */
					uint32_t data[2];
					long oldpos = ftell(tif);
					fseek(tif, tagvalue, SEEK_SET);
					fread(data, sizeof(uint32_t), 2, tif);
					if (data[1]!=0)
						raw->exposure = (double)data[0]/data[1];
					fseek(tif, oldpos, SEEK_SET);
				}
			}
		}
	}

	/* Process maker note */
	if (res==0 && raw->makernote_offset) {
		if (fseek(tif, raw->makernote_offset, SEEK_SET)!=0) {
			res = CMPACK_ERR_READ_ERROR;
		} else
		if (fread(&ifdsize, 2, 1, tif)!=1) {
			res = CMPACK_ERR_READ_ERROR;
		} else {
			for (i=0; i<ifdsize; i++) {
				fread(&ifdentry, 12, 1, tif);
				memcpy(&tagid, ifdentry, sizeof(int16_t));
				memcpy(&tagtype, ifdentry+2, sizeof(int16_t));
				memcpy(&tagcount, ifdentry+4, sizeof(int32_t));
				memcpy(&tagvalue, ifdentry+8, sizeof(int32_t));
				if (tagid==0x10 && tagtype==4 && tagcount==1) {
					/* CanonModelID tag */
					raw->model_id = tagvalue;
				} else
				if (tagid==0xE0 && tagtype==3 && tagcount>=17) {
					/* SensorInfo tag */
					raw->sensorinfo_offset = tagvalue;
					raw->sensorinfo_size = tagcount;
				} else
				if (tagid==0x0D && tagtype==7 && tagcount>=13) {
					/* CameraInfo tag */
					raw->camerainfo_offset = tagvalue;
					raw->camerainfo_size = tagcount;
				}
			}
		}
	}

	/* Process SensorInfo tag */
	if (res==0 && raw->sensorinfo_offset) {
		if (fseek(tif, raw->sensorinfo_offset, SEEK_SET)!=0) {
			res = CMPACK_ERR_READ_ERROR;
		} else {
			uint16_t *data = cmpack_malloc(raw->sensorinfo_size*sizeof(uint16_t));
			if (fread(data, 2, raw->sensorinfo_size, tif)!=raw->sensorinfo_size) {
				res = CMPACK_ERR_READ_ERROR;
			} else {
				raw->crw_width = data[1];
				raw->crw_height = data[2];
				raw->left = data[5];
				raw->top = data[6];
				raw->right = data[7];
				raw->bottom = data[8];
			}
			cmpack_free(data);
		}
	}

	/* Process CameraInfo tag */
	if (res==0 && raw->camerainfo_offset) {
		if (fseek(tif, raw->camerainfo_offset, SEEK_SET)!=0) {
			res = CMPACK_ERR_READ_ERROR;
		} else {
			uint8_t *data = cmpack_malloc(raw->camerainfo_size*sizeof(uint8_t));
			if (fread(data, 1, raw->camerainfo_size, tif)!=raw->camerainfo_size) {
				res = CMPACK_ERR_READ_ERROR;
			} else {
				switch (raw->model_id) 
				{
				case 0x80000176:
				case 0x80000190:
				case 0x80000254:
					raw->ccd_temp = data[24] - 128;
					break;
				case 0x80000218:
				case 0x80000250:
				case 0x80000252:
				case 0x80000261:
				case 0x80000270:
				case 0x80000286:
				case 0x80000287:
				case 0x80000288:
					raw->ccd_temp = data[25] - 128;
					break;
				case 0x80000285:
					raw->ccd_temp = data[27] - 128;
					break;
				}
			}
			cmpack_free(data);
		}
	}

	if (raw->crw_height<=0 || raw->crw_width<=0 || !raw->strip_offset || !raw->strip_bytes)
		res = CMPACK_ERR_UNKNOWN_FORMAT;
	if (res!=0) {
		cmpack_free(raw);
		fclose(tif);
		return res;
	}
	raw->img_width = ((raw->right-raw->left)+1)/2;
	raw->img_height = ((raw->bottom-raw->top)+1)/2;

	*handle = raw;
	return CMPACK_ERR_OK;
}

/* Close file and free allocated memory */
void crw_close(tHandle handle)
{
	crwfile *raw = (crwfile*)handle;

	if (raw) {
		if (raw->ifd)
			fclose(raw->ifd);
		cmpack_free(raw->camera_name);
		cmpack_free(raw->date_time);
		cmpack_free(raw);
	}
}

/* Get magic string */
CmpackBitpix crw_getbitpix(tHandle handle)
{
	return CMPACK_BITPIX_SLONG;
}

/* Get image data range */
int crw_getrange(tHandle handle, double *minvalue, double *maxvalue)
{
	if (minvalue)
		*minvalue = 0.0;
	if (maxvalue)
		*maxvalue = 4095.0;
	return 0;
}

/* Get image data */
int crw_getimage(tHandle handle, void *buf, int bufsize, CmpackChannel channel)
{
	int nx, ny, i, pixels;
	unsigned chmask;
	crwfile *raw = (crwfile*)handle;

	nx = raw->img_width;
	ny = raw->img_height;
	if (nx<=0 || nx>=0x4000 || ny<=0 || ny>=0x4000) {
		/* Invalid image size */
		return CMPACK_ERR_INVALID_SIZE;
	}
	if (bufsize < (int)(nx*ny*sizeof(int))) {
		/* Insufficient buffer */
		return CMPACK_ERR_BUFFER_TOO_SMALL;
	}
	if (!raw->strip_offset || !raw->strip_bytes) {
		/* Invalid file format */
		return CMPACK_ERR_UNKNOWN_FORMAT;
	}
	if (fseek(raw->ifd, raw->strip_offset, SEEK_SET)!=0) {
		/* Invalid image offset */
		return CMPACK_ERR_READ_ERROR;
	}
	switch (channel)
	{
	case CMPACK_CHANNEL_RED:	
		if ((raw->top&2)==0 && raw->model_id!=0x80000285)
			chmask = 0x04;
		else
			chmask = 0x01; 
		break;
	case CMPACK_CHANNEL_GREEN:	
		if ((raw->top&2)==0 && raw->model_id!=0x80000285)
			chmask = 0x09; 
		else
			chmask = 0x06;
		break;
	case CMPACK_CHANNEL_BLUE:	
		if ((raw->top&2)==0 && raw->model_id!=0x80000285)
			chmask = 0x02; 
		else
			chmask = 0x08;
		break;
	case CMPACK_CHANNEL_RGG:
		if ((raw->top & 2) == 0 && raw->model_id != 0x80000285)
			chmask = 0x0C;
		else
			chmask = 0x07;
		break;
	case CMPACK_CHANNEL_0:		chmask = 0x01; break;
	case CMPACK_CHANNEL_1:		chmask = 0x02; break;
	case CMPACK_CHANNEL_2:		chmask = 0x04; break;
	case CMPACK_CHANNEL_3:		chmask = 0x08; break;
	default:					chmask = 0x0F; break;
	}
	lossless_jpeg_load_raw(raw->ifd, raw->cr2_slice, raw->crw_width, raw->crw_height,
		raw->top, raw->left, raw->img_width, raw->img_height, chmask, (int*)buf);
	if (channel == CMPACK_CHANNEL_GREEN) {
		pixels = nx*ny;
		for (i=0; i<pixels; i++)
			((int*)buf)[i] /= 2;
	}
	return CMPACK_ERR_OK;
}

int crw_getsize(tHandle handle, int *width, int *height)
{
	crwfile *raw = (crwfile*)handle;

	if (width) 
		*width = raw->img_width;
	if (height) 
		*height = raw->img_height;
	return 0;
}

int crw_copyheader(tHandle fs, CmpackImageHeader *hdr, CmpackChannel channel, CmpackConsole *con)
{
	int		avg_frames, sum_frames;
	char	datestr[64], timestr[64], *filter;
	CmpackDateTime dt;
	crwfile *src = (crwfile*)fs;
	fitsfile *dst = (fitsfile*)hdr->fits;

	/* Set date and time of observation */
	if (src->date_time) {
		memset(&dt, 0, sizeof(CmpackDateTime));
		if (sscanf(src->date_time, "%4d:%2d:%2d %2d:%2d:%2d", &dt.date.year, &dt.date.month,
			&dt.date.day, &dt.time.hour, &dt.time.minute, &dt.time.second) == 6) {
			sprintf(datestr, "%04d-%02d-%02d", dt.date.year, dt.date.month, dt.date.day);
			ffpkys(dst, "DATE-OBS", (char*)datestr, "UT DATE OF START", &hdr->status);
			sprintf(timestr, "%02d:%02d:%02d", dt.time.hour, dt.time.minute, dt.time.second);
			ffpkys(dst, "TIME-OBS", (char*)timestr, "UT TIME OF START", &hdr->status);
		}
	}

	/* Other parameters */
	if (src->exposure>0) 
		ffpkyg(dst,"EXPTIME",src->exposure,2,"EXPOSURE IN SECONDS",&hdr->status);
	filter = crw_getfilter(fs, channel);
	if (filter) {
		ffpkys(dst,"FILTER",filter,"COLOR CHANNEL",&hdr->status);
		cmpack_free(filter);
	}
	if (src->ccd_temp > -128 && src->ccd_temp < 999)
		ffukyg(dst, "CCD-TEMP", src->ccd_temp, 2, "AVERAGE CCD TEMPERATURE", &hdr->status);
	avg_frames = sum_frames = 1;
	crw_getframes(fs, channel, &avg_frames, &sum_frames);
	if (avg_frames>1)
		ffpkyj(dst,"FR_AVG",avg_frames,"No. of subframes averaged",&hdr->status);
	if (sum_frames>1)
		ffpkyj(dst,"FR_SUM",sum_frames,"No. of subframes summed",&hdr->status);

	return (hdr->status!=0 ? CMPACK_ERR_WRITE_ERROR : 0);
}

/* Get magic string */
char *crw_getmagic(tHandle handle)
{
	crwfile *raw = (crwfile*)handle;
	return cmpack_strdup(raw->camera_name);
}

/* Get date and time of observation */
int crw_getdatetime(tHandle handle, CmpackDateTime *dt)
{
	crwfile *raw = (crwfile*)handle;

	if (raw->date_time) {
		memset(dt, 0, sizeof(CmpackDateTime));
		if (sscanf(raw->date_time, "%4d:%2d:%2d %2d:%2d:%2d", &dt->date.year, &dt->date.month,
			&dt->date.day, &dt->time.hour, &dt->time.minute, &dt->time.second) == 6) {
			return CMPACK_ERR_OK;
		}
	}
	return CMPACK_ERR_KEY_NOT_FOUND;
}

/* Get exposure duration */
int crw_getexptime(tHandle handle, double *val)
{
	crwfile *raw = (crwfile*)handle;
	if (raw->exposure>0) {
		*val = raw->exposure;
		return CMPACK_ERR_OK;
	} else {
		*val = 0;
		return CMPACK_ERR_KEY_NOT_FOUND;
	}
}

/* Get sensor temperature */
int crw_getccdtemp(tHandle handle, double *val)
{
	crwfile *raw = (crwfile*)handle;
	if (raw->ccd_temp!=INVALID_CAMERA_TEMPERATURE) {
		*val = raw->ccd_temp;
		return CMPACK_ERR_OK;
	} else {
		*val = INVALID_TEMP;
		return CMPACK_ERR_KEY_NOT_FOUND;
	}
}

/* Get filter name */
char *crw_getfilter(tHandle handle, CmpackChannel channel)
{
	switch (channel)
	{
	case CMPACK_CHANNEL_RED:
		return cmpack_strdup("Red");
	case CMPACK_CHANNEL_GREEN:
		return cmpack_strdup("Green");
	case CMPACK_CHANNEL_BLUE:
		return cmpack_strdup("Blue");
	default:
		return NULL;
	}
}

/* Set number of frames averaged */
void crw_getframes(tHandle handle, CmpackChannel channel, int *avg_frames, int *sum_frames)
{
	if (channel == CMPACK_CHANNEL_GREEN) {
		if (avg_frames)	*avg_frames = 2;
	}
	if (channel == CMPACK_CHANNEL_DEFAULT) {
		if (sum_frames) *sum_frames = 4;
	}
}
