/*
 * 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>

#ifdef __USE_XOPEN2K
#include <stdlib.h>
#define GGG
#else
#define __USE_XOPEN2K /* to get the definition for setenv in stdlib.h */
#include <stdlib.h>
#undef __USE_XOPEN2K
#endif

#include <cpl.h>

#include "kmo_utils.h"
#include "kmos_pfits.h"
#include "kmo_functions.h"
#include "kmo_priv_wave_cal.h"
#include "kmo_priv_functions.h"
#include "kmo_cpl_extensions.h"
#include "kmo_dfs.h"
#include "kmo_error.h"
#include "kmo_constants.h"
#include "kmo_debug.h"

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

static int kmos_wave_cal_check_inputs(cpl_frameset *, int *, int *, int *, 
        double *, int *, lampConfiguration *);

static int kmos_wave_cal_create(cpl_plugin *);
static int kmos_wave_cal_exec(cpl_plugin *);
static int kmos_wave_cal_destroy(cpl_plugin *);
static int kmos_wave_cal(cpl_parameterlist *, cpl_frameset *);

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

static char kmos_wave_cal_description[] =
"This recipe creates the wavelength calibration frame needed for all three\n"
"detectors. It must be called after the kmo_flat recipe, which generates the\n"
"two spatial calibration frames needed in this recipe. As input a lamp-on \n"
"frame, a lamp-off frame, the spatial calibration frames and the list with \n"
"the reference arclines are required.\n"
"An additional output frame is the resampled image of the reconstructed arc\n"
"frame. All slitlets of all IFUs are aligned one next to the other. This \n"
"frame serves for quality control. One can immediately see if the \n"
"calibration was successful.\n"
"The lists of reference arclines are supposed to contain the lines for both\n"
"available calibration arc-lamps, i.e. Argon and Neon. The list is supposed\n"
"to be a F2L KMOS FITS file with three columns:\n"
"\t1. Reference wavelength\n"
"\t2. Relative strength\n"
"\t3. String either containing “Ar” or “Ne”\n"
"The recipe extracts, based on the header keywords, either the applying\n"
"argon and/or neon emission lines. Below are the plots of the emission lines\n"
"for both argon and neon. The marked lines are the ones used for wavelength \n"
"calibration.\n"
"\n"
"Furthermore frames can be provided for several rotator angles. In this case\n"
"the resulting calibration frames for each detector are repeatedly saved as \n"
"extension for every angle.\n"
"\n"
"BASIC PARAMETERS:\n"
"-----------------\n"
"--order\n"
"The polynomial order to use for the fit of the wavelength solution.\n"
"0: (default) The appropriate order is choosen automatically depending on\n"
"the waveband (4 for IZ band, 5 for HK, 6 for the others)\n"
"\n"
"ADVANCED PARAMETERS\n"
"-------------------\n"
"--b_samples\n"
"The number of samples in spectral direction for the reconstructed cube.\n"
"Ideally this number should be greater than 2048, the detector size.\n"
"\n"
"--b_start\n"
"--b_end\n"
"Used to define manually the start and end wavelength for the reconstructed\n"
"cube. By default the internally defined values are used.\n"
"\n"
"--suppress_extension\n"
"If set to TRUE, the arbitrary filename extensions are supressed. If\n"
"multiple products with the same category are produced, they will be numered\n"
"consecutively starting from 0.\n"
"\n"
"--lines_estimation\n"
"If set to TRUE, the lines estimation method is used\n"
"\n"
"----------------------------------------------------------------------------\n"
"Input files:\n"
"\n"
"   DO category       Type   Explanation                    Required #Frames\n"
"   -----------       -----  -----------                    -------- -------\n"
"   ARC_ON            RAW    Arclamp-on exposure                Y        >=1\n"
"   ARC_OFF           RAW    Arclamp-off exposure               Y          1\n"
"   XCAL              F2D    x calibration frame                Y          1\n"
"   YCAL              F2D    y calibration frame                Y          1\n"
"   ARC_LIST          F2L    List of arclines                   Y          1\n"
"   FLAT_EDGE         F2L    Fitted edge parameters             Y          1\n"
"   REF_LINES         F2L    Reference line table               Y          1\n"
"   WAVE_BAND         F2L    Table with start-/end-wavelengths  Y          1\n"
"\n"
"Output files:\n"
"\n"
"   DO category       Type   Explanation\n"
"   -----------       -----  -----------\n"
"   LCAL              F2D    Wavelength calibration frame\n"
"                            (3 Extensions)\n"
"   DET_IMG_WAVE      F2D    reconstructed arclamp-on exposure\n"
"                            (4 extensions: 3 detector images + \n"
"                            the arclines list table)\n"
"----------------------------------------------------------------------------\n"
"\n";

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

/**
 * @defgroup kmos_wave_cal Creates a frame encoding the spectral position
 */

/**@{*/

/*----------------------------------------------------------------------------*/
/**
  @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_wave_cal",
            "Create a wavelength calibration frame",
            kmos_wave_cal_description,
            "Alex Agudo Berbel, Yves Jung",
            "usd-help@eso.org",
            kmos_get_license(),
            kmos_wave_cal_create,
            kmos_wave_cal_exec,
            kmos_wave_cal_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_wave_cal_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
    p = cpl_parameter_new_range("kmos.kmos_wave_cal.order", CPL_TYPE_INT,
            "The fitting polynomial order used for the wavelength solution. "
            "By default, 4 for IZ band, 5 for HK, 6 for the others",
            "kmos.kmos_wave_cal", 0, 0, 7);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "order");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --suppress_extension */
    p = cpl_parameter_new_value("kmos.kmos_wave_cal.suppress_extension",
            CPL_TYPE_BOOL, "Suppress arbitrary filename extension",
            "kmos.kmos_wave_cal", FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "suppress_extension");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --lines_estimation */
    p = cpl_parameter_new_value("kmos.kmos_wave_cal.lines_estimation",
            CPL_TYPE_BOOL, "Trigger lines estimation method",
            "kmos.kmos_wave_cal", FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "lines_estimation");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* Add parameters for band-definition */
    kmos_band_pars_create(recipe->parameters, "kmos.kmos_wave_cal");

    /* --detector */
    p = cpl_parameter_new_value("kmos.kmos_wave_cal.detector",
            CPL_TYPE_INT, "Only reduce the specified detector",
            "kmos.kmos_wave_cal", 0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "det");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --angle */
    p = cpl_parameter_new_value("kmos.kmos_wave_cal.angle",
            CPL_TYPE_DOUBLE, "Only reduce the specified angle",
            "kmos.kmos_wave_cal", 370.0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "angle");
    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_wave_cal_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_wave_cal(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_wave_cal_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

  Possible _cpl_error_code_ set in this function:
  @li CPL_ERROR_ILLEGAL_INPUT      if operator not valid,
                                   if first operand not 3d or
                                   if second operand not valid
  @li CPL_ERROR_INCOMPATIBLE_INPUT if the dimensions of the two operands
                                   do not match
 */
/*----------------------------------------------------------------------------*/
static int kmos_wave_cal(cpl_parameterlist *parlist, cpl_frameset *frameset)
{
    const cpl_parameter     *   par ;
    int                         suppress_extension, fit_order_par, fit_order ;
    int                         nx, ny, next, reduce_det, lines_estimation ;
    double                      exptime, gain, angle_found, reduce_angle ;
    cpl_frame               *   frame ; 
    cpl_propertylist        *   mh_on ;
    cpl_propertylist        *   plist ;
    char                    *   suffix ;
    lampConfiguration           lamp_config;
    char                    **  filter_ids ;
    int                     *   angles_array ;
    int                         nb_angles ;
    int                         non_dest_rom ;

    cpl_propertylist        **  stored_sub_headers_lcal ;
    cpl_propertylist        **  stored_sub_headers_det_img ;
    cpl_image               **  stored_lcal ;
    cpl_image               **  stored_det_img ;
    int                     *   stored_qc_arc_sat ;
    double                  *   stored_qc_ar_eff ;
    double                  *   stored_qc_ne_eff ;
    cpl_table               *   detector_edges[KMOS_IFUS_PER_DETECTOR] ;

    int                         a, i, j, x, y ;

    cpl_image               *   det_lamp_on ;
    cpl_image               *   det_lamp_off ;
    cpl_image               *   det_lamp_on_copy ;

    cpl_table               *   arclines ;
    cpl_table               *   reflines ;
    cpl_bivector            *   lines ;

    cpl_image               *   bad_pix_mask ;
    float                   *   pbad_pix_mask ;
    cpl_image               *   xcal ;
    cpl_image               *   ycal ;
    cpl_image               *   lcal ;

    int                         nr_sat ;

    cpl_propertylist        *   qc_header ;

    cpl_array               **  unused_ifus_before ;
    cpl_array               **  unused_ifus_after ;
    char                    *   extname ;
    char                    *   fn_suffix ;
    char                    *   last_env ;
    const char              *   tmp_str ;
    cpl_error_code              err ;

    /* Check entries */
    if (parlist == NULL || frameset == NULL) {
        cpl_msg_error(__func__, "Null Inputs") ;
        cpl_error_set(__func__, CPL_ERROR_NULL_INPUT) ;
        return -1 ;
    }
    
    /* Get Parameters */
    par = cpl_parameterlist_find_const(parlist, "kmos.kmos_wave_cal.order");
    fit_order_par = cpl_parameter_get_int(par);
    par=cpl_parameterlist_find_const(parlist, 
            "kmos.kmos_wave_cal.lines_estimation");
    lines_estimation = cpl_parameter_get_bool(par);
    par=cpl_parameterlist_find_const(parlist, 
            "kmos.kmos_wave_cal.suppress_extension");
    suppress_extension = cpl_parameter_get_bool(par);
    par = cpl_parameterlist_find_const(parlist, "kmos.kmos_wave_cal.angle");
    reduce_angle = cpl_parameter_get_double(par);
    par = cpl_parameterlist_find_const(parlist, "kmos.kmos_wave_cal.detector");
    reduce_det = cpl_parameter_get_int(par);

    kmos_band_pars_load(parlist, "kmos.kmos_wave_cal");

    /* Check Parameters */
    if (fit_order_par < 0 || fit_order_par > 7) {
        cpl_msg_error(__func__, "Fitting Order must be in [0,7]") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }
    if (reduce_det < 0 || reduce_det > 3) {
        cpl_msg_error(__func__, "detector must be in [1,3]") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }

    /* Identify the RAW and CALIB frames in the input frameset */
    if (kmo_dfs_set_groups(frameset) != 1) {
        cpl_msg_error(__func__, "Cannot identify RAW and CALIB frames") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }

    /* Check the inputs consistency */
    if (kmos_wave_cal_check_inputs(frameset, &nx, &ny, &next, &exptime,
                &non_dest_rom, &lamp_config) != 1) {
        cpl_msg_error(__func__, "Input frameset is not consistent") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }
   
    /* Instrument setup */
    suffix = kmo_dfs_get_suffix(kmo_dfs_get_frame(frameset, XCAL), TRUE, FALSE);
    cpl_msg_info(__func__, "Detected instrument setup:   %s", suffix+1);

    /* Check that filter and grating match for each detector */
    /* filter/grating can be different for each detector */
    frame = kmo_dfs_get_frame(frameset, ARC_ON);
    mh_on = cpl_propertylist_load(cpl_frame_get_filename(frame), 0);
    filter_ids =  kmo_get_filter_setup(mh_on, next, TRUE) ;
    cpl_propertylist_delete(mh_on);
    if (filter_ids == NULL) {
        cpl_free(suffix);
        cpl_msg_error(__func__, "Cannot get Filter informations") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }

    /* Get Rotator angles */
    if ((angles_array = kmos_get_angles(frameset, &nb_angles, ARC_ON)) == NULL){
        cpl_msg_error(__func__, "Cannot get Angles informations") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        for (i = 0; i < next ; i++) cpl_free(filter_ids[i]);
        cpl_free(filter_ids);
        cpl_free(suffix);
        return -1 ;
    }

    /* Check the ARC_LIST filter */
    frame = kmo_dfs_get_frame(frameset, ARC_LIST);
    plist = cpl_propertylist_load(cpl_frame_get_filename(frame), 0);
    tmp_str = cpl_propertylist_get_string(plist, FILT_ID);
    if (strcmp(filter_ids[0], tmp_str) != 0) {
        cpl_msg_error(__func__, "Wrong ARC_LIST filter") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        for (i = 0; i < next ; i++) cpl_free(filter_ids[i]);
        cpl_free(filter_ids);
        cpl_free(angles_array);
        cpl_propertylist_delete(plist); 
        return -1 ;
    }
    cpl_propertylist_delete(plist); 

    /* Load the lines as a CPL table */
    arclines = kmo_dfs_load_table(frameset, ARC_LIST, 1, 0);
    lines = kmos_get_lines(arclines, lamp_config);
    cpl_table_delete(arclines);
    /* TODO : check not null */
    cpl_msg_info(__func__, "Arc lines: %lld", cpl_bivector_get_size(lines));

    /* Load REFLINES */
    if ((reflines = kmo_dfs_load_table(frameset, REF_LINES, 1, 0)) == NULL) {
        cpl_msg_error(__func__, "Missing REF_LINES calibration file") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        for (i = 0; i < next ; i++) cpl_free(filter_ids[i]);
        cpl_free(filter_ids);
        cpl_free(angles_array);
        cpl_bivector_delete(lines) ;
        return -1 ;
    }

    /* Check which IFUs are active for all FLAT frames */
    unused_ifus_before = kmo_get_unused_ifus(frameset, 0, 0);
    unused_ifus_after = kmo_duplicate_unused_ifus(unused_ifus_before);
    kmo_print_unused_ifus(unused_ifus_before, FALSE);
    if (unused_ifus_before != NULL) kmo_free_unused_ifus(unused_ifus_before);

    /* make sure no reconstruction lookup table (LUT) is used */
    if (getenv("KMCLIPM_PRIV_RECONSTRUCT_LUT_MODE") != NULL) {
        last_env = getenv("KMCLIPM_PRIV_RECONSTRUCT_LUT_MODE");
    } else {
        last_env = NULL ;
    }
    setenv("KMCLIPM_PRIV_RECONSTRUCT_LUT_MODE","NONE",1);

    /* the frames have to be stored temporarily because the QC params */
    /* for the main header are calculated per detector. So they can be */
    /* stored only when all detectors are processed */
    stored_lcal = (cpl_image**)cpl_calloc(next * nb_angles, sizeof(cpl_image*));
    stored_det_img = (cpl_image**)cpl_calloc(next*nb_angles,sizeof(cpl_image*));
    stored_sub_headers_lcal = (cpl_propertylist**)cpl_calloc(next * nb_angles,
            sizeof(cpl_propertylist*));
    stored_sub_headers_det_img = (cpl_propertylist**)cpl_calloc(next*nb_angles,
            sizeof(cpl_propertylist*));
    stored_qc_arc_sat = (int*)cpl_calloc(next, nb_angles * sizeof(int));
    stored_qc_ar_eff=(double*)cpl_calloc(next, nb_angles * sizeof(double));
    stored_qc_ne_eff=(double*)cpl_calloc(next, nb_angles * sizeof(double));

    /* Loop all Rotator Angles and Detectors  */
    for (a = 0; a < nb_angles; a++) {
        /* Reduce only one angle */
        if (reduce_angle <= 360 && angles_array[a] != reduce_angle) continue ;

        cpl_msg_info(__func__, "Processing rotator angle %d -> %d degree", 
                a, angles_array[a]);
        cpl_msg_indent_more();
        for (i = 1; i <= next ; i++) {
            /* Compute only one detetor */
            if (reduce_det != 0 && i != reduce_det) continue ;

            cpl_msg_info(__func__,"Processing detector No. %d", i);
            cpl_msg_indent_more();

            /* Load edge parameters */
            frame=kmo_dfs_get_frame(frameset, FLAT_EDGE);
            for (j = 0; j < KMOS_IFUS_PER_DETECTOR; j++) {
                detector_edges[j] = kmclipm_cal_table_load(
                        cpl_frame_get_filename(frame), 
                        (i-1) * KMOS_IFUS_PER_DETECTOR + j + 1,
                        angles_array[a], 0, &angle_found);
                
                /* IFU is inactive: proceed */
                if (cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT) {
                    cpl_error_reset();
                }
            }

            /* Set default fit orders for the different bands */
            if (fit_order_par == 0) {
                if ((strcmp(filter_ids[i-1], "H") == 0) ||
                    (strcmp(filter_ids[i-1], "K") == 0) ||
                    (strcmp(filter_ids[i-1], "YJ") == 0)) {
                    fit_order = 6;
                } else if (strcmp(filter_ids[i-1], "IZ") == 0) {
                    fit_order = 4;
                } else if (strcmp(filter_ids[i-1], "HK") == 0) {
                    fit_order = 5;
                }
                cpl_msg_info(__func__, 
                        "Order of wavelength spectrum fit for %s-band: %d",
                        filter_ids[i-1], fit_order);
            } else {
                fit_order = fit_order_par;
            }

            /* Get ARC_ON frame and Load it */
            frame = kmos_get_angle_frame(frameset, angles_array[a], ARC_ON);
            det_lamp_on = kmo_dfs_load_image_frame(frame,i,FALSE, TRUE,&nr_sat);
            int sx = a * next + (i - 1);

            /* Count saturated pixels for each detector */
            if (non_dest_rom)   
                stored_qc_arc_sat[sx] = nr_sat;
            else 
                stored_qc_arc_sat[sx] = kmo_image_get_saturated(det_lamp_on,
                        KMO_FLAT_SATURATED);

            det_lamp_on_copy = cpl_image_duplicate(det_lamp_on);

            /* Get ARC_OFF frame and Load it */
            frame = kmo_dfs_get_frame(frameset, ARC_OFF);
            det_lamp_off = kmo_dfs_load_image_frame(frame, i, FALSE, FALSE, 
                    NULL);

            /* ARC_ON = ARC_ON - ARC_OFF */
            cpl_image_subtract(det_lamp_on, det_lamp_off);

            /* Load XCAL,YCAL */
            xcal = kmo_dfs_load_cal_image(frameset, XCAL, i, 0, 
                    (double)angles_array[a], FALSE, NULL, &angle_found, -1,0,0);
            ycal = kmo_dfs_load_cal_image(frameset, YCAL, i, 0, 
                    (double)angles_array[a], FALSE, NULL, &angle_found, -1,0,0);
            if (xcal == NULL || ycal == NULL) {
                /* Missing calibration for this detector */
                cpl_error_reset() ;
                stored_det_img[sx] = NULL ;
                stored_lcal[sx] = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
                kmo_image_fill(stored_lcal[sx], 0.0);
                if (xcal != NULL) cpl_image_delete(xcal) ;
                if (ycal != NULL) cpl_image_delete(ycal) ;
                cpl_image_delete(det_lamp_on_copy) ;
                cpl_image_delete(det_lamp_on) ;
                cpl_image_delete(det_lamp_off) ;
                continue ;
            }

            /* Derive BPM from XCAL : NaNs to 0, Others to 1  */
            bad_pix_mask = cpl_image_duplicate(xcal);
            pbad_pix_mask = cpl_image_get_data_float(bad_pix_mask);
            for (x = 0; x < nx; x++) {
                for (y = 0; y < ny; y++) {
                    if (isnan(pbad_pix_mask[x+nx*y])) {
                        pbad_pix_mask[x+nx*y] = 0.;
                    } else {
                        pbad_pix_mask[x+nx*y] = 1.;
                    }
                }
            }

            /* Compute wavelength calibration */
            err = kmos_calc_wave_calib(det_lamp_on, bad_pix_mask,
                    filter_ids[i-1], lamp_config, i, unused_ifus_after[i-1], 
                    detector_edges, lines, reflines, &lcal, 
                    &(stored_qc_ar_eff[sx]), &(stored_qc_ne_eff[sx]), fit_order,
                    lines_estimation);
            cpl_image_delete(det_lamp_on); 
            for (j = 0; j < KMOS_IFUS_PER_DETECTOR; j++) {
                cpl_table_delete(detector_edges[j]); 
            }
            if (err == CPL_ERROR_NONE) {
                /* Update QC parameters */
                if (stored_qc_ar_eff[sx] != -1.0) 
                    stored_qc_ar_eff[sx] /= exptime;
                if (stored_qc_ne_eff[sx] != -1.0) 
                    stored_qc_ne_eff[sx] /= exptime;

                /* Apply the badpixel mask to the produced frame */
                cpl_image_multiply(lcal, bad_pix_mask);
                kmo_image_reject_from_mask(lcal, bad_pix_mask);

                /* Store Result frame */
                stored_lcal[sx] = lcal;
            } else if (err == CPL_ERROR_UNSPECIFIED) {
                /* All IFUs seem to be deactivated */
                /* Continue processing - just save empty frame */
                cpl_error_reset();
                stored_lcal[sx] = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
                kmo_image_fill(stored_lcal[sx], 0.0);
            } else {
                cpl_error_reset();
                cpl_msg_warning(__func__,
                        "Couldn't identify any line - Check the line list");
                cpl_msg_warning(__func__,
                        "Band defined in header of detector %d: %s",
                        i, filter_ids[i-1]);
                cpl_msg_warning(__func__, "Arc line file defined: %s",
                        cpl_frame_get_filename(kmo_dfs_get_frame(frameset, 
                                ARC_LIST)));
            }
            cpl_image_delete(bad_pix_mask);

            /* CREATE RECONSTRUCTED AND RESAMPLED ARC FRAME */
            stored_det_img[sx] = kmo_reconstructed_arc_image(frameset,
                    det_lamp_on_copy, det_lamp_off, xcal, ycal, stored_lcal[sx],
                    unused_ifus_after[i-1], FALSE, i, suffix, filter_ids[i-1], 
                    lamp_config, &qc_header);
            cpl_image_delete(det_lamp_on_copy);
            cpl_image_delete(det_lamp_off); 
            cpl_image_delete(xcal);
            cpl_image_delete(ycal);
            if (cpl_error_get_code() != CPL_ERROR_NONE) {
                cpl_msg_error(__func__,"Cannot reconstruct IFUs on detector %d",
                        i);
                cpl_error_reset();
            }

            /* CREATE EXTENSION HEADER FOR THE PRODUCT */
            stored_sub_headers_lcal[sx] = kmo_dfs_load_sub_header(frameset, 
                    ARC_ON, i, FALSE);
            /* update EXTNAME */
            extname = kmo_extname_creator(detector_frame, i, EXT_DATA);
            kmclipm_update_property_string(stored_sub_headers_lcal[sx], EXTNAME,
                    extname, "FITS extension name");
            cpl_free(extname); 

            kmclipm_update_property_int(stored_sub_headers_lcal[sx], EXTVER, 
                    sx+1, "FITS extension ver");

            // add first QC parameters
            kmclipm_update_property_int(stored_sub_headers_lcal[sx], 
                    QC_ARC_SAT, stored_qc_arc_sat[sx], 
                    "[] nr. saturated pixels of arc exp.");

            gain=kmo_dfs_get_property_double(stored_sub_headers_lcal[sx],GAIN);

            if (stored_qc_ar_eff[sx] != -1.0) {
                kmclipm_update_property_double(stored_sub_headers_lcal[sx], 
                        QC_ARC_AR_EFF, stored_qc_ar_eff[sx]/gain,
                        "[e-/s] Argon lamp efficiency");
            }

            if (stored_qc_ne_eff[sx] != -1.0) {
                kmclipm_update_property_double(stored_sub_headers_lcal[sx], 
                        QC_ARC_NE_EFF, stored_qc_ne_eff[sx]/gain,
                        "[e-/s] Neon lamp efficiency");
            }

            kmclipm_update_property_double(stored_sub_headers_lcal[sx],
                    CAL_ROTANGLE, ((double) angles_array[a]),
                    "[deg] Rotator relative to nasmyth");

            /* append QC parameters */
            cpl_propertylist_append(stored_sub_headers_lcal[sx], qc_header);
            cpl_propertylist_delete(qc_header); 

            stored_sub_headers_det_img[sx]=cpl_propertylist_duplicate(
                    stored_sub_headers_lcal[sx]);

            cpl_propertylist_erase(stored_sub_headers_lcal[sx], CRVAL1);
            cpl_propertylist_erase(stored_sub_headers_lcal[sx], CRVAL2);
            cpl_propertylist_erase(stored_sub_headers_lcal[sx], CTYPE1);
            cpl_propertylist_erase(stored_sub_headers_lcal[sx], CTYPE2);
            cpl_propertylist_erase(stored_sub_headers_lcal[sx], CDELT1);
            cpl_propertylist_erase(stored_sub_headers_lcal[sx], CDELT2);

            cpl_msg_indent_less();
        } // for i devices
        cpl_msg_indent_less() ;
    } // for a angles
    
    /* Free */
    cpl_free(angles_array) ;
    for (i = 0; i < next; i++) cpl_free(filter_ids[i]);
    cpl_free(filter_ids);
    cpl_bivector_delete(lines);
    
    cpl_free(stored_qc_arc_sat);
    cpl_free(stored_qc_ar_eff);
    cpl_free(stored_qc_ne_eff);
    cpl_table_delete(reflines); 
    
    /* QC parameters & saving */
    cpl_msg_info(__func__, "Saving data...");

    /* load, update & save primary header */
    if (!suppress_extension)    fn_suffix = cpl_sprintf("%s", suffix);
    else                        fn_suffix = cpl_sprintf("%s", "");
    cpl_free(suffix);

    /* update which IFUs are not used */
    frame = kmo_dfs_get_frame(frameset, ARC_ON);
    mh_on = kmclipm_propertylist_load(cpl_frame_get_filename(frame), 0);
    kmo_set_unused_ifus(unused_ifus_after, mh_on, "kmos_wave_cal");
    kmo_dfs_save_main_header(frameset, LCAL, fn_suffix, frame, mh_on, parlist, 
            cpl_func);
    kmo_dfs_save_main_header(frameset, DET_IMG_WAVE, fn_suffix, frame, mh_on, 
            parlist, cpl_func);
    cpl_propertylist_delete(mh_on); 

    /* Save sub-frames */
    for (a = 0; a < nb_angles; a++) {
        for (i = 1; i <= next ; i++) {
            int sx = a * next + (i - 1);
            /* save lcal-frame */
            kmo_dfs_save_image(stored_lcal[sx], LCAL, fn_suffix, 
                    stored_sub_headers_lcal[sx], 0./0.);

            /* save detector image */
            kmo_dfs_save_image(stored_det_img[sx], DET_IMG_WAVE, fn_suffix, 
                    stored_sub_headers_det_img[sx], 0./0.);
        } // for i = nxte
    } // for a angles
   
    /* Free */
    cpl_free(fn_suffix);
    for (i = 0; i < next * nb_angles; i++) {
        cpl_image_delete(stored_lcal[i]);
        cpl_image_delete(stored_det_img[i]);
        cpl_propertylist_delete(stored_sub_headers_lcal[i]);
        cpl_propertylist_delete(stored_sub_headers_det_img[i]);
    }
    cpl_free(stored_lcal);
    cpl_free(stored_det_img);
    cpl_free(stored_sub_headers_lcal); 
    cpl_free(stored_sub_headers_det_img);

    /* print which IFUs are not used */
    kmo_print_unused_ifus(unused_ifus_after, TRUE);
    if (unused_ifus_after != NULL) kmo_free_unused_ifus(unused_ifus_after);

    if (last_env != NULL) {
        setenv("KMCLIPM_PRIV_RECONSTRUCT_LUT_MODE",last_env,1);
    } else {
        unsetenv("KMCLIPM_PRIV_RECONSTRUCT_LUT_MODE");
    }
    return 0;
}

/**@}*/

/*----------------------------------------------------------------------------*/
/**
  @brief    Make consistency checks
  @param    frameset        Set of frames with ARC_OFF and several ARC_ON
  @param    nx [out]        images x size
  @param    ny [out]        images y size
  @param    next [out]        frames nb of extensions
  @param    exptime [out]   exposure time
  @param    non_dest_rom    Flag to identify non destructive ROM
  @param    lamp_config     ARGON / NEON / ARGON+NEON
  @return   1 if consistent, 0 if not, -1 in error case
 */
/*----------------------------------------------------------------------------*/
static int kmos_wave_cal_check_inputs(
        cpl_frameset            *   frameset, 
        int                     *   nx, 
        int                     *   ny,
        int                     *   next,
        double                  *   exptime,
        int                     *   non_dest_rom,
        lampConfiguration       *   lamp_config)
{
    const cpl_frame     *   frame_off ;
    const cpl_frame     *   frame_on ;
    cpl_propertylist    *   mh_off ;
    cpl_propertylist    *   mh_on ;
    cpl_propertylist    *   eh_off ;
    cpl_propertylist    *   eh_on ;
    int                     ext, next_off, next_on, nx_on, ny_on, nx_off,ny_off;
    double                  ndit_off, ndit_on, exptime_off, exptime_on ;
    const char          *   readmode_off ;
    const char          *   readmode_on ;

    /* TODO Check Lamps TODO */

    /* Check Entries */
    if (nx == NULL || ny == NULL || next == NULL || frameset == NULL) return -1;

    /* Setup lamp config */
    frame_on = kmo_dfs_get_frame(frameset, ARC_ON);
    mh_on = cpl_propertylist_load(cpl_frame_get_filename(frame_on), 0);
    if ((kmo_check_lamp(mh_on, INS_LAMP1_ST) == TRUE) &&
        (kmo_check_lamp(mh_on, INS_LAMP2_ST) == FALSE)) {
        *lamp_config = ARGON;
        cpl_msg_info(__func__, "Arc lamp: Argon");
    } else if ((kmo_check_lamp(mh_on, INS_LAMP1_ST) == FALSE) &&
               (kmo_check_lamp(mh_on, INS_LAMP2_ST) == TRUE)) {
        *lamp_config = NEON;
        cpl_msg_info(__func__, "Arc lamp: Neon");
    } else if ((kmo_check_lamp(mh_on, INS_LAMP1_ST) == TRUE) &&
               (kmo_check_lamp(mh_on, INS_LAMP2_ST) == TRUE)) {
        *lamp_config = ARGON_NEON;
        cpl_msg_info(__func__, "Arc lamp: Argon + Neon");
    } else {
        *lamp_config = -1 ;
        cpl_msg_warning(__func__, "Arc lamp: UNDEFINED");
    }

    /* Check READ OUT MODE */
    readmode_on = kmos_pfits_get_readmode(mh_on);
    if (!strcmp(readmode_on, "Nondest")) {
        *non_dest_rom = 1 ;
    } else {
        *non_dest_rom = 0 ;
    }
    cpl_propertylist_delete(mh_on);

    /* Get ARC_OFF */
    frame_off = kmo_dfs_get_frame(frameset, ARC_OFF);
    if (frame_off == NULL) {
        cpl_msg_error(__func__, "No ARC_OFF frame found") ;
        return -1 ;
    }

    /* Get ARC_OFF main header infos */
    next_off = cpl_frame_get_nextensions(frame_off);
    mh_off = cpl_propertylist_load(cpl_frame_get_filename(frame_off), 0);
    ndit_off = kmos_pfits_get_ndit(mh_off) ;
    exptime_off = kmos_pfits_get_exptime(mh_off) ;
    readmode_off = kmos_pfits_get_readmode(mh_off);

    /* Get ARC_ON frames and loop on them */
    frame_on = kmo_dfs_get_frame(frameset, ARC_ON);
    if (frame_on == NULL) {
        cpl_msg_error(__func__, "No ARC_ON frame found") ;
        cpl_propertylist_delete(mh_off);
        return -1 ;
    }
    while (frame_on != NULL) {
        /* Get ARC_ON main header infos */
        next_on = cpl_frame_get_nextensions(frame_on);
        mh_on = cpl_propertylist_load(cpl_frame_get_filename(frame_on), 0);
        ndit_on = kmos_pfits_get_ndit(mh_on) ;
        exptime_on = kmos_pfits_get_exptime(mh_on) ;
        readmode_on = kmos_pfits_get_readmode(mh_on);

        /* Check consistency */
        if (ndit_on != ndit_off || strcmp(readmode_on, readmode_off) || 
                fabs(exptime_on-exptime_off) > 0.01 || next_off != next_on) {
            cpl_msg_warning(__func__, "Inconsistency for frame %s", 
                    cpl_frame_get_filename(frame_on)) ;
            cpl_propertylist_delete(mh_off);
            cpl_propertylist_delete(mh_on);
            return 0 ;
        }
        cpl_propertylist_delete(mh_on);

        /* Get next frame */
        frame_on = kmo_dfs_get_frame(frameset, NULL);
    }
    cpl_propertylist_delete(mh_off);

    /* Check the extensions */
    for (ext = 1; ext <= next_off ; ext++) {
        eh_off = cpl_propertylist_load(cpl_frame_get_filename(frame_off), ext);
        nx_off = kmos_pfits_get_naxis1(eh_off) ;
        ny_off = kmos_pfits_get_naxis2(eh_off) ;

        frame_on = kmo_dfs_get_frame(frameset, ARC_ON);
        while (frame_on != NULL) {
            eh_on = cpl_propertylist_load(cpl_frame_get_filename(frame_on),ext);
            nx_on = kmos_pfits_get_naxis1(eh_on) ;
            ny_on = kmos_pfits_get_naxis2(eh_on) ;
            /* Check consistency */
            if (nx_on != nx_off || ny_off != ny_on) { 
                cpl_msg_warning(__func__, "Inconsistency for frame %s", 
                        cpl_frame_get_filename(frame_on)) ;
                cpl_propertylist_delete(eh_off);
                cpl_propertylist_delete(eh_on);
                return 0 ;
            }
            cpl_propertylist_delete(eh_on);

            /* Get next frame */
            frame_on = kmo_dfs_get_frame(frameset, NULL);
        }
        cpl_propertylist_delete(eh_off);
    }
 
    /* FLAT_EDGE Checks */
    frame_on = kmo_dfs_get_frame(frameset, FLAT_EDGE);
    if (cpl_frame_get_nextensions(frame_on) % 24 != 0) {
        cpl_msg_warning(__func__, "FLAT_EDGE frame is not consistent") ;
        return 0 ;
    }

    /* Checks on XCAL YCAL */
    kmo_check_frame_setup(frameset, ARC_ON, XCAL, TRUE, FALSE, FALSE);
    kmo_check_frame_setup(frameset, ARC_ON, YCAL, TRUE, FALSE, FALSE);
    kmo_check_frame_setup_md5_xycal(frameset);
    if (cpl_error_get_code() != CPL_ERROR_NONE) {
        cpl_msg_warning(__func__, "XCAL / YCAL checks failed") ;
        return 0 ;
    }

    /* Return */
    *nx = nx_off ;
    *ny = ny_off ;
    *next = next_off ;
    *exptime = exptime_off ;
    return 1 ;
}

 
