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

mdark.c (C-Munipack project)
Master-dark tool
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 <stdint.h>
#include <float.h>

#include "comfun.h"
#include "console.h"
#include "list.h"
#include "robmean.h"
#include "ccdfile.h"
#include "cmpack_common.h"
#include "cmpack_mdark.h"

/***********************   DATA TYPES   ***********************/

/* Master-dark configuration context */
struct _CmpackMasterDark
{
	int refcnt;					/**< Reference counter */
	CmpackConsole *con;			/**< Console */
	CmpackCcdFile *outfile;		/**< Output file context */
	CmpackBitpix bitpix;		/**< Required image data format */
	int scalable;		   		/**< Make scalable master-dark */
	double minvalue, maxvalue;	/**< Bad pixel value, overexposed value */
	CmpackBorder border;		/**< Border size */
	CmpackBitpix in_bitpix;     /**< Output image data format */
	int in_width;				/**< Width of frames */
	int in_height;         		/**< Height of frames */
	double exptime;				/**< Exposure duration in seconds */
	CmpackImageHeader header;	/**< Header of first frame */
	CmpackList *frames;    		/**< List of source frames */
};

static void frame_free(void *frame)
{
	cmpack_image_destroy((CmpackImage*)frame);
}

/**********************   LOCAL FUNCTIONS   ***********************/

/* Clean configuration context */
static void mdark_clear(CmpackMasterDark *lc)
{
	list_free_with_items(lc->frames, &frame_free);
	cmpack_image_header_destroy(&lc->header);
	if (lc->outfile) {
		cmpack_ccd_destroy(lc->outfile);
		lc->outfile = NULL;
	}
	if (lc->con) {
		cmpack_con_destroy(lc->con);
		lc->con = NULL;
	}
}

/********************   PUBLIC FUNCTIONS   **************************/

/* Initializes the context */
CmpackMasterDark *cmpack_mdark_init(void)
{
	CmpackMasterDark *f = (CmpackMasterDark*)cmpack_calloc(1, sizeof(CmpackMasterDark));
	f->refcnt = 1;
	f->minvalue = 0;
	f->maxvalue = 65535.0;
	return f;
}

/* Increment the reference counter */
CmpackMasterDark *cmpack_mdark_reference(CmpackMasterDark *ctx)
{
	ctx->refcnt++;
	return ctx;
}

/* Decrement reference counter / detroy the instance */
void cmpack_mdark_destroy(CmpackMasterDark *ctx)
{
	if (ctx) {
		ctx->refcnt--;
		if (ctx->refcnt==0) {
			mdark_clear(ctx);
			cmpack_free(ctx);
		}
	}
}

/* Attach console */
void cmpack_mdark_set_console(CmpackMasterDark *ctx, CmpackConsole *con)
{
	if (con!=ctx->con) {
		if (ctx->con) 
			cmpack_con_destroy(ctx->con);
		ctx->con = con;
		if (ctx->con) 
			cmpack_con_reference(ctx->con);
	}
}

/* Set output image data format */
void cmpack_mdark_set_bitpix(CmpackMasterDark *lc, CmpackBitpix bitpix)
{
	lc->bitpix = bitpix;
}

/* Set output image data format */
CmpackBitpix cmpack_mdark_get_bitpix(CmpackMasterDark *lc)
{
	return lc->bitpix;
}

/* Set output image data format */
void cmpack_mdark_set_scalable(CmpackMasterDark *lc, int scalable)
{
	lc->scalable = scalable;
}

/* Set output image data format */
int cmpack_mdark_get_scalable(CmpackMasterDark *lc)
{
	return lc->scalable;
}

/* Set image border */
void cmpack_mdark_set_border(CmpackMasterDark *lc, const CmpackBorder *border)
{
	if (border)
		lc->border = *border;
	else
		memset(&lc->border, 0, sizeof(CmpackBorder));
}

/* Get image border */
void cmpack_mdark_get_border(CmpackMasterDark *lc, CmpackBorder *border)
{
	*border = lc->border;
}

/* Set image border */
void cmpack_mdark_set_thresholds(CmpackMasterDark *lc, double minvalue, double maxvalue)
{
	lc->minvalue = minvalue;
	lc->maxvalue = maxvalue;
}

/* Set minimum pixel value */
void cmpack_mdark_set_minvalue(CmpackMasterDark *lc, double minvalue)
{
	lc->minvalue = minvalue;
}

/* Set minimum pixel value */
double cmpack_mdark_get_minvalue(CmpackMasterDark *lc)
{
	return lc->minvalue;
}

/* Set maximum pixel value */
void cmpack_mdark_set_maxvalue(CmpackMasterDark *lc, double maxvalue)
{
	lc->maxvalue = maxvalue;
}

/* Set minimum pixel value */
double cmpack_mdark_get_maxvalue(CmpackMasterDark *lc)
{
	return lc->maxvalue;
}

/* Reads first image and opens destination file */
int cmpack_mdark_open(CmpackMasterDark *lc, CmpackCcdFile *outfile)
{
	/* Print configuration parameters */
	if (is_debug(lc->con)) {
		printout(lc->con, 1, "Master-dark context:");
		printpari(lc->con, "BitPix", 1, lc->bitpix);
		printpari(lc->con, "Scalable", 1, lc->scalable);
		printparvi(lc->con, "Border", 1, 4, (int*)(&lc->border));
		printpard(lc->con, "Bad pixel threshold", 1, lc->minvalue, 2); 
		printpard(lc->con, "Overexp. pixel threshold", 1, lc->maxvalue, 2); 
	}

	/* Initialize context */
	lc->outfile = cmpack_ccd_reference(outfile);
	cmpack_image_header_destroy(&lc->header);
	list_free_with_items(lc->frames, &frame_free);
	lc->frames = NULL;
    return 0;
}

/* Reads the input frame and stores it in the image list */
int cmpack_mdark_read(CmpackMasterDark *lc, CmpackCcdFile *file)
{
	int res, nx, ny;
	CmpackImage *image;
	CmpackBitpix bitpix;
	CmpackCcdParams params;

    /* Check source file */
    if (!file) {
        printout(lc->con, 0, "Invalid file context");
 	    return CMPACK_ERR_INVALID_PAR;
    }

    /* Read parameters of source file */
	if (cmpack_ccd_get_params(file, CMPACK_CM_IMAGE | CMPACK_CM_EXPOSURE, &params)!=0) {
		printout(lc->con, 0, "Failed to read image parameters from the file.");
		return CMPACK_ERR_READ_ERROR;
	}
	nx = params.image_width;
	ny = params.image_height;
	if (nx<=0 || ny<=0 || nx>=65536 || ny>=65536) {
		printout(lc->con, 0, "Invalid size of the source image");
	    return CMPACK_ERR_INVALID_SIZE;
	}
	bitpix = params.image_format;
	if (bitpix==CMPACK_BITPIX_UNKNOWN) {
		printout(lc->con, 0, "Invalid data format of the source image");
		return CMPACK_ERR_INVALID_BITPIX;
	}
	if (params.exposure < 0) {
		printout(lc->con, 0, "Invalid exposure duration in the source image");
		return CMPACK_ERR_INVALID_EXPTIME;
	}

    if (lc->in_width==0 && lc->in_height==0) {
        /* First image */
        lc->in_width = nx;
        lc->in_height= ny;
		lc->exptime = params.exposure;
		lc->in_bitpix = params.image_format;
		cmpack_image_header_init(&lc->header);
		ccd_save_header(file, &lc->header, lc->con);
	} else {
		/* Next image */
		if (nx!=lc->in_width || ny!=lc->in_height) {
			printout(lc->con, 0, "The size of the image is different from the previous images");
			return CMPACK_ERR_DIFF_SIZE_SRC;
		}
		if (bitpix!=lc->in_bitpix) {
			printout(lc->con, 0, "The data format of the image is different from the previous images");
			return CMPACK_ERR_DIFF_BITPIX_SRC;
		}
	}

    /* read image data */
	res = cmpack_ccd_to_image(file, CMPACK_BITPIX_AUTO, &image);
	if (res!=0) 
		return res;

	/* Add this image to image list */
	lc->frames = list_prepend(lc->frames, image);
	return 0;
}

int cmpack_mdark_close(CmpackMasterDark *lc)
{
	int res, x, y, ix, jx, iy, jy, i, j, k, nframes, nx, ny;
	int underflow = 0, overflow = 0, badpixels = 0;
	double a, b, *idata, *fbuf, value, minvalue, maxvalue;
	char msg[MAXLINE];
	CmpackImage **inf, *outf;
	CmpackList *ptr;
	CmpackCcdParams params;
	CmpackBitpix bitpix;

	/* Check context */
	if (!lc->outfile) {
		printout(lc->con, 0, "No destination file defined");
		return CMPACK_ERR_NO_OUTPUT_FILE;
	}

	/* Check input data */
	nframes = list_count(lc->frames);
	if (nframes<=0) {
		cmpack_ccd_destroy(lc->outfile);
		lc->outfile = NULL;
		printout(lc->con, 0, "No source files defined");
		return CMPACK_ERR_NO_INPUT_FILES;
	}
	nx = lc->in_width;
	ny = lc->in_height;
	if (nx<=0 || ny<=0) {
		cmpack_ccd_destroy(lc->outfile);
		lc->outfile = NULL;
		printout(lc->con, 0, "Invalid size of the destination image");
		return CMPACK_ERR_INVALID_SIZE;
	}

	if (lc->bitpix==CMPACK_BITPIX_AUTO) {
		/* The output frame have the same format as the first source frame */
		bitpix = lc->in_bitpix;
	} else {
		/* Use required image data format */
		bitpix = lc->bitpix;
	}
	if (bitpix==CMPACK_BITPIX_UNKNOWN) {
		/* Invalid image data format */
		cmpack_ccd_destroy(lc->outfile);
		lc->outfile = NULL;
		printout(lc->con, 0, "Invalid data format of the target image");
		return CMPACK_ERR_INVALID_BITPIX;
	}

	/* makes array of images for faster access to data */
	inf = (CmpackImage**)cmpack_malloc(nframes*sizeof(CmpackImage*));
	for (ptr=lc->frames, k=0; ptr!=NULL; ptr=ptr->next, k++)
		inf[k] = (CmpackImage*)ptr->ptr;

	outf = cmpack_image_new(nx, ny, CMPACK_BITPIX_DOUBLE);
	if (!outf) {
		cmpack_ccd_destroy(lc->outfile);
		lc->outfile = NULL;
		printout(lc->con, 0, "Memory allocation error");
		return CMPACK_ERR_MEMORY;
	}

	fbuf = cmpack_malloc(nframes*sizeof(double));
	ix = lc->border.left; jx = nx - lc->border.right;
	iy = lc->border.top;  jy = ny - lc->border.bottom;
	minvalue = lc->minvalue;
	maxvalue = lc->maxvalue;
	idata = (double*)cmpack_image_data(outf);
	for (y=0; y<ny; y++) {
		for (x=0; x<nx; x++) {
			i = x + y*nx;
			if (y>=iy && y<jy && x>=ix && x<jx) {
				/* Computation of mean */
				for (k=j=0; k<nframes; k++) {
					value = cmpack_image_getpixel(inf[k], x, y);
					if (value>minvalue && value<maxvalue)
						fbuf[j++] = value;
				}
				if (j>0) {
					cmpack_robustmean(j, fbuf, &a, &b);
					/* Range checking */
					if (a < minvalue) {
						underflow++;
						a = minvalue;
					} else
					if (a > maxvalue) {
						overflow++;
						a = maxvalue;
					}
				} else {
					a = minvalue;
					badpixels++;
				}
			} else {
				a = minvalue;
			}
			/* Store the mean into output frame */
			idata[i] = a;
		}
	}

	/* Clear input frames */
	list_free_with_items(lc->frames, &frame_free);
	lc->frames = NULL;
	cmpack_free(inf);
	cmpack_free(fbuf);

	/* Write header information */
	ccd_prepare(lc->outfile, nx, ny, bitpix);
	ccd_restore_header(lc->outfile, &lc->header, lc->con);
	memset(&params, 0, sizeof(CmpackCcdParams));
	params.object.designation = "Master-dark frame";
	params.subframes_avg = nframes;
	params.exposure = lc->exptime;
	cmpack_ccd_set_params(lc->outfile, CMPACK_CM_OBJECT | CMPACK_CM_EXPOSURE |
		CMPACK_CM_SUBFRAMES, &params);
	ccd_set_bool(lc->outfile, "SCALABLE", lc->scalable, "SCALABLE DARK FRAME");
	ccd_set_origin(lc->outfile);
	ccd_set_pcdate(lc->outfile);

	/* Write image data */
	res = ccd_write_image(lc->outfile, outf);
	cmpack_image_destroy(outf);

	/* Range check */
	if (overflow>0) {
		sprintf(msg,"Warning: An overflow has been occurred on %d of %d pixels during computation (max.=%.12g).", 
			overflow, nx*ny, maxvalue);
		printout(lc->con, 0, msg);
	}
	if (underflow>0) {
		sprintf(msg,"Warning: An underflow has been occurred on %d of %d pixels during computation (min.=%.12g).", 
			underflow, nx*ny, minvalue);
		printout(lc->con, 0, msg);
	}

	/* Clear file name */
	cmpack_image_header_destroy(&lc->header);
	cmpack_ccd_destroy(lc->outfile);
	lc->outfile = NULL;
	return res;
}
