/* $Id: visir_spc_photom.c,v 1.54 2010/09/30 06:48:35 llundin 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: llundin $
 * $Date: 2010/09/30 06:48:35 $
 * $Revision: 1.54 $
 * $Name: visir-3_5_1 $
 */

#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_phot_config *,
                                          double *, double *,
                                          const cpl_table *);

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

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

/**@{*/

/*----------------------------------------------------------------------------*/
/**
  @brief    Compute the sensitivity
  @param    combined       The combined image 
  @param    rawframes      List of input raw frames
  @param    recipename     The name of the caller recipe
  @param    parlist        The recipe parameter list
  @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
  @return   a table or NULL in error case
 */
/*----------------------------------------------------------------------------*/
cpl_table * visir_spc_phot_sensit(
        cpl_image    ** combined,
        const irplib_framelist * rawframes,
        const char   * recipename,
        const cpl_parameterlist * parlist,
        const visir_spc_phot_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)
{
    cpl_errorstate cleanstate = cpl_errorstate_get();
    const cpl_propertylist * plist;
    double                  exptime;
    double                  dit, ndit;
    int                     ncycles, nnod;
    int                     icol1, icol2;
    int                     jcol1, jcol2;
    double                  ra, dec;
    cpl_imagelist       *   hcycle = NULL;
    cpl_image           *   imhcycle = NULL;
    cpl_image           *   comorder = NULL;
    cpl_image           *   order = NULL;
    const char          *   star_name;
    cpl_bivector        *   flux_model = NULL;
    cpl_table           *   spc_table = NULL;
    double                  factor;
    double              *   ext_array;
    double              *   spc_array;
    double              *   err_array;
    double                  sens_mean = -1.0; /* Avoid (false) uninit warning */
    double                  sens_stdev = -1.0; /* Ditto */
    int                     i;
    

    *pweight2d = NULL;

    skip_if (0);

    skip_if (rawframes == NULL);

    jcol1 = visir_parameterlist_get_int(parlist, recipename,
                                        VISIR_PARAM_REJLEFT);
    jcol2 = visir_parameterlist_get_int(parlist, recipename,
                                        VISIR_PARAM_REJRIGHT);

    skip_if (0);

    /* DIT must be present every where */  
    skip_if(irplib_framelist_contains(rawframes, VISIR_PFITS_DOUBLE_DIT,
                                      CPL_TYPE_DOUBLE, CPL_FALSE, 0.0));
    if(irplib_framelist_contains(rawframes, VISIR_PFITS_DOUBLE_DIT,
                                 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");
    }


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

    skip_if (0);

    /* Get the total exposure time */
    /* DIT */
    dit = visir_pfits_get_dit(plist);
    /* NDIT */
    ndit = visir_pfits_get_ndit(plist);
    /* NNOD */
    nnod = irplib_framelist_get_size(rawframes);
    /* Number of chopping cycles */
    ncycles = visir_pfits_get_chop_ncycles(plist);
    /* exptime *= 2 because of chopping */
    exptime = 2 * dit * ndit * nnod * ncycles;

    skip_if (0);

    /* 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 RA / DEC */
    ra  = visir_pfits_get_ra(plist);
    dec = visir_pfits_get_dec(plist);
    
    skip_if (0);

    /* 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_get(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));

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

    } else {
        icol1 = 1;
        icol2 = cpl_image_get_size_x(imhcycle);

        skip_if (visir_qc_append_background(qclist, rawframes, 0, 0));
    }

    if (do_ech) {
        if (jcol1 != 0) {
            cpl_msg_info(cpl_func, "Ignoring %d leftmost columns from %d to %d",
                         jcol1, icol1, icol1 + jcol1);
            icol1 += jcol1;
        }
        if (jcol2 != 0) {
            cpl_msg_info(cpl_func, "Ignoring %d rightmost columns from %d to %d",
                         jcol2, icol2 - jcol2, icol2);
            icol2 -= jcol2;
        }
    } else {
        if (jcol1 != 0) {
            cpl_msg_info(cpl_func, "Ignoring %d leftmost columns", jcol1);
            icol1 += jcol1;
        }
        if (jcol2 != 0) {
            cpl_msg_info(cpl_func, "Ignoring %d rightmost columns", jcol2);
            icol2 -= jcol2;
        }
    }

    if (icol1 != 1 || icol2 != cpl_image_get_size_x(imhcycle)) {
        order = visir_spc_column_extract(imhcycle, icol1, icol2,
                                          pconfig->plot);
        skip_if (0);

        comorder = visir_spc_column_extract(*combined, icol1, icol2,
                                             pconfig->plot);
        skip_if (0);

    } else {
        order = imhcycle;
        comorder = *combined;
    }

    /* 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_imagelist_delete(hcycle);
    hcycle = NULL;

    /* 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");

    factor = sqrt(exptime)/6.0; /* sqrt(exptime/3600) * 10; */
    for (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", 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, &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, 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_imagelist_delete(hcycle);
    cpl_bivector_delete(flux_model);

    if (order != imhcycle) cpl_image_delete(order);
    if (comorder != *combined) 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 in error case

  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);
    v_ra = cpl_vector_wrap(nb_stars, (double*)dd); /* v_ra is _not_ modified */
    bug_if( v_ra  == NULL);

    dd  = cpl_table_get_data_double_const(catalog, "DEC");
    skip_if (dd == NULL);
    v_dec = cpl_vector_wrap(nb_stars, (double*)dd); /* v_dec is _not_ modified */
    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);
    cpl_table_delete((cpl_table*)catalog);

    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    Compute some statistics on the sensitivity
  @param    pconfig     The config struct
  @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_phot_config * pconfig,
                                          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;
    const int    npix = cpl_table_get_nrow(spc_table);
    const int    npix_ignore = 3;
    int i;
    int iok;

    skip_if(0);

    skip_if(pconfig  == NULL);

    /* Compute mean and stdev of the sensitivity signal
       - ignore first and last npix_ignore elements */
    /* FIXME: Use cpl_table_select() */
    /* sens_ignore will _not_ be modified */
    sens_ignore = cpl_vector_wrap( npix - 2*npix_ignore, (double*)
                               cpl_table_get_data_double_const(spc_table,
                                                 "SENSITIVITY") + npix_ignore);
    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->emis_tol * (emis_max - emis_min);

    iok = 0;
    for (i=0; i < npix - 2*npix_ignore; i++) {
      const double emis = cpl_table_get(spc_table, "SPC_EMISSIVITY",
                                        i + npix_ignore, 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);

    }


    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    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 sens_mean,
                                        double sens_stdev)
{

    bug_if (cpl_propertylist_append_double(self, "ESO QC EXPTIME", exptime));
    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();

}
