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

bias.c (C-Munipack project)
Bias 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 <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <float.h>

#include "comfun.h"
#include "ccdfile.h"
#include "console.h"
#include "image.h"
#include "cmpack_common.h"
#include "cmpack_image.h"
#include "cmpack_bias.h"

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

/* Bias correction context */
struct _CmpackBiasCorr
{
	int refcnt;						/**< Reference counter */
	CmpackConsole *con;				/**< Output console */
	CmpackBorder border;			/**< Border size */
	CmpackImage *bias;				/**< Bias frame image data */
	double minvalue, maxvalue;		/**< Bad pixel value, overexposed value */
};

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

static void bias_bias(CmpackBiasCorr *lc, CmpackImage *image)
{
	int i, width, height, left, right, top, bottom, x, y;
	int underflow, overflow;
	double *sdata, *bdata, value, minvalue, maxvalue;

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

	/* bias 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;
	sdata = (double*)cmpack_image_data(image);
	bdata = (double*)cmpack_image_data(lc->bias);
	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 -= bdata[i];
					if (value<minvalue) {
						value = minvalue;
						underflow = 1;
					}
					if (value>maxvalue) {
						value = maxvalue;
						overflow = 1;
					}
				}
			} 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 bias_clear(CmpackBiasCorr *ctx)
{
	if (ctx->bias) {
		cmpack_image_destroy(ctx->bias);
		ctx->bias = NULL;
	}
	if (ctx->con) {
		cmpack_con_destroy(ctx->con);
		ctx->con = NULL;
	}
}

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

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

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

/* Decrement reference counter / detroy the instance */
void cmpack_bias_destroy(CmpackBiasCorr *ctx)
{
	if (ctx) {
		ctx->refcnt--;
		if (ctx->refcnt==0) {
			bias_clear(ctx);
			cmpack_free(ctx);
		}
	}
}

/* Attach console */
void cmpack_bias_set_console(CmpackBiasCorr *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 image border */
void cmpack_bias_set_border(CmpackBiasCorr *lc, const CmpackBorder *border)
{
	if (border)
		lc->border = *border;
	else
		memset(&lc->border, 0, sizeof(CmpackBorder));
}

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

/* Set threshold */
void cmpack_bias_set_thresholds(CmpackBiasCorr *lc, double minvalue, double maxvalue)
{
	lc->minvalue = minvalue;
	lc->maxvalue = maxvalue;
}

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

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

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

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

/* Read bias frame */
int cmpack_bias_rbias(CmpackBiasCorr *lc, CmpackCcdFile *bias)
{
	int res, nx, ny;

	/* Clear previous bias frame */
	if (lc->bias) 
		cmpack_image_destroy(lc->bias);
	lc->bias = NULL;

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

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

	/* Get image data */
	res = cmpack_ccd_to_image(bias, CMPACK_BITPIX_DOUBLE, &lc->bias);
	if (res!=0) 
		return res;

	/* Print parameters */
	if (is_debug(lc->con)) {
		printout(lc->con, 1, "Bias correction frame:");
		printpari(lc->con, "Width", 1, cmpack_image_width(lc->bias));
		printpari(lc->con, "Height", 1, cmpack_image_height(lc->bias));
	}
    return 0;
}

/* Computes the output image from source image and bias image (out=sci-bias) */
int cmpack_bias(CmpackBiasCorr *lc, CmpackCcdFile *file)
{
	int res, nx, ny;
	CmpackBitpix bitpix;
	CmpackImage *image;

	/* Check bias file */
	if (!lc->bias) {
		printout(lc->con, 0, "Missing bias frame");
		return CMPACK_ERR_NO_BIAS_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->bias)!=nx || cmpack_image_height(lc->bias)!=ny) {
		printout(lc->con, 0, "The size of the bias 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 exposure data */
	res = cmpack_ccd_to_image(file, CMPACK_BITPIX_DOUBLE, &image);
	if (res!=0) 
		return res;
	
	/* Bias correction */
	bias_bias(lc, image);

	/* Update image data */
	res = ccd_write_image(file, image);
	if (res==0)
		ccd_update_history(file, "Bias frame subtracted.");
	cmpack_image_destroy(image);
	
	return res;
}

/* Computes the output image from source image and bias image (out=sci-bias) */
int cmpack_bias_ex(CmpackBiasCorr *lc, CmpackCcdFile *infile, CmpackCcdFile *outfile)
{
	int res, nx, ny;
	CmpackBitpix bitpix;
	CmpackImage *image;

	/* Check bias file */
	if (!lc->bias) {
		printout(lc->con, 0, "Missing bias frame");
		return CMPACK_ERR_NO_BIAS_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->bias)!=nx || cmpack_image_height(lc->bias)!=ny) {
		printout(lc->con, 0, "The size of the bias 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 exposure data */
	res = cmpack_ccd_to_image(infile, CMPACK_BITPIX_DOUBLE, &image);
	if (res!=0) 
		return res;

	/* Bias correction */
	bias_bias(lc, image);

	/* 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, "Bias frame subtracted.");

	cmpack_image_destroy(image);
	return res;
}
