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

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

/* File descriptor */
typedef struct _mrwfile
{
	FILE*		ifd;						/* Source file */
	char*		camera_name;				/* Camera type */
	char*		date_time;					/* Date of observation */
	double		exposure;					/* Exposure duration in seconds */
	int			raw_width, raw_height;		/* Raw image size */
	int			img_width, img_height;		/* Decoded image size */
	long		data_offset;
} mrwfile;

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 uint16_t swap_ushort(uint16_t val, int intel_format)
{
	if (intel_format)
		return val;
	else
		return (val & 0xFF) << 8 | (val & 0xFF00) >> 8;
}

static int process_exif(FILE* tif, long base, long offset, mrwfile* raw)
{
	static const int intel_format = 0;

	int i;
	long oldpos;
	uint16_t ifdsize, tagid, tagtype;
	uint32_t tagcount, tagvalue, data[2];
	char ifdentry[12];

	if (fseek(tif, base + offset, 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 == 0x829a && tagtype == 5 && tagcount == 1) {
			/* Exposure duration */
			oldpos = ftell(tif), offset = swap_uint(tagvalue, intel_format);
			fseek(tif, base + offset, 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 parse_ifd(FILE* f, long base, mrwfile* mrw)
{
	static const int intel_order = 0;
	int i, width = 0, height = 0, comp = 0;

	unsigned short count = 0;
	if (fread(&count, 2, 1, f) != 1)
		return CMPACK_ERR_READ_ERROR;
	count = swap_ushort(count, intel_order);
	for (i = 0; i < count; i++) {
		char ifd_entry[12];
		if (fread(ifd_entry, 12, 1, f) != 1)
			return CMPACK_ERR_READ_ERROR;

		unsigned tagid = swap_ushort(*(unsigned short*)(ifd_entry), intel_order);
		unsigned tagtype = swap_ushort(*(unsigned short*)(ifd_entry + 2), intel_order);
		unsigned tagcount = swap_uint(*(unsigned*)(ifd_entry + 4), intel_order);
		unsigned tagvalue = *(unsigned*)(ifd_entry + 8);

		if (tagid == 0x0100 && tagtype == 4 && tagcount == 1)
			width = swap_uint(tagvalue, intel_order);
		else if (tagid == 0x0100 && tagtype == 3 && tagcount == 1)
			width = swap_ushort(tagvalue, intel_order);
		else if (tagid == 0x101 && tagtype == 4 && tagcount == 1)
			height = swap_uint(tagvalue, intel_order);
		else if (tagid == 0x101 && tagtype == 3 && tagcount == 1)
			height = swap_ushort(tagvalue, intel_order);
		else if (tagid == 0x103 && tagtype == 3 && tagcount == 1)
			comp = swap_ushort(tagvalue, intel_order);
		else if (tagid == 0x8769) {
			/* EXIF tag */
			long oldpos = ftell(f), offset = swap_uint(tagvalue, intel_order);
			process_exif(f, base, offset, mrw);
			fseek(f, oldpos, SEEK_SET);
		}
		else if (tagid == 0x0110 && tagtype == 2 && tagcount > 0) {
			/* Camera name */
			long oldpos = ftell(f), offset = swap_uint(tagvalue, intel_order);
			fseek(f, base + offset, SEEK_SET);
			mrw->camera_name = (char*)cmpack_malloc(tagcount);
			fread(mrw->camera_name, 1, tagcount, f);
			fseek(f, oldpos, SEEK_SET);
		}
		else if (tagid == 0x0132 && tagtype == 2 && tagcount > 0) {
			/* Date & time */
			long oldpos = ftell(f), offset = swap_uint(tagvalue, intel_order);
			fseek(f, base + offset, SEEK_SET);
			mrw->date_time = (char*)cmpack_malloc(tagcount);
			fread(mrw->date_time, 1, tagcount, f);
			fseek(f, oldpos, SEEK_SET);
		}
	}

	return CMPACK_ERR_OK;
}

static int parse_tiff(FILE* f, mrwfile* mrw)
{
	static const int intel_order = 0;

	long first_ifd, base_pos = ftell(f);
	char header[8];

	if (fread(header, 1, 8, f) != 8)
		return CMPACK_ERR_READ_ERROR;
	if (header[0] != 'M' || header[1] != 'M' || header[2] != '\0' || header[3] != '*')
		return CMPACK_ERR_READ_ERROR;

	first_ifd = swap_uint(*(unsigned long*)(header + 4), intel_order);
	while (first_ifd > 0) {
		if (fseek(f, first_ifd + base_pos, SEEK_SET) != 0)
			return CMPACK_ERR_READ_ERROR;
		if (parse_ifd(f, base_pos, mrw))
			return CMPACK_ERR_READ_ERROR;
		if (fread(&first_ifd, 1, 4, f) != 4)
			return CMPACK_ERR_READ_ERROR;
		first_ifd = swap_uint(first_ifd, intel_order);
	}

	return CMPACK_ERR_OK;
}

/* Returns nonzero if the file is in raw format */
int mrw_test(const char* block, size_t length, size_t filesize)
{
	uint32_t buf[4];
	if (filesize >= 16 && length >= 16) {
		memcpy(buf, block, 4 * sizeof(uint32_t));
		return (buf[0] == 0x4D524D00) && (buf[2] == 0x44525000);
	}
	return 0;
}

/* Read parameters from SBIG file */
int mrw_open(tHandle* handle, const char* filename, CmpackOpenMode flags)
{
	int res = 0, intel_format = 0;
	mrwfile* raw;
	FILE* f;
	char header[8];
	long offset, tag, length;

	*handle = NULL;

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

	/* TIFF header */
	if (fread(header, 8, 1, f) != 1)
		res = CMPACK_ERR_UNKNOWN_FORMAT;
	if (header[0] != '\0' || header[1] != 'M' || header[2] != 'R' || header[3] != 'M')
		res = CMPACK_ERR_UNKNOWN_FORMAT;
	if (res != 0) {
		fclose(f);
		return res;
	}

	raw = (mrwfile*)cmpack_calloc(1, sizeof(mrwfile));
	raw->ifd = f;

	memcpy(&offset, header + 4, sizeof(uint32_t));
	offset = swap_uint(offset, intel_format);

	while (ftell(f) < offset) {
		if (fread(&tag, 4, 1, f) != 1) {
			res = CMPACK_ERR_READ_ERROR;
			break;
		}
		tag = swap_uint(tag, intel_format);

		if (fread(&length, 4, 1, f) != 1) {
			res = CMPACK_ERR_READ_ERROR;
			break;
		}
		length = swap_uint(length, intel_format);

		long pos = ftell(f);
		if (tag == 0x505244 && length >= 12) {
			/* PRD: image size */
			char data[12];
			if (fread(data, 12, 1, f) != 1) {
				res = CMPACK_ERR_READ_ERROR;
				break;
			}
			raw->raw_width = swap_ushort(*(unsigned short*)(data + 10), intel_format);
			raw->raw_height = swap_ushort(*(unsigned short*)(data + 8), intel_format);
		}
		if (tag == 0x545457) {
			/* TIFF */
			if (parse_tiff(f, raw) != 0) {
				res = CMPACK_ERR_READ_ERROR;
				break;
			}
			raw->data_offset = offset + 8;
		}
		if (fseek(f, pos + length, SEEK_SET) != 0) {
			res = CMPACK_ERR_READ_ERROR;
			break;
		}
	}

	if (raw->raw_height <= 0 || raw->raw_width <= 0 || raw->data_offset <= 0)
		res = CMPACK_ERR_UNKNOWN_FORMAT;
	if (res != 0) {
		cmpack_free(raw);
		fclose(f);
		return res;
	}

	raw->img_width = raw->raw_width / 2;
	raw->img_height = raw->raw_height / 2;
	*handle = raw;
	return CMPACK_ERR_OK;
}

/* Close file and free allocated memory */
void mrw_close(tHandle handle)
{
	mrwfile* raw = (mrwfile*)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 mrw_getbitpix(tHandle handle)
{
	return CMPACK_BITPIX_USHORT;
}

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

int mrw_getsize(tHandle handle, int* width, int* height)
{
	mrwfile* raw = (mrwfile*)handle;

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

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

	/* Set date and time of observation */
	if (src->date_time) {
		memset(&dt, 0, sizeof(CmpackDateTime));
		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);
		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 = mrw_getfilter(fs, channel);
	if (filter) {
		ffpkys(dst, "FILTER", filter, "COLOR CHANNEL", &hdr->status);
		cmpack_free(filter);
	}
	avg_frames = sum_frames = 1;
	mrw_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* mrw_getmagic(tHandle handle)
{
	mrwfile* raw = (mrwfile*)handle;
	return cmpack_strdup(raw->camera_name);
}

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

	if (raw->date_time) {
		memset(dt, 0, sizeof(CmpackDateTime));
		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);
		return CMPACK_ERR_OK;
	}
	return CMPACK_ERR_KEY_NOT_FOUND;
}

/* Get exposure duration */
int mrw_getexptime(tHandle handle, double* val)
{
	mrwfile* raw = (mrwfile*)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* mrw_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 mrw_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;
	}
}

/* Get image data */
int mrw_getimage(tHandle handle, void* buf, int bufsize, CmpackChannel channel)
{
	static const int tiff_bps = 12;

	int bits, encoded_size, irow, icol;
	unsigned chmask;
	uint32_t bitbuf = 0;
	mrwfile* raw = (mrwfile*)handle;
	int raw_width = raw->raw_width, raw_height = raw->raw_height;
	unsigned char* tmp;
	const unsigned char* sptr;
	unsigned short* image, val;

	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;
	}

	int 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(uint16_t))) {
		/* Insufficient buffer */
		return CMPACK_ERR_BUFFER_TOO_SMALL;
	}
	if (!raw->data_offset) {
		/* Invalid file format */
		return CMPACK_ERR_UNKNOWN_FORMAT;
	}
	if (fseek(raw->ifd, raw->data_offset, SEEK_SET) != 0) {
		/* Invalid file format */
		return CMPACK_ERR_UNKNOWN_FORMAT;
	}

	encoded_size = raw_width * tiff_bps / 8 * raw_height;

	tmp = cmpack_malloc(encoded_size);
	if (fread(tmp, 1, encoded_size, raw->ifd) != encoded_size) {
		cmpack_free(tmp);
		return CMPACK_ERR_UNKNOWN_FORMAT;
	}

	sptr = tmp;
	image = (unsigned short*)buf;
	memset(image, 0, nx * ny * sizeof(uint16_t));
	for (irow = 0; irow < raw_height; irow++) {
		bits = 0;
		for (icol = 0; icol < raw_width; icol++) {
			bits += tiff_bps;
			while (bits > 0) {
				bitbuf = (bitbuf << 8) | (*sptr++);
				bits -= 8;
			}
			val = (unsigned short)(bitbuf << (32 - tiff_bps + bits) >> (32 - tiff_bps));
			if ((1 << ((icol % 2) + 2 * (irow % 2))) & chmask) {
				int x = (icol / 2), y = (irow / 2);
				if (x < nx && y < ny) 
					image[y * nx + x] += val;
			}
		}
	}
	cmpack_free(tmp);
	return 0;
}
