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

dark.c (C-Munipack project)
Dark correction
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 <float.h>

#include "comfun.h"
#include "console.h"
#include "ccdfile.h"
#include "image.h"
#include "cmpack_common.h"
#include "cmpack_dark.h"

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

/* Dark correction context */
struct _CmpackDarkCorr
{
	int refcnt;						/**< Reference counter */
	CmpackConsole *con;				/**< Console context */
	CmpackBorder border;			/**< Border size */
	CmpackImage *dark;				/**< Dark frame image data */
	int scaling;					/**< Use dark-frame scaling */
	int scalable;					/**< Is dark frame scalable? */
	double exptime;					/**< Exposure duration for dark-frame */
	double minvalue, maxvalue;		/**< Bad pixel value, overexposed value */
};

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

/* Computes the output image from source image and dark image (out=sci-dark) */
static void dark_dark(CmpackDarkCorr *lc, CmpackImage *image, double exptime)
{
	int i, width, height, x, y, left, top, right, bottom;
	int underflow, overflow;
	double scale, *sdata, *ddata, minvalue, maxvalue, value;

	if (is_debug(lc->con)) {
		printpars(lc->con, "Image data format", 1, pixformat(cmpack_image_bitpix(image)));
		printpard(lc->con, "Bad pixel threshold", 1, lc->minvalue, 2); 
		printpard(lc->con, "Overexp. pixel threshold", 1, lc->maxvalue, 2); 
		printparvi(lc->con, "Border", 1, 4, (int*)(&lc->border));
	}

	/* Dark subtraction and range checking */
	underflow = overflow = 0;
	width = cmpack_image_width(image);
	height = cmpack_image_height(image);
	left = lc->border.left;
	top = lc->border.top;
	right = width - lc->border.right;
	bottom = height - lc->border.bottom;
	minvalue = lc->minvalue;
	maxvalue = lc->maxvalue;
	scale = (lc->scaling && lc->scalable && lc->exptime > 0 && exptime > 0 ? exptime / lc->exptime : 1.0);
	sdata = (double*)cmpack_image_data(image);
	ddata = (double*)cmpack_image_data(lc->dark);
	for (y=0;y<height;y++) {
		for (x=0;x<width;x++) {
			i = x + y*width;
			if (x>=left && x<right && y>=top && y<bottom) {
				value = sdata[i];
				if (value>minvalue && value<maxvalue) {
					value -= scale*ddata[i];
					if (value<minvalue) {
						value = minvalue;
						underflow = 1;
					}
					if (value>maxvalue) {
						value = maxvalue;
						overflow = 1;
					}
				} else {
					value = minvalue;
				}
			} else {
				value = minvalue;
			}
			sdata[i] = value;
		}
	}

	/* Normal return */
	if (overflow)
		printout(lc->con, 1, "Warning: An overflow has been occurred during computation");
	if (underflow)
		printout(lc->con, 1, "Warning: An underflow has been occurred during computation");
}

static void dark_clear(CmpackDarkCorr *ctx)
{
	if (ctx->dark) {
		cmpack_image_destroy(ctx->dark);
		ctx->dark = NULL;
	}
	if (ctx->con) {
		cmpack_con_destroy(ctx->con);
		ctx->con = NULL;
	}
}

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

/* Initializes the variables and loads the dark-frame */
CmpackDarkCorr *cmpack_dark_init(void)
{
	CmpackDarkCorr *f = (CmpackDarkCorr*)cmpack_calloc(1, sizeof(CmpackDarkCorr));
	f->refcnt = 1;
	f->minvalue = 0;
	f->maxvalue = 65535.0;
	return f;
}

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

/* Decrement reference counter / detroy the instance */
void cmpack_dark_destroy(CmpackDarkCorr *ctx)
{
	if (ctx) {
		ctx->refcnt--;
		if (ctx->refcnt==0) {
			dark_clear(ctx);
			cmpack_free(ctx);
		}
	}
}

/* Attach console */
void cmpack_dark_set_console(CmpackDarkCorr *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);
	}
}

/* Enable or disable dark-frame scaling */
void cmpack_dark_set_scaling(CmpackDarkCorr *ctx, int scaling)
{
	ctx->scaling = scaling!=0;
}

/* Get current status of "scaling" flag */
int cmpack_dark_get_scaling(CmpackDarkCorr *ctx)
{
	return ctx->scaling;
}

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

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

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

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

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

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

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

/* Initializes the variables and loads the dark-frame */
int cmpack_dark_rdark(CmpackDarkCorr *lc, CmpackCcdFile *dark)
{
	int res, nx, ny, scalable;
	CmpackCcdParams params;
	double exptime;

	/* Clear previous dark frame */
	if (lc->dark)
		cmpack_image_destroy(lc->dark);
	lc->dark = NULL;
	lc->scalable = 0;
	lc->exptime = 0.0;

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

    /* Read and check dimensions */
	nx = cmpack_ccd_width(dark);
	ny = cmpack_ccd_height(dark);
	if (nx<=0 || nx>=65536 || ny<=0 || ny>=65536) {
		printout(lc->con, 1, "Invalid dimensions of the dark frame");
		return CMPACK_ERR_INVALID_SIZE;
	}

	/* Is the dark frame scalable? */
	if (cmpack_ccd_get_params(dark, CMPACK_CM_EXPOSURE, &params)!=0) {
		printout(lc->con, 0, "Failed to read image parameters from the file.");
		return CMPACK_ERR_READ_ERROR;
	}
	exptime = params.exposure;
	if (exptime < 0) {
		printout(lc->con, 1, "Invalid exposure duration in the dark frame");
		return CMPACK_ERR_INVALID_EXPTIME;
	}
	if (cmpack_ccd_gkyl(dark, "SCALABLE", &scalable)==0) {
		lc->scalable = scalable && exptime>0;
		lc->exptime = exptime;
	}

	/* Read dark frame data */
	res = cmpack_ccd_to_image(dark, CMPACK_BITPIX_DOUBLE, &lc->dark);
	if (res!=0) 
		return res;

	/* Print parameters */
	if (is_debug(lc->con)) {
		printout(lc->con, 1, "Dark correction frame:");
		printpari(lc->con, "Width", 1, cmpack_image_width(lc->dark));
		printpari(lc->con, "Height", 1, cmpack_image_height(lc->dark));
		printpard(lc->con, "Exp. time", lc->exptime >= 0, lc->exptime, 2);
		if (lc->scaling) {
			if (lc->scalable && lc->exptime>0) 
				printout(lc->con, 1, "This is scalable dark, using advanced calibration");
			else
				printout(lc->con, 1, "Dark frame is not scalable, using standard calibration!");
		}
	}
    return 0;
}

/* Computes the output image from source image and dark image (out=sci-dark) */
int cmpack_dark(CmpackDarkCorr *lc, CmpackCcdFile *file)
{
	int res, nx, ny;
	CmpackBitpix bitpix;
	CmpackImage *image;
	double exptime;
	char msg[MAXLINE];

	/* Check dark file */
	if (!lc->dark) {
		printout(lc->con, 0, "Missing dark frame");
		return CMPACK_ERR_NO_DARK_FRAME;
	}

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

	/* Check image size */
	nx = cmpack_ccd_width(file);
	ny = cmpack_ccd_height(file);
	if (nx<=0 || ny<=0 || nx>=65536 || ny>=65536) {
		printout(lc->con, 0, "Invalid size of the source image");
		return CMPACK_ERR_INVALID_SIZE;
	}
	if (cmpack_image_width(lc->dark)!=nx || cmpack_image_height(lc->dark)!=ny) {
		printout(lc->con, 0, "The size of the dark frame is different from the source image");
		return CMPACK_ERR_DIFF_SIZE_FLAT;
	}
	bitpix = cmpack_ccd_bitpix(file);
	if (bitpix==CMPACK_BITPIX_UNKNOWN) {
		printout(lc->con, 0, "Unsupported data format of the source frame");
		return CMPACK_ERR_READ_ERROR;
	}

	/* Read and check exposure duration */
	exptime = 0.0;
	if (lc->scaling && lc->scalable) {
		CmpackCcdParams params;
		if (cmpack_ccd_get_params(file, CMPACK_CM_EXPOSURE, &params)!=0) {
			printout(lc->con, 0, "Failed to read image parameters from the file.");
			return CMPACK_ERR_READ_ERROR;
		}
		exptime = params.exposure;
		if (exptime < 0) {
			printout(lc->con, 1, "Invalid exposure duration in the source frame");
			return CMPACK_ERR_INVALID_EXPTIME;
		}
		sprintf(msg,"Exposure duration: %.2f s", exptime);
		printout(lc->con, 0, msg);
	}

	/* Read exposure data */
	res = cmpack_ccd_to_image(file, CMPACK_BITPIX_DOUBLE, &image);
	if (res!=0) 
		return res;
	
	/* Dark correction */
	dark_dark(lc, image, exptime);

	/* Store image data */
	res = ccd_write_image(file, image);
	if (res==0)
		ccd_update_history(file, "Dark frame subtracted.");
	cmpack_image_destroy(image);

	return res;
}


/* Computes the output image from source image and dark image (out=sci-dark) */
int cmpack_dark_ex(CmpackDarkCorr *lc, CmpackCcdFile *infile, CmpackCcdFile *outfile)
{
	int res, nx, ny;
	CmpackBitpix bitpix;
	CmpackImage *image;
	double exptime;
	char msg[MAXLINE];

	/* Check dark file */
	if (!lc->dark) {
		printout(lc->con, 0, "Missing dark frame");
		return CMPACK_ERR_NO_DARK_FRAME;
	}

  	/* Check parameters */
	if (!infile) {
		printout(lc->con, 0, "Invalid input frame context");
		return CMPACK_ERR_INVALID_PAR;
	}
	if (!outfile) {
		printout(lc->con, 0, "Invalid output frame context");
		return CMPACK_ERR_INVALID_PAR;
	}

	/* Check image size */
	nx = cmpack_ccd_width(infile);
	ny = cmpack_ccd_height(infile);
	if (nx<=0 || ny<=0 || nx>=65536 || ny>=65536) {
		printout(lc->con, 0, "Invalid size of the source image");
		return CMPACK_ERR_INVALID_SIZE;
	}
	if (cmpack_image_width(lc->dark)!=nx || cmpack_image_height(lc->dark)!=ny) {
		printout(lc->con, 0, "The size of the dark frame is different from the source image");
		return CMPACK_ERR_DIFF_SIZE_FLAT;
	}
	bitpix = cmpack_ccd_bitpix(infile);
	if (bitpix==CMPACK_BITPIX_UNKNOWN) {
		printout(lc->con, 0, "Unsupported data format of the source frame");
		return CMPACK_ERR_READ_ERROR;
	}

	/* Read and check exposure duration */
	exptime = 0.0;
	if (lc->scaling && lc->scalable) {
		CmpackCcdParams params;
		if (cmpack_ccd_get_params(infile, CMPACK_CM_EXPOSURE, &params)!=0) {
			printout(lc->con, 0, "Failed to read image parameters from the file.");
			return CMPACK_ERR_READ_ERROR;
		}
		exptime = params.exposure;
		if (exptime < 0) {
			printout(lc->con, 1, "Invalid exposure duration in the source frame");
			return CMPACK_ERR_INVALID_EXPTIME;
		}
		sprintf(msg,"Exposure duration: %.2f s", exptime);
		printout(lc->con, 0, msg);
	}

	/* Read exposure data */
	res = cmpack_ccd_to_image(infile, CMPACK_BITPIX_DOUBLE, &image);
	if (res!=0) 
		return res;
	
	/* Dark correction */
	dark_dark(lc, image, exptime);

	/* Update output frame */
	res = ccd_prepare(outfile, nx, ny, bitpix);
	if (res==0) 
		res = ccd_copy_header(outfile, infile, lc->con, 0);
	if (res==0)
		res = ccd_write_image(outfile, image);
	if (res==0)
		ccd_update_history(outfile, "Dark frame subtracted.");
	
	cmpack_image_destroy(image);
	return res;
}
