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

aflat.c (C-Munipack project)
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 "config.h"
#include "comfun.h"
#include "console.h"
#include "list.h"
#include "robmean.h"
#include "ccdfile.h"
#include "cmpack_common.h"
#include "cmpack_mflat.h"

/* Max. number of pixels used to determine the mean brightness */
#define MAXSKY		10000

/* Default mean output level in ADU */
#define DEF_LEVEL	10000.0

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

/* Master-flat frame context */
struct _CmpackMasterFlat
{
	int refcnt;					/**< Reference counter */
	CmpackConsole *con;			/**< Console */
	CmpackCcdFile *outfile;		/**< Output file context */
	CmpackBitpix bitpix;		/**< Required image data format */
	double minvalue, maxvalue;	/**< Bad pixel value, overexposed value */
	double level;          		/**< Required mean output level */
	CmpackBorder border;		/**< Border size */
	CmpackBitpix in_bitpix;     /**< Output image data format */
	int in_width;          		/**< Width of frames */
	int in_height;         		/**< Height of frames */
	CmpackImageHeader header;	/**< Header of first frame */
	CmpackList *frames;			/**< List of source frames */
};

typedef struct _MFlatFrame
{
	CmpackImage *frame;
	double coef;
} MFlatFrame;

static void frame_free(void *frame)
{
	if (((MFlatFrame*)frame)->frame)
		cmpack_image_destroy(((MFlatFrame*)frame)->frame);
	cmpack_free(frame);
}

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

/* Clean configuration context */
static void mflat_clear(CmpackMasterFlat *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 */
CmpackMasterFlat *cmpack_mflat_init(void)
{
	CmpackMasterFlat *f = (CmpackMasterFlat*)cmpack_calloc(1, sizeof(CmpackMasterFlat));
	f->refcnt = 1;
	f->level = DEF_LEVEL;
	f->minvalue = 0;
	f->maxvalue = 65535.0;
	return f;
}

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

/* Decrement reference counter / detroy the instance */
void cmpack_mflat_destroy(CmpackMasterFlat *ctx)
{
	if (ctx) {
		ctx->refcnt--;
		if (ctx->refcnt==0) {
			mflat_clear(ctx);
			cmpack_free(ctx);
		}
	}
}

/* Attach console */
void cmpack_mflat_set_console(CmpackMasterFlat *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_mflat_set_bitpix(CmpackMasterFlat *lc, CmpackBitpix bitpix)
{
	lc->bitpix = bitpix;
}

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

/* Set output image data format */
void cmpack_mflat_set_level(CmpackMasterFlat *lc, double level)
{
	lc->level = level;
}

/* Set output image data format */
double cmpack_mflat_get_level(CmpackMasterFlat *lc)
{
	return lc->level;
}

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

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

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

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

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

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

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

/* Reads first image and opens destination file */
int cmpack_mflat_open(CmpackMasterFlat *lc, CmpackCcdFile *outfile)
{
	/* Print configuration parameters */
	if (is_debug(lc->con)) {
		printout(lc->con, 1, "Master-flat parametets:");
		printpari(lc->con, "BitPix", 1, lc->bitpix);
		printpard(lc->con, "Level", 1, lc->level, 1);
		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 one image and puts it into the list */
int cmpack_mflat_read(CmpackMasterFlat *lc, CmpackCcdFile *file)
{
	int res, x, y, nx, ny, ix, iy, jx, jy, pixels;
	char msg[MAXLINE];
    double skymed, skysig, *temp, minvalue, maxvalue, value;
	MFlatFrame *img;
	CmpackCcdParams params;
	CmpackBitpix bitpix;

    /* 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, &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 (lc->in_width==0 && lc->in_height==0) {
        /* First image */
        lc->in_width = nx;
        lc->in_height = ny;
		lc->in_bitpix = bitpix;
		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 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 previous images");
			return CMPACK_ERR_DIFF_BITPIX_SRC;
		}
	}

    /* image data input */
	img = (MFlatFrame*)cmpack_malloc(sizeof(MFlatFrame));
	res = cmpack_ccd_to_image(file, CMPACK_BITPIX_AUTO, &img->frame);
	if (res!=0) {
		cmpack_free(img);
		return res;
	}

    /* Image median computation */
	temp = (double*)cmpack_malloc(nx*ny*sizeof(double));
	pixels = 0;
	minvalue = lc->minvalue;
	maxvalue = lc->maxvalue;
	ix = lc->border.left; jx = nx - lc->border.right;
	iy = lc->border.top;  jy = ny - lc->border.bottom;
	for (y=iy; y<jy; y++) {
		for (x=ix; x<jx; x++) {
			value = cmpack_image_getpixel(img->frame, x, y);
			if (value>minvalue && value<maxvalue)
				temp[pixels++] = value;
		}
	}
	if (pixels>0) {
		cmpack_robustmean(pixels, temp, &skymed, &skysig);
	} else {
		skymed = skysig = 0.0;
	}
	cmpack_free(temp);
	if (skymed==0) {
		cmpack_image_destroy(img->frame);
		cmpack_free(img);
		printout(lc->con, 0, "Cannot compute the mean value of the input frame. Check the input parameters.");
		return CMPACK_ERR_MEAN_ZERO;
	}
	img->coef = 1.0/skymed;

	if (is_debug(lc->con)) {
		sprintf(msg,"Median       : %.4f",skymed); 
  		printout(lc->con, 1, msg);
		sprintf(msg,"Skysig       : %.4f",skysig);
  		printout(lc->con, 1, msg);
	}

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

int cmpack_mflat_close(CmpackMasterFlat *lc)
/* Computes the output image and writes it to the file */
{
	int	res, x, y, ix, iy, jx, jy, i, j, k, nframes, pixels, nx, ny;
	int	underflow = 0, overflow = 0, badpixels = 0;
	double a, b, *fbuf, *idata, *temp, value, skymed, skysig, minvalue, maxvalue;
	MFlatFrame **inf;
	CmpackList *ptr;
	CmpackImage *outf;
	CmpackCcdParams params;
	CmpackBitpix bitpix;
	char msg[MAXLINE];
	
	/* 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) {
		printout(lc->con, 0, "No source files defined");
		cmpack_ccd_destroy(lc->outfile);
		lc->outfile = NULL;
		return CMPACK_ERR_NO_INPUT_FILES;
	}
	nx = lc->in_width;
	ny = lc->in_height;
	if (nx<=0 || ny<=0) {
		printout(lc->con, 0, "Invalid size of the destination image");
		cmpack_ccd_destroy(lc->outfile);
		lc->outfile = NULL;
		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 */
		printout(lc->con, 0, "Invalid data format of the destination image");
		cmpack_ccd_destroy(lc->outfile);
		lc->outfile = NULL;
		return CMPACK_ERR_INVALID_BITPIX;
	}

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

	/* Computation of mean of values for each pixel */
	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]->frame, x, y);
					if (value>minvalue && value<maxvalue)
						fbuf[j++] = value * inf[k]->coef;
				}
				if (j>0) {
					/* Compute their median */
					cmpack_robustmean(j, fbuf, &a, &b);
					a *= lc->level;
					/* Range checking */
					if (a < minvalue) {
						underflow++;
						a = minvalue;
					} else 
					if (a > maxvalue) {
						overflow++;
						a = maxvalue;
					}
				} else {
					badpixels++;
					a = minvalue;
				}
			} 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);

	/* Compute the median of output frame */
	temp = cmpack_malloc(nx*ny*sizeof(double));
	pixels = 0;
	ix = lc->border.left; jx = nx - lc->border.right;
	iy = lc->border.top;  jy = ny - lc->border.bottom;
	minvalue = lc->minvalue;
	maxvalue = lc->maxvalue;
	for (y=iy; y<jy; y++) {
		for (x=ix; x<jx; x++) {
			value = idata[x+y*nx];
			if (value > minvalue && value < maxvalue)
				temp[pixels++] = value;
		}
	}
	if (pixels>0) 
		cmpack_robustmean(pixels, temp, &skymed, &skysig);
	else
		skymed = skysig = 0.0;
	cmpack_free(temp);

	/* 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-flat frame";
	params.subframes_avg = nframes;
	cmpack_ccd_set_params(lc->outfile, CMPACK_CM_OBJECT | CMPACK_CM_SUBFRAMES, &params);
	ccd_set_dbl(lc->outfile, "SKYMOD", skymed, 2, "MEAN LEVEL");
	ccd_set_dbl(lc->outfile, "SKYSIG", skysig, 2, "FLAT-FIELD NOISE");
	ccd_set_origin(lc->outfile);
	ccd_set_pcdate(lc->outfile);

	if (is_debug(lc->con)) {
		sprintf(msg,"Final median : %.1f", skymed);
		printout(lc->con, 1, msg);
		sprintf(msg,"Final skysig : %.1f", skysig);
		printout(lc->con, 1, msg);
	}

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

	/* Range check */
	if (overflow>0) {
		sprintf(msg,"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,"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, filter */
	cmpack_image_header_destroy(&lc->header);
	cmpack_ccd_destroy(lc->outfile);
	lc->outfile = NULL;
	return res;
}
