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

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 "konvnef.h"
#include "comfun.h"
#include "console.h"

static uint32_t swap_uint(uint32_t val, int intel_format)
{
	if (intel_format)
		return val;
	else
		return (val & 0xFF) << 24 |	(val & 0xFF00) << 8 | (val & 0xFF0000) >> 8 | (val & 0xFF000000) >> 24;
}

static int16_t swap_short(int16_t val, int intel_format)
{
	if (intel_format)
		return val;
	else
		return (val & 0xFF) << 8 | (val & 0xFF00) >> 8;
}

static uint16_t swap_ushort(uint16_t val, int intel_format)
{
	if (intel_format)
		return val;
	else
		return (val & 0xFF) << 8 | (val & 0xFF00) >> 8;
}

static void read_shorts(uint16_t *pixel, int count, FILE *ifp, int intel_format)
{
	if (fread (pixel, 2, count, ifp) == count) {
		int i;
		for (i = 0; i < count; i++)
			pixel[i] = swap_ushort(pixel[i], intel_format);
	}
}

/*-------------------------   HUFFMAN TABLES -------------------------*/

struct jhead 
{
	unsigned bitbuf;
	int vbits, reset;
	int zero_after_ff;
	int error;
};

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 uint16_t* make_decoder(const uint8_t *source)
{
	return make_decoder_ref(&source);
}

/*
   getbits(n) where 0 <= n <= 25 returns an n-bit integer
 */
static unsigned getbithuff(struct 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 nikon_inflate(FILE *ifp, int meta_offset, int iwidth, int raw_width, int iheight, 
	int raw_height, int tiff_bps, uint32_t data_offset, unsigned chmask, uint32_t *image, int intel_format)
{
	static const uint8_t nikon_tree[][32] = {
		{ 0,1,5,1,1,1,1,1,1,2,0,0,0,0,0,0,	/* 12-bit lossy */
			5,4,3,6,2,7,1,0,8,9,11,10,12 },
		{ 0,1,5,1,1,1,1,1,1,2,0,0,0,0,0,0,	/* 12-bit lossy after split */
			0x39,0x5a,0x38,0x27,0x16,5,4,3,2,1,0,11,12,12 },
		{ 0,1,4,2,3,1,2,0,0,0,0,0,0,0,0,0,  /* 12-bit lossless */
			5,4,6,3,7,2,8,1,9,0,10,11,12 },
		{ 0,1,4,3,1,1,1,1,1,2,0,0,0,0,0,0,	/* 14-bit lossy */
			5,6,4,7,8,3,9,2,1,0,10,11,12,13,14 },
		{ 0,1,5,1,1,1,1,1,1,1,2,0,0,0,0,0,	/* 14-bit lossy after split */
			8,0x5c,0x4b,0x3a,0x29,7,6,5,4,3,2,1,0,13,14 },
		{ 0,1,4,2,2,3,1,2,0,0,0,0,0,0,0,0,	/* 14-bit lossless */
			7,6,8,5,9,4,10,3,11,12,2,0,1,13,14 } };

	uint16_t *huff, ver0, ver1, vpred[2][2], hpred[2], csize, value;
	int i, x, y, max, step=0, tree=0, split=0, row, col, len, shl, diff;
	struct jhead jh;

	memset(&jh, 0, sizeof(struct jhead));
	memset(image, 0, iwidth*iheight*sizeof(uint32_t)); 

	fseek (ifp, meta_offset, SEEK_SET);
	ver0 = fgetc(ifp);
	ver1 = fgetc(ifp);
	if (ver0 == 0x49 || ver1 == 0x58)
		fseek (ifp, 2110, SEEK_CUR);
	if (ver0 == 0x46) tree = 2;
	if (tiff_bps == 14) tree += 3;
	read_shorts (vpred[0], 4, ifp, intel_format);
	max = 1 << tiff_bps & 0x7fff;
	read_shorts(&csize, 1, ifp, intel_format);
	if (csize > 1)
		step = max / (csize-1);
	if (ver0 == 0x44 && ver1 == 0x20 && step > 0) {
		fseek (ifp, meta_offset+562, SEEK_SET);
		read_shorts(&value, 1, ifp, intel_format);
		split = value;
	}
	huff = make_decoder(nikon_tree[tree]);
	fseek (ifp, data_offset, SEEK_SET);
	for (row=0; row < raw_height; row++) {
		if (split && row == split) {
			cmpack_free (huff);
			huff = make_decoder(nikon_tree[tree+1]);
		}
		for (col=0; col < raw_width; col++) {
			i = getbithuff(&jh, ifp, *huff, huff+1);
			len = i & 15;
			shl = i >> 4;
			diff = ((getbithuff(&jh,ifp,len-shl,0) << 1) + 1) << shl >> 1;
			if ((diff & (1 << (len-1))) == 0)
				diff -= (1 << len) - !shl;
			if (col < 2) 
				hpred[col] = vpred[row & 1][col] += diff;
			else	   
				hpred[col & 1] += diff;
			if ((1<<((col%2)+2*(row%2))) & chmask) {
				x = (col/2); 
				y = (row/2);
				if (x < iwidth && y < iheight)
					image[y*iwidth + x] += hpred[col & 1];
			}
		}
	}
	cmpack_free (huff);

	return CMPACK_ERR_OK;
}

/*------------------------   NIKON RAW FORMAT   --------------------------------------*/

/* File descriptor */
typedef struct _neffile
{
	FILE		*ifd;						/* Source file */
	char		*camera_name;				/* Camera type */
	char		*date_time;					/* Date of observation */
	uint32_t	strip_offset;				/* Position of the image data */
	uint32_t	strip_bytes;				/* Size of image data */
	uint32_t	meta_offset;				/* Metadata offset */
	int			raw_width, raw_height;		/* Original image size */
	int			raw_bps;					/* Original bits per sample */
	int			comp;						/* Compression type */
	double		exposure;					/* Exposure duration in seconds */
	int			img_width, img_height;		/* Decoded image size */
	int			intel_format;				/* 0 = Motorola format, 1 = Intel format */
} neffile;

static int process_makernote(neffile *raw, FILE *tif, uint32_t offset)
{
	int intel_format;
	char mnheader[10], header[8], ifdentry[12];
	uint16_t i, ifdsize, tagid, tagtype;
	uint32_t next_ifd, tagvalue, tagcount, base;

	/* Makernote header */
	if (fseek(tif, offset, SEEK_SET)!=0) 
		return CMPACK_ERR_READ_ERROR;
	if (fread(mnheader, 10, 1, tif)!=1)
		return CMPACK_ERR_READ_ERROR;
	if (memcmp(mnheader, "Nikon\x00\x02\x10\x00\x00", 10)!=0 && memcmp(mnheader, "Nikon\x00\x02\x11\x00\x00", 10) != 0)
		return CMPACK_ERR_UNKNOWN_FORMAT;
	base = offset + 10;
	
	/* TIFF header */
	if (fseek(tif, base, SEEK_SET)!=0) 
		return CMPACK_ERR_READ_ERROR;
	if (fread(header, 8, 1, tif)!=1)
		return CMPACK_ERR_READ_ERROR;
	if (header[0] == 'M' && header[1] == 'M' && header[2] == '\0' && header[3] == '*')
		intel_format = 0;
	else if (header[0] == 'I' && header[1] == 'I' && header[2] == '*' && header[3] == '\0')
		intel_format = 1;
	else 
		return CMPACK_ERR_READ_ERROR;

	/* First IFD */
	memcpy(&next_ifd, header+4, sizeof(uint32_t));
	next_ifd = swap_uint(next_ifd, intel_format);
	while (next_ifd) {
		if (fseek(tif, next_ifd + base, SEEK_SET)!=0)
			return CMPACK_ERR_READ_ERROR;
		if (fread(&ifdsize, 2, 1, tif)!=1)
			return CMPACK_ERR_READ_ERROR;
		ifdsize = swap_short(ifdsize, intel_format);
		for (i=0; i<ifdsize; i++) {
			if (fread(&ifdentry, 12, 1, tif)!=1)
				return CMPACK_ERR_READ_ERROR;
			memcpy(&tagid, ifdentry, sizeof(int16_t));
			tagid = swap_ushort(tagid, intel_format);
			memcpy(&tagtype, ifdentry+2, sizeof(int16_t));
			tagtype = swap_ushort(tagtype, intel_format);
			memcpy(&tagcount, ifdentry+4, sizeof(uint32_t));
			tagcount = swap_uint(tagcount, intel_format);
			memcpy(&tagvalue, ifdentry+8, sizeof(uint32_t));
			tagvalue = swap_uint(tagvalue, intel_format);
			if (tagid==0x8c || tagid==0x96)
				raw->meta_offset = tagvalue + base;
		}
		if (fread(&next_ifd, 4, 1, tif)!=1)
			return CMPACK_ERR_READ_ERROR;
		next_ifd = swap_uint(next_ifd, intel_format);
	}
	return CMPACK_ERR_OK;
}

static int process_exif(neffile *raw, FILE *tif, uint32_t base, int intel_format)
{
	int i;
	long oldpos;
	uint16_t ifdsize, tagid, tagtype;
	uint32_t tagcount, tagvalue, data[2];
	char ifdentry[12];

	if (fseek(tif, base, SEEK_SET)!=0) 
		return CMPACK_ERR_READ_ERROR;
	if (fread(&ifdsize, 2, 1, tif)!=1) 
		return CMPACK_ERR_READ_ERROR;
	ifdsize = swap_ushort(ifdsize, intel_format);
	for (i=0; i<ifdsize; i++) {
		if (fread(&ifdentry, 12, 1, tif)!=1) 
			return CMPACK_ERR_READ_ERROR;
		memcpy(&tagid, ifdentry, sizeof(int16_t));
		tagid = swap_ushort(tagid, intel_format);
		memcpy(&tagtype, ifdentry+2, sizeof(int16_t));
		tagtype = swap_ushort(tagtype, intel_format);
		memcpy(&tagcount, ifdentry+4, sizeof(uint32_t));
		tagcount = swap_uint(tagcount, intel_format);
		memcpy(&tagvalue, ifdentry+8, sizeof(uint32_t));
		tagvalue = swap_uint(tagvalue, intel_format);
		if (tagid == 0x927c) {
			/* Offset to the makernote */
			oldpos = ftell(tif);
			process_makernote(raw, tif, tagvalue);
			fseek(tif, oldpos, SEEK_SET);
		} else
		if (tagid == 0x829a && tagtype==5 && tagcount==1) {
			/* Exposure duration */
			oldpos = ftell(tif);
			fseek(tif, tagvalue, SEEK_SET);
			fread(data, sizeof(uint32_t), 2, tif);
			if (data[1]!=0)
				raw->exposure = ((double)swap_uint(data[0], intel_format))/swap_uint(data[1], intel_format);
			fseek(tif, oldpos, SEEK_SET);
		}
	}
	return CMPACK_ERR_OK;
}

static int process_subifd(neffile *raw, FILE *tif, uint32_t base, int intel_format)
{
	int i, width = 0, height = 0, bps = 0, comp = 0;
	uint16_t ifdsize, tagid, tagtype;
	uint32_t tagcount, tagvalue;
	uint32_t offset = 0, bytes = 0;
	char ifdentry[12];

	if (fseek(tif, base, SEEK_SET)!=0) 
		return CMPACK_ERR_READ_ERROR;
	if (fread(&ifdsize, 2, 1, tif)!=1) 
		return CMPACK_ERR_READ_ERROR;
	ifdsize = swap_ushort(ifdsize, intel_format);
	for (i=0; i<ifdsize; i++) {
		if (fread(&ifdentry, 12, 1, tif)!=1) 
			return CMPACK_ERR_READ_ERROR;
		memcpy(&tagid, ifdentry, sizeof(int16_t));
		tagid = swap_ushort(tagid, intel_format);
		memcpy(&tagtype, ifdentry+2, sizeof(int16_t));
		tagtype = swap_ushort(tagtype, intel_format);
		memcpy(&tagcount, ifdentry+4, sizeof(uint32_t));
		tagcount = swap_uint(tagcount, intel_format);
		memcpy(&tagvalue, ifdentry+8, sizeof(uint32_t));
		if (tagid == 0x0100 && tagtype == 4 && tagcount == 1)
			width = swap_uint(tagvalue, intel_format);
		else if (tagid == 0x0100 && tagtype == 3 && tagcount == 1)
			width = swap_ushort(tagvalue, intel_format);
		else if (tagid == 0x101 && tagtype == 4 && tagcount == 1)
			height = swap_uint(tagvalue, intel_format);
		else if (tagid == 0x101 && tagtype == 3 && tagcount == 1)
			height = swap_ushort(tagvalue, intel_format);
		else if (tagid == 0x111 && tagtype == 4 && tagcount == 1)
			offset = swap_uint(tagvalue, intel_format);
		else if (tagid == 0x0117 && tagtype == 4 && tagcount == 1)
			bytes = swap_uint(tagvalue, intel_format);
		else if (tagid == 0x102 && tagtype == 3 && tagcount == 1)
			bps = swap_ushort(tagvalue, intel_format);
		else if (tagid == 0x103 && tagtype == 3 && tagcount == 1)
			comp = swap_ushort(tagvalue, intel_format);
	}
	if (width>0 && height>0 && width<65536 && height<65536 && bps>0 && offset>0 && bytes>0 && (comp == 1 || comp == 34713)) {
		if (width>raw->raw_width && height>raw->raw_height) {
			raw->raw_width = width;
			raw->raw_height = height;
			raw->raw_bps =  bps;
			raw->strip_offset = offset;
			raw->strip_bytes = bytes;
			raw->comp = comp;
		}
	}
	return CMPACK_ERR_OK;
}

/* Returns nonzero if the file is in raw format */
int nef_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] == 0x2A004D4D) && (buf[1] == 0x08000000)) || ((buf[0] == 0x002A4949) && (buf[1] == 0x00000008));
	}
	return 0;
}

/* Read parameters from SBIG file */
int nef_open(tHandle *handle, const char *filename, CmpackOpenMode mode, unsigned flags)
{
	int res = 0, intel_format = 0;
	neffile *raw;
	FILE *tif;
	char header[8], ifdentry[12];
	uint32_t k, next_ifd, tagcount, tagvalue, offset;
	long oldpos, oldpos2;
	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] == 'M' && header[1] == 'M' && header[2] == '\0' && header[3] == '*')
		intel_format = 0;
	else if (header[0] == 'I' && header[1] == 'I' && header[2] == '*' && header[3] == '\0')
		intel_format = 1;
	else 
		res = CMPACK_ERR_UNKNOWN_FORMAT;
	if (res != 0) {
		fclose(tif);
		return res;
	}
	
	raw = (neffile*)cmpack_calloc(1, sizeof(neffile));
	raw->ifd = tif;
	raw->intel_format = intel_format;

	memcpy(&next_ifd, header+4, sizeof(uint32_t));
	next_ifd = swap_uint(next_ifd, intel_format);
	while (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;
		}
		ifdsize = swap_ushort(ifdsize, intel_format);
		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));
			tagid = swap_ushort(tagid, intel_format);
			memcpy(&tagtype, ifdentry+2, sizeof(int16_t));
			tagtype = swap_ushort(tagtype, intel_format);
			memcpy(&tagcount, ifdentry+4, sizeof(uint32_t));
			tagcount = swap_uint(tagcount, intel_format);
			memcpy(&tagvalue, ifdentry+8, sizeof(uint32_t));
			tagvalue = swap_uint(tagvalue, intel_format);
			if (tagid==0x0110 && tagtype==2 && tagcount>0) {
				/* Camera model */
				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) {
				/* Date and time */
				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==0x8769) {
				/* EXIF tag */
				long oldpos = ftell(tif);
				process_exif(raw, tif, tagvalue, intel_format);
				fseek(tif, oldpos, SEEK_SET);
			}
			if (tagid == 0x14A && tagcount == 1) {
				/* Sub-IFD */
				oldpos = ftell(tif);
				process_subifd(raw, tif, tagvalue, intel_format);
				fseek(tif, oldpos, SEEK_SET);
			}
			if (tagid == 0x14A && tagcount > 1 && tagcount < 16) {
				/* Sub-IFDs */
				oldpos = ftell(tif);
				fseek(tif, tagvalue, SEEK_SET);
				for (k=0; k<tagcount; k++) {
					if (fread(&offset, 4, 1, tif)!=1)
						res = CMPACK_ERR_READ_ERROR;
					oldpos2 = ftell(tif);
					process_subifd(raw, tif, swap_uint(offset, intel_format), intel_format);
					fseek(tif, oldpos2, SEEK_SET);
				}
				fseek(tif, oldpos, SEEK_SET);
			}	
		}
		if (res==0 && fread(&next_ifd, 4, 1, tif)!=1) {
			res = CMPACK_ERR_READ_ERROR;
			break;
		}
		next_ifd = swap_uint(next_ifd, intel_format);
	}

	if (raw->raw_height<=0 || raw->raw_width<=0 || raw->raw_bps<=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->raw_width-42)/2;
	raw->img_height = raw->raw_height/2;

	*handle = raw;
	return CMPACK_ERR_OK;
}

/* Close file and free allocated memory */
void nef_close(tHandle handle)
{
	neffile *raw = (neffile*)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 nef_getbitpix(tHandle handle)
{
	return CMPACK_BITPIX_SLONG;
}

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

#define FC(row,col) \
	(filters >> ((((row) << 1 & 14) + ((col) & 1)) << 1) & 3)

static int fcol(int row, int col)
{
	static const char filter[16][16] =
	{ { 2,1,1,3,2,3,2,0,3,2,3,0,1,2,1,0 },
	  { 0,3,0,2,0,1,3,1,0,1,1,2,0,3,3,2 },
	  { 2,3,3,2,3,1,1,3,3,1,2,1,2,0,0,3 },
	  { 0,1,0,1,0,2,0,2,2,0,3,0,1,3,2,1 },
	  { 3,1,1,2,0,1,0,2,1,3,1,3,0,1,3,0 },
	  { 2,0,0,3,3,2,3,1,2,0,2,0,3,2,2,1 },
	  { 2,3,3,1,2,1,2,1,2,1,1,2,3,0,0,1 },
	  { 1,0,0,2,3,0,0,3,0,3,0,3,2,1,2,3 },
	  { 2,3,3,1,1,2,1,0,3,2,3,0,2,3,1,3 },
	  { 1,0,2,0,3,0,3,2,0,1,1,2,0,1,0,2 },
	  { 0,1,1,3,3,2,2,1,1,3,3,0,2,1,3,2 },
	  { 2,3,2,0,0,1,3,0,2,0,1,2,3,0,1,0 },
	  { 1,3,1,2,3,2,3,2,0,2,0,1,1,0,3,0 },
	  { 0,2,0,3,1,0,0,1,1,3,3,2,3,2,2,1 },
	  { 2,1,3,2,3,1,2,1,0,3,0,2,0,2,0,2 },
	  { 0,3,1,0,0,2,0,3,2,1,3,1,1,3,1,3 } };

	static const int filters = 0xb4b4b4b4;

	return FC(row, col);
}

/* Unpacked Nikon data */
int nef_get_unpacked(FILE *ifp, int meta_offset, int iwidth, int raw_width, int iheight,
	int raw_height, int tiff_bps, uint32_t data_offset, unsigned chmask, uint32_t *image, int intel_format, int load_flags)
{
	int row, col, bits = tiff_bps;

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

	unsigned short *tmp = (unsigned short*)cmpack_malloc(raw_width*raw_height * sizeof(short));

	fseek(ifp, data_offset, SEEK_SET);
	read_shorts(tmp, raw_width*raw_height, ifp, intel_format);
	for (row = 0; row < raw_height; row++) {
		for (col = 0; col < raw_width; col++) {
			if ((1 << ((col % 2) + 2 * (row % 2))) & chmask) {
				int x = (col / 2), y = (row / 2);
				if (x < iwidth && y < iheight) {
					int value = tmp[row*raw_width + col];
					image[y*iwidth + x] += value;
				}
			}
		}
	}
	   
	cmpack_free(tmp);
	return CMPACK_ERR_OK;
}


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

	intel_format = raw->intel_format;
	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(uint32_t))) {
		/* Insufficient buffer */
		return CMPACK_ERR_BUFFER_TOO_SMALL;
	}
	if (!raw->strip_offset || !raw->strip_bytes) {
		/* Invalid file format */
		return CMPACK_ERR_UNKNOWN_FORMAT;
	}

	switch (channel)
	{
	case CMPACK_CHANNEL_RED:	chmask = 0x01; break;
	case CMPACK_CHANNEL_GREEN:	chmask = 0x06; break;
	case CMPACK_CHANNEL_BLUE:	chmask = 0x08; 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;
	case CMPACK_CHANNEL_RGG:	chmask = 0x07; break;
	default:					chmask = 0x0F; break;
	}
	if (raw->comp == 34713) {
		if (raw->raw_width*raw->raw_height * 2 != raw->strip_bytes && raw->raw_width*raw->raw_height * 3 != raw->strip_bytes) {
			res = nikon_inflate(raw->ifd, raw->meta_offset, nx, raw->raw_width, ny, raw->raw_height,
				raw->raw_bps, raw->strip_offset, chmask, (uint32_t*)buf, intel_format);
		}
		else {
			/* Invalid file format */
			return CMPACK_ERR_UNKNOWN_FORMAT;
		}
	}
	else if (raw->comp == 1) {
		res = nef_get_unpacked(raw->ifd, raw->meta_offset, nx, raw->raw_width, ny, raw->raw_height,
			raw->raw_bps, raw->strip_offset, chmask, (uint32_t*)buf, intel_format, 0);
	}
	else {
		/* Invalid file format */
		return CMPACK_ERR_UNKNOWN_FORMAT;
	}

	if (res == 0 && channel == CMPACK_CHANNEL_GREEN) {
		pixels = nx * ny;
		for (i = 0; i < pixels; i++)
			((int*)buf)[i] /= 2;
	}
	return res;
}

int nef_getsize(tHandle handle, int *width, int *height)
{
	neffile *raw = (neffile*)handle;

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

int nef_copyheader(tHandle fs, CmpackImageHeader *hdr, CmpackChannel channel, CmpackConsole *con)
{
	int		avg_frames, sum_frames;
	char	datestr[64], timestr[64], *filter;
	CmpackDateTime dt;
	neffile *src = (neffile*)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 = nef_getfilter(fs, channel);
	if (filter) {
		ffpkys(dst,"FILTER",filter,"COLOR CHANNEL",&hdr->status);
		cmpack_free(filter);
	}
	avg_frames = sum_frames = 1;
	nef_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 *nef_getmagic(tHandle handle)
{
	neffile *raw = (neffile*)handle;
	return cmpack_strdup(raw->camera_name);
}

/* Get date and time of observation */
int nef_getdatetime(tHandle handle, CmpackDateTime *dt)
{
	neffile *raw = (neffile*)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 nef_getexptime(tHandle handle, double *val)
{
	neffile *raw = (neffile*)handle;
	if (raw->exposure>0) {
		*val = raw->exposure;
		return CMPACK_ERR_OK;
	} else {
		*val = 0;
		return CMPACK_ERR_KEY_NOT_FOUND;
	}
}

/* Get filter name */
char *nef_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 nef_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;
	}
}
