/* $Id: xsh_detmon.c,v 1.11 2013-07-19 12:00:24 jtaylor Exp $
 *
 * This file is part of the irplib package
 * 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 St, Fifth Floor, Boston, MA 02111-1307 USA
 */

/*
 * $Author: jtaylor $
 * $Date: 2013-07-19 12:00:24 $
 * $Revision: 1.11 $
 * $Name: not supported by cvs2svn $
 */

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

#include <complex.h>

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

#include <math.h>
#include <string.h>
#include <assert.h>
#include <float.h>

#include <cpl.h>

#include "xsh_detmon.h"

#include "xsh_ksigma_clip.h"
//#include "irplib_hist.h"
#include "irplib_utils.h"


/* Computes the square of an euclidean distance bet. 2 points */
#define pdist(x1,y1,x2,y2) (((x1-x2)*(x1-x2))+((y1-y2)*(y1-y2)))

#define cpl_drand() ((double)rand()/(double)RAND_MAX)
/**@{*/
/*--------------------------------------------------------------------------*/

/*
 * @defgroup xsh_detmon        Detector monitoring functions
 */

/*--------------------------------------------------------------------------*/

/*---------------------------------------------------------------------------
                                  Defines
 ---------------------------------------------------------------------------*/


#define HIST_FACT 2.354820045

enum pixeltypes
{
    HOT = 0,
    DEAD = 1,
    NOISY = 2
};

enum stackingtypes
{
    MINMAX = 0,
    MEAN = 1,
    MEDIAN = 2,
    KSIGMA = 3
};

enum readouts
{
    HORIZONTAL = 1,
    VERTICAL = 2
};


static struct
{
    const char * ron_method;
    const char * dsnu_method;
    int         exts;
    int         nb_extensions;
    cpl_boolean opt_nir;
} xsh_detmon_dark_config;

#define NIR TRUE
#define OPT FALSE

/*---------------------------------------------------------------------------
  Private function prototypes
  ---------------------------------------------------------------------------*/

/* Functions for RON/Bias recipe, xsh_detmon_ronbias() */

cpl_error_code
xsh_detmon_rm_bpixs(cpl_image **,
                const double,
                int         ,
                int         );

/* The following 2 functions are duplicated from cpl_det */



static cpl_bivector    *
irplib_bivector_gen_rect_poisson(const int *r,
                                 const int np,
                                 const int homog);

static cpl_error_code
xsh_detmon_dark_save(const cpl_parameterlist *,
                 cpl_frameset *,
                 const char *,
                 const char *,
                 const char *,
                 const char *,
                 const char *,
                 const char *,
                 cpl_imagelist **,
                 cpl_table **,
                 cpl_imagelist **,
                 cpl_propertylist **,
                 const int,
                 const int,
                 const cpl_frameset *);



static cpl_error_code
xsh_detmon_retrieve_dark_params(const char *,
                            const char *,
                            const cpl_parameterlist *);


/*---------------------------------------------------------------------------*/

/*
 * @brief  Fill input parameters values
 * @param  parlist      parameters list
 * @param  recipe_name  recipe name
 * @param  pipeline_name pipeline name
 * @param  npars  number of parameters

 * @return CPL_ERROR_NONE on success.
 */

/*---------------------------------------------------------------------------*/
cpl_error_code
xsh_detmon_fill_parlist(cpl_parameterlist * parlist,
                    const char *recipe_name,
                    const char *pipeline_name,
                    int npars, ...)
{

    va_list                 ap;

    char                   *group_name;

    int                     pars_counter = 0;

    group_name = cpl_sprintf("%s.%s", pipeline_name, recipe_name);
    assert(group_name != NULL);

#define insert_par(PARNAME, PARDESC, PARVALUE, PARTYPE)                      \
    do {                                                                     \
        char * par_name = cpl_sprintf("%s.%s", group_name, PARNAME);          \
        cpl_parameter * p;                                                       \
        assert(par_name != NULL);                                                \
        p = cpl_parameter_new_value(par_name, PARTYPE,                           \
                                    PARDESC, group_name, PARVALUE);              \
        cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, PARNAME);             \
        cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);                        \
        cpl_parameterlist_append(parlist, p);                                    \
        cpl_free(par_name);                                                      \
    } while(0);


    va_start(ap, npars);

    while(pars_counter < npars) {
        char                   *name = va_arg(ap, char *);
        char                   *desc = va_arg(ap, char *);
        char                   *type = va_arg(ap, char *);

        if(!strcmp(type, "CPL_TYPE_INT")) {
            int                     v1 = va_arg(ap, int);

            insert_par(name, desc, v1, CPL_TYPE_INT);
        } else if(!strcmp(type, "CPL_TYPE_BOOL")) {
            char                   *v2 = va_arg(ap, char *);

            if(!strcmp(v2, "CPL_FALSE"))
                insert_par(name, desc, CPL_FALSE, CPL_TYPE_BOOL);
            if(!strcmp(v2, "CPL_TRUE"))
                insert_par(name, desc, CPL_TRUE, CPL_TYPE_BOOL);
        } else if(!strcmp(type, "CPL_TYPE_STRING")) {
            char                   *v2 = va_arg(ap, char *);

            insert_par(name, desc, v2, CPL_TYPE_STRING);
        } else if(!strcmp(type, "CPL_TYPE_DOUBLE")) {
            double v3 = va_arg(ap, double);
            insert_par(name, desc, v3, CPL_TYPE_DOUBLE);
        }

        pars_counter++;
    }

    va_end(ap);

    cpl_free(group_name);

#undef insert_par
    return 0;
}

/*---------------------------------------------------------------------------*/

/*
 * @brief  Retrieve input int parameter
 * @param  pipeline_name        Input image
 * @param  recipe_name          Input image
 * @param  parlist              Shift to apply on the x-axis
 * @return CPL_ERROR_NONE on success.
 */

/*---------------------------------------------------------------------------*/
int
xsh_detmon_retrieve_par_int(const char *parn,
                        const char *pipeline_name,
                        const char *recipe_name,
                        const cpl_parameterlist * parlist)
{
    char                   *par_name;
    cpl_parameter          *par;
    int                     value;

    par_name = cpl_sprintf("%s.%s.%s", pipeline_name, recipe_name, parn);
    assert(par_name != NULL);
    par = cpl_parameterlist_find((cpl_parameterlist *) parlist, par_name);
    value = cpl_parameter_get_int(par);
    cpl_free(par_name);

    return value;
}

/*---------------------------------------------------------------------------*/

/*
 * @brief  Retrieve input double parameter
 * @param  pipeline_name        Input image
 * @param  recipe_name          Input image
 * @param  parlist              Shift to apply on the x-axis
 * @return CPL_ERROR_NONE on success.
 */

/*---------------------------------------------------------------------------*/
double
xsh_detmon_retrieve_par_double(const char *parn,
                           const char *pipeline_name,
                           const char *recipe_name,
                           const cpl_parameterlist * parlist)
{
    char                   *par_name;
    cpl_parameter          *par;
    double                     value;

    par_name = cpl_sprintf("%s.%s.%s", pipeline_name, recipe_name, parn);
    assert(par_name != NULL);
    par = cpl_parameterlist_find((cpl_parameterlist *) parlist, par_name);
    value = cpl_parameter_get_double(par);
    cpl_free(par_name);

    return value;
}


/*---------------------------------------------------------------------------*/

/*
 * @brief  Retrieve exposure time
 * @param  plist      parameter list
 * @return "EXPTIME" keyword value.
 */

/*---------------------------------------------------------------------------*/

double
irplib_pfits_get_exptime(const cpl_propertylist * plist)
{
    double                  exptime;

    exptime = cpl_propertylist_get_double(plist, "EXPTIME");

    return exptime;
}

/*---------------------------------------------------------------------------*/
/*
 * @brief  Generate propertylist with product category, type, technique
 * @param  procatg product category
 * @param  protype product type
 * @param  protech product technique
 * @param  proscience switch to identify a science product
 * @return filled cpl_propertylist (to be deallocated)
 */
/*---------------------------------------------------------------------------*/
cpl_propertylist *
xsh_detmon_fill_prolist(const char * procatg,
                    const char * protype,
                    const char * protech,
                    cpl_boolean  proscience)
{
    cpl_propertylist * prolist = cpl_propertylist_new();

    cpl_propertylist_append_string(prolist, CPL_DFS_PRO_CATG,    procatg);
    cpl_propertylist_append_bool(prolist,   CPL_DFS_PRO_SCIENCE, proscience);
    if (protype) {
        cpl_propertylist_append_string(prolist, CPL_DFS_PRO_TYPE,    protype);
    }
    if (protech) {
        cpl_propertylist_append_string(prolist, CPL_DFS_PRO_TECH,    protech);
    }

    return prolist;
}

/*---------------------------------------------------------------------------*/

/*
 * @brief  Remove bad pixels
 * @param  image  input/output image
 * @param  kappa  kappa for kappa-sigma clip
 * @param  nffts  number of fft
 * @param  nsamples number of sampling areas
 * @return CPL_ERROR_NONE on success.
 */

/*---------------------------------------------------------------------------*/


cpl_error_code
xsh_detmon_rm_bpixs(cpl_image ** image,
                const double kappa, int nffts, int nsamples)
{
    int                     i, j;

    float                  *data = cpl_image_get_data_float(*image);
    int k = 0;
    for(i = 0; i < nffts; i++) {
        for(j = 0; j < nsamples; j++) {
            float                   neighbours = 0;
            int                     nneighs = 0;
            float                   average = 0;

            /*
             * Look for the way to optimize this:
             * Some of the points added to neighbours coincide
             * in one iteration and the following
             */
            if(i > 0) {
                neighbours += *(data + (i - 1) * nsamples + j);
                nneighs++;
            }
            if(i < nffts - 1) {
                neighbours += *(data + (i + 1) * nsamples + j);
                nneighs++;
            }
            if(j > 0) {
                neighbours += *(data + i * nsamples + (j - 1));
                nneighs++;
            }
            if(j < nsamples - 1) {
                neighbours += *(data + i * nsamples + (j + 1));
                nneighs++;
            }
            average = neighbours / nneighs;
            if(average > 0) {
                if(*(data + i * nsamples + j) < average * (-1 * kappa) ||
                   *(data + i * nsamples + j) > average * (kappa)) {
                    k++;
                    *(data + i * nsamples + j) = average;
                }
            }
            if(average < 0) {
                if(*(data + i * nsamples + j) > average * (-1 * kappa) ||
                   *(data + i * nsamples + j) < average * (kappa)) {
                    k++;
                    *(data + i * nsamples + j) = average;
                }
            }

        }
    }


    return cpl_error_get_code();

}

/*---------------------------------------------------------------------------*/

/*
 * @brief  Generates Poisson distributed values
 * @param  r        Input values
 * @param  np       number of points
 * @param  homog    homogeneity factor
 * @return CPL_ERROR_NONE on success.
 */

/*---------------------------------------------------------------------------*/
static cpl_bivector    *
irplib_bivector_gen_rect_poisson(const int *r, const int np, const int homog)
{
    double                  min_dist;
    int                     i;
    int                     gnp;
    cpl_bivector           *list;
    double                  cand_x, cand_y;
    int                     ok;
    int                     start_ndx;
    int                     xmin, xmax, ymin, ymax;

    /* Corrected Homogeneity factor */
    const int               homogc = 0 < homog && homog < np ? homog : np;
    double                 *px;
    double                 *py;

    /* error handling: test arguments are correct */
    cpl_ensure(r, CPL_ERROR_NULL_INPUT, NULL);
    cpl_ensure(np > 0, CPL_ERROR_ILLEGAL_INPUT, NULL);

    list = cpl_bivector_new(np);
    cpl_ensure(list, CPL_ERROR_NULL_INPUT, NULL);
    px = cpl_bivector_get_x_data(list);
    py = cpl_bivector_get_y_data(list);

    xmin = r[0];
    xmax = r[1];
    ymin = r[2];
    ymax = r[3];

    min_dist =
        CPL_MATH_SQRT1_2 * ((xmax - xmin) * (ymax - ymin) / (double) (homogc + 1));
    gnp = 1;
    px[0] = 0;
    py[0] = 0;

    /* First: generate <homog> points */
    while(gnp < homogc) {
        /* Pick a random point within requested range */
        cand_x = cpl_drand() * (xmax - xmin) + xmin;
        cand_y = cpl_drand() * (ymax - ymin) + ymin;

        /* Check the candidate obeys the minimal Poisson distance */
        ok = 1;
        for(i = 0; i < gnp; i++) {
            if(pdist(cand_x, cand_y, px[i], py[i]) < min_dist) {
                /* does not check Poisson law: reject point */
                ok = 0;
                break;
            }
        }
        if(ok) {
            /* obeys Poisson law: register the point as valid */
            px[gnp] = cand_x;
            py[gnp] = cand_y;
            gnp++;
        }
    }

    /* Iterative process: */
    /* Pick points out of Poisson distance of the last <homogc-1> points. */
    start_ndx = 0;
    while(gnp < np) {
        /* Pick a random point within requested range */
        cand_x = cpl_drand() * (xmax - xmin) + xmin;
        cand_y = cpl_drand() * (ymax - ymin) + ymin;

        /* Check the candidate obeys the minimal Poisson distance */
        ok = 1;
        for(i = 0; i < homogc; i++) {
            if(pdist(cand_x,
                     cand_y,
                     px[start_ndx + i], py[start_ndx + i]) < min_dist) {
                /* does not check Poisson law: reject point */
                ok = 0;
                break;
            }
        }
        if(ok) {
            /* obeys Poisson law: register the point as valid */
            px[gnp] = cand_x;
            py[gnp] = cand_y;
            gnp++;
        }
    }

    /* Iterative process: */
    /* Pick points out of Poisson distance of the last <homogc-1> points. */
    start_ndx = 0;
    while(gnp < np) {
        /* Pick a random point within requested range */
        cand_x = cpl_drand() * (xmax - xmin) + xmin;
        cand_y = cpl_drand() * (ymax - ymin) + ymin;

        /* Check the candidate obeys the minimal Poisson distance */
        ok = 1;
        for(i = 0; i < homogc; i++) {
            if(pdist(cand_x,
                     cand_y,
                     px[start_ndx + i], py[start_ndx + i]) < min_dist) {
                /* does not check Poisson law: reject point */
                ok = 0;
                break;
            }
        }
        if(ok) {
            /* obeys Poisson law: register the point as valid */
            px[gnp] = cand_x;
            py[gnp] = cand_y;
            gnp++;
            start_ndx++;
        }
    }
    return list;
}


/*---------------------------------------------------------------------------*/

/*
 * @brief  Retrieve input parameters
 * @param  pipeline_name        Input pipeline name
 * @param  recipe_name          Input recipe name
 * @param  parlist              Input parameter list
 * @return CPL_ERROR_NONE on success.
 */

/*---------------------------------------------------------------------------*/
static                  cpl_error_code
xsh_detmon_retrieve_dark_params(const char *pipeline_name,
                            const char *recipe_name,
                            const cpl_parameterlist * parlist)
{
    char                   *par_name;
    cpl_parameter          *par;

    /* --ron.method */
    par_name = cpl_sprintf("%s.%s.ron.method", pipeline_name, recipe_name);
    assert(par_name != NULL);
    par = cpl_parameterlist_find((cpl_parameterlist *) parlist, par_name);
    xsh_detmon_dark_config.ron_method = cpl_parameter_get_string(par);
    cpl_free(par_name);

    /* --dsnu.method */
    par_name = cpl_sprintf("%s.%s.dsnu.method", pipeline_name, recipe_name);
    assert(par_name != NULL);
    par = cpl_parameterlist_find((cpl_parameterlist *) parlist, par_name);
    xsh_detmon_dark_config.dsnu_method = cpl_parameter_get_string(par);
    cpl_free(par_name);

    /* --opt_nir */
    par_name = cpl_sprintf("%s.%s.opt_nir", pipeline_name, recipe_name);
    assert(par_name != NULL);
    par = cpl_parameterlist_find((cpl_parameterlist *) parlist, par_name);
    xsh_detmon_dark_config.opt_nir = cpl_parameter_get_bool(par);
    cpl_free(par_name);

    /* --exts */
    xsh_detmon_dark_config.exts =
        xsh_detmon_retrieve_par_int("exts", pipeline_name, recipe_name,
                                parlist);

    if(cpl_error_get_code()) {
        cpl_msg_error(cpl_func, "Failed to retrieve the input parameters");
        cpl_ensure_code(0, CPL_ERROR_DATA_NOT_FOUND);
    }


    return CPL_ERROR_NONE;
}


/*---------------------------------------------------------------------------*/

/*
 * @brief  Save dark recipe products
 * @param  parlist        input parameter list
 * @param  frameset       input frameset
 * @param  recipe_name    input recipe name
 * @param  pipeline_name  input pipeline name
 * @param  procatg_master input procatg of master product
 * @param  procatg_tbl    input procatg table
 * @param  procatg_dsnu   input procatg dsnu product
 * @param  package        input package name
 * @param  masters        list of masters products
 * @param  dsnu_table     dsnu table product
 * @param  dsnu           list of dsnu images products
 * @param  qclist         qc parameters
 * @param  flag_sets      switch
 * @param  which_set      specifier
 * @param  usedframes     used frames
 * @return CPL_ERROR_NONE on success.
 */

/*---------------------------------------------------------------------------*/
static                  cpl_error_code
xsh_detmon_dark_save(const cpl_parameterlist * parlist,
                 cpl_frameset * frameset,
                 const char *recipe_name,
                 const char *pipeline_name,
                 const char *procatg_master,
                 const char *procatg_tbl,
                 const char *procatg_dsnu,
                 const char *package,
                 cpl_imagelist ** masters,
                 cpl_table ** dsnu_table,
                 cpl_imagelist ** dsnu,
                 cpl_propertylist ** qclist,
                 const int flag_sets,
                 const int which_set,
                 const cpl_frameset * usedframes)
{

    cpl_frame              *ref_frame;
    cpl_propertylist       *plist;
    char                   *name_o = NULL; /* Avoid (false) uninit warning */
    int                     i, j;
    cpl_propertylist       *paflist;
    cpl_error_code          error;
    int nb_images;

    /***************************/
    /*  Write the MASTER FITS  */
    /***************************/

    nb_images = cpl_imagelist_get_size(masters[0]);
    cpl_ensure_code(nb_images > 0, CPL_ERROR_DATA_NOT_FOUND);


    for(i = 0; i < nb_images; i++) {
        /* Set the file name for each image */
        if(!flag_sets) {
            name_o =
                cpl_sprintf("%s_master_dit_%d.fits", recipe_name, i+1);
            assert(name_o != NULL);
        } else {
            name_o =
                cpl_sprintf("%s_master_dit_%d_set%02d.fits",
                            recipe_name, i, which_set);
            assert(name_o != NULL);
        }


        /* Save the image */
        if(xsh_detmon_dark_config.exts >= 0) {
            cpl_propertylist * pro_master = cpl_propertylist_new();

            cpl_propertylist_append_string(pro_master,
                                           CPL_DFS_PRO_CATG, procatg_master);

            cpl_propertylist_append(pro_master, qclist[0]);

            if(cpl_dfs_save_image
               (frameset, NULL, parlist, usedframes, NULL,
                cpl_imagelist_get(*masters, i), CPL_BPP_IEEE_FLOAT,
                recipe_name, pro_master, NULL, package,
                name_o)) {
                cpl_msg_error(cpl_func, "Cannot save the product: %s",
                              name_o);
                cpl_free(name_o);
                cpl_ensure_code(0, CPL_ERROR_FILE_NOT_CREATED);

            }

            cpl_propertylist_delete(pro_master);
        } else {
            cpl_propertylist * pro_master = cpl_propertylist_new();

            cpl_propertylist_append_string(pro_master,
                                           CPL_DFS_PRO_CATG, procatg_master);

            cpl_propertylist_append(pro_master, qclist[0]);

            if(cpl_dfs_save_image(frameset, NULL, parlist, usedframes, NULL,
                                  NULL, CPL_BPP_IEEE_FLOAT, recipe_name,
                                  pro_master, NULL,
                                  package, name_o)) {
                cpl_msg_error(cpl_func, "Cannot save the product: %s",
                              name_o);
                cpl_free(name_o);
                cpl_ensure_code(0, CPL_ERROR_FILE_NOT_CREATED);
            }

            cpl_propertylist_delete(pro_master);
            for(j = 0; j < xsh_detmon_dark_config.nb_extensions; j++) {
                error =
                    cpl_image_save(cpl_imagelist_get(masters[j], i),
                                   name_o, CPL_BPP_IEEE_FLOAT, qclist[j],
                                   CPL_IO_EXTEND);
                cpl_ensure_code(!error, error);
            }
        }
        cpl_free(name_o);
    }

    if (xsh_detmon_dark_config.opt_nir == OPT) {
        cpl_propertylist * pro_tbl = cpl_propertylist_new();

        cpl_propertylist_append_string(pro_tbl,
                                       CPL_DFS_PRO_CATG, procatg_tbl);

        cpl_propertylist_append(pro_tbl, qclist[0]);
        /*******************************/
        /*  Write the LINEARITY TABLE  */
        /*******************************/

        /* Set the file name for the table */
        if(!flag_sets) {
            name_o = cpl_sprintf("%s_dsnu_table.fits", recipe_name);
            assert(name_o != NULL);
        } else {
            name_o =
                cpl_sprintf("%s_dsnu_table_set%02d.fits", recipe_name,
                            which_set);
            assert(name_o != NULL);
        }
        /* Save the table */
        if(cpl_dfs_save_table(frameset, NULL, parlist, usedframes, NULL,
                              dsnu_table[0], NULL, recipe_name, pro_tbl, NULL,
                              package, name_o)) {
            cpl_msg_error(cpl_func, "Cannot save the product: %s", name_o);
            cpl_free(name_o);
            cpl_ensure_code(0, CPL_ERROR_FILE_NOT_CREATED);
        }

        cpl_propertylist_delete(pro_tbl);

        if(xsh_detmon_dark_config.exts < 0) {

            for(i = 1; i < xsh_detmon_dark_config.nb_extensions; i++) {
                error =
                    cpl_table_save(dsnu_table[i], NULL, qclist[i], name_o,
                                   CPL_IO_EXTEND);
                cpl_ensure_code(!error, error);
            }
        }

        /* Free */
        cpl_free(name_o);

        /***************************/
        /*  Write the DSNU_MAP FITS  */
        /***************************/

        for(i = 0; i < nb_images; i++) {
            /* Set the file name for each image */
            if(!flag_sets) {
                name_o =
                    cpl_sprintf("%s_dsnu_map_dit_%d.fits", recipe_name, i+1);
                assert(name_o != NULL);
            } else {
                name_o =
                    cpl_sprintf("%s_dsnu_map_dit_%d_set%02d.fits",
                                recipe_name, i, which_set);
                assert(name_o != NULL);
            }


            /* Save the image */
            if(xsh_detmon_dark_config.exts >= 0) {
                cpl_propertylist * pro_dsnu = cpl_propertylist_new();

                cpl_propertylist_append_string(pro_dsnu,
                                               CPL_DFS_PRO_CATG, procatg_dsnu);

                cpl_propertylist_append(pro_dsnu, qclist[0]);

                if(cpl_dfs_save_image
                   (frameset, NULL, parlist, usedframes, NULL,
                    cpl_imagelist_get(*dsnu, i), CPL_BPP_IEEE_FLOAT,
                    recipe_name, pro_dsnu, NULL, package,
                    name_o)) {
                    cpl_msg_error(cpl_func, "Cannot save the product: %s",
                                  name_o);
                    cpl_free(name_o);
                    cpl_ensure_code(0, CPL_ERROR_FILE_NOT_CREATED);

                }

                cpl_propertylist_delete(pro_dsnu);
            } else {
                cpl_propertylist * pro_dsnu = cpl_propertylist_new();

                cpl_propertylist_append_string(pro_dsnu,
                                               CPL_DFS_PRO_CATG, procatg_dsnu);

                cpl_propertylist_append(pro_dsnu, qclist[0]);

                if(cpl_dfs_save_image(frameset, NULL, parlist, usedframes,
                                      NULL, NULL,
                                      CPL_BPP_IEEE_FLOAT, recipe_name,
                                      pro_dsnu, NULL,
                                      package, name_o)) {
                    cpl_msg_error(cpl_func, "Cannot save the product: %s",
                                  name_o);
                    cpl_free(name_o);
                    cpl_ensure_code(0, CPL_ERROR_FILE_NOT_CREATED);
                }

                cpl_propertylist_delete(pro_dsnu);
                for(j = 0; j < xsh_detmon_dark_config.nb_extensions; j++) {
                    error =
                        cpl_image_save(cpl_imagelist_get(dsnu[j], i),
                                       name_o, CPL_BPP_IEEE_FLOAT, qclist[j],
                                       CPL_IO_EXTEND);
                    cpl_ensure_code(!error, error);
                }
            }
            cpl_free(name_o);
        }



    } /* End of if(OPT) */

    /*******************************/
    /*  Write the PAF file(s)      */
    /*******************************/

    /* Get FITS header from reference file */
    ref_frame = cpl_frameset_get_position(frameset, 0);
    if((plist = cpl_propertylist_load(cpl_frame_get_filename(ref_frame),
                                      0)) == NULL) {
        cpl_msg_error(cpl_func, "getting header from reference frame");
        cpl_ensure_code(0, cpl_error_get_code());
    }

    /* Get the keywords for the paf file */
    paflist = cpl_propertylist_new();
    cpl_propertylist_copy_property_regexp(paflist, plist,
                                          "^(ARCFILE|MJD-OBS|ESO TPL ID|"
                                          "DATE-OBS|ESO DET DIT|ESO DET NDIT|"
                                          "ESO DET NCORRS|"
                                          "ESO DET MODE NAME)$", 0);

    for(i = 0; i < xsh_detmon_dark_config.nb_extensions; i++) {
        cpl_propertylist * c_paflist = cpl_propertylist_duplicate(paflist);
        error = cpl_propertylist_append(c_paflist, qclist[i]);
        cpl_ensure_code(!error, error);

        /* Set the file name for the bpm */
        if(xsh_detmon_dark_config.exts >= 0) {
            if(!flag_sets) {
                name_o = cpl_sprintf("%s.paf", recipe_name);
                assert(name_o != NULL);
            } else {
                name_o = cpl_sprintf("%s_set%02d.paf", recipe_name, which_set);
                assert(name_o != NULL);
            }
        } else {
            if(!flag_sets) {
                name_o = cpl_sprintf("%s_ext%02d.paf", recipe_name, i+1);
                assert(name_o != NULL);
            } else {
                name_o = cpl_sprintf("%s_set%02d_ext%02d.paf", recipe_name, which_set, i+1);
                assert(name_o != NULL);
            }
        }
        /* Save the PAF */
        if(cpl_dfs_save_paf(pipeline_name, recipe_name, c_paflist, name_o)) {
            cpl_msg_error(cpl_func, "Cannot save the product: %s", name_o);
            cpl_free(name_o);
            cpl_propertylist_delete(paflist);
            cpl_propertylist_delete(plist);
            cpl_free(name_o);
            cpl_ensure_code(0, CPL_ERROR_FILE_NOT_CREATED);
        }
        cpl_propertylist_delete(c_paflist);
        cpl_free(name_o);
    }

    cpl_propertylist_delete(plist);
    cpl_propertylist_delete(paflist);

    return cpl_error_get_code();
}



/**@}*/
