/* $Id: visir_spc_photom.c,v 1.64 2013-03-04 09:00:00 jtaylor Exp $
 *
 * This file is part of the VISIR 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 St, Fifth Floor, Boston, MA  02111-1307  USA
 */

/*
 * $Author: jtaylor $
 * $Date: 2013-03-04 09:00:00 $
 * $Revision: 1.64 $
 * $Name: not supported by cvs2svn $
 */

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

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

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

#include "irplib_framelist.h"

#include "visir_spc_photom.h"

#include "visir_parameter.h"
#include "visir_utils.h"
#include "visir_pfits.h"
#include "visir_spc_distortion.h"

/*-----------------------------------------------------------------------------
                          Private Function prototypes
 -----------------------------------------------------------------------------*/

static cpl_bivector * visir_spc_phot_model_from_cat(const char *, double,
                                                    double);
static int visir_spc_phot_model_rebin(const cpl_bivector *, cpl_table *);

static cpl_error_code visir_spc_sens_stat(const visir_spc_config *,
                                          const visir_spc_resol,
                                          double *, double *, double *,
                                          const cpl_table *);

static cpl_error_code visir_spc_phot_qc(cpl_propertylist *,
                                        const char *, double, double, double,
                                        double, double);

/*----------------------------------------------------------------------------*/
/**
 * @defgroup visir_spc_photom   Photometric functionality
 */
/*----------------------------------------------------------------------------*/

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

/* For QC wavelenghts less than this one are ignored */
#ifndef VISIR_WAVELEN_QC_MIN
#define VISIR_WAVELEN_QC_MIN 8e-6
#endif
/* For QC wavelenghts greater than this one are ignored */
#ifndef VISIR_WAVELEN_QC_MAX
#define VISIR_WAVELEN_QC_MAX 13e-6
#endif

/* Minimum number of spectral values to ignore, from each end */
#ifndef VISIR_WAVELEN_QC_IGNORE
#define VISIR_WAVELEN_QC_IGNORE 3
#endif

/**@{*/


/*----------------------------------------------------------------------------*/
/**
  @brief    Compute the sensitivity from an extracted spectrum
  @param    rawframes      List of input raw frames
  @param    pconfig        The config struct
  @param    plist          The properties (DIT, NDIT etc.)
  @param    star_cat       The standard star catalog
  @param    pweight2d      2D weights image to be created or NULL on error
  @param    qclist         QC Properties are appended to this list
  @param    spc_table      The Spectral Table
  @param    resol          Resolution
  @param    dit_key        The key of the DIT card
  @return   a table or NULL on error
  @see visir_spc_phot_sensit2()
 */
/*----------------------------------------------------------------------------*/
cpl_error_code
visir_spc_phot_sensit(const irplib_framelist * rawframes,
                      const visir_spc_config * pconfig,
                      const cpl_propertylist * plist,
                      const char   * star_cat,
                      cpl_image   ** pweight2d,
                      cpl_propertylist * qclist,
                      cpl_table *   spc_table,
                      const visir_spc_resol resol,
                      const char * dit_key)

{
    cpl_errorstate cleanstate = cpl_errorstate_get();
    cpl_bivector * flux_model = NULL;
    double * ext_array;
    double * spc_array;
    double * err_array;
    double sens_median = -1.0; /* Avoid (false) uninit warning */
    double sens_mean = -1.0; /* Avoid (false) uninit warning */
    double sens_stdev = -1.0; /* Ditto */
    double exptime, factor, exectime = -1.;
    /* Get RA / DEC */
    const double ra  = visir_pfits_get_ra(plist);
    const double dec = visir_pfits_get_dec(plist);
    const char * star_name;

    skip_if (0);

    /* DIT must be present every where */
    skip_if(irplib_framelist_contains(rawframes, dit_key,
                                      CPL_TYPE_DOUBLE, CPL_FALSE, 0.0));
    if(irplib_framelist_contains(rawframes, dit_key,
                                 CPL_TYPE_DOUBLE, CPL_TRUE, 1e-5)) {
        /* Allow 0.1 ms difference - or warn */
        /* FIXME: The recipe does not properly handle non-uniform DITSs */
        visir_error_reset("DIT differs by more than %g", 1e-5);
    }

    /* FIXME: Verify the angular distance */
    skip_if(irplib_framelist_contains(rawframes, VISIR_PFITS_DOUBLE_RA,
                                      CPL_TYPE_DOUBLE, CPL_FALSE, 0.0));

    /* FIXME: Allow 1 degree difference */
    skip_if(irplib_framelist_contains(rawframes, VISIR_PFITS_DOUBLE_DEC,
                                      CPL_TYPE_DOUBLE, CPL_TRUE, 1.0));

    skip_if(irplib_framelist_contains(rawframes, VISIR_PFITS_INT_CHOP_NCYCLES,
                                      CPL_TYPE_INT, CPL_TRUE, 0.0));

    skip_if(irplib_framelist_contains(rawframes, VISIR_PFITS_INT_NDIT,
                                      CPL_TYPE_INT, CPL_TRUE, 0.0));

    if (irplib_framelist_contains(rawframes, VISIR_PFITS_STRING_STARNAME,
                                  CPL_TYPE_STRING, CPL_TRUE, 0.0)) {
        visir_error_reset("Rawframe(s) missing standard star name");
    }

    if (cpl_propertylist_has(plist, "ESO QC EXPTIME"))
        exptime = cpl_propertylist_get_double(plist, "ESO QC EXPTIME");
    else {
        const int nnod = irplib_framelist_get_size(rawframes);
        exptime = visir_utils_get_exptime(nnod, plist);
    }
    cpl_msg_info(cpl_func, "Exposure time: %.3f", exptime);
    if (cpl_propertylist_has(plist, "ESO QC EXECTIME"))
        exectime = cpl_propertylist_get_double(plist, "ESO QC EXECTIME");

    skip_if (exptime <= 0.0);

    factor = sqrt(exptime)/6.0; /* sqrt(exptime/3600) * 10; */

    /* Get the standard star name */
    star_name = visir_pfits_get_starname(plist);
    if (star_name == NULL) visir_error_reset("Could not get standard star name");

    /* Get the flux model from the catalog */
    if ((flux_model=visir_spc_phot_model_from_cat(star_cat, ra, dec)) == NULL) {
        cpl_msg_error(cpl_func, "Cannot retrieve the flux model from the cat.");
        skip_if(1);
    }

    /* Extract the interesting part of the flux model and rebin it */
    if (visir_spc_phot_model_rebin(flux_model, spc_table) == -1) {
        cpl_msg_error(cpl_func, "Cannot rebin the flux model");
        skip_if(1);
    }
    cpl_bivector_delete(flux_model);
    flux_model = NULL;

    /* Compute the sensitivity */
    /* Sensitivity = model * error * 10 * sqrt(DIT*NDIT*NFILES*NCHOP*2)
       / extracted */
    cpl_table_duplicate_column(spc_table, "SENSITIVITY", spc_table,
                               "STD_STAR_MODEL");
    cpl_table_set_column_unit(spc_table, "SENSITIVITY", "mJy");

    ext_array = cpl_table_get_data_double(spc_table, "SENSITIVITY");
    spc_array = cpl_table_get_data_double(spc_table, "SPC_EXTRACTED");
    err_array = cpl_table_get_data_double(spc_table, "SPC_ERROR");

    for (cpl_size i = 0; i < cpl_table_get_nrow(spc_table); i++) {
        ext_array[i] *= factor * err_array[i];
        if (ext_array[i] < 0 || spc_array[i] <= 0) {
            cpl_msg_warning(cpl_func, "Setting non-physical sensitivity in row %d "
                            "to 0: %g/%g", (int)i+1, ext_array[i], spc_array[i]);
            ext_array[i] = 0;
        } else {
            ext_array[i] /= spc_array[i];
        }
    }

    skip_if(visir_spc_sens_stat(pconfig, resol, &sens_median, &sens_mean,
                                &sens_stdev, spc_table));

    /* This column is not part of the product */
    skip_if (cpl_table_erase_column(spc_table, "SPC_EMISSIVITY"));

    skip_if (visir_spc_phot_qc(qclist, star_name, exptime, exectime,
                               sens_median, sens_mean, sens_stdev));

    /* Plot the results */
    if (pconfig->plot) {
        visir_table_plot("", "t 'Extracted spectrum' w lines", "", spc_table,
                         "WLEN", "SPC_EXTRACTED");
        visir_table_plot("", "t 'Extracted spectrum error' w lines", "",
                         spc_table, "WLEN", "SPC_ERROR");
        visir_table_plot("", "t 'Standard star model' w lines", "", spc_table,
                         "WLEN", "STD_STAR_MODEL");
        visir_table_plot("set grid;", "t 'Sensitivity (mJy)' w lines", "",
                         spc_table, "WLEN", "SENSITIVITY");
        visir_image_plot("", "t 'The weight map'", "", *pweight2d);
    }

    end_skip;
    cpl_bivector_delete(flux_model);

    return cpl_error_get_code();
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Extract spectrum from image and compute sensitivity
  @param    combined       The combined image
  @param    rawframes      List of input raw frames
  @param    pconfig        The config struct
  @param    star_cat       The standard star catalog
  @param    spc_cal_lines  The sky lines table
  @param    spc_cal_qeff   The quantum efficiency
  @param    pweight2d      2D weights image to be created or NULL on error
  @param    qclist         QC Properties are appended to this list
  @param    do_ech         True iff the image is of type echelle
  @param    wlen           Central wavelength
  @param    slitw          The slit width
  @param    temp           The telescope (M1) temperature [Kelvin]
  @param    fwhm           The spectral FWHM [pixel]
  @param    resol          Resolution
  @param    dit_key        The key of the DIT card
  @return   a table or NULL on error
  @see visir_spc_phot_sensit()
 */
/*----------------------------------------------------------------------------*/
cpl_table *
visir_spc_phot_sensit_from_image(cpl_image    ** combined,
                                 const irplib_framelist * rawframes,
                                 const visir_spc_config * pconfig,
                                 const char   * star_cat,
                                 const char   * spc_cal_lines,
                                 const char   * spc_cal_qeff,
                                 cpl_image   ** pweight2d,
                                 cpl_propertylist * qclist,
                                 cpl_boolean    do_ech,
                                 double         wlen,
                                 double         slitw,
                                 double         temp,
                                 double         fwhm,
                                 const visir_spc_resol resol,
                                 const char * dit_key)
{
    const cpl_propertylist * plist;
    const cpl_frame     *   frm = NULL;
    cpl_imagelist       *   hcycle = NULL;
    cpl_image           *   imhcycle = NULL;
    cpl_image           *   flipped = NULL;
    cpl_image           *   comorder = NULL;
    cpl_image           *   order = NULL;
    cpl_table           *   spc_table = NULL;
    visir_data_type         dtype;

    *pweight2d = NULL;

    skip_if (rawframes == NULL);

    /* Get the first frame */
    plist = irplib_framelist_get_propertylist_const(rawframes, 0);
    frm = irplib_framelist_get_const(rawframes, 0);

    skip_if (visir_get_data_type(frm, plist, &dtype, NULL));

    /* Get the HCYCLE frame */
    hcycle = visir_load_imagelist(rawframes, 0, CPL_FALSE);
    skip_if (0);

    skip_if (pconfig->do_fixcombi &&
             visir_spc_det_fix(combined, 1, CPL_TRUE,
                               wlen, resol,
                               pconfig->phi,
                               pconfig->ksi,
                               pconfig->eps,
                               pconfig->delta,
                               pconfig->plot));


    imhcycle = cpl_imagelist_unset(hcycle, 0);
    skip_if (0);

    skip_if (visir_spc_det_fix(&imhcycle, 1, CPL_FALSE,
                               wlen, resol,
                               pconfig->phi,
                               pconfig->ksi,
                               pconfig->eps,
                               pconfig->delta,
                               pconfig->plot));

    /* Convert the combined image */
    flipped = visir_spc_flip(*combined, wlen, resol, dtype);
    skip_if (0);
    cpl_image_delete(*combined);
    *combined = flipped;
    flipped = NULL;

    /* Convert the half cycle image */
    flipped = visir_spc_flip(imhcycle, wlen, resol, dtype);
    skip_if (0);
    cpl_image_delete(imhcycle);
    imhcycle = flipped;
    flipped = NULL;

    skip_if(visir_spc_extract_order(&order, &comorder,
                                    *combined, imhcycle,
                                    wlen, pconfig, do_ech,
                                    visir_data_is_aqu(dtype)));

    if (do_ech) {
        int icol1, icol2;
        skip_if (visir_spc_echelle_limit(&icol1, &icol2, wlen,
                                         pconfig->orderoffset,
                                         1, cpl_image_get_size_y(*combined),
                                         visir_data_is_aqu(dtype)));
        skip_if(visir_qc_append_background(qclist, rawframes, icol1, icol2));
    }
    else
        skip_if(visir_qc_append_background(qclist, rawframes, 0, 0));

    /* Extract the spectrum */
    skip_if (visir_spc_extract_wcal(comorder,
                                    order, wlen, slitw, temp, fwhm,
                                    resol, pconfig->orderoffset,
                                    spc_cal_lines, spc_cal_qeff,
                                    &spc_table, pweight2d,
                                    qclist,
                                    pconfig->plot, CPL_FALSE,
                                    visir_data_is_aqu(dtype)));

    visir_spc_phot_sensit(rawframes, pconfig, plist, star_cat, pweight2d,
                          qclist, spc_table, resol, dit_key);

    cpl_imagelist_delete(hcycle);
    hcycle = NULL;


    end_skip;

    cpl_imagelist_delete(hcycle);

    cpl_image_delete(flipped);
    cpl_image_delete(imhcycle);
    cpl_image_delete(order);
    cpl_image_delete(comorder);

    if (cpl_error_get_code()) {
        cpl_table_delete(spc_table);
        spc_table = NULL;
        cpl_image_delete(*pweight2d);
        *pweight2d = NULL;
    }

    return spc_table;
}

/**@}*/
 
/*----------------------------------------------------------------------------*/
/**
  @brief    Get the Jy value from the catalog 
  @param    star_cat    the star catalog
  @param    ra          the RA value in degrees
  @param    dec         the DEC value in degrees
  @return   The flux model in a cpl_bivector or NULL on error

  The catalog contains wavelengths in microns and model values in
  W/m2/m. To convert to mJy, they must be multiplied by
  3.33.10^8*lambda^2.
 */
/*----------------------------------------------------------------------------*/
static cpl_bivector * visir_spc_phot_model_from_cat(const char * star_cat,
                                                    double       ra,
                                                    double       dec)
{
    const double      max_radius = VISIR_STAR_MAX_RADIUS;
    const cpl_table * catalog  = NULL;  /* Flux model for all stars */ 
    int               nb_stars;
    const double    * dd;
    const cpl_array * da;
    cpl_vector      * v_ra = NULL;
    cpl_vector      * v_dec = NULL;
    int               min_dist_ind;
    cpl_bivector    * model   = NULL;
    cpl_vector      * model_x = NULL;
    cpl_vector      * model_y = NULL;
    const double      conv_mJy = 3.33E8; /* FIXME: 1e8/3 ? */
    double            star_dist;
    int               nb_vals;


    bug_if (0);  

    /* Open the star catalog */
    catalog = cpl_table_load(star_cat, 1, 0);
    if (catalog == NULL) {
        cpl_msg_error(cpl_func, "Could not open the star catalog: %s",
                     star_cat ? star_cat : "<NULL>");
        skip_if(1);
    }

    nb_stars = cpl_table_get_nrow(catalog);

    skip_if(nb_stars < 1);

    /* Get the RA and DEC columns */
    dd  = cpl_table_get_data_double_const(catalog, "RA");
    skip_if (dd == NULL);
    IRPLIB_DIAG_PRAGMA_PUSH_IGN(-Wcast-qual);
    v_ra = cpl_vector_wrap(nb_stars, (double*)dd); /* v_ra is _not_ modified */
    IRPLIB_DIAG_PRAGMA_POP;
    bug_if( v_ra  == NULL);

    dd  = cpl_table_get_data_double_const(catalog, "DEC");
    skip_if (dd == NULL);
    IRPLIB_DIAG_PRAGMA_PUSH_IGN(-Wcast-qual);
    v_dec = cpl_vector_wrap(nb_stars, (double*)dd); /* v_dec is _not_ modified */
    IRPLIB_DIAG_PRAGMA_POP;
    bug_if( v_dec  == NULL);

    /* Find the star closest to the given ra, dec */
    min_dist_ind = visir_star_find(v_ra, v_dec, ra, dec, max_radius, &star_dist);

    skip_if (min_dist_ind < 0);

    cpl_msg_info(cpl_func, "The standard star closest to (RA,DEC)=(%g,%g) is "
                 "no. %d, '%s' at (RA,DEC)=(%g,%g) with the distance [degree]: "
                 "%g", ra, dec, 1+min_dist_ind,
                 cpl_table_get_string(catalog, "STARS", min_dist_ind),
                 cpl_table_get_double(catalog, "RA",    min_dist_ind, NULL),
                 cpl_table_get_double(catalog, "DEC",   min_dist_ind, NULL),
                 star_dist);

    /* Get the wavelengths of the star model */
    da = cpl_table_get_array(catalog, "WAVELENGTHS", min_dist_ind);
    skip_if (da == NULL);
    dd = cpl_array_get_data_double_const(da);
    skip_if (dd == NULL);

    nb_vals = cpl_array_get_size(da);

    model_x = cpl_vector_new(nb_vals);
    memcpy(cpl_vector_get_data(model_x), dd, nb_vals * sizeof(double));

    /* Get the corresponding fluxes of the star model */
    da = cpl_table_get_array(catalog, "MODEL_FLUX", min_dist_ind);
    skip_if (da == NULL);
    dd = cpl_array_get_data_double_const(da);
    skip_if (dd == NULL);

    skip_if (nb_vals != cpl_array_get_size(da));

    model_y = cpl_vector_new(nb_vals);
    memcpy(cpl_vector_get_data(model_y), dd, nb_vals * sizeof(double));

    /* Convert from W/m2/m to mJy with * 3.33 * 10^8 * lamda * lamda  */
    bug_if (cpl_vector_multiply_scalar(model_y, conv_mJy));
    bug_if (cpl_vector_multiply(model_y, model_x));
    bug_if (cpl_vector_multiply(model_y, model_x));

    /* Convert from microns to meters */
    bug_if (cpl_vector_multiply_scalar(model_x, 1e-6));

    model = cpl_bivector_wrap_vectors(model_x, model_y);

    bug_if (model == NULL);

    end_skip;

    (void)cpl_vector_unwrap(v_ra);
    (void)cpl_vector_unwrap(v_dec);
    IRPLIB_DIAG_PRAGMA_PUSH_IGN(-Wcast-qual);
    cpl_table_delete((cpl_table*)catalog);
    IRPLIB_DIAG_PRAGMA_POP;

    if (cpl_error_get_code()) {
        /* At this point model is certain to be NULL */
        cpl_vector_delete(model_x);
        cpl_vector_delete(model_y);
    }   

    return model;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Rebin a spectrum
  @param    
  @return   int
 */
/*----------------------------------------------------------------------------*/
static int visir_spc_phot_model_rebin(
        const cpl_bivector * model,
        cpl_table          * spec_tab)
{
    cpl_vector      *   bounds = NULL;
    cpl_vector      *   spec = NULL;
    double              bin_pos;
    const int           nrow = cpl_table_get_nrow(spec_tab);
    int                 i;


    skip_if (0);

    /* Create bounds */
    bounds = cpl_vector_new(nrow + 1);
    for (i=1 ; i<cpl_vector_get_size(bounds) - 1 ; i++) {
        bin_pos = (cpl_table_get(spec_tab, "WLEN", i-1, NULL) + 
                cpl_table_get(spec_tab, "WLEN", i, NULL)) / 2.0;
        cpl_vector_set(bounds, i, bin_pos);
    }
    bin_pos = cpl_table_get(spec_tab, "WLEN", 0, NULL) - 
        ((cpl_table_get(spec_tab, "WLEN", 1, NULL) -
         cpl_table_get(spec_tab, "WLEN", 0, NULL)) / 2.0);
    cpl_vector_set(bounds, 0, bin_pos);
    bin_pos = 
        cpl_table_get(spec_tab, "WLEN", nrow-1, NULL) + 
        ((cpl_table_get(spec_tab,"WLEN", 1, NULL) -
         cpl_table_get(spec_tab, "WLEN", 0, NULL)) / 2.0);
    cpl_vector_set(bounds, cpl_vector_get_size(bounds)-1, bin_pos);
   
    /* Create the interpolated spectrum */
    spec = cpl_vector_new(nrow);

    /* Interpolate the spectrum */
    if (visir_vector_resample(spec, bounds, model)) {
        cpl_msg_error(cpl_func, "Cannot rebin the spectrum");
        skip_if(1);
    }

    /* Add the result in spec_tab */
    cpl_table_new_column(spec_tab, "STD_STAR_MODEL", CPL_TYPE_DOUBLE);
    cpl_table_set_column_unit(spec_tab, "STD_STAR_MODEL", "mJy");
    for (i=0 ; i<nrow ; i++) {
        cpl_table_set_double(spec_tab, "STD_STAR_MODEL", i, 
                cpl_vector_get(spec, i));
    }

    end_skip;

    cpl_vector_delete(spec);
    cpl_vector_delete(bounds);

    return cpl_error_get_code();
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Find index of first and last wavelengths to use for QC params
  @param    self   The Spectral Table
  @param    pstart The index of the first element to use, or unchanged on error
  @param    pend  The index of the last element to use, or unchanged on error
  @return   CPL_ERROR_NONE or the relevant #_cpl_error_code_
  @note     If the input spectrum has no values in the hardcoded interval the
            CPL_DATA_NOT_FOUND error is returned.
 */
/*----------------------------------------------------------------------------*/
static cpl_error_code visir_spc_wl_start_end(const cpl_table * self,
                                             cpl_size * pstart, cpl_size * pend)
{
    const cpl_size npix = cpl_table_get_nrow(self);
    cpl_vector * vwave = NULL;

    /* FIXME: Use cpl_table_select() */
    /* vwave will _not_ be modified */
    IRPLIB_DIAG_PRAGMA_PUSH_IGN(-Wcast-qual);
    vwave = cpl_vector_wrap(npix, (double*)
                            cpl_table_get_data_double_const(self, "WLEN"));
    IRPLIB_DIAG_PRAGMA_POP;

    assert(VISIR_WAVELEN_QC_MIN < VISIR_WAVELEN_QC_MAX);

    cpl_size l = visir_lower_bound(vwave, VISIR_WAVELEN_QC_MIN);
    cpl_size u = visir_upper_bound(vwave, VISIR_WAVELEN_QC_MAX);

    l = CPL_MAX(l, VISIR_WAVELEN_QC_IGNORE);
    u = CPL_MIN(u, npix - VISIR_WAVELEN_QC_IGNORE);

    if (u - l <= 0) {
        (void)cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
                                    "For QC the calibrated wavelength range "
                                    "(%g, %g) does not intersect with the "
                                    "required (%g, %g) [m]",
                                    cpl_vector_get(vwave, l),
                                    cpl_vector_get(vwave, u),
                                    VISIR_WAVELEN_QC_MIN, VISIR_WAVELEN_QC_MAX);
    } else {
        *pstart = l;
        *pend  = u;
    }

    cpl_vector_unwrap(vwave);

    return cpl_error_get_code();
}


/*----------------------------------------------------------------------------*/
/**
  @brief    Compute some statistics on the sensitivity
  @param    pconfig      The config struct
  @param    resol        Resolution
  @param    psens_median The median of the sensitivity (over the wavelenghts)
  @param    psens_mean   The mean of the sensitivity (over the wavelenghts)
  @param    psens_stdev  The stdev of the sensitivity (over the wavelenghts)
  @param    spc_table    The Spectral Table
  @return   CPL_ERROR_NONE or the relevant #_cpl_error_code_
 */
/*----------------------------------------------------------------------------*/
static cpl_error_code visir_spc_sens_stat(const visir_spc_config * pconfig,
                                          const visir_spc_resol resol,
                                          double * psens_median,
                                          double * psens_mean,
                                          double * psens_stdev,
                                          const cpl_table * spc_table)
{

    cpl_vector * sens_ignore = NULL;
    cpl_vector * sens_mini = NULL;
    double       emis_min, emis_max;
    cpl_size          npix_start = 0;
    cpl_size          npix_end = 0;
    cpl_size          nuse;
    int iok;

    skip_if(0);

    skip_if(pconfig  == NULL);

    if (resol == VISIR_SPC_R_LRP || resol == VISIR_SPC_R_LR) {
        skip_if(visir_spc_wl_start_end(spc_table, &npix_start, &npix_end));
    } else {
        npix_start = VISIR_WAVELEN_QC_IGNORE;
        npix_end = cpl_table_get_nrow(spc_table) - VISIR_WAVELEN_QC_IGNORE;
    }

    nuse = npix_end - npix_start;

    cpl_msg_info(cpl_func, "QC computed from %lld spectral values in the range "
                 "(%g, %g) [m]", (long long)nuse, VISIR_WAVELEN_QC_MIN,
                 VISIR_WAVELEN_QC_MAX);

    /* Compute mean and stdev of the sensitivity signal
       - using the selected range of elements */
    /* FIXME: Use cpl_table_select() */
    /* sens_ignore will _not_ be modified */
    IRPLIB_DIAG_PRAGMA_PUSH_IGN(-Wcast-qual);
    sens_ignore = cpl_vector_wrap( nuse, (double*)
                               cpl_table_get_data_double_const(spc_table,
                                                 "SENSITIVITY") + npix_start);
    IRPLIB_DIAG_PRAGMA_POP;
    skip_if(0);

    if (pconfig->plot)
        visir_vector_plot("set grid;","t 'Truncated Sensitivity (mJy)' w lines",
                          "", sens_ignore);

    sens_mini = cpl_vector_duplicate(sens_ignore);
    skip_if(0);

    emis_min = cpl_table_get_column_min(spc_table, "SPC_EMISSIVITY");
    emis_max = cpl_table_get_column_max(spc_table, "SPC_EMISSIVITY");
    skip_if(0);

    emis_max = emis_min + pconfig->phot_emis_tol * (emis_max - emis_min);

    iok = 0;
    for (cpl_size i=0; i < nuse; i++) {
      const double emis = cpl_table_get(spc_table, "SPC_EMISSIVITY",
                                        i + npix_start, NULL);

      skip_if(0);

      if (emis > emis_max) continue;

      if (i > iok)
          skip_if(cpl_vector_set(sens_mini, iok, cpl_vector_get(sens_mini, i)));

      iok++;

    }

    assert( iok > 0);

    skip_if(cpl_vector_set_size(sens_mini, iok));

    *psens_mean =  cpl_vector_get_mean(sens_mini);

    if (iok == 1) {
        cpl_msg_warning(cpl_func, "Sensitivity computed on only 1 wavelength "
                     "with emissivity %f", emis_max);
        *psens_stdev = 0;
    } else {
        cpl_msg_info(cpl_func, "Sensitivity computed on %d wavelengths with "
                     "emissivity at most %f", iok, emis_max);

        *psens_stdev = cpl_vector_get_stdev(sens_mini);

    }

    *psens_median =  cpl_vector_get_median(sens_mini);

    end_skip;

    cpl_vector_unwrap(sens_ignore);
    cpl_vector_delete(sens_mini);

    return cpl_error_get_code();

}


/*----------------------------------------------------------------------------*/
/**
  @brief    Append the spectro-phot QC parameters to the supplied list
  @param    self        The list of properties to be extended
  @param    star_name   The name of the star, or NULL
  @param    exptime     The exposure time
  @param    exectime    The execution time
  @param    sens_median The median of the sensitivity (over the wavelenghts)
  @param    sens_mean   The mean of the sensitivity (over the wavelenghts)
  @param    sens_stdev  The stdev of the sensitivity (over the wavelenghts)
  @return   CPL_ERROR_NONE, or the relevant CPL_ERROR.

 */
/*----------------------------------------------------------------------------*/
static cpl_error_code visir_spc_phot_qc(cpl_propertylist * self,
                                        const char * star_name,
                                        double exptime,
                                        double exectime,
                                        double sens_median,
                                        double sens_mean,
                                        double sens_stdev)
{

    bug_if (cpl_propertylist_append_double(self, "ESO QC EXPTIME", exptime));
    if (exectime > 0) {
        bug_if (cpl_propertylist_append_double(self, "ESO QC EXECTIME",
                                               exectime));
    }
    bug_if (cpl_propertylist_append_double(self, "ESO QC SENS MEDIAN",
                                           sens_median));
    bug_if (cpl_propertylist_append_double(self, "ESO QC SENS MEAN", sens_mean));
    bug_if (cpl_propertylist_append_double(self, "ESO QC SENS STDEV",
                                           sens_stdev));
    /* FIXME: Verify that this is the correct behaviour */
    bug_if (cpl_propertylist_append_string(self, "ESO QC STARNAME",
                                           star_name ? star_name : ""));

    end_skip;

    return cpl_error_get_code();

}
