/* $Id: fors_flat.cc,v 1.9 2013/09/10 19:25:32 cgarcia Exp $
 *
 * This file is part of the FORS Data Reduction Pipeline
 * Copyright (C) 2002-2010 European Southern Observatory
 *
 * 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

/*
 * $Author: cgarcia $
 * $Date: 2013/09/10 19:25:32 $
 * $Revision: 1.9 $
 * $Name:  $
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif


#include <limits>
#include <vector>
#include <exception>
#include <math.h>
#include <cpl.h>
#include <moses.h>
#include <fors_dfs.h>
#include <fors_utils.h>
#include <fors_image.h>
#include "detected_slits.h"
#include "wavelength_calibration.h"
#include "fors_ccd_config.h"
#include "fors_detmodel.h"
#include "fors_overscan.h"
#include "fors_subtract_bias.h"
#include "fors_saturation_mos.h"
#include "flat_combine.h"
#include "fiera_config.h"
#include "grism_config.h"
#include "fors_grism.h"
#include "fors_bpm.h"

static int fors_flat_create(cpl_plugin *);
static int fors_flat_exec(cpl_plugin *);
static int fors_flat_destroy(cpl_plugin *);
static int fors_flat(cpl_parameterlist *, cpl_frameset *);
void fors_flat_get_parameters(cpl_parameterlist * parlist, 
                              double& smooth_sed, std::string& stack_method, 
                              double& khigh, double& klow, int& kiter, 
                              double& nonlinear_level, double& max_nonlinear_ratio);

static char fors_flat_description[] =
"This recipe is used to subtract the master bias, produced by the recipe\n"
"fors_bias, from a set of raw flat field frames. The input raw frames are\n"
"summed, the master bias frame is rescaled accordingly, and subtracted\n"
"from the result. The overscan regions, if present, are used to compensate\n"
"for variations of the bias level between master bias and input raw frames.\n"
"The overscan regions are then trimmed from the result.\n"
"In the table below the MXU acronym can be alternatively read as MOS and\n"
"LSS.\n\n"
"Input files:\n\n"
"  DO category:               Type:       Explanation:         Required:\n"
"  SCREEN_FLAT_MXU            Raw         Raw data frame          Y\n"
"  SLIT_LOCATION_MXU          Calib       Slits positions on CCD  Y\n"
"  CURV_COEFF_MXU             Calib       Slits tracing fits      Y\n"
"  DISP_COEFF_MXU             Calib       Wavelength calibration  Y\n"
"  MASTER_BIAS                Calib       Master bias frame       Y\n\n"
"Output files:\n\n"
"  DO category:               Data type:  Explanation:\n"
"  MASTER_SCREEN_FLAT_MXU     FITS image  Bias subtracted sum frame\n\n";

#define fors_flat_exit(message)               \
{                                             \
if ((const char *)message != NULL) cpl_msg_error(recipe_name, message);  \
fors_image_delete(&master_bias);                \
cpl_propertylist_delete(header);              \
cpl_msg_indent_less();                        \
return -1;                                    \
}

#define fors_flat_exit_memcheck(message)        \
{                                               \
if ((const char *)message != NULL) cpl_msg_info(recipe, message);     \
printf("free master_flat (%p)\n", master_flat); \
cpl_image_delete(master_flat);                  \
printf("free master_bias (%p)\n", master_bias); \
cpl_image_delete(master_bias);                  \
printf("free header (%p)\n", header);           \
cpl_propertylist_delete(header);                \
cpl_msg_indent_less();                          \
return 0;                                       \
}


/**
 * @brief    Build the list of available plugins, for this module. 
 *
 * @param    list    The plugin list
 *
 * @return   0 if everything is ok, -1 otherwise
 *
 * Create the recipe instance and make it available to the application 
 * using the interface. This function is exported.
 */

int cpl_plugin_get_info(cpl_pluginlist *plist)
{
    cpl_recipe *recipe = static_cast<cpl_recipe *>(cpl_calloc(1, sizeof *recipe ));
    cpl_plugin *plugin = &recipe->interface;

    cpl_plugin_init(plugin,
                    CPL_PLUGIN_API,
                    FORS_BINARY_VERSION,
                    CPL_PLUGIN_TYPE_RECIPE,
                    "fors_spec_mflat",
                    "Computes master spectroscopic flat, removing bias first",
                    fors_flat_description,
                    "Carlo Izzo",
                    PACKAGE_BUGREPORT,
                    fors_get_license(),
                    fors_flat_create,
                    fors_flat_exec,
                    fors_flat_destroy);

    cpl_pluginlist_append(plist, plugin);
    
    return 0;
}


/**
 * @brief    Setup the recipe options    
 *
 * @param    plugin  The plugin
 *
 * @return   0 if everything is ok
 *
 * Defining the command-line/configuration parameters for the recipe.
 */

static int fors_flat_create(cpl_plugin *plugin)
{
    cpl_recipe    *recipe;
    cpl_parameter *p;

    /* 
     * Check that the plugin is part of a valid recipe 
     */

    if (cpl_plugin_get_type(plugin) == CPL_PLUGIN_TYPE_RECIPE) 
        recipe = (cpl_recipe *)plugin;
    else 
        return -1;

    /* 
     * Create the parameters list in the cpl_recipe object 
     */

    recipe->parameters = cpl_parameterlist_new(); 


    /*
     * Number of iterations
     */
    p = cpl_parameter_new_value("fors.fors_spec_mflat.smooth_sed",
                                CPL_TYPE_DOUBLE,
                                "Smoothing size for each flat sed",
                                "fors.fors_spec_mflat",
                                0.0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "smooth_sed");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /*
     * Stacking method
     */
    p = cpl_parameter_new_enum("fors.fors_spec_mflat.stack_method",
                                CPL_TYPE_STRING,
                                "Frames combination method",
                                "fors.fors_spec_mflat",
                                "sum", 4,
                                "sum", "mean", "median", "ksigma");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "stack_method");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* 
     * Low threshold for the sigma clipping algorithm 
     */
    p = cpl_parameter_new_value("fors.fors_spec_mflat.klow",
                                CPL_TYPE_DOUBLE,
                                "Low threshold in ksigma method",
                                "fors.fors_spec_mflat",
                                3.0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "klow");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* 
     * High threshold for the sigma clipping algorithm 
     */
    p = cpl_parameter_new_value("fors.fors_spec_mflat.khigh",
                                CPL_TYPE_DOUBLE,
                                "High threshold in ksigma method",
                                "fors.fors_spec_mflat",
                                3.0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "khigh");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* 
     * Number of iterations for the sigma clipping algorithm 
     */
    p = cpl_parameter_new_value("fors.fors_spec_mflat.kiter",
                                CPL_TYPE_INT,
                                "Max number of iterations in ksigma method",
                                "fors.fors_spec_mflat",
                                999);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "kiter");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* 
     * Saturation level of the detector
     */
    p = cpl_parameter_new_value("fors.fors_spec_mflat.nonlinear_level",
                                CPL_TYPE_DOUBLE,
                                "Level above which the detector is not linear",
                                "fors.fors_spec_mflat",
                                50000.);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "nonlinear_level");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* 
     * Maximum allowed ratio of saturated pixels per slit 
     */
    p = cpl_parameter_new_value("fors.fors_spec_mflat.max_nonlinear_ratio",
                                CPL_TYPE_DOUBLE,
                                "Maximum allowed ratio of non-linear pixels per slit",
                                "fors.fors_spec_mflat",
                                0.2);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "max_nonlinear_ratio");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    return 0;
}


/**
 * @brief    Execute the plugin instance given by the interface
 *
 * @param    plugin  the plugin
 *
 * @return   0 if everything is ok
 */

static int fors_flat_exec(cpl_plugin *plugin)
{
    cpl_recipe  *   recipe ;
    int             status = 1;

    /* Get the recipe out of the plugin */
    if (cpl_plugin_get_type(plugin) == CPL_PLUGIN_TYPE_RECIPE)
        recipe = (cpl_recipe *)plugin ;
    else return -1 ;

    /* Issue a banner */
    fors_print_banner();

    try
    {
        status = fors_flat(recipe->parameters, recipe->frames);
    }
    catch(std::exception& ex)
    {
        cpl_msg_error(cpl_func, "Exception: %s", ex.what());
    }
    catch(...)
    {
        cpl_msg_error(cpl_func, "An uncaught error during recipe execution");
    }

    return status;
}


/**
 * @brief    Destroy what has been created by the 'create' function
 *
 * @param    plugin  The plugin
 *
 * @return   0 if everything is ok
 */

static int fors_flat_destroy(cpl_plugin *plugin)
{
    cpl_recipe *recipe;
    
    if (cpl_plugin_get_type(plugin) == CPL_PLUGIN_TYPE_RECIPE) 
        recipe = (cpl_recipe *)plugin;
    else 
        return -1;

    cpl_parameterlist_delete(recipe->parameters); 

    return 0;
}


/**
 * @brief    Interpret the command line options and execute the data processing
 *
 * @param    parlist     The parameters list
 * @param    frameset    The set-of-frames
 *
 * @return   0 if everything is ok
 */

static int fors_flat(cpl_parameterlist *parlist, cpl_frameset *frameset)
{

    const char *recipe_name = "fors_spec_mflat";

    /*
     * CPL objects
     */

    fors_image       *master_bias = NULL;
    cpl_propertylist *header      = NULL;

    /*
     * Auxiliary variables
     */

    char        version[80];
    const char *master_bias_tag = "MASTER_BIAS";
    const char *slit_location_tag = NULL;
    const char *curv_coeff_tag = NULL;
    const char *disp_coeff_tag = NULL;
    const char *flat_tag = NULL;
    const char *pro_tag = NULL;
    const char *instrume = NULL;
    int         flat_mxu;
    int         flat_mos;
    int         flat_lss;
    size_t      nbias, nflat;
    double      smooth_size;
    std::string stack_method;
    double      khigh, klow;
    int         kiter;
    double      nonlinear_level;
    double      max_nonlinear_ratio;
    
    
    cpl_errorstate      error_prevstate = cpl_errorstate_get();


    cpl_msg_set_indentation(2);


    /* 
     * Get configuration parameters
     */
    fors_flat_get_parameters(parlist, smooth_size, stack_method, 
                             khigh, klow, kiter, nonlinear_level, 
                             max_nonlinear_ratio);
    
    /*
     * Check input frames
     */
    cpl_msg_info(recipe_name, "Check input set-of-frames:");
    cpl_msg_indent_more();

    fors_dfs_set_groups(frameset);
    nbias = cpl_frameset_count_tags(frameset, master_bias_tag);
    if (nbias == 0) {
        cpl_msg_error(recipe_name, "Missing required input: %s", master_bias_tag);
        fors_flat_exit(NULL);
    }
    if (nbias > 1) {
        cpl_msg_error(recipe_name, "Too many in input (%zd > 1): %s", 
                      nbias, master_bias_tag);
        fors_flat_exit(NULL);
    }

    nflat  = flat_mxu = cpl_frameset_count_tags(frameset, "SCREEN_FLAT_MXU");
    nflat += flat_mos = cpl_frameset_count_tags(frameset, "SCREEN_FLAT_MOS");
    nflat += flat_lss = cpl_frameset_count_tags(frameset, "SCREEN_FLAT_LSS");

    if (nflat == 0) {
        fors_flat_exit("Missing required input raw frames");
    }

    if (flat_mxu) {
        flat_tag = "SCREEN_FLAT_MXU";
        pro_tag = "MASTER_SCREEN_FLAT_MXU";
        slit_location_tag = "SLIT_LOCATION_MXU";
        curv_coeff_tag = "CURV_COEFF_MXU";
        disp_coeff_tag = "DISP_COEFF_MXU";
    }
    else if (flat_mos) {
        flat_tag = "SCREEN_FLAT_MOS";
        pro_tag = "MASTER_SCREEN_FLAT_MOS";
        slit_location_tag = "SLIT_LOCATION_MOS";
        curv_coeff_tag = "CURV_COEFF_MOS";
        disp_coeff_tag = "DISP_COEFF_MOS";
    }
    else if (flat_lss) {
        flat_tag = "SCREEN_FLAT_LSS";
        pro_tag = "MASTER_SCREEN_FLAT_LSS";
        slit_location_tag = "SLIT_LOCATION_LSS";
        curv_coeff_tag = "CURV_COEFF_LSS";
        disp_coeff_tag = "DISP_COEFF_LSS";
    }

    if (!dfs_equal_keyword(frameset, "ESO INS GRIS1 ID"))
        cpl_msg_warning(cpl_func,"Input frames are not from the same grism");

    if (!dfs_equal_keyword(frameset, "ESO INS FILT1 ID"))
        cpl_msg_warning(cpl_func,"Input frames are not from the same filter");

    if (!dfs_equal_keyword(frameset, "ESO DET CHIP1 ID")) 
        cpl_msg_warning(cpl_func,"Input frames are not from the same chip");

    header = dfs_load_header(frameset, flat_tag, 0);

    if (header == NULL) {
        cpl_msg_error(recipe_name, "Cannot load header of %s frame", flat_tag);
        fors_flat_exit(NULL);
    }

    instrume = (const char *)cpl_propertylist_get_string(header, "INSTRUME");
    if (instrume == NULL) {
        cpl_msg_error(recipe_name, "Missing keyword INSTRUME in %s header", 
                      flat_tag);
        fors_flat_exit(NULL);
    }

    if (instrume[4] == '1')
        snprintf(version, 80, "%s/%s", "fors1", VERSION);
    if (instrume[4] == '2')
        snprintf(version, 80, "%s/%s", "fors2", VERSION);


    cpl_msg_indent_less();
    cpl_msg_info(recipe_name, "Loading input frames:");
    cpl_msg_indent_more();

    /* Reading  master  bias */
    const cpl_frame * bias_frame = 
            cpl_frameset_find_const(frameset, master_bias_tag);
    master_bias = fors_image_load(bias_frame);

    if (master_bias == NULL)
        fors_flat_exit("Cannot load master bias");

    /* Allocate the bad pixel masks*/
    cpl_mask ** non_linear_flat_masks = 
            (cpl_mask **)cpl_malloc(nflat* sizeof(cpl_mask*));; 
    cpl_mask ** saturated_flat_masks = 
            (cpl_mask **)cpl_malloc(nflat* sizeof(cpl_mask*));; 

    /* Reading individual raw flats */
    std::vector<mosca::image> basiccal_flats;
    cpl_frameset * flatframes = fors_frameset_extract(frameset, flat_tag);
    for (size_t i_flat = 0; i_flat < nflat; i_flat++)
    {
        cpl_msg_info(recipe_name, "Loading flat %zd (total: %zd)", i_flat + 1,
                     nflat);
        cpl_frame * flatframe = cpl_frameset_get_position(flatframes, i_flat);
        const char * filename = cpl_frame_get_filename(flatframe);
        fors_image * flat_raw = fors_image_load(flatframe);
        cpl_propertylist * plist = cpl_propertylist_load(filename, 0) ;
        fors::fiera_config ccd_config(plist);
        if (!flat_raw)
            fors_flat_exit("Cannot load flat field");
        if(!cpl_errorstate_is_equal(error_prevstate))
            fors_flat_exit("Could not retrieve header");

        /* Update RON estimation from bias */
        cpl_propertylist * master_bias_header =
           cpl_propertylist_load(cpl_frame_get_filename(bias_frame), 0);
        fors::update_ccd_ron(ccd_config, master_bias_header);
        if(!cpl_errorstate_is_equal(error_prevstate))
            fors_flat_exit("Could not get RON from master bias"
                    " (missing QC DET OUT? RON keywords)");
        
        /* Check that the overscan configuration is consistent */
        bool perform_preoverscan = !fors_is_preoverscan_empty(ccd_config);
        if(perform_preoverscan != 
           fors_is_master_bias_preoverscan_corrected(master_bias_header))
        {
            cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                    "Master bias overscan configuration doesn't match science");
            return NULL;
        }
        cpl_propertylist_delete(master_bias_header);

        /* Create arc variances map */
        std::vector<double> overscan_levels; 
        if(perform_preoverscan)
            overscan_levels = fors_get_bias_levels_from_overscan(flat_raw, 
                                                                 ccd_config);
        else
            overscan_levels = fors_get_bias_levels_from_mbias(master_bias, 
                                                              ccd_config);
        fors_image_variance_from_detmodel(flat_raw, ccd_config, 
                                          overscan_levels);
        if(!cpl_errorstate_is_equal(error_prevstate))
            fors_flat_exit("Could not create errors from detector error model");
        
        /* Get the non-linear pixels */
        non_linear_flat_masks[i_flat] = 
                cpl_mask_threshold_image_create(flat_raw->data,
                                nonlinear_level, std::numeric_limits<double>::max());
        /* Get the A/D saturated pixels */
        saturated_flat_masks[i_flat] = 
                cpl_mask_threshold_image_create(flat_raw->data,
                                65535., std::numeric_limits<double>::max());
        cpl_mask * saturated_0 = 
                cpl_mask_threshold_image_create(flat_raw->data,
                                -std::numeric_limits<double>::max(), 
                                std::numeric_limits<double>::min());
        cpl_mask_or(saturated_flat_masks[i_flat], saturated_0);
        cpl_mask_delete(saturated_0);

        /* Subtract overscan */
        fors_image * flat;
        if(perform_preoverscan)
            flat = fors_subtract_prescan(flat_raw, ccd_config);
        else 
        {
            flat = fors_image_duplicate(flat_raw); 
            //The rest of the recipe assumes that the images carry a bpm.
            fors_bpm_image_make_explicit(flat); 
        }
        if(!cpl_errorstate_is_equal(error_prevstate))
            fors_flat_exit("Could not subtract overscan");

        /* Trimm pre/overscan */
        if(perform_preoverscan)
        {
            fors_trimm_preoverscan(flat, ccd_config);
            fors_trimm_preoverscan(non_linear_flat_masks[i_flat], ccd_config);
            fors_trimm_preoverscan(saturated_flat_masks[i_flat], ccd_config);
        }
        fors_image_delete(&flat_raw);
        if(!cpl_errorstate_is_equal(error_prevstate))
            fors_flat_exit("Could not trimm overscan");

        /* Subtract master bias */
        fors_subtract_bias(flat, master_bias);
        if(!cpl_errorstate_is_equal(error_prevstate))
            fors_flat_exit("Could not subtract master bias");

        /* Transforming into mosca::image, which takes ownership */
        cpl_image * flat_data = flat->data; 
        cpl_image * flat_err = flat->variance;
        cpl_image_power(flat_err, 0.5);

        mosca::image new_flat(flat_data, flat_err, true, mosca::X_AXIS);
        basiccal_flats.push_back(new_flat);
        //Only the structure is freed, the images are taken over by new_flat
        cpl_free(flat); 
    }

    if(!cpl_errorstate_is_equal(error_prevstate))
        fors_flat_exit("Could not read the flats");
    

    /* Read the slit locations */
    cpl_msg_info(cpl_func, "Reading slit information");
    cpl_frameset * slit_frames = fors_frameset_extract(frameset, slit_location_tag);
    cpl_frameset * curv_frames = fors_frameset_extract(frameset, curv_coeff_tag);
    if(cpl_frameset_get_size(slit_frames) != 1)
        fors_flat_exit("One slit position frame is needed");
    if(cpl_frameset_get_size(curv_frames) != 1)
        fors_flat_exit("One curvature coefficients frame is needed");

    const char * slit_filename = cpl_frame_get_filename(cpl_frameset_get_position(slit_frames, 0));
    const char * curv_filename = cpl_frame_get_filename(cpl_frameset_get_position(curv_frames, 0));
    
    mosca::detected_slits det_slits = 
            mosca::detected_slits_load_fits(slit_filename, curv_filename,
                                            basiccal_flats[0].size_dispersion());
    
    if(!cpl_errorstate_is_equal(error_prevstate))
        fors_flat_exit("Could not read the slits");

    /* Read grism configuration */
    //TODO: Add the waveref to the grism_tables
    double wave_ref =  cpl_propertylist_get_double(header, "ESO INS GRIS1 WLEN");
    if (wave_ref < 3000.0)   /* Perhaps in nanometers... */
        wave_ref *= 10;

    cpl_frameset * grism_frame = fors_frameset_extract(frameset, "GRISM_TABLE");
    std::auto_ptr<mosca::grism_config> grism_cfg = 
            fors_grism_config_from_frame(cpl_frameset_get_position(grism_frame, 0), wave_ref);

    
    /* Read wave calib */
    cpl_msg_info(cpl_func, "Reading wavelength calibration");
    cpl_frameset * wave_frames = fors_frameset_extract(frameset, disp_coeff_tag);
    if(cpl_frameset_get_size(wave_frames) != 1)
        fors_flat_exit("One dispersion coefficients frame is needed");
    const char * wave_filename = cpl_frame_get_filename(cpl_frameset_get_position(wave_frames, 0));
    mosca::wavelength_calibration wave_cal(wave_filename, grism_cfg->wave_ref());

    if(!cpl_errorstate_is_equal(error_prevstate))
        fors_flat_exit("Could not read the wavelength calibration");

    
    /* Get the calibrated slits */
    fors::calibrated_slits calib_slits = fors::create_calibrated_slits(
    								   det_slits, wave_cal, *grism_cfg,
                                       basiccal_flats[0].size_dispersion(),
                                       basiccal_flats[0].size_spatial()); 

    /* Reject slits that have too many non-linear pixels */
    std::vector<std::vector<double> > slit_sat_ratio;
    std::vector<std::vector<int> > slit_sat_count;
    fors_saturation_reject_sat_slits(basiccal_flats, calib_slits, 
                                     non_linear_flat_masks,
                                     saturated_flat_masks, max_nonlinear_ratio,
                                     slit_sat_ratio, slit_sat_count);

    /* Computing master flat */
    cpl_msg_info(cpl_func, "Computing master flat");
    std::auto_ptr<mosca::image> master_flat;
    if(stack_method == "mean" || stack_method == "sum")
    {
        mosca::reduce_mean reduce_method;
        master_flat = mosca::flat_combine<float, mosca::reduce_mean>
            (basiccal_flats, calib_slits, wave_cal, *grism_cfg, smooth_size, reduce_method);
        if(stack_method == "sum")
        {
            cpl_image_multiply_scalar(master_flat->get_cpl_image(), nflat);
            cpl_image_multiply_scalar(master_flat->get_cpl_image_err(), nflat);
        }
    }
    else if(stack_method == "median")
    {
        mosca::reduce_median reduce_method;
        master_flat = mosca::flat_combine<float, mosca::reduce_median>
            (basiccal_flats, calib_slits, wave_cal, *grism_cfg, smooth_size, reduce_method);        
    }
    else if(stack_method == "ksigma")
    {
        mosca::reduce_sigma_clipping reduce_method(khigh, klow, kiter);
        master_flat = mosca::flat_combine<float, mosca::reduce_sigma_clipping>
            (basiccal_flats, calib_slits, wave_cal, *grism_cfg, smooth_size, reduce_method);        
    }
    if(!cpl_errorstate_is_equal(error_prevstate))
        fors_flat_exit("Could not compute master flat");
        
    fors_image_delete(&master_bias); master_bias = NULL;

    /* Create flat bad pixel mask */
    cpl_msg_info(cpl_func, "Creating final bad pixel mask");
    cpl_image * flat_mask = fors_bpm_create_combined_bpm(non_linear_flat_masks,
                                                        saturated_flat_masks,
                                                        nflat);

    cpl_msg_indent_less();


    /* Save product */
    cpl_msg_info(cpl_func, "Saving products");
    fors_image * fors_master_flat = fors_image_new(master_flat->get_cpl_image(),
            cpl_image_power_create(master_flat->get_cpl_image_err(), 2.));

    cpl_propertylist *save_header  = cpl_propertylist_new();
    cpl_propertylist_update_int(save_header, "ESO PRO DATANCOM", nflat);    

    fors_dfs_save_image_err_mask(frameset, fors_master_flat, flat_mask,
                                 pro_tag, save_header, parlist, recipe_name,
                                 cpl_frameset_get_position_const(flatframes, 0));

    cpl_propertylist_delete(header); header = NULL;

    return 0;
}

void fors_flat_get_parameters(cpl_parameterlist * parlist, 
                              double& smooth_sed, std::string& stack_method, 
                              double& khigh, double& klow, int& kiter, 
                              double& nonlinear_level, double& max_nonlinear_ratio)
{
    const char *recipe_name = "fors_spec_mflat";
    cpl_parameter * par;
    cpl_msg_info(recipe_name, "Recipe %s configuration parameters:", recipe_name);
    cpl_msg_indent_more();

    par = cpl_parameterlist_find(parlist, "fors.fors_spec_mflat.smooth_sed");
    smooth_sed = cpl_parameter_get_double(par) ;

    cpl_msg_info(cpl_func, "fors.fors_spec_mflat.smooth_sed = %f", smooth_sed);

    par = cpl_parameterlist_find(parlist, "fors.fors_spec_mflat.stack_method");
    stack_method = cpl_parameter_get_string(par) ;

    cpl_msg_info(cpl_func, "fors.fors_spec_mflat.stack_method = %s", stack_method.c_str());

    par = cpl_parameterlist_find(parlist, "fors.fors_spec_mflat.khigh");
    khigh = cpl_parameter_get_double(par);

    cpl_msg_info(cpl_func, "fors.fors_spec_mflat.khigh = %f", khigh);

    par = cpl_parameterlist_find(parlist, "fors.fors_spec_mflat.klow");
    klow = cpl_parameter_get_double(par) ;

    cpl_msg_info(cpl_func, "fors.fors_spec_mflat.klow = %f", klow);

    par = cpl_parameterlist_find(parlist, "fors.fors_spec_mflat.kiter");
    kiter = cpl_parameter_get_int(par) ;

    cpl_msg_info(cpl_func, "fors.fors_spec_mflat.kiter = %d", kiter);

    par = cpl_parameterlist_find(parlist, "fors.fors_spec_mflat.nonlinear_level");
    nonlinear_level = cpl_parameter_get_double(par);

    cpl_msg_info(cpl_func, "fors.fors_spec_mflat.nonlinear_level = %f", nonlinear_level);

    par = cpl_parameterlist_find(parlist, "fors.fors_spec_mflat.max_nonlinear_ratio");
    max_nonlinear_ratio = cpl_parameter_get_double(par);

    cpl_msg_info(cpl_func, "fors.fors_spec_mflat.max_nonlinear_ratio = %f", max_nonlinear_ratio);

    /* 
     * Check validity of parameters
     */
    if(stack_method != "mean" && stack_method != "median" && 
            stack_method != "ksigma" && stack_method != "sum")
        throw std::invalid_argument(stack_method+" stacking algorithm invalid");

}
