/*
 * This file is part of the KMOS 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

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

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

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

#include <cpl.h>

#include "kmclipm_priv_splines.h"
#include "kmclipm_functions.h"
#include "kmclipm_constants.h"

#include "kmo_utils.h"
#include "kmos_pfits.h"
#include "kmo_dfs.h"

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

static cpl_vector * kmos_gen_telluric_resample(
        cpl_vector          *   vec,
        int                     b_samples,
        double                  crval1,
        double                  cdelt1,
        double              *   new_crval1,
        double              *   new_cdelt1) ;
static int kmos_gen_telluric_get_avg_zpoint(
        const cpl_frame     *   zp_frame, 
        double              *   zp1_avg,
        double              *   zp2_avg,
        double              *   zp3_avg) ;
static int kmos_gen_telluric_create_file_multiply(
        const cpl_frame         *   data1_frame,
        const cpl_frame         *   data2_frame,
        const cpl_frame         *   zp_frame,
        const char              *   filename,
        cpl_frameset            *   allframes,
		int							b_samples,
        const cpl_parameterlist *   parlist) ;
static int kmos_gen_telluric_create_file_simple(
        const cpl_frame         *   data_frame,
        const cpl_frame         *   zp_frame,
        const cpl_frame         *   resp_frame,
        const char              *   filename,
        cpl_frameset            *   allframes,
        int                         b_samples,
        int                         fill_empty_ifus,
        const cpl_parameterlist *   parlist) ;

static int kmos_gen_telluric_check_inputs(cpl_frameset *, int);

static int kmos_gen_telluric_create(cpl_plugin *);
static int kmos_gen_telluric_exec(cpl_plugin *);
static int kmos_gen_telluric_destroy(cpl_plugin *);
static int kmos_gen_telluric(cpl_parameterlist *, cpl_frameset *);

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

static char kmos_gen_telluric_description[] =
"This recipe creates the TELLURIC frame needed by kmos_sci_red by merging\n"
"the TELLURIC (produced by kmos_std_star), the static RESPONЅE frame and \n"
"the TELLURIC_CORR (produced with molecfit).\n"
"The way the frames are combined is controlled by the --method parameter:\n" 
"   - 0 (default) : use TELLURIC and get zpoint from TELLURIC\n"
"           If TELLURIC missing, use RESPONSE and get zpoint from RESPONSE\n"
"   - 1 : use TELLURIC_CORR and get zpoint from TELLURIC_CORR\n"
"   - 2 : use RESPONSE and get zpoint from TELLURIC\n"
"           If TELLURIC missing, get zpoint from RESPONSE\n"
"           For missing ZPOINTs, use the average of other IFUs for the \n"
"           same detector. \n"
"   - 3 : use RESPONSE x TELLURIC_CORR and get zpoint from TELLURIC_CORR\n"
"\n"
"----------------------------------------------------------------------------\n"
"Input files:\n"
"\n"
"   DO category         Explanation                    Required \n"
"   -----------         -----------                    -------- \n"
"   TELLURIC            Produced  by kmos_std_star          N\n"
"   TELLURIC_CORR       Produced  by molecfit               N\n"
"   RESPONSE            static calibration                  N\n"
"\n"
"Output files:\n"
"\n"
"   DO category         Explanation\n"
"   -----------         -----------\n"
"   TELLURIC_GEN        Used by kmos_sci_red\n"
"----------------------------------------------------------------------------\n"
"\n";

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

/**
 * @defgroup kmos_gen_telluric  Generate a TELLURIC frame
 */

/**@{*/

/*----------------------------------------------------------------------------*/
/**
  @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,
            KMOS_BINARY_VERSION,
            CPL_PLUGIN_TYPE_RECIPE,
            "kmos_gen_telluric",
            "Generate a TELLURIC frame",
            kmos_gen_telluric_description,
            "Yves Jung",
            "usd-help@eso.org",
            kmos_get_license(),
            kmos_gen_telluric_create,
            kmos_gen_telluric_exec,
            kmos_gen_telluric_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 kmos_gen_telluric_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();

    // Fill the parameters list
    /* --method */
    p = cpl_parameter_new_value("kmos.kmos_gen_telluric.method",
            CPL_TYPE_INT, "How the TELLURIC is generated",
            "kmos.kmos_gen_telluric", 0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "method");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --b_samples */
    p = cpl_parameter_new_value("kmos.kmos_gen_telluric.b_samples",
            CPL_TYPE_INT, "The number of samples in wavelength",
            "kmos.kmos_gen_telluric", -1);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "b_samples");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --fill_empty_ifus */
    p = cpl_parameter_new_value("kmos.kmos_gen_telluric.fill_empty_ifus",
            CPL_TYPE_BOOL, "Flag to fill empty TELLURIC IFUs (only method 0)",
            "kmos.kmos_gen_telluric", FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "fill_empty_ifus");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    return 0;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Execute the plugin instance given by the interface
  @param    plugin  the plugin
  @return   0 if everything is ok
 */
/*----------------------------------------------------------------------------*/
static int kmos_gen_telluric_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;

    return kmos_gen_telluric(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 kmos_gen_telluric_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 kmos_gen_telluric(cpl_parameterlist *parlist, cpl_frameset *frameset)
{
    const cpl_parameter     *   par ;
    int                         method, fill_empty_ifus, b_samples ;
    cpl_frame               *   tell_frame ;
    cpl_frame               *   tell_corr_frame ;
    cpl_frame               *   resp_frame ;
    const char              *   fn ;
    
    /* Check initial Entries */
    if (kmos_check_and_set_groups(frameset) != CPL_ERROR_NONE) {
        return cpl_error_get_code();
    }

    /* Initialise */
    fn = "kmos_gen_telluric.fits" ;
    
    /* Get Parameters */
    par = cpl_parameterlist_find_const(parlist,"kmos.kmos_gen_telluric.method");
    method = cpl_parameter_get_int(par);
    par = cpl_parameterlist_find_const(parlist,
            "kmos.kmos_gen_telluric.b_samples");
    b_samples = cpl_parameter_get_int(par);
    par = cpl_parameterlist_find_const(parlist,
            "kmos.kmos_gen_telluric.fill_empty_ifus");
    fill_empty_ifus = cpl_parameter_get_bool(par);

    /* Check Parameters */
    if (method != 0 && fill_empty_ifus != 0) {
        fill_empty_ifus = 0 ;
        cpl_msg_warning(__func__, 
                "Filling empty IFUs is only possible in method 0 - disable it");
    }

    /* Check the inputs consistency */
    if (kmos_gen_telluric_check_inputs(frameset, method) != 1) {
        cpl_msg_error(__func__, "Input frameset / parameter is unconsistent") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }

    /* Get frames */
    tell_frame = cpl_frameset_find(frameset, TELLURIC) ;
    resp_frame = cpl_frameset_find(frameset, RESPONSE) ;
    tell_corr_frame = cpl_frameset_find(frameset, TELLURIC_CORR) ;

    /* Switch on the methods */
    if (method == 0) {
        if (tell_frame != NULL) {
            cpl_msg_info(__func__, "Use %s for Data and Zpoint", 
                    cpl_frame_get_filename(tell_frame)) ;
            kmos_gen_telluric_create_file_simple(tell_frame, NULL, resp_frame, 
                    fn, frameset, b_samples, fill_empty_ifus, parlist) ;
        } else {
            cpl_msg_info(__func__, "Use %s for Data and Zpoint", 
                    cpl_frame_get_filename(resp_frame)) ;
            kmos_gen_telluric_create_file_simple(resp_frame, NULL, NULL, 
                    fn, frameset, b_samples, 0, parlist) ;
        }
    } else if (method == 1) {
        cpl_msg_info(__func__, "Use %s for Data and Zpoint", 
                cpl_frame_get_filename(tell_corr_frame)) ;
            kmos_gen_telluric_create_file_simple(tell_corr_frame, NULL, NULL, 
                    fn, frameset, b_samples, 0, parlist) ;
    } else if (method == 2) {
        if (tell_frame != NULL) {
            cpl_msg_info(__func__, "Use %s for Data and %s for Zpoint",
                    cpl_frame_get_filename(resp_frame),
                    cpl_frame_get_filename(tell_frame)) ;
            kmos_gen_telluric_create_file_simple(resp_frame, tell_frame, NULL, 
                    fn, frameset, b_samples, 0, parlist) ;
        } else {
            cpl_msg_info(__func__, "Use %s for Data and Zpoint", 
                    cpl_frame_get_filename(resp_frame)) ;
            kmos_gen_telluric_create_file_simple(resp_frame, NULL, NULL, 
                    fn, frameset, b_samples, 0, parlist) ;
        }
    } else if (method == 3) {
        cpl_msg_info(__func__, "Use %s X %s for Data and %s for Zpoint", 
                cpl_frame_get_filename(resp_frame),
                cpl_frame_get_filename(tell_corr_frame),
                cpl_frame_get_filename(tell_corr_frame)) ;
        kmos_gen_telluric_create_file_multiply(resp_frame, tell_corr_frame, 
                tell_corr_frame, fn, frameset, b_samples, parlist) ;
    } else {
        cpl_msg_error(__func__, "Unsupported - Should never come here") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }
    return 0;
}

/**@}*/

static int kmos_gen_telluric_create_file_multiply(
        const cpl_frame         *   data1_frame,
        const cpl_frame         *   data2_frame,
        const cpl_frame         *   zp_frame,
        const char              *   filename,
        cpl_frameset            *   allframes,
		int							b_samples,
        const cpl_parameterlist *   parlist)
{
    cpl_propertylist        *   mh ;
    cpl_propertylist        *   applist ;
    cpl_propertylist        *   eh ;
    cpl_propertylist        *   zph ;
    cpl_vector              *   vec1 ;
    cpl_vector              *   vec2 ;
    cpl_vector              *   vec_resampled ;
    double                      zpoint, crval1, cdelt1, new_crval1, new_cdelt1 ;
    enum kmo_frame_type         ft ;
    char                        content[256] ;
    const char              *   extname ;
    char                	*   keyword ;
    int                         i, ext_nb, id, ifu_nr ;
    
    /* Initialise */
    id = -1 ;
    ft = illegal_frame ; 

    /* Get Extensions number */
    ext_nb = cpl_fits_count_extensions(cpl_frame_get_filename(data1_frame)) ;

    if (cpl_fits_count_extensions(cpl_frame_get_filename(data2_frame))!=ext_nb){
        cpl_msg_error(__func__, "Extensions numbers do not match - abort") ;
        return -1 ;
    }

    /* Load main header */
    mh = cpl_propertylist_load(cpl_frame_get_filename(data1_frame), 0);

    /* Create applist with PRO CATG  */
    applist = cpl_propertylist_new() ;
    cpl_propertylist_update_string(applist, CPL_DFS_PRO_CATG, TELLURIC_GEN) ;

    /* Create PRO_STD keywords */
    for (i = 0; i < ext_nb ; i++) {
        /* Load extension header */
        eh = cpl_propertylist_load(cpl_frame_get_filename(data2_frame),i+1);
        /* Read EXTNAME */
        extname = cpl_propertylist_get_string(eh, "EXTNAME") ;
        kmo_extname_extractor(extname, &ft, &ifu_nr, content) ;
        cpl_propertylist_delete(eh); 
        /* Load the data */
        vec1 = cpl_vector_load(cpl_frame_get_filename(data2_frame), i+1) ;
        if (vec1 != NULL) {
            cpl_vector_delete(vec1) ;

            /* The Telluric is present in this IFU - write the keyword */
            keyword = cpl_sprintf("%s%d", PRO_STD, ifu_nr);
            cpl_propertylist_update_int(applist, keyword, 1);
            cpl_free(keyword);
        } else {
            cpl_error_reset() ;
        }
    }

    /* Save product */
    cpl_dfs_save_propertylist(allframes, mh, parlist, allframes, NULL,
            "kmos_gen_telluric", applist, NULL, PACKAGE "/" PACKAGE_VERSION, 
            filename) ;

    cpl_propertylist_delete(mh); 
    cpl_propertylist_delete(applist); 

    /* Loop on the extensions */
    for (i = 0; i < ext_nb ; i++) {

        /* Load extension header */
        eh = cpl_propertylist_load(cpl_frame_get_filename(data1_frame),i+1);

        /* Read EXTNAME */
        extname = cpl_propertylist_get_string(eh, "EXTNAME") ;
        kmo_extname_extractor(extname, &ft, &id, content) ;

        /* Load the data */
        vec1 = cpl_vector_load(cpl_frame_get_filename(data1_frame), i+1) ;
        vec2 = cpl_vector_load(cpl_frame_get_filename(data2_frame), i+1) ;
        if (vec1 == NULL || vec2 == NULL) cpl_error_reset() ;

        /* Resample vec1 if needed */
        if (vec1 != NULL &&
                cpl_vector_get_size(vec1) != b_samples && b_samples > 0) {

            /* Get crval1, cdelt1 */
            cdelt1 = kmos_pfits_get_cdelt1(eh) ;
            crval1 = kmos_pfits_get_crval1(eh) ;

            /* Recreate vec */
            vec_resampled = kmos_gen_telluric_resample(vec1, b_samples, crval1,
                    cdelt1, &new_crval1, &new_cdelt1) ;
            if (vec_resampled != NULL) {
                cpl_vector_delete(vec1) ;
                vec1 = vec_resampled ;

                /* Update CRVAL1 / CDELT1 in eh */
                cpl_propertylist_update_double(eh, "CRVAL1", new_crval1) ;
                cpl_propertylist_update_double(eh, "CDELT1", new_cdelt1) ;
            }
        }

		/* Resample vec2 if needed */
        if (vec1 != NULL && vec2 != NULL && 
                cpl_vector_get_size(vec1) != cpl_vector_get_size(vec2)) {

            /* Get crval1, cdelt1 */
            cdelt1 = kmos_pfits_get_cdelt1(eh) ;
            crval1 = kmos_pfits_get_crval1(eh) ;

            /* Recreate vec */
            vec_resampled = kmos_gen_telluric_resample(vec2, b_samples, crval1,
                    cdelt1, &new_crval1, &new_cdelt1) ;
            if (vec_resampled != NULL) {
                cpl_vector_delete(vec2) ;
                vec2 = vec_resampled ;
            }
        }

        /* Need to update ZPOINT ? */
        if (vec1 != NULL && vec2 != NULL && zp_frame != NULL) {
            zph=cpl_propertylist_load(cpl_frame_get_filename(zp_frame),i+1);
            zpoint = cpl_propertylist_get_double(zph, "ESO QC ZPOINT");
            cpl_propertylist_delete(zph); 
            if (cpl_error_get_code() != CPL_ERROR_NONE) {
                cpl_msg_warning(__func__, "No QC ZPOINT found in %s",
                        cpl_frame_get_filename(zp_frame)) ;
                cpl_error_reset() ;
            } else {
                cpl_propertylist_update_double(eh, "ESO QC ZPOINT", zpoint) ;
            }
        }

        /* Multiply the 2 vectors if Data */
        if (!strcmp(content, "DATA")) cpl_vector_multiply(vec1, vec2) ;
        cpl_vector_delete(vec2) ;

        /* Save the Extension */
        if (cpl_error_get_code() == CPL_ERROR_NONE) {
            /* Save the data */
            cpl_vector_save(vec1, filename, CPL_TYPE_DOUBLE, eh,CPL_IO_EXTEND) ;
        } else {
            /* Only the header */
            cpl_error_reset() ;
            cpl_propertylist_save(eh, filename, CPL_IO_EXTEND) ;
        }
        cpl_vector_delete(vec1) ;
        cpl_propertylist_delete(eh); 
    }
    return 0 ;
}

static int kmos_gen_telluric_create_file_simple(
        const cpl_frame         *   data_frame,
        const cpl_frame         *   zp_frame,
        const cpl_frame         *   resp_frame,
        const char              *   filename,
        cpl_frameset            *   allframes,
        int                         b_samples,
        int                         fill_empty_ifus,
        const cpl_parameterlist *   parlist)
{
    cpl_propertylist        *   mh ;
    cpl_propertylist        *   applist ;
    cpl_propertylist        *   eh ;
    cpl_propertylist        *   tmp_eh ;
    cpl_propertylist        *   zph ;
    cpl_vector              *   vec ;
    cpl_vector              *   vec_ref ;
    cpl_vector              *   resp_ref ;
    cpl_vector              *   resp_cur ;
    cpl_vector              *   vec_resampled ;
    double                      zpoint, zpoint_vec_ref, zpoint_resp_ref,
                                zpoint_resp_cur, zp1_avg, zp2_avg, zp3_avg,
                                cdelt1, crval1, new_crval1, new_cdelt1;
    int                         i, ext_nb, id, det_nr, ifu_nr, first_data_det1,
                                first_data_det2, first_data_det3, 
                                first_noise_det1, first_noise_det2, 
                                first_noise_det3, ref_ifu ;
    enum kmo_frame_type         ft ;
    char                        content[256] ;
    char                	*   keyword ;
    const char              *   extname ;
    
    /* Initialise */
    zp1_avg = zp2_avg = zp3_avg = -1.0 ;
    id = -1 ;
    ft = illegal_frame ; 
    first_data_det1 = first_data_det2 = first_data_det3 = -1 ;
    first_noise_det1 = first_noise_det2 = first_noise_det3 = -1 ;

    /* Get Extensions number */
    ext_nb = cpl_fits_count_extensions(cpl_frame_get_filename(data_frame)) ;

    /* Load main header */
    mh = cpl_propertylist_load(cpl_frame_get_filename(data_frame), 0);

    /* Create applist with PRO CATG  */
    applist = cpl_propertylist_new() ;
    cpl_propertylist_update_string(applist, CPL_DFS_PRO_CATG, TELLURIC_GEN) ;

    /* Create PRO_STD keywords */
    for (i = 0; i < ext_nb ; i++) {
        /* Load extension header */
        eh = cpl_propertylist_load(cpl_frame_get_filename(data_frame), i+1);
        /* Read EXTNAME */
        extname = cpl_propertylist_get_string(eh, "EXTNAME") ;

        kmo_extname_extractor(extname, &ft, &ifu_nr, content) ;
        cpl_propertylist_delete(eh); 

        /* Get Detector */
        if (ifu_nr >= 1 && ifu_nr <= 8)         det_nr = 1 ;
        else if (ifu_nr >= 9 && ifu_nr <= 16)   det_nr = 2 ;
        else if (ifu_nr >= 17 && ifu_nr <= 24)  det_nr = 3 ;
        else {
            cpl_msg_error(__func__, "Cannot Identify the detector") ;
            cpl_propertylist_delete(mh);
            cpl_propertylist_delete(applist);
            return -1 ;
        }

        /* Try to load the data */
        vec = cpl_vector_load(cpl_frame_get_filename(data_frame), i+1) ;
        if (vec == NULL) cpl_error_reset() ;

        /* Store the first non-null extensions per detector */
        if (vec != NULL) {
            if (!strcmp(content, "DATA")) {
                if (det_nr == 1 && first_data_det1 < 0) first_data_det1 = i+1 ;
                if (det_nr == 2 && first_data_det2 < 0) first_data_det2 = i+1 ;
                if (det_nr == 3 && first_data_det3 < 0) first_data_det3 = i+1 ;
            } else if (!strcmp(content, "NOISE")) {
                if (det_nr == 1 && first_noise_det1 < 0) first_noise_det1 = i+1;
                if (det_nr == 2 && first_noise_det2 < 0) first_noise_det2 = i+1;
                if (det_nr == 3 && first_noise_det3 < 0) first_noise_det3 = i+1;
            }
        }

        if (vec != NULL || fill_empty_ifus == 1) {
            if (vec != NULL) cpl_vector_delete(vec) ;

            /* The Telluric is present in this IFU - write the keyword */
            keyword = cpl_sprintf("%s%d", PRO_STD, ifu_nr);
            cpl_propertylist_update_int(applist, keyword, 1);
            cpl_free(keyword);
        } else {
            cpl_error_reset() ;
        }
    }

    /* Save product */
    cpl_dfs_save_propertylist(allframes, mh, parlist, allframes, NULL,
            "kmos_gen_telluric", applist, NULL, PACKAGE "/" PACKAGE_VERSION, 
            filename) ;

    cpl_propertylist_delete(mh); 
    cpl_propertylist_delete(applist); 

    /* Get ZPOINT averages per detector from the Zp_frame */
    if (zp_frame != NULL) {
         kmos_gen_telluric_get_avg_zpoint(zp_frame,&zp1_avg,&zp2_avg,&zp3_avg) ;
    }

    /* Loop on the extensions */
    for (i = 0; i < ext_nb ; i++) {

        /* Load extension header */
        eh = cpl_propertylist_load(cpl_frame_get_filename(data_frame), i+1);

        /* Read EXTNAME */
        extname = cpl_propertylist_get_string(eh, "EXTNAME") ;
        kmo_extname_extractor(extname, &ft, &id, content) ;

        if (id >= 1 && id <= 8)         det_nr = 1 ;
        else if (id >= 9 && id <= 16)   det_nr = 2 ;
        else if (id >= 17 && id <= 24)  det_nr = 3 ;
        else {
            cpl_msg_error(__func__, "Cannot Identify the detector") ;
            cpl_propertylist_delete(eh); 
            return -1 ;
        }

        /* Load the data */
        vec = cpl_vector_load(cpl_frame_get_filename(data_frame), i+1) ;
        if (vec == NULL) cpl_error_reset() ;

        /* Recover vec if requested */
        if (vec == NULL && fill_empty_ifus == 1) {

            /* Initialise */
            vec_ref = NULL ;
            ref_ifu = -1 ;
            if (!strcmp(content, "NOISE")) {
                /* Get ref_ifu */
                if (det_nr==1)  ref_ifu = first_noise_det1 ;
                if (det_nr==2)  ref_ifu = first_noise_det2 ;
                if (det_nr==3)  ref_ifu = first_noise_det3 ;
 
                /* Load noise reference */
                vec_ref=cpl_vector_load(cpl_frame_get_filename(data_frame),
                        ref_ifu);

            } else if (!strcmp(content, "DATA")) {

                /* Get ref_ifu */
                if (det_nr==1)  ref_ifu = first_data_det1 ;
                if (det_nr==2)  ref_ifu = first_data_det2 ;
                if (det_nr==3)  ref_ifu = first_data_det3 ;

                /* Load data reference */
                vec_ref=cpl_vector_load(cpl_frame_get_filename(data_frame),
                        ref_ifu);

                resp_ref = cpl_vector_load(cpl_frame_get_filename(resp_frame), 
                        ref_ifu) ;
                resp_cur = cpl_vector_load(cpl_frame_get_filename(resp_frame), 
                        i+1) ;
                if (resp_ref != NULL && resp_cur != NULL) {
                    /* Resample resp_ref if needed */
                    if (cpl_vector_get_size(vec_ref) !=
                            cpl_vector_get_size(resp_ref)) {

                        /* Get crval1, cdelt1 from the resp_ref_ifu */
                        tmp_eh = cpl_propertylist_load(
                                cpl_frame_get_filename(resp_frame), ref_ifu);
                        cdelt1 = kmos_pfits_get_cdelt1(tmp_eh) ;
                        crval1 = kmos_pfits_get_crval1(tmp_eh) ;
                        cpl_propertylist_delete(tmp_eh) ;

                        /* Resample */
                        vec_resampled = kmos_gen_telluric_resample(resp_ref, 
                                cpl_vector_get_size(vec_ref), crval1, cdelt1, 
                                &new_crval1, &new_cdelt1) ;
                        if (vec_resampled != NULL) {
                            cpl_vector_delete(resp_ref) ;
                            resp_ref = vec_resampled ;
                        }
                    }

                    /* Resample resp_cur if needed */
                    if (cpl_vector_get_size(vec_ref) !=
                            cpl_vector_get_size(resp_cur)) {

                        /* Get crval1, cdelt1 from the resp_cur_ifu */
                        tmp_eh = cpl_propertylist_load(
                                cpl_frame_get_filename(resp_frame), i+1);
                        cdelt1 = kmos_pfits_get_cdelt1(tmp_eh) ;
                        crval1 = kmos_pfits_get_crval1(tmp_eh) ;
                        cpl_propertylist_delete(tmp_eh) ;

                        /* Resample */
                        vec_resampled = kmos_gen_telluric_resample(resp_cur, 
                                cpl_vector_get_size(vec_ref), crval1, cdelt1, 
                                &new_crval1, &new_cdelt1) ;
                        if (vec_resampled != NULL) {
                            cpl_vector_delete(resp_ref) ;
                            resp_ref = vec_resampled ;
                        }
                    }
                    cpl_vector_divide(vec_ref, resp_ref);
                    cpl_vector_multiply(vec_ref, resp_cur);
                }
                if (resp_ref != NULL) cpl_vector_delete(resp_ref) ;
                if (resp_cur != NULL) cpl_vector_delete(resp_cur) ;
            }
            vec = vec_ref ;

            /* Get crval1, cdelt1 from the ref_ifu */
            tmp_eh = cpl_propertylist_load(
                    cpl_frame_get_filename(data_frame), ref_ifu);

            cpl_propertylist_copy_property(eh, tmp_eh, "CTYPE1") ;
            cpl_propertylist_copy_property(eh, tmp_eh, "CRPIX1") ;
            cpl_propertylist_copy_property(eh, tmp_eh, "CRVAL1") ;
            cpl_propertylist_copy_property(eh, tmp_eh, "CDELT1") ;
            cpl_propertylist_delete(tmp_eh) ;
            cpl_propertylist_erase(eh, "CRPIX2") ;
            cpl_propertylist_erase(eh, "CTYPE2") ;
            cpl_propertylist_erase(eh, "CD1_1") ;
            cpl_propertylist_erase(eh, "CD1_2") ;
            cpl_propertylist_erase(eh, "CD2_1") ;
            cpl_propertylist_erase(eh, "CD2_2") ;

            /* Compute ZPOINT */
            tmp_eh = cpl_propertylist_load(
                    cpl_frame_get_filename(data_frame), ref_ifu);
            zpoint_vec_ref = cpl_propertylist_get_double(tmp_eh,
                    "ESO QC ZPOINT");
            cpl_propertylist_delete(tmp_eh) ;
            tmp_eh = cpl_propertylist_load(
                    cpl_frame_get_filename(resp_frame), ref_ifu);
            zpoint_resp_ref = cpl_propertylist_get_double(tmp_eh,
                    "ESO QC ZPOINT");
            cpl_propertylist_delete(tmp_eh) ;
            tmp_eh = cpl_propertylist_load(
                    cpl_frame_get_filename(resp_frame), i+1);
            zpoint_resp_cur = cpl_propertylist_get_double(tmp_eh,
                    "ESO QC ZPOINT");
            cpl_propertylist_delete(tmp_eh) ;
            /* Compute new ZPOINT */
            zpoint = zpoint_vec_ref - zpoint_resp_ref + zpoint_resp_cur ;
            cpl_propertylist_update_double(eh, "ESO QC ZPOINT", zpoint) ;
        }

        /* Need to update ZPOINT ? */
        if (vec != NULL && zp_frame != NULL) {
            zph = cpl_propertylist_load(cpl_frame_get_filename(zp_frame),i+1);
            zpoint = cpl_propertylist_get_double(zph, "ESO QC ZPOINT");
            cpl_propertylist_delete(zph); 
            if (cpl_error_get_code() != CPL_ERROR_NONE) {
                cpl_error_reset() ;
                if (det_nr == 1)        zpoint = zp1_avg ;
                else if (det_nr == 2)   zpoint = zp2_avg ;
                else                    zpoint = zp3_avg ;
                cpl_msg_warning(__func__, 
                        "No QC ZPOINT in %s[%d] - Use average for Det %d: %g",
                        cpl_frame_get_filename(zp_frame), i+1, det_nr, zpoint) ;
            }
            cpl_propertylist_update_double(eh, "ESO QC ZPOINT", zpoint) ;
        }

        /* Resample vec if needed */
        if (vec != NULL && 
                cpl_vector_get_size(vec) != b_samples && b_samples > 0) {

            /* Get crval1, cdelt1 */
            cdelt1 = kmos_pfits_get_cdelt1(eh) ;
            crval1 = kmos_pfits_get_crval1(eh) ;

            /* Recreate vec */
            vec_resampled = kmos_gen_telluric_resample(vec, b_samples, crval1,
                    cdelt1, &new_crval1, &new_cdelt1) ;
            if (vec_resampled != NULL) {
                cpl_vector_delete(vec) ;
                vec = vec_resampled ;

                /* Update CRVAL1 / CDELT1 in eh */
                cpl_propertylist_update_double(eh, "CRVAL1", new_crval1) ;
                cpl_propertylist_update_double(eh, "CDELT1", new_cdelt1) ;
            }
        }

        /* Save the Extension */
        if (vec != NULL) {
            /* Save the data */
            cpl_vector_save(vec, filename, CPL_TYPE_DOUBLE, eh, CPL_IO_EXTEND) ;
            cpl_vector_delete(vec) ;
        } else {
            /* Only the header */
            cpl_propertylist_save(eh, filename, CPL_IO_EXTEND) ;
        }
        cpl_propertylist_delete(eh); 
    }
    return 0 ;
}
/*----------------------------------------------------------------------------*/
/**
  @brief    Resample 1d vector
  @param    vec         Vector to resample
  @param    b_samples   Number of samples wished
  @param    crval1      
  @param    cdelt1
  @param    new_crval1  [out]
  @param    new_cdelt1  [out]
  @return   resampled vector or NULL in error case
 */
/*----------------------------------------------------------------------------*/
static cpl_vector * kmos_gen_telluric_resample(
        cpl_vector          *   vec,
        int                     b_samples,
        double                  crval1,
        double                  cdelt1,
        double              *   new_crval1,
        double              *   new_cdelt1)
{
    cpl_bivector        *   fold ;
    cpl_bivector        *   fnew ;
    cpl_vector          *   old_x ;
    cpl_vector          *   new_x ;
    cpl_vector          *   new_y ;
	int                     vec_size ;
    cpl_error_code          err;
    double                  current_lambda ;
    int                     i, j ;

    /* Check entries */
    if (vec == NULL || new_crval1 == NULL || new_cdelt1 == NULL) return NULL;

    /* Initialise */
    vec_size = cpl_vector_get_size(vec) ;

    /* No resample needed */
    if (b_samples == vec_size) {
        *new_crval1 = crval1 ;
        *new_cdelt1 = cdelt1 ;
        return cpl_vector_duplicate(vec) ;
    }

    /* Prepare data */
    *new_crval1 = crval1 ;
    *new_cdelt1 = cdelt1 * vec_size / b_samples ;

    /* Fill the old lambda */
    old_x = cpl_vector_new(vec_size) ;
    for (i=0 ; i<vec_size ; i++) {
        cpl_vector_set(old_x, i, crval1 + i*cdelt1) ;
    }

    /* Fill the new lambda */
    new_x = cpl_vector_new(b_samples) ;
    new_y = cpl_vector_new(b_samples) ;
    for (i=0 ; i<b_samples ; i++) {
        cpl_vector_set(new_x, i, *new_crval1 + i*(*new_cdelt1)) ;
    }

    if (b_samples < vec_size) {
        /* Undersampling */
        fold = cpl_bivector_wrap_vectors(old_x, (cpl_vector *)vec) ;
        fnew = cpl_bivector_wrap_vectors(new_x, new_y) ;

        /* Resample */
        err = cpl_bivector_interpolate_linear(fnew, fold) ;

        /* Unwrap */
        cpl_bivector_unwrap_vectors(fold) ;
        cpl_bivector_unwrap_vectors(fnew) ;

        if (err != CPL_ERROR_NONE) {
            cpl_msg_error(__func__, "Cannot Resample") ;
            cpl_vector_delete(old_x) ;
            cpl_vector_delete(new_x) ;
            cpl_vector_delete(new_y) ;
            return NULL ;
        }
    } else {
        /* Oversampling */
        for (i=0 ; i<b_samples ; i++) {
            /* Current wavelength */
            current_lambda = cpl_vector_get(new_x, i) ;
            /* Find j : index of this wl in the old vector */
            for (j=0 ; j<vec_size-1 ; j++) {
                if (cpl_vector_get(old_x, j) > current_lambda) break ;
            }
            cpl_vector_set(new_y, i, cpl_vector_get(vec, j)) ;
        }
    }
    cpl_vector_delete(old_x) ;
    cpl_vector_delete(new_x) ;
    return new_y ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Compute Average ZPOINTs per detector
  @param    zp_frame    Input frame
  @param    zp1_avg     Avg ZPOINTS on Det 1
  @param    zp2_avg     Avg ZPOINTS on Det 2
  @param    zp3_avg     Avg ZPOINTS on Det 3
  @return   0 if ok, -1 in error case
 */
/*----------------------------------------------------------------------------*/
static int kmos_gen_telluric_get_avg_zpoint(
        const cpl_frame     *   zp_frame, 
        double              *   zp1_avg,
        double              *   zp2_avg,
        double              *   zp3_avg)
{
    double                  zp1_avg_loc, zp2_avg_loc, zp3_avg_loc, zpoint ; ;
    int                     zp1_nb, zp2_nb, zp3_nb, ext_nb, id, det_nr, i;
    cpl_propertylist    *   eh ;
    const char          *   extname ;
    enum kmo_frame_type     ft ;
    char                    content[256] ;

    /* Check entries */
    if (zp_frame==NULL || zp1_avg==NULL || zp2_avg==NULL || zp3_avg==NULL)
        return -1 ;

    /* Initialise */
    *zp1_avg = *zp2_avg = *zp3_avg = -1.0 ;
    zp1_nb = zp2_nb = zp3_nb = 0 ;
    zp1_avg_loc = zp2_avg_loc = zp3_avg_loc = 0.0 ;
    id = -1 ;
    ft = illegal_frame ; 

    /* Get Extensions number */
    ext_nb = cpl_fits_count_extensions(cpl_frame_get_filename(zp_frame)) ;

    /* Loop on the extensions */
    for (i = 0; i < ext_nb ; i++) {
        /* Load extension header */
        eh = cpl_propertylist_load(cpl_frame_get_filename(zp_frame), i+1);

        /* Read EXTNAME */
        extname = cpl_propertylist_get_string(eh, "EXTNAME") ;
        kmo_extname_extractor(extname, &ft, &id, content) ;

        /* Is Data ? */
        if (!strcmp(content, "DATA")) {
            /* Read ZPOINT */
            zpoint = cpl_propertylist_get_double(eh, "ESO QC ZPOINT");

            /* ZPOINT is there ? */
            if (cpl_error_get_code() == CPL_ERROR_NONE) {
                /* Get the detector Nr */
                if (id >= 1 && id <= 8)         det_nr = 1 ;
                else if (id >= 9 && id <= 16)   det_nr = 2 ;
                else if (id >= 17 && id <= 24)  det_nr = 3 ;
                else {
                    cpl_msg_error(__func__, "Cannot Identify the detector") ;
                    cpl_propertylist_delete(eh); 
                    return -1 ;
                }

                if (det_nr == 1) {
                    zp1_avg_loc += zpoint ;
                    zp1_nb ++ ;
                } else if (det_nr == 2) {
                    zp2_avg_loc += zpoint ;
                    zp2_nb ++ ;
                } else if (det_nr == 3) {
                    zp3_avg_loc += zpoint ;
                    zp3_nb ++ ;
                } else {
                    cpl_msg_error(__func__, "Cannot Identify the detector") ;
                    cpl_propertylist_delete(eh); 
                    return -1 ;
                }
            } else {
                cpl_error_reset() ;
            }
        }
        cpl_propertylist_delete(eh); 
    }

    /* Divide by the number */
    if (zp1_nb > 0) *zp1_avg = zp1_avg_loc / zp1_nb ;
    if (zp2_nb > 0) *zp2_avg = zp2_avg_loc / zp2_nb ;
    if (zp3_nb > 0) *zp3_avg = zp3_avg_loc / zp3_nb ;
    return 0 ;
} 
    
/*----------------------------------------------------------------------------*/
/**
  @brief    Make consistency checks
  @param    frameset        Set of frames 
  @param    method          Input parameter
  @return   1 if consistent, 0 if not, -1 in error case
 */
/*----------------------------------------------------------------------------*/
static int kmos_gen_telluric_check_inputs(
        cpl_frameset            *   frameset, 
        int                         method)
{
    cpl_frame       *   tell_frame ;
    cpl_frame       *   tell_corr_frame ;
    cpl_frame       *   resp_frame ;

    /* Check Entries */
    if (frameset == NULL) return -1;
    if (method < 0 || method > 3) return -1 ;

    tell_frame = cpl_frameset_find(frameset, TELLURIC) ;
    resp_frame = cpl_frameset_find(frameset, RESPONSE) ;
    tell_corr_frame = cpl_frameset_find(frameset, TELLURIC_CORR) ;

    if (method == 0 && tell_frame == NULL && resp_frame == NULL) 
        return 0 ;
    if (method == 1 && tell_corr_frame == NULL) 
        return 0 ;
    if (method == 2 && resp_frame == NULL) 
        return 0 ;
    if (method == 3 && (resp_frame == NULL || tell_corr_frame == NULL)) 
        return 0 ;
    return 1 ;
}

 
