/* 
 * This file is part of the HAWKI Pipeline
 * Copyright (C) 2002,2003 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 Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

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

/*-----------------------------------------------------------------------------
                                Includes
 -----------------------------------------------------------------------------*/

#include <math.h>
#include <locale.h>
#include <cpl.h>

#include "irplib_utils.h"

#include "hawki_utils_legacy.h"
#include "hawki_image_stats.h"
#include "hawki_pfits_legacy.h"
#include "hawki_dfs_legacy.h"
#include "hawki_load.h"
#include "hawki_save.h"
#include "hawki_variance.h"

/*-----------------------------------------------------------------------------
                            Functions prototypes
 -----------------------------------------------------------------------------*/

static int hawki_cal_dark_create(cpl_plugin *) ;
static int hawki_cal_dark_exec(cpl_plugin *) ;
static int hawki_cal_dark_destroy(cpl_plugin *) ;
static int hawki_cal_dark(cpl_parameterlist *, cpl_frameset *) ;

void hawki_cal_dark_initialise_qc(void);
static int hawki_cal_dark_retrieve_input_param
(cpl_parameterlist * parlist);
static double hawki_cal_dark_ron(const cpl_image *, const cpl_image *, int) ;
static int hawki_cal_dark_save
(const cpl_imagelist *   dark,
 const cpl_imagelist *   master_dark_err,
 const cpl_imagelist *   bpmdark,
 cpl_table           **  raw_dark_stats,
 const cpl_vector    **  rons,
 const cpl_frameset  *   used_frames,
 cpl_parameterlist   *   parlist,
 cpl_frameset        *   set);

/*-----------------------------------------------------------------------------
                            Static variables
 -----------------------------------------------------------------------------*/

static struct {
    /* Inputs */
    int         hsize ;
    int         nsamples ;
    double      sigma ;
    int         llx ;
    int         lly ;
    int         urx ;
    int         ury ;
    double      gain;
    double      ron;
    int         error_tracking;
} hawki_cal_dark_config ;

static struct {
    /* Outputs */
    int         nb_badpix[HAWKI_NB_DETECTORS] ;
    double      master_dark_mean[HAWKI_NB_DETECTORS] ;
    double      master_dark_med[HAWKI_NB_DETECTORS] ;
    double      master_dark_stdev[HAWKI_NB_DETECTORS] ;
    double      master_dark_error_mean[HAWKI_NB_DETECTORS] ;
    double      master_dark_error_med[HAWKI_NB_DETECTORS] ;
    double      master_dark_error_stdev[HAWKI_NB_DETECTORS] ;
    double      vc_mean[HAWKI_NB_DETECTORS][HAWKI_NB_VC] ;
    double      vc_med[HAWKI_NB_DETECTORS][HAWKI_NB_VC] ;
    double      vc_stdev[HAWKI_NB_DETECTORS][HAWKI_NB_VC] ;
    double      dit;
    int         ndit;
    int         ndsamples;
} hawki_cal_dark_outputs;

static char hawki_cal_dark_description[] =
"(OBSOLETE) hawki_cal_dark -- Dark recipe\n"
"The files listed in the Set Of Frames (sof-file) must be tagged:\n"
"raw-file.fits "HAWKI_CAL_DARK_RAW" or\n"
"raw-file.fits "HAWKI_TEC_FLAT_RAW".\n"
"The recipe creates as an output:\n"
"hawki_cal_dark.fits ("HAWKI_CALPRO_DARK"): The master dark\n"
"hawki_cal_dark_bpmdark.fits("HAWKI_CALPRO_BPM_HOT"): The bad pixel mask associated to the dark\n"
"hawki_cal_dark_stats.fits("HAWKI_CALPRO_DARK_STATS"): Statistics of the raw darks\n"
"Optionally it also creates:\n"
"hawki_cal_dark_err.fits("HAWKI_CALPRO_DARK_ERR"): The error in the master dark\n"
"Return code:\n"
"esorex exits with an error code of 0 if the recipe completes successfully\n"
"or 1 otherwise";

/*-----------------------------------------------------------------------------
                                Functions code
 -----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------*/
/**
  @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 * list)
{
    cpl_recipe  *   recipe = cpl_calloc(1, sizeof *recipe ) ;
    cpl_plugin  *   plugin = &recipe->interface ;

    cpl_plugin_init(plugin,
                    CPL_PLUGIN_API,
                    HAWKI_BINARY_VERSION,
                    CPL_PLUGIN_TYPE_RECIPE,
                    "hawki_cal_dark",
                    "(OBSOLETE) Dark recipe",
                    hawki_cal_dark_description,
                    "ESO Pipeline Group",
                    PACKAGE_BUGREPORT,
                    hawki_get_license_legacy(),
                    hawki_cal_dark_create,
                    hawki_cal_dark_exec,
                    hawki_cal_dark_destroy) ;

    cpl_pluginlist_append(list, 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 hawki_cal_dark_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() ; 
    if (recipe->parameters == NULL)
        return 1;

    /* Fill the parameters list */
    /* --sigma */
    p = cpl_parameter_new_value("hawki.hawki_cal_dark.sigma",
            CPL_TYPE_DOUBLE, "sigma for hot bad pixels detection",
            "hawki.hawki_cal_dark", 10.0) ;
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "sigma") ;
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV) ;
    cpl_parameterlist_append(recipe->parameters, p) ;
    /* --nsamples */
    p = cpl_parameter_new_value("hawki.hawki_cal_dark.nsamples",
            CPL_TYPE_INT, "number of samples for RON computation",
            "hawki.hawki_cal_dark", 100) ;
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "nsamples") ;
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV) ;
    cpl_parameterlist_append(recipe->parameters, p) ;
    /* --hsize */
    p = cpl_parameter_new_value("hawki.hawki_cal_dark.hsize",
            CPL_TYPE_INT, "half size of the window for RON computation",
            "hawki.hawki_cal_dark", 6) ;
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "hsize") ;
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV) ;
    cpl_parameterlist_append(recipe->parameters, p) ;
    /* --zone */
    p = cpl_parameter_new_value("hawki.hawki_cal_dark.zone",
                                CPL_TYPE_STRING,
                                "Stats zone",
                                "hawki.hawki_cal_dark",
                                "512,512,1536,1536") ;
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "zone") ;
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV) ;
    cpl_parameterlist_append(recipe->parameters, p) ;
    /* --gain */
    p = cpl_parameter_new_value("hawki.hawki_cal_dark.gain",
                                CPL_TYPE_DOUBLE,
                                "Detector nominal gain (e-/ADU)",
                                "hawki.hawki_cal_dark",
                                -1.);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "gain") ;
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV) ;
    cpl_parameterlist_append(recipe->parameters, p) ;
    /* --ron */
    p = cpl_parameter_new_value("hawki.hawki_cal_dark.ron",
                                CPL_TYPE_DOUBLE,
                                "Detector nominal RON for a single readout (ADU)",
                                "hawki.hawki_cal_dark",
                                -1.);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "ron") ;
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV) ;
    cpl_parameterlist_append(recipe->parameters, p) ;

    /* Return */
    return 0;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Execute the plugin instance given by the interface
  @param    plugin  the plugin
  @return   0 if everything is ok
 */
/*----------------------------------------------------------------------------*/
static int hawki_cal_dark_exec(cpl_plugin * plugin)
{
    cpl_recipe  *   recipe ;
    
    /* 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 */
    hawki_print_banner();

    return hawki_cal_dark(recipe->parameters, recipe->frames) ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Destroy what has been created by the 'create' function
  @param    plugin  the plugin
  @return   0 if everything is ok
 */
/*----------------------------------------------------------------------------*/
static int hawki_cal_dark_destroy(cpl_plugin * plugin)
{
    cpl_recipe  *   recipe ;
    
    /* Get the recipe out of the plugin */
    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 frames list
  @return   0 if everything is ok
 */
/*----------------------------------------------------------------------------*/
static int hawki_cal_dark(
        cpl_parameterlist   *   parlist, 
        cpl_frameset        *   frameset)
{
    cpl_frameset        *   rawframes ;
    cpl_frame           *   ref_frame ;
    cpl_propertylist    *   plist ;
    cpl_imagelist       *   darks_raw ;
    cpl_imagelist       *   master_dark;
    cpl_imagelist       *   master_dark_err;
    cpl_imagelist       *   bpmdark;
    cpl_image           *   bpm ;
    cpl_image           *   ima_curr ;
    cpl_image           *   ima_next ;
    cpl_image           *   ima_accu ;
    cpl_image           *   ima_accu_err = NULL;
    int                     nframes ;
    cpl_vector          *   rons[HAWKI_NB_DETECTORS] ;
    cpl_table           **  raw_dark_stats;
    double                  ron ;
    int                     vc_urx, vc_ury, vc_llx, vc_lly ;
    int                     j, k ;
    int                     idet;
    cpl_errorstate          error_prevstate;      
    
    /* Initialise */
    rawframes = NULL ;
    ima_accu = NULL ;
    ima_next = NULL ;
    master_dark_err = NULL;
    hawki_cal_dark_initialise_qc();

    /* Retrieve input parameters */
    if(hawki_cal_dark_retrieve_input_param(parlist))
    {
        cpl_msg_error(__func__, "Wrong parameters");
        return -1;
    }
 
    /* Identify the RAW and CALIB frames in the input frameset */
    if (hawki_dfs_set_groups_legacy(frameset)) {
        cpl_msg_error(__func__, "Cannot identify RAW and CALIB frames") ;
        return -1 ;
    }
        
    /* Retrieve raw frames */
    rawframes = hawki_extract_frameset(frameset, HAWKI_CAL_DARK_RAW) ;

    /* Test if raw frames have been found */
    if (rawframes == NULL) {
        cpl_msg_error(__func__, "No raw frame in input (%s)",HAWKI_CAL_DARK_RAW);
        return -1 ;
    }

    /* At least 3 frames */
    if (cpl_frameset_get_size(rawframes) < 3) {
        cpl_msg_error(__func__, "Not enough input frames");
        cpl_frameset_delete(rawframes) ;
        return -1 ;
    }
    
    /* Get DIT / NDIT from the header */
    error_prevstate = cpl_errorstate_get();
    ref_frame = cpl_frameset_get_position(rawframes, 0) ;
    if ((plist=cpl_propertylist_load(cpl_frame_get_filename(ref_frame),
                    0)) == NULL) {
        cpl_msg_error(__func__, "Cannot get header from frame");
        cpl_msg_indent_less() ;
        cpl_frameset_delete(rawframes) ;
        return -1 ;
    }
    hawki_cal_dark_outputs.dit = hawki_pfits_get_dit_legacy(plist) ;
    hawki_cal_dark_outputs.ndit = hawki_pfits_get_ndit_legacy(plist) ;
    hawki_cal_dark_outputs.ndsamples = hawki_pfits_get_ndsamples(plist);
    cpl_propertylist_delete(plist) ;
    if(!cpl_errorstate_is_equal(error_prevstate))
    {
        cpl_msg_error(__func__, "Cannot get the DIT/NDIT/NDSAMPLES from the header") ;
        cpl_msg_indent_less() ;
        cpl_frameset_delete(rawframes) ;
        return -1 ;
    }
    cpl_msg_info(__func__, "DIT value: %g sec.", hawki_cal_dark_outputs.dit);
    cpl_msg_info(__func__, "NDIT value: %d", hawki_cal_dark_outputs.ndit);
    cpl_msg_info(__func__, "NDSAMPLES value: %d", hawki_cal_dark_outputs.ndsamples);
    
    /* Check that DIT/NDIT and NDSAMPLES are the same for all the frames */
    if(!hawki_utils_check_equal_double_keys(rawframes, &hawki_pfits_get_dit_legacy) ||
       !hawki_utils_check_equal_int_keys(rawframes, &hawki_pfits_get_ndit_legacy)||
       !hawki_utils_check_equal_int_keys(rawframes, &hawki_pfits_get_ndsamples))
    {
        cpl_msg_error(__func__, "Not all input darks have the same "
                "DIT/NDIT/NDSAMPLES values");
        cpl_msg_indent_less() ;
        return -1 ;        
    }

    /* Number of frames */
    nframes = cpl_frameset_get_size(rawframes) ;

    /* Create the statistics table */
    raw_dark_stats = cpl_malloc(HAWKI_NB_DETECTORS * sizeof(cpl_table *));
    for( idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++)
    {
        raw_dark_stats[idet] = cpl_table_new(nframes);
    }
    hawki_image_stats_initialize(raw_dark_stats);
    
    /* Loop on the detectors */
    master_dark = cpl_imagelist_new();
    if(hawki_cal_dark_config.error_tracking)
        master_dark_err = cpl_imagelist_new();
    bpmdark = cpl_imagelist_new();
    cpl_msg_info(__func__, "Dark computation");
    cpl_msg_indent_more() ;
    for (idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++) {
        cpl_msg_info(__func__, "Handle chip number %d", idet+1) ;

        /* Create the rons vectors */
        rons[idet] = cpl_vector_new(nframes) ;
        
        /* Load the input data */
        darks_raw = hawki_load_detector(rawframes, idet+1, CPL_TYPE_FLOAT) ;

        /* Loop on the frames */
        for (j=0 ; j<nframes ; j++) {
            /* Load the current and next images */
            if (j==nframes-1) {
                ima_curr = cpl_imagelist_get(darks_raw, j) ;
                ima_next = cpl_imagelist_get(darks_raw, 0) ;
            } else {
                ima_curr = cpl_imagelist_get(darks_raw, j) ;
                ima_next = cpl_imagelist_get(darks_raw, j+1) ;
            }

            /* Compute the dark stats and store in table */
            if(hawki_image_stats_fill_from_image
                (raw_dark_stats,
                 ima_curr,
                 hawki_cal_dark_config.llx,
                 hawki_cal_dark_config.lly,
                 hawki_cal_dark_config.urx,
                 hawki_cal_dark_config.ury,
                 idet,
                 j) != 0)
            {
                cpl_msg_error(__func__, "Cannot compute statistics") ;
                cpl_msg_indent_less() ; 
                cpl_frameset_delete(rawframes) ;
                cpl_imagelist_delete(master_dark);
                if(hawki_cal_dark_config.error_tracking)
                    cpl_imagelist_delete(master_dark_err);                
                cpl_imagelist_delete(darks_raw); 
                cpl_imagelist_delete(bpmdark) ;
                for (k=0 ; k<=idet ; k++) 
                    cpl_vector_delete(rons[k]) ;
                for( idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++) 
                    cpl_table_delete(raw_dark_stats[idet]);
                cpl_free(raw_dark_stats);
                return -1 ;
            }
           
            /* Compute the RON */
            ron = hawki_cal_dark_ron(ima_curr, ima_next, hawki_cal_dark_outputs.ndit) ;
            cpl_vector_set(rons[idet], j, ron);
        }
        
        /* Collapse */
        if (nframes > 2)
        {
            ima_accu = cpl_imagelist_collapse_minmax_create(darks_raw, 0, 1) ;
            if(hawki_cal_dark_config.error_tracking)
            {
                cpl_imagelist * variances;
                cpl_image     * accu_var;
                cpl_msg_info(__func__, "Computing the uncertainty in dark");
                variances = hawki_imglist_create_variances_and_delete
                    (darks_raw, hawki_cal_dark_config.gain, 
                     hawki_cal_dark_config.ron, hawki_cal_dark_outputs.ndit,
                     hawki_cal_dark_outputs.ndsamples);
                /* The variances are collapsed, like the dark_raw. Given that
                 * the variances are a monotically increasing function with
                 * respect to the dark_raw, the minmax algorithm will select
                 * the same values as for the dark_raw 
                 * The nframes - 1 is because only one frame is being rejected*/  
                accu_var = cpl_imagelist_collapse_minmax_create(variances,0,1);
                cpl_image_divide_scalar(accu_var, nframes - 1);
                ima_accu_err = cpl_image_duplicate(accu_var);
                cpl_image_power(ima_accu_err, 0.5);
                cpl_imagelist_delete(variances);
                cpl_image_delete(accu_var);
            }

        } else {
            ima_accu = cpl_imagelist_collapse_create(darks_raw) ;
            if(hawki_cal_dark_config.error_tracking)
            {
                cpl_imagelist * variances;
                cpl_image     * accu_var;
                cpl_msg_info(__func__, "Computing the uncertainty in dark");
                variances = hawki_imglist_create_variances_and_delete 
                    (darks_raw, hawki_cal_dark_config.gain, 
                     hawki_cal_dark_config.ron, hawki_cal_dark_outputs.ndit,
                     hawki_cal_dark_outputs.ndsamples);
                accu_var = cpl_imagelist_collapse_create(variances);                
                cpl_image_divide_scalar(accu_var, nframes); 
                ima_accu_err = cpl_image_duplicate(accu_var);
                cpl_image_power(ima_accu_err, 0.5);
                cpl_imagelist_delete(variances);
                cpl_image_delete(accu_var);
            }
        }
        if (ima_accu == NULL) {
            cpl_msg_error(__func__, "Cannot compute the average") ;
            cpl_frameset_delete(rawframes) ;
            cpl_imagelist_delete(bpmdark) ;
            cpl_imagelist_delete(master_dark) ;
            if(ima_accu_err != NULL)
                cpl_image_delete(ima_accu_err);
            cpl_imagelist_delete(darks_raw); 
            for (idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++)
                cpl_vector_delete(rons[idet]) ;
            for( idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++) 
                 cpl_table_delete(raw_dark_stats[idet]);
            cpl_free(raw_dark_stats);
            return -1 ;
        }
        cpl_imagelist_delete(darks_raw) ;

        /* Put the result in the list */
        cpl_imagelist_set(master_dark, ima_accu, idet) ;
        if(hawki_cal_dark_config.error_tracking)
            cpl_imagelist_set(master_dark_err, ima_accu_err, idet) ;

        /* Compute the dark_med and stdev */
        hawki_cal_dark_outputs.master_dark_med[idet]=
            cpl_image_get_median(ima_accu) / hawki_cal_dark_outputs.dit;
        hawki_cal_dark_outputs.master_dark_mean[idet] =
            cpl_image_get_mean(ima_accu) / hawki_cal_dark_outputs.dit;
        hawki_cal_dark_outputs.master_dark_stdev[idet] =
            cpl_image_get_stdev(ima_accu) / hawki_cal_dark_outputs.dit;
        if(hawki_cal_dark_config.error_tracking)
        {
            hawki_cal_dark_outputs.master_dark_error_med[idet]=
                cpl_image_get_median(ima_accu_err) / hawki_cal_dark_outputs.dit;
            hawki_cal_dark_outputs.master_dark_error_mean[idet] =
                cpl_image_get_mean(ima_accu_err) / hawki_cal_dark_outputs.dit;
            hawki_cal_dark_outputs.master_dark_error_stdev[idet] =
                cpl_image_get_stdev(ima_accu_err) / hawki_cal_dark_outputs.dit;
        }

        /* Compute the Video Channels stats */
        vc_lly = 973 ;
        vc_ury = 1036 ;
        for (j=0 ; j<HAWKI_NB_VC ; j++) {
            vc_llx = j*(2048/HAWKI_NB_VC) + 1 ;
            vc_urx = (j+1)*(2048/HAWKI_NB_VC) ;

            hawki_cal_dark_outputs.vc_mean[idet][j] =
                cpl_image_get_mean_window(ima_accu, vc_llx, vc_lly,
                        vc_urx, vc_ury) ;

            hawki_cal_dark_outputs.vc_med[idet][j] =
                cpl_image_get_median_window(ima_accu, vc_llx, vc_lly,
                        vc_urx, vc_ury) ;

            hawki_cal_dark_outputs.vc_stdev[idet][j] =
                cpl_image_get_stdev_window(ima_accu, vc_llx, vc_lly,
                        vc_urx, vc_ury) ;
        }

        /* Compute the HOT pixels map */
        cpl_msg_info(__func__, "Compute the BPM from the dark") ;
        cpl_msg_indent_more() ;
        if ((bpm=hawki_compute_darkbpm(ima_accu, 
                        hawki_cal_dark_config.sigma)) == NULL) {
            cpl_msg_error(__func__, "Cannot compute the hot pixels") ;
            cpl_msg_indent_less() ; 
            cpl_frameset_delete(rawframes) ;
            cpl_imagelist_delete(bpmdark) ;
            cpl_imagelist_delete(master_dark);
            if(hawki_cal_dark_config.error_tracking)
                cpl_imagelist_delete(master_dark_err);
            for (idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++)
                cpl_vector_delete(rons[idet]) ;
            for (idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++) 
                cpl_table_delete(raw_dark_stats[idet]);
            cpl_free(raw_dark_stats);
            return -1 ;
        }
        cpl_imagelist_set(bpmdark, bpm, idet) ;
        hawki_cal_dark_outputs.nb_badpix[idet]=(int)cpl_image_get_flux(bpm);
        cpl_msg_indent_less() ;
    }
    cpl_msg_indent_less() ;
    
    /* Divide by DIT */
    cpl_msg_info(__func__, "Division by DIT") ;
    cpl_imagelist_divide_scalar(master_dark, hawki_cal_dark_outputs.dit);
    if(hawki_cal_dark_config.error_tracking)
        cpl_imagelist_divide_scalar(master_dark_err, hawki_cal_dark_outputs.dit);

    /* Save the product */
    cpl_msg_info(__func__, "Save the products") ;
    cpl_msg_indent_more() ;
    if (hawki_cal_dark_save(master_dark, master_dark_err,
                            bpmdark, raw_dark_stats, 
                            (const cpl_vector **)rons,
                            rawframes,
                            parlist, frameset)) 
        cpl_msg_warning(__func__,"Some data could not be saved. "
                                 "Check permisions or disk space");

    /* Free */
    cpl_frameset_delete(rawframes) ;
    for (idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++)
        cpl_vector_delete(rons[idet]) ;
    cpl_imagelist_delete(master_dark) ;
    if(hawki_cal_dark_config.error_tracking)
        cpl_imagelist_delete(master_dark_err);
    cpl_imagelist_delete(bpmdark) ;
    cpl_msg_indent_less() ;
    for( idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++) 
         cpl_table_delete(raw_dark_stats[idet]);
    cpl_free(raw_dark_stats);

    /* Return */
    if (cpl_error_get_code())
    {
        cpl_msg_error(__func__,
                      "HAWK-I pipeline could not recover from previous errors");
        return -1 ;
    }
    else return 0 ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    The RON computation
  @param    ima1        the first input image
  @param    ima2        the second input image
  @param    ndit        the NDIT
  @return   the RON or -1 in error case
 */
/*----------------------------------------------------------------------------*/
static double hawki_cal_dark_ron(
        const cpl_image     *   ima1, 
        const cpl_image     *   ima2, 
        int                     ndit) 
{
    cpl_image       *   ima ;
    double              norm ;
    double              ron ;

    /* Test entries */
    if (ima1 == NULL)   return -1.0 ;
    if (ima2 == NULL)   return -1.0 ;
    if (ndit < 1)       return -1.0 ;

    /* Compute norm */
    norm = 0.5 * ndit ;
    norm = sqrt(norm) ;

    /* Subtraction */
    if ((ima = cpl_image_subtract_create(ima2, ima1)) == NULL) return -1.0 ;
   
    /* RON measurement */
    cpl_flux_get_noise_window(ima, NULL, hawki_cal_dark_config.hsize,
            hawki_cal_dark_config.nsamples, &ron, NULL) ;
    cpl_image_delete(ima) ;
    return norm*ron ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Save the products
  @param    dark        the dark images
  @param    bpmdark     the BPM from the dark
  @param    rons        the list of RONS for the HAWKI_NB_DETECTORS detectors
  @param    parlist     the parameters list
  @param    set         the frames set
  @return   0 if everything is ok
 */
/*----------------------------------------------------------------------------*/
static int hawki_cal_dark_save
(const cpl_imagelist *   master_dark,
 const cpl_imagelist *   master_dark_err,
 const cpl_imagelist *   bpmdark,
 cpl_table           **  raw_dark_stats,
 const cpl_vector    **  rons,
 const cpl_frameset  *   used_frames,
 cpl_parameterlist   *   parlist,
 cpl_frameset        *   set)
{
    cpl_propertylist    **  qclists ;
    const cpl_frame     *   ref_frame ;
    char                    sval[32] ;
    cpl_propertylist    *   inputlist ;
    int                     ext_nb ;
    const char          *   recipe_name = "hawki_cal_dark" ;
    int                     i, j ;
    cpl_errorstate          error_prevstate = cpl_errorstate_get();
    

    /* Get the reference frame */
    ref_frame = irplib_frameset_get_first_from_group(set, CPL_FRAME_GROUP_RAW) ;

    /* Create the QC lists */
    qclists = cpl_malloc(HAWKI_NB_DETECTORS * sizeof(cpl_propertylist*)) ;
    for (i=0 ; i<HAWKI_NB_DETECTORS ; i++) {
        qclists[i] = cpl_propertylist_new();
        cpl_propertylist_append_int(qclists[i], "ESO QC DARK NBADPIX",
                hawki_cal_dark_outputs.nb_badpix[i]);
        cpl_propertylist_append_double(qclists[i], "ESO QC DARK MEAN",
                hawki_cal_dark_outputs.master_dark_mean[i]);
        cpl_propertylist_append_double(qclists[i], "ESO QC DARK MED",
                hawki_cal_dark_outputs.master_dark_med[i]);
        cpl_propertylist_append_double(qclists[i], "ESO QC DARK STDEV",
                hawki_cal_dark_outputs.master_dark_stdev[i]);
        cpl_propertylist_append_double(qclists[i], "ESO QC DARK NONORM MEAN",
                hawki_cal_dark_outputs.master_dark_mean[i] * hawki_cal_dark_outputs.dit);
        cpl_propertylist_append_double(qclists[i], "ESO QC DARK NONORM MED",
                hawki_cal_dark_outputs.master_dark_med[i] * hawki_cal_dark_outputs.dit);
        cpl_propertylist_append_double(qclists[i], "ESO QC DARK NONORM STDEV",
                hawki_cal_dark_outputs.master_dark_stdev[i] * hawki_cal_dark_outputs.dit);
        if(hawki_cal_dark_config.error_tracking)
        {
            cpl_propertylist_append_double(qclists[i], "ESO QC DARK ERR MEAN",
                 hawki_cal_dark_outputs.master_dark_error_mean[i]);
            cpl_propertylist_append_double(qclists[i], "ESO QC DARK ERR MED",
                 hawki_cal_dark_outputs.master_dark_error_med[i]);
            cpl_propertylist_append_double(qclists[i], "ESO QC DARK ERR STDEV",
                 hawki_cal_dark_outputs.master_dark_error_stdev[i]);
            cpl_propertylist_append_double(qclists[i], "ESO QC DARK ERR NONORM MEAN",
                 hawki_cal_dark_outputs.master_dark_error_mean[i] * hawki_cal_dark_outputs.dit);
            cpl_propertylist_append_double(qclists[i], "ESO QC DARK ERR NONORM MED",
                 hawki_cal_dark_outputs.master_dark_error_med[i] * hawki_cal_dark_outputs.dit);
            cpl_propertylist_append_double(qclists[i], "ESO QC DARK ERR NONORM STDEV",
                 hawki_cal_dark_outputs.master_dark_error_stdev[i] * hawki_cal_dark_outputs.dit);
        }
        for (j=0 ; j<HAWKI_NB_VC ; j++) {
            sprintf(sval, "ESO QC DARK VC%d MEAN", j+1) ;
            cpl_propertylist_append_double(qclists[i], sval,
                    hawki_cal_dark_outputs.vc_mean[i][j]) ;
            sprintf(sval, "ESO QC DARK VC%d MED", j+1) ;
            cpl_propertylist_append_double(qclists[i], sval,
                    hawki_cal_dark_outputs.vc_med[i][j]) ;
            sprintf(sval, "ESO QC DARK VC%d STDEV", j+1) ;
            cpl_propertylist_append_double(qclists[i], sval,
                    hawki_cal_dark_outputs.vc_stdev[i][j]) ;
        }
        for (j=0 ; j<cpl_vector_get_size(rons[i]) ; j++) {
            sprintf(sval, "ESO QC RON%d", j+1) ;
            cpl_propertylist_append_double(qclists[i], sval,
                    cpl_vector_get(rons[i], j)) ;
        } 
        cpl_propertylist_append_double(qclists[i], "ESO QC RON MEAN",
                cpl_vector_get_mean(rons[i])) ;
        cpl_propertylist_append_double(qclists[i], "ESO QC RON MED",
                cpl_vector_get_median_const(rons[i])) ;
        cpl_propertylist_append_double(qclists[i], "ESO QC RON STDEV",
                cpl_vector_get_stdev(rons[i])) ;
        cpl_propertylist_append_double(qclists[i], "ESO QC DATANCOM",
                cpl_frameset_get_size(set)) ;
       
        /* Propagate some keywords from input raw frame extensions */
        ext_nb=hawki_get_ext_from_detector(cpl_frame_get_filename(ref_frame), i+1);
        inputlist = cpl_propertylist_load_regexp(
                cpl_frame_get_filename(ref_frame), ext_nb, 
                HAWKI_HEADER_EXT_FORWARD, 0) ;
        cpl_propertylist_append(qclists[i], inputlist) ; 
        cpl_propertylist_delete(inputlist) ;
    }
    /* Statistics of the raw images in the QC */
    hawki_image_stats_stats(raw_dark_stats, qclists);

    /* Write the dark image */
    hawki_imagelist_save(set,
                         parlist,
                         used_frames, 
                         master_dark, 
                         recipe_name,
                         HAWKI_CALPRO_DARK, 
                         HAWKI_PROTYPE_DARK,
                         NULL,
                         (const cpl_propertylist**)qclists,
                         "hawki_cal_dark.fits") ;

    /* Write the dark image error */
    if(master_dark_err != NULL)
    {
        hawki_imagelist_save(set,
                             parlist,
                             used_frames, 
                             master_dark_err, 
                             recipe_name,
                             HAWKI_CALPRO_DARK_ERR, 
                             HAWKI_PROTYPE_DARK_ERR,
                             NULL,
                             NULL,
                             "hawki_cal_dark_err.fits") ;
    }

    /* Write the bpmdark pixels image */
    hawki_imagelist_save(set,
                         parlist,
                         used_frames, 
                         bpmdark, 
                         recipe_name,
                         HAWKI_CALPRO_BPM_HOT, 
                         HAWKI_PROTYPE_BPM,
                         NULL,
                         NULL,
                         "hawki_cal_dark_bpmdark.fits") ;

    
    /* Write the table with the statistics */
    hawki_tables_save(set,
                      parlist,
                      used_frames,
                      (const cpl_table **)raw_dark_stats,
                      recipe_name,
                      HAWKI_CALPRO_DARK_STATS,
                      HAWKI_PROTYPE_DARK_STATS,
                      NULL,
                      NULL,
                      "hawki_cal_dark_stats.fits") ;

    /* Free and return */
    for (i=0 ; i<HAWKI_NB_DETECTORS ; i++) {
        cpl_propertylist_delete(qclists[i]) ;
    }
    cpl_free(qclists) ;
    if(!cpl_errorstate_is_equal(error_prevstate))
    {
        cpl_error_reset();
        return -1;
    }
    return  0;
}

static int hawki_cal_dark_retrieve_input_param
(cpl_parameterlist * parlist)
{
    cpl_parameter       *   par ;
    const char          *   sval ;
    cpl_errorstate          error_prevstate = cpl_errorstate_get();
    
    /* Retrieve input parameters */
    par = cpl_parameterlist_find(parlist, "hawki.hawki_cal_dark.sigma") ;
    hawki_cal_dark_config.sigma = cpl_parameter_get_double(par) ;
    par = cpl_parameterlist_find(parlist, "hawki.hawki_cal_dark.hsize") ;
    hawki_cal_dark_config.hsize = cpl_parameter_get_int(par) ;
    par = cpl_parameterlist_find(parlist, "hawki.hawki_cal_dark.nsamples") ;
    hawki_cal_dark_config.nsamples = cpl_parameter_get_int(par) ;
    par = cpl_parameterlist_find(parlist, "hawki.hawki_cal_dark.zone") ;
    sval = cpl_parameter_get_string(par) ;
    setlocale(LC_NUMERIC, "C"); // Behaviour of sscanf depends on the locale
    if (sscanf(sval, "%d,%d,%d,%d",
                    &hawki_cal_dark_config.llx,
                    &hawki_cal_dark_config.lly,
                    &hawki_cal_dark_config.urx,
                    &hawki_cal_dark_config.ury)!=4) {
        return -1 ;
    }
    par = cpl_parameterlist_find(parlist, "hawki.hawki_cal_dark.gain") ;
    hawki_cal_dark_config.gain = cpl_parameter_get_double(par);
    par = cpl_parameterlist_find(parlist, "hawki.hawki_cal_dark.ron") ;
    hawki_cal_dark_config.ron = cpl_parameter_get_double(par);
    hawki_cal_dark_config.error_tracking = 0; 
    if(hawki_cal_dark_config.gain > 0 && hawki_cal_dark_config.ron > 0)
        hawki_cal_dark_config.error_tracking = 1; 
    
    if(!cpl_errorstate_is_equal(error_prevstate))
        return -1;

    return 0;
}

void hawki_cal_dark_initialise_qc(void)
{
    int idet;
    int j;
    
    for(idet=0; idet<HAWKI_NB_DETECTORS; idet++) 
    {
        hawki_cal_dark_outputs.nb_badpix[idet] = -1 ;
        hawki_cal_dark_outputs.master_dark_mean[idet] = -1.0 ;
        hawki_cal_dark_outputs.master_dark_med[idet] = -1.0 ;
        hawki_cal_dark_outputs.master_dark_stdev[idet] = -1.0 ;
        hawki_cal_dark_outputs.master_dark_error_mean[idet] = -1.0 ;
        hawki_cal_dark_outputs.master_dark_error_med[idet] = -1.0 ;
        hawki_cal_dark_outputs.master_dark_error_stdev[idet] = -1.0 ;
        for (j=0 ; j<HAWKI_NB_VC ; j++) 
        {
            hawki_cal_dark_outputs.vc_mean[idet][j] = -1.0 ;
            hawki_cal_dark_outputs.vc_med[idet][j] = -1.0 ;
            hawki_cal_dark_outputs.vc_stdev[idet][j] = -1.0 ;
        }
    }
}
