/* 
 * 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 <cpl_wcs.h>

#include "kmo_debug.h"
#include "kmo_utils.h"
#include "kmo_dfs.h"
#include "kmo_error.h"
#include "kmo_priv_functions.h"
#include "kmo_cpl_extensions.h"
#include "kmo_constants.h"
#include "kmo_priv_combine.h"

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

static char * kmos_combine_create_ifus_string(const int *,const int *,int);
static cpl_bivector * kmos_combine_parse_skipped(const char *) ;
static int kmos_combine_is_skipped(const cpl_bivector *, int, int) ;

static int kmos_combine_create(cpl_plugin *);
static int kmos_combine_exec(cpl_plugin *);
static int kmos_combine_destroy(cpl_plugin *);
static int kmos_combine(cpl_parameterlist *, cpl_frameset *);

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

static char kmos_combine_description[] =
"This recipe shifts several exposures of an object and combines them. Diffe-\n"
"rent methods to match the exposures are described here (--method parameter).\n"
"The output cube is larger than the input cubes, according to the shifts to\n"
"be applied. Additionally a border of NaN values is added. The WCS is the\n"
"same as for the first exposure.\n"
"For each spatial/spectral pixel a new value is calculated (according the\n"
"--cmethod parameter) and written into the output cube.\n"
"Only exposures with the same WCS orientation can be combined (except\n"
"-–method=”none”), north must point to the same direction. It is recommended\n"
"to apply any rotation possibly after combining.\n"
"\n"
"The behavior of the selection of IFUs to combine differs for some templates\n"
"and can be controlled with the parameters --name and --ifus.\n"
"If the input data cubes stem from templates KMOS_spec_obs_mapping8 or\n"
"KMOS_spec_obs_mapping24 all extensions from all input frames are combined\n"
"into a single map by default (like in recipe kmo_sci_red). If just the area\n"
"of a specific IFU should be combined, the parameter --ifus can be specified,\n"
"or more easily --name.\n"
"If the input data cubes stem from other templates like e.g.\n"
"KMOS_spec_obs_freedither all extensions of all input frames are combined\n"
"into several output frames by default. The input IFUs are grouped according\n"
"to their targeted object name stored in the keywords ESO OCS ARMx NAME. If \n"
"just a specific object should be combined, its name can be specified with \n"
"--name. If arbitrary IFUs shoukd be comined, one can specify these with the\n"
"parameter --ifus.\n"
"\n"
"The default mapping mode is done via the --name parameter, where the name of\n"
"the object has to be provided. The recipe searches in input data cubes IFUs\n"
"pointing to that object.\n"
"\n"
"---------------------------------------------------------------------------\n"
"  Input files:\n"
"\n"
"   DO                  KMOS                                                \n"
"   category            Type   Explanation                  Required #Frames\n"
"   --------            -----  -----------                  -------- -------\n"
"   CUBE_OBJECT         F3I    data frame                       Y      2-n  \n"
"\n"
"  Output files:\n"
"\n"
"   DO                      KMOS\n"
"   category                Type    Explanation\n"
"   --------                -----   -----------\n"
"   COMBINE_                F3I     Combined data cube\n"
"   EXP_MASK                F3I     Exposure time mask\n"
"   SCI_COMBINED_COLL               (optional) Collapsed combined cube\n"
"                                   (set --collapse_combined)\n"
"---------------------------------------------------------------------------\n"
"\n";

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

/*----------------------------------------------------------------------------*/
/**
 * @defgroup kmos_combine Combine cubes
 */
/*----------------------------------------------------------------------------*/

/**@{*/

/*----------------------------------------------------------------------------*/
/**
  @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_combine",
            "Combine reconstructed cubes",
            kmos_combine_description,
            "Alex Agudo Berbel, Y. Jung",
            "usd-help@eso.org",
            kmos_get_license(),
            kmos_combine_create,
            kmos_combine_exec,
            kmos_combine_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_combine_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 */
    /* --name */
    p = cpl_parameter_new_value("kmos.kmos_combine.name", CPL_TYPE_STRING,
            "Name of the object to combine.", "kmos.kmos_combine", "");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "name");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --ifus */
    p = cpl_parameter_new_value("kmos.kmos_combine.ifus", CPL_TYPE_STRING,
            "The indices of the IFUs to combine. " "\"ifu1;ifu2;...\"", 
            "kmos.kmos_combine", "");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "ifus");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --method */
    p = cpl_parameter_new_value("kmos.kmos_combine.method", CPL_TYPE_STRING,
            "The shifting method:   "
            "'none': no shifting, combined directly, "
            "'header': shift according to WCS (default), "
            "'center': centering algorithm, "
            "'user': read shifts from file",
            "kmos.kmos_combine", "none");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "method");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --fmethod */
    p = cpl_parameter_new_value("kmos.kmos_combine.fmethod", CPL_TYPE_STRING,
            "The fitting method (applies only when method='center'):   "
            "'gauss': fit a gauss function to collapsed image (default), "
            "'moffat': fit a moffat function to collapsed image",
            "kmos.kmos_combine", "gauss");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "fmethod");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --filename */
    p = cpl_parameter_new_value("kmos.kmos_combine.filename", CPL_TYPE_STRING,
            "The path to the file with the shift vectors (method='user')",
            "kmos.kmos_combine", "");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "filename");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --flux */
    p = cpl_parameter_new_value("kmos.kmos_combine.flux", CPL_TYPE_BOOL,
            "Apply flux conservation: (TRUE (apply) or FALSE (don't apply)",
            "kmos.kmos_combine", FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "flux");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --edge_nan */
    p = cpl_parameter_new_value("kmos.kmos_combine.edge_nan", CPL_TYPE_BOOL,
            "Set borders of cubes to NaN before combining them."
            "(TRUE (apply) or FALSE (don't apply)", "kmos.kmos_combine", FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "edge_nan");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --skipped_frames */
    p = cpl_parameter_new_value("kmos.kmos_combine.skipped_frames", 
            CPL_TYPE_STRING,
            "Comma separated List of IFUs to skip for the combination. "
            "An IFU is specified with R:I."
            "R is the index (start 1) of the recons. frame, I the IFU nb", 
            "kmos.kmos_combine", "");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "skipped_frames");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --suppress_extension */
    p = cpl_parameter_new_value("kmos.kmos_combine.suppress_extension",
            CPL_TYPE_BOOL, "Suppress arbitrary filename extension.",
            "kmos.kmos_combine", 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);

    /* --collapse_combined */
    p = cpl_parameter_new_value("kmos.kmos_combine.collapse_combined",
            CPL_TYPE_BOOL, "Flag to collapse the combined images",
            "kmos.kmos_combine", FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "collapse_combined");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    return kmos_combine_pars_create(recipe->parameters, "kmos.kmos_combine",
            DEF_REJ_METHOD, FALSE);
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Execute the plugin instance given by the interface
  @param    plugin  the plugin
  @return   0 if everything is ok
*/
/*----------------------------------------------------------------------------*/
static int kmos_combine_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_combine(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_combine_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_combine(cpl_parameterlist *parlist, cpl_frameset *frameset)
{
    const cpl_parameter *   par ;
    cpl_frameset        *   rawframes ;
    const char      * filename,* fmethod,* method,* ifus_txt,* cmethod,
                    * skipped_frames;
    double          cpos_rej, cneg_rej ;
    cpl_vector      *   ifus ;
    int                 citer, cmin, cmax, nr_frames, index,
                        data_cube_counter, skip_cube_counter, 
                        noise_cube_counter, flux, edge_nan, name_vec_size, 
                        found, suppress_extension, suppress_index, ifu_nr, nv, 
                        collapse_combined, mapping_id ;
    char            * tmp_str, * fn_combine, * fn_mask, * mapping_mode,
                    * name, ** name_vec, * name_loc ;
    const char      * frame_filename, * tmp_strc ;
    cpl_image       * exp_mask ;
    cpl_imagelist   ** data_cube_list, ** noise_cube_list, * cube_combined_data,
                    * cube_combined_noise ;
    cpl_propertylist    * main_header, **data_header_list, **noise_header_list,
                        * tmp_header, * pro_plist ;
    char                *   reflex_suffix ;
    cpl_bivector    *   skipped_bivector ;
    cpl_frame       *   frame ;
    cpl_size            ci ;
    main_fits_desc      desc;
    int             *   used_frame_idx ;
    int             *   used_ifus ;
    char            *   used_ifus_str ;
    int             *   skip_frame_idx ;
    int             *   skip_ifus ;
    char            *   skip_ifus_str ;
    enum extrapolationType  extrapol_enum = NONE_CLIPPING;
    int             i, j ;

    /* 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_combine.method");
    method = cpl_parameter_get_string(par) ;
    par = cpl_parameterlist_find_const(parlist, "kmos.kmos_combine.fmethod");
    fmethod = cpl_parameter_get_string(par) ;
    par = cpl_parameterlist_find_const(parlist, "kmos.kmos_combine.filename");
    filename = cpl_parameter_get_string(par) ;
    par = cpl_parameterlist_find_const(parlist, "kmos.kmos_combine.ifus");
    ifus_txt = cpl_parameter_get_string(par) ;
    par = cpl_parameterlist_find_const(parlist, "kmos.kmos_combine.name");
    name = (char*)cpl_parameter_get_string(par) ;
    par = cpl_parameterlist_find_const(parlist, "kmos.kmos_combine.flux");
    flux = cpl_parameter_get_bool(par) ;
    par = cpl_parameterlist_find_const(parlist, 
            "kmos.kmos_combine.skipped_frames");
    skipped_frames = cpl_parameter_get_string(par) ;
    par = cpl_parameterlist_find_const(parlist, "kmos.kmos_combine.edge_nan");
    edge_nan = cpl_parameter_get_bool(par) ;
    par = cpl_parameterlist_find_const(parlist,
            "kmos.kmos_combine.suppress_extension");
    suppress_extension = cpl_parameter_get_bool(par) ;
    kmos_combine_pars_load(parlist, "kmos.kmos_combine", &cmethod, &cpos_rej, 
            &cneg_rej, &citer, &cmin, &cmax, FALSE);
    par = cpl_parameterlist_find_const(parlist,
            "kmos.kmos_combine.collapse_combined");
    collapse_combined = cpl_parameter_get_bool(par);

    /* Check Parameters */
    if (strcmp(method, "none") && strcmp(method, "header") &&
            strcmp(method, "center") && strcmp(method, "user")) {
        cpl_msg_error(__func__,
            "shift methods must be 'none', 'header', 'center' or 'user'") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }
    if (strcmp(ifus_txt, "") && strcmp(name, "")) {
        cpl_msg_error(__func__,
                "name and IFU indices cannot be both provided") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }
    if (!strcmp(method, "user") && !strcmp(filename, "")) {
        cpl_msg_error(__func__,
                "path of file with shift information must be provided") ;
        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 ;
    }

    /* Parse the skipped frames option */
    skipped_bivector = NULL ;
    if (strcmp(skipped_frames, "")) {
        skipped_bivector = kmos_combine_parse_skipped(skipped_frames) ;
        if (skipped_bivector != NULL) 
            cpl_msg_info(__func__, "Skip the following IFU: %s", 
                    skipped_frames);
    } 

    /* Get the frames to combine - Filter out OH_SPEC */
    rawframes = cpl_frameset_duplicate(frameset) ;
    cpl_frameset_erase(rawframes, "OH_SPEC") ;

    /* Check Nb of frames */
    nr_frames = cpl_frameset_get_size(rawframes);
    if (nr_frames < 2) {
        if (skipped_bivector!=NULL) cpl_bivector_delete(skipped_bivector) ;
        cpl_frameset_delete(rawframes) ;
        cpl_msg_error(__func__, "At least two frames must be provided") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }

    /* Load IFUS if specified */
    if (strcmp(ifus_txt, "")) {
        ifus = kmo_identify_values(ifus_txt);
        if (ifus == NULL || cpl_vector_get_size(ifus) != nr_frames) {
            if (ifus != NULL) cpl_vector_delete(ifus);
            if (skipped_bivector!=NULL) cpl_bivector_delete(skipped_bivector) ;
            cpl_frameset_delete(rawframes) ;
            cpl_msg_error(__func__, "ifus size must match the science frames") ;
            cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
            return -1 ;
        }
    } else {
        ifus = NULL ;
    }

    /* Check for mapping mode */
    mapping_mode = NULL ;
    cpl_size fs_size = cpl_frameset_get_size(rawframes);
    for (ci = 0; ci < fs_size; ci++) {
        frame = cpl_frameset_get_position(rawframes, ci);
        tmp_header = kmclipm_propertylist_load(cpl_frame_get_filename(frame),0);
        if (cpl_propertylist_has(tmp_header, TPL_ID)) {
            tmp_strc = cpl_propertylist_get_string(tmp_header, TPL_ID);
            if (mapping_mode == NULL) {
                if (!strcmp(tmp_strc, MAPPING8)) 
                    mapping_mode = cpl_sprintf("%s", tmp_strc);
                if (!strcmp(tmp_strc, MAPPING24)) 
                    mapping_mode = cpl_sprintf("%s", tmp_strc);
            } else {
                if (strcmp(tmp_strc, mapping_mode)) {
                    cpl_msg_warning(__func__,
                            "There are different TPL IDs in input: %s and %s",
                            tmp_strc, mapping_mode);
                }
            }
        }
        cpl_propertylist_delete(tmp_header);
    }

    if (mapping_mode != NULL) {
        if (!strcmp(ifus_txt, "") && !strcmp(name, "")) {
            cpl_msg_info(__func__,"*****************************************");
            cpl_msg_info(__func__,"* A map with all IFUs will be generated *");
            cpl_msg_info(__func__,"*****************************************");
            extrapol_enum = BCS_NATURAL;
        } else {
            cpl_msg_info(__func__, "No Map as name / ifu is specified");
            cpl_free(mapping_mode);
            mapping_mode = NULL ;
        }
    }

    /* Mapping Id */
    mapping_id = -1 ;
    if (mapping_mode == NULL) {
        mapping_id = 0 ;
    } else {
        if (!strcmp(mapping_mode, MAPPING8)) mapping_id = 1 ;
        if (!strcmp(mapping_mode, MAPPING24)) mapping_id = 2 ;
    }

    /* Create name/ifu map... */
    name_vec = cpl_calloc(nr_frames*KMOS_NR_IFUS, sizeof(char*));

    /* No name / IFU specified - Non mapping mode */
    if (!strcmp(ifus_txt, "") && !strcmp(name, "") && mapping_mode==NULL) {
        /* All available names should be combined in one go */
        name_vec_size = 0;
        for (i = 0; i < nr_frames; i++) {
            tmp_str = cpl_sprintf("%d", i);
            frame = kmo_dfs_get_frame(rawframes, tmp_str);
            cpl_free(tmp_str);
            for (ifu_nr = 1; ifu_nr <= KMOS_NR_IFUS; ifu_nr++) {
                found = 0;
                tmp_str = kmo_get_name_from_ocs_ifu(frame, ifu_nr);
                if (tmp_str != NULL) {
                    for (j = 0; j < name_vec_size; j++) {
                        if (!strcmp(name_vec[j], tmp_str)) {
                            found = TRUE;
                            break;
                        }
                    }
                    if (!found)     name_vec[name_vec_size++] = tmp_str;
                    else            cpl_free(tmp_str);
                }
            }
        }
    } else {
        /* Standard behavior: either ifu_nr- or name- or mapping-case */
        name_vec_size = 1;
        if (mapping_mode != NULL) {
            name_vec[0] = cpl_sprintf("mapping");
        } else {
            if (ifus != NULL) {
                char *tmptmp = NULL;

                // replace all ; with _
                char *found_char = NULL;
                found_char = strstr(ifus_txt, ";");
                while (found_char != NULL) {
                    strncpy(found_char, "_", 1);
                    found_char = strstr(ifus_txt, ";");
                }

                if (strlen(ifus_txt) > 10) {
                    tmptmp = kmo_shorten_ifu_string(ifus_txt);
                    cpl_msg_info(__func__, "Truncate to ..ifu%s..", tmptmp);
                } else {
                    tmptmp = cpl_sprintf("%s", ifus_txt);
                }
                name_vec[0] = cpl_sprintf("IFU%s", tmptmp);
                ifus_txt = "";
                cpl_free(tmptmp); 
            } else {
                name_vec[0] = cpl_sprintf("%s", name);
            }
        }
    }
   
    /* Load data and noise */
    data_cube_list = cpl_calloc(nr_frames*KMOS_NR_IFUS, 
            sizeof(cpl_imagelist*));
    data_header_list = cpl_calloc(nr_frames*KMOS_NR_IFUS, 
            sizeof(cpl_propertylist*));
    noise_cube_list = cpl_calloc(nr_frames*KMOS_NR_IFUS,
            sizeof(cpl_imagelist*));
    noise_header_list = cpl_calloc(nr_frames*KMOS_NR_IFUS,
            sizeof(cpl_propertylist*));
 
    /*
    if (ifus != NULL) cpl_vector_delete(ifus);
    cpl_frameset_delete(rawframes) ;
    cpl_free(data_cube_list);
    cpl_free(noise_cube_list);
    cpl_free(data_header_list);
    cpl_free(noise_header_list);
    for (i = 0; i < name_vec_size ; i++) cpl_free(name_vec[i]);
    cpl_free(name_vec);
    if (mapping_mode != NULL) cpl_free(mapping_mode) ;
    return 0 ;
    */

    /* Load all data (and noise if existent) cubes and store them */
    for (nv = 0; nv < name_vec_size; nv++) {
        name_loc = name_vec[nv];

        used_frame_idx = cpl_calloc(nr_frames*KMOS_NR_IFUS, sizeof(int)) ;
        used_ifus = cpl_calloc(nr_frames*KMOS_NR_IFUS, sizeof(int)) ;
        skip_frame_idx = cpl_calloc(nr_frames*KMOS_NR_IFUS, sizeof(int)) ;
        skip_ifus = cpl_calloc(nr_frames*KMOS_NR_IFUS, sizeof(int)) ;
        
        data_cube_counter = 0;
        skip_cube_counter = 0;
        noise_cube_counter = 0;
        for (i = 0; i < nr_frames; i++) {
            tmp_str = cpl_sprintf("%d", i);
            frame = kmo_dfs_get_frame(rawframes, tmp_str);
            frame_filename = cpl_frame_get_filename(frame);
            kmo_init_fits_desc(&desc);
            desc = kmo_identify_fits_header(frame_filename);

            if (mapping_mode != NULL) {
                /* Mapping mode */
                for (j = 1; j <= KMOS_NR_IFUS; j++) {
                    /* Loop over all IFUs */
                    if (desc.sub_desc[j-1].valid_data == TRUE &&
                            !kmos_combine_is_skipped(skipped_bivector,i+1,j)) {
                        /* Load data frames */
                        override_err_msg = TRUE;
                        data_cube_list[data_cube_counter] =
                            kmo_dfs_load_cube(rawframes, tmp_str, j,FALSE);
                        override_err_msg = FALSE;
                        if (data_cube_list[data_cube_counter] == NULL) {
                            cpl_error_reset();
                        } else {
                            if (edge_nan) kmo_edge_nan(
                                    data_cube_list[data_cube_counter], j);
                            data_header_list[data_cube_counter] =
                                kmo_dfs_load_sub_header(rawframes, 
                                        tmp_str, j, FALSE);
                            cpl_propertylist_update_string(
                                    data_header_list[data_cube_counter],
                                    "ESO PRO FRNAME", frame_filename);
                            cpl_propertylist_update_int(
                                    data_header_list[data_cube_counter],
                                    "ESO PRO IFUNR", j);
                            used_frame_idx[data_cube_counter] = i+1 ;
                            used_ifus[data_cube_counter] = j ;
                            data_cube_counter++;
                        }

                        /* Load noise frames */
                        override_err_msg = TRUE;
                        noise_cube_list[noise_cube_counter] =
                            kmo_dfs_load_cube(rawframes, tmp_str, j, TRUE);

                        override_err_msg = FALSE;
                        if (noise_cube_list[noise_cube_counter] == NULL) {
                            // no noise found for this IFU
                            cpl_error_reset();
                        } else {
                            if (edge_nan) kmo_edge_nan(
                                    noise_cube_list[noise_cube_counter], j);
                            noise_header_list[noise_cube_counter] =
                                kmo_dfs_load_sub_header(rawframes, tmp_str,
                                        j, TRUE);
                            noise_cube_counter++;
                        }

                        /* Check if number of data and noise frames match */
                        if (noise_cube_counter > 0) {
                            if (data_cube_counter != noise_cube_counter) {
                                cpl_msg_error(__func__, "Noise missing") ;
                                cpl_error_set(__func__,CPL_ERROR_ILLEGAL_INPUT);
                                /* TODO - deallocate */
                                return -1 ;
                            }
                        }
                    } else if (kmos_combine_is_skipped(skipped_bivector, 
                                i+1, j)) {
                        skip_frame_idx[skip_cube_counter] = i+1 ;
                        skip_ifus[skip_cube_counter] = j ;
                        skip_cube_counter++;
                    }
                }
            } else {
                /* name/ifu mode (single) */
                if (ifus != NULL)   ifu_nr = cpl_vector_get(ifus, i);
                else ifu_nr = kmo_get_index_from_ocs_name(frame, name_loc);
                if (ifu_nr > 0) {
                    index = kmo_identify_index(frame_filename, ifu_nr , FALSE);
                    if (desc.sub_desc[index-1].valid_data == TRUE &&
                            !kmos_combine_is_skipped(skipped_bivector,i+1,
                                ifu_nr)) {
                        /* Load data frames */
                        override_err_msg = TRUE;
                        data_cube_list[data_cube_counter] =
                            kmo_dfs_load_cube(rawframes, tmp_str, ifu_nr, 
                                    FALSE);
                        override_err_msg = FALSE;
                        if (data_cube_list[data_cube_counter] == NULL) {
                            /* No data found for this IFU */
                            cpl_error_reset();
                            if (ifus != NULL)   cpl_msg_warning(cpl_func, 
                                    "IFU %d miѕsing in frame %s",
                                    ifu_nr, frame_filename);
                            else                cpl_msg_warning(cpl_func, 
                                    "Object %s missing in Frame %d (%s)",
                                    name_loc,  i+1, frame_filename) ;
                        } else {
                            if (edge_nan) kmo_edge_nan(
                                    data_cube_list[data_cube_counter], ifu_nr);
                            data_header_list[data_cube_counter] =
                                kmo_dfs_load_sub_header(rawframes, tmp_str,
                                        ifu_nr, FALSE);
                            cpl_propertylist_update_string(
                                    data_header_list[data_cube_counter],
                                    "ESO PRO FRNAME", frame_filename);
                            cpl_propertylist_update_int(
                                    data_header_list[data_cube_counter],
                                    "ESO PRO IFUNR", ifu_nr);
                            used_frame_idx[data_cube_counter] = i+1 ;
                            used_ifus[data_cube_counter] = ifu_nr ;
                            data_cube_counter++;
                        }

                        /* Load noise frames */
                        override_err_msg = TRUE;
                        noise_cube_list[noise_cube_counter] =
                            kmo_dfs_load_cube(rawframes, tmp_str, ifu_nr, 
                                    TRUE);
                        override_err_msg = FALSE;
                        if (noise_cube_list[noise_cube_counter] == NULL) {
                            /* No noise found for this IFU */
                            cpl_error_reset();
                        } else {
                            if (edge_nan) kmo_edge_nan(
                                    noise_cube_list[noise_cube_counter],ifu_nr);
                            noise_header_list[noise_cube_counter] =
                                    kmo_dfs_load_sub_header(rawframes, 
                                            tmp_str, ifu_nr, TRUE);
                            noise_cube_counter++;
                        }

                        /* Check if number of data and noise frames match */
                        if (noise_cube_counter > 0) {
                            if (data_cube_counter != noise_cube_counter) {
                                /* TODO - deallocate */
                                cpl_msg_error(__func__, "Noise missing") ;
                                cpl_error_set(__func__,CPL_ERROR_ILLEGAL_INPUT);
                                return -1 ;
                            }
                        }
                    } else if (kmos_combine_is_skipped(skipped_bivector, 
                                i+1, ifu_nr)) {
                        skip_frame_idx[skip_cube_counter] = i+1 ;
                        skip_ifus[skip_cube_counter] = ifu_nr ;
                        skip_cube_counter++;
                    }
                } 
            }
            kmo_free_fits_desc(&desc);
            cpl_free(tmp_str);
        } 
 
        /* Create String for the output header - IFUs usage */
        used_ifus_str = kmos_combine_create_ifus_string(used_frame_idx, 
                used_ifus, data_cube_counter);
        cpl_free(used_frame_idx) ;
        cpl_free(used_ifus) ;
        skip_ifus_str = kmos_combine_create_ifus_string(skip_frame_idx, 
                skip_ifus, skip_cube_counter);
        cpl_free(skip_frame_idx) ;
        cpl_free(skip_ifus) ;

        /* Combine data */
        exp_mask = NULL ;
        cube_combined_noise = NULL ;
        cube_combined_data = NULL ;
        if (kmo_priv_combine(data_cube_list, noise_cube_list, data_header_list,
                    noise_header_list, data_cube_counter, noise_cube_counter, 
                    name_loc, ifus_txt, method, "BCS", fmethod, filename, 
                    cmethod, cpos_rej, cneg_rej, citer, cmin, cmax, 
                    extrapol_enum, flux, &cube_combined_data, 
                    &cube_combined_noise, &exp_mask) != CPL_ERROR_NONE) {
            for (i = 0; i < data_cube_counter ; i++) 
                cpl_imagelist_delete(data_cube_list[i]);
            for (i = 0; i < noise_cube_counter ; i++) 
                cpl_imagelist_delete(noise_cube_list[i]);
            for (i = 0; i < data_cube_counter ; i++) 
                cpl_propertylist_delete(data_header_list[i]);
            for (i = 0; i < noise_cube_counter ; i++) 
                cpl_propertylist_delete(noise_header_list[i]);
            if (ifus != NULL) cpl_vector_delete(ifus);
            cpl_free(data_cube_list);
            cpl_free(noise_cube_list);
            cpl_free(data_header_list);
            cpl_free(noise_header_list);
            for (i = 0; i < name_vec_size ; i++) cpl_free(name_vec[i]);
            cpl_free(name_vec);
            cpl_free(used_ifus_str) ;
            cpl_free(skip_ifus_str) ;
            cpl_frameset_delete(rawframes) ;
            if (mapping_mode != NULL) cpl_free(mapping_mode) ;
            cpl_msg_error(__func__, "Failed Combination") ;
            cpl_error_set(__func__,CPL_ERROR_ILLEGAL_INPUT);
            return -1 ;
        }
        for (i = 0; i < data_cube_counter ; i++) 
            cpl_imagelist_delete(data_cube_list[i]);
        for (i = 0; i < noise_cube_counter ; i++) 
            cpl_imagelist_delete(noise_cube_list[i]);
        
        /*
        for (i = 0; i < data_cube_counter ; i++) 
            cpl_propertylist_delete(data_header_list[i]);
        for (i = 0; i < noise_cube_counter ; i++) 
            cpl_propertylist_delete(noise_header_list[i]);
        if (ifus != NULL) cpl_vector_delete(ifus);
        cpl_free(data_cube_list);
        cpl_free(noise_cube_list);
        cpl_free(data_header_list);
        cpl_free(noise_header_list);
        for (i = 0; i < name_vec_size ; i++) cpl_free(name_vec[i]);
        cpl_free(name_vec);
        if (mapping_mode != NULL) cpl_free(mapping_mode) ;
        cpl_image_delete(exp_mask);
        cpl_imagelist_delete(cube_combined_noise);
        cpl_imagelist_delete(cube_combined_data);
        if (noise_header_list!=NULL && noise_cube_counter==0) 
            cpl_propertylist_delete(noise_header_list[0]) ;
        cpl_frameset_delete(rawframes) ;
        */
        /* Save data */
        if (!suppress_extension) {
            /* Setup output category COMBINE + ESO PRO CATG */
            main_header = kmo_dfs_load_primary_header(rawframes, "0");
            fn_combine = cpl_sprintf("%s_%s_%s", COMBINE,
                    cpl_propertylist_get_string(main_header, CPL_DFS_PRO_CATG),
                    name_vec[nv]);
            fn_mask = cpl_sprintf("%s_%s_%s", EXP_MASK,
                    cpl_propertylist_get_string(main_header, CPL_DFS_PRO_CATG),
                    name_vec[nv]);
            cpl_propertylist_delete(main_header);
        } else {
            fn_combine = cpl_sprintf("%s_%d", COMBINE, suppress_index);
            fn_mask = cpl_sprintf("%s_%d", EXP_MASK, suppress_index++);
        }

        /* Create PRO keys plist */
        pro_plist = cpl_propertylist_new() ;
        if (used_ifus_str != NULL) 
            cpl_propertylist_update_string(pro_plist, "ESO PRO USEDIFUS", 
                    used_ifus_str) ;
        if (skip_ifus_str != NULL) 
            cpl_propertylist_update_string(pro_plist, "ESO PRO SKIPPEDIFUS", 
                    skip_ifus_str) ;

        /* Add REFLEX SUFFIX keyword */
        reflex_suffix = kmos_get_reflex_suffix(mapping_id,
                ifus_txt, name, name_loc) ;
        cpl_propertylist_update_string(pro_plist, "ESO PRO REFLEX SUFFIX", 
                reflex_suffix) ;
        cpl_free(reflex_suffix) ;

        /* Save Headers first */
        frame = cpl_frameset_find(rawframes, NULL);
        kmo_dfs_save_main_header(frameset, fn_combine, "", frame, pro_plist, 
                parlist, cpl_func);
        kmo_dfs_save_main_header(frameset, fn_mask, "", frame, pro_plist, 
                parlist, cpl_func);
        cpl_propertylist_delete(pro_plist) ;
        cpl_free(used_ifus_str) ;
        cpl_free(skip_ifus_str) ;

        /* Clean */
        if (data_header_list[0] != NULL) {
            if (cpl_propertylist_has(data_header_list[0], "ESO PRO FRNAME")) {
                cpl_propertylist_erase(data_header_list[0], "ESO PRO FRNAME");
            }
            if (cpl_propertylist_has(data_header_list[0], "ESO PRO IFUNR")) {
                cpl_propertylist_erase(data_header_list[0], "ESO PRO IFUNR");
            }
        }
        if (noise_header_list[0] != NULL) {
            if (cpl_propertylist_has(noise_header_list[0], "ESO PRO FRNAME")) {
                cpl_propertylist_erase(noise_header_list[0], "ESO PRO FRNAME");
            }
            if (cpl_propertylist_has(noise_header_list[0], "ESO PRO IFUNR")) {
                cpl_propertylist_erase(noise_header_list[0], "ESO PRO IFUNR");
            }
        }
        kmo_dfs_save_cube(cube_combined_data, fn_combine, "", 
                data_header_list[0], 0./0.);
        cpl_imagelist_delete(cube_combined_data);
        kmo_dfs_save_cube(cube_combined_noise, fn_combine, "", 
                noise_header_list[0], 0./0.);
        cpl_imagelist_delete(cube_combined_noise);
        cpl_free(fn_combine);
        
        cpl_propertylist_erase(data_header_list[0], CRPIX3);
        cpl_propertylist_erase(data_header_list[0], CRPIX3);
        cpl_propertylist_erase(data_header_list[0], CRVAL3);
        cpl_propertylist_erase(data_header_list[0], CDELT3);
        cpl_propertylist_erase(data_header_list[0], CD1_3);
        cpl_propertylist_erase(data_header_list[0], CD2_3);
        cpl_propertylist_erase(data_header_list[0], CD3_1);
        cpl_propertylist_erase(data_header_list[0], CD3_2);
        cpl_propertylist_erase(data_header_list[0], CD3_3);
        cpl_propertylist_erase(data_header_list[0], CTYPE3);
        kmo_dfs_save_image(exp_mask, fn_mask, "", data_header_list[0], 0./0.);
        cpl_free(fn_mask);
        cpl_image_delete(exp_mask);

        for (i = 0; i < data_cube_counter ; i++) 
            cpl_propertylist_delete(data_header_list[i]);
        for (i = 0; i < noise_cube_counter ; i++) 
            cpl_propertylist_delete(noise_header_list[i]);
        if (noise_header_list!=NULL && noise_cube_counter==0) 
            cpl_propertylist_delete(noise_header_list[0]) ;
    } 
    cpl_frameset_delete(rawframes) ;
    if (skipped_bivector!=NULL) cpl_bivector_delete(skipped_bivector) ;
    if (ifus != NULL) cpl_vector_delete(ifus);
    cpl_free(data_cube_list);
    cpl_free(noise_cube_list);
    cpl_free(data_header_list);
    cpl_free(noise_header_list);
    for (i = 0; i < name_vec_size ; i++) cpl_free(name_vec[i]);
    cpl_free(name_vec);
    cpl_free(mapping_mode) ;

    /* Collapse the combined cubes if requested */
    if (collapse_combined) {
        kmos_collapse_cubes(COMBINED_RECONS, frameset, parlist, 0.1, "",
                DEF_REJ_METHOD, DEF_POS_REJ_THRES, DEF_NEG_REJ_THRES,
                DEF_ITERATIONS, DEF_NR_MIN_REJ, DEF_NR_MAX_REJ) ;
    }
    return 0;
}

/**@}*/

/*----------------------------------------------------------------------------*/
/**
  @brief 
  @param  
  @return 
 */
/*----------------------------------------------------------------------------*/
static char * kmos_combine_create_ifus_string(
        const int   *   frame_idx,
        const int   *   ifus,
        int             nb)
{
    char        out[8192] ;
    char        tmp[7] ;
    int         i ;

    if (nb > 1000) return NULL ;
    if (nb == 0) return NULL ;
    for (i=0 ; i<nb ; i++) {
        /* Build the current string */
        if (frame_idx[i] < 10 && ifus[i] < 10) 
            if (i==0)   sprintf(tmp, "%1d:%1d", frame_idx[i], ifus[i]);
            else        sprintf(tmp, ",%1d:%1d", frame_idx[i], ifus[i]);
        else if (frame_idx[i] < 10 && ifus[i] >= 10) 
            if (i==0)   sprintf(tmp, "%1d:%2d", frame_idx[i], ifus[i]);
            else        sprintf(tmp, ",%1d:%2d", frame_idx[i], ifus[i]);
        else if (frame_idx[i] >= 10 && ifus[i] < 10) 
            if (i==0)   sprintf(tmp, "%2d:%1d", frame_idx[i], ifus[i]);
            else        sprintf(tmp, ",%2d:%1d", frame_idx[i], ifus[i]);
        else if (frame_idx[i] >= 10 && ifus[i] >= 10) 
            if (i==0)   sprintf(tmp, "%2d:%2d", frame_idx[i], ifus[i]);
            else        sprintf(tmp, ",%2d:%2d", frame_idx[i], ifus[i]);
        else return NULL ;

        if (i==0)   strcpy(out, tmp) ;
        else        strcat(out, tmp);
    }

    /* Warning : If larger than 51 char, it does not fit in the header card */
    if (strlen(out) > 51) return NULL ;

    return cpl_strdup(out) ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief 
  @param  
  @return 
 */
/*----------------------------------------------------------------------------*/
static cpl_bivector * kmos_combine_parse_skipped(const char * str)
{
    cpl_bivector    *   out ;
    cpl_vector      *   out_x ;
    cpl_vector      *   out_y ;
    char            *   my_str ;
    int                 nb_values ;
    char            *   s1 ;
    char            *   s2 ;
    int                 val1, val2, ret ;
    
    /* Check Entries */
    if (str == NULL) return NULL ;

    /* Initialise */
    nb_values = 0 ;
    my_str = cpl_strdup(str) ;
    
    /* Count the values */
    for (s2 = my_str; s2; ) {
        while (*s2 == ' ' || *s2 == '\t') s2++;
        s1 = strsep(&s2, ",") ;
        if (*s1) {
            if (sscanf(s1," %i:%i", &val1, &val2) == 2) {
                nb_values ++ ;
            }
        }
    }
    cpl_free(my_str) ;
    if (nb_values == 0) return NULL ;

    /* Create the vector */
    out = cpl_bivector_new(nb_values) ;
    out_x = cpl_bivector_get_x(out) ;
    out_y = cpl_bivector_get_y(out) ;
    
    /* Fill the vector */
    nb_values = 0 ;
    my_str = cpl_strdup(str) ;
    for (s2 = my_str; s2; ) {
        while (*s2 == ' ' || *s2 == '\t') s2++;
        s1 = strsep(&s2, ",") ;
        if (*s1) {
            if (sscanf(s1," %i:%i", &val1, &val2) == 2) {
                cpl_vector_set(out_x, nb_values, val1) ;
                cpl_vector_set(out_y, nb_values, val2) ;
                nb_values ++ ;
            }
        }
    }
    cpl_free(my_str) ;
    return out;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Tells if an IFU is skipped or not
  @param    The skipped bivector
  @param    frame index (1->first raw file)
  @param    The IFU number
  @return   Boolean : 1 if the IFU needs to be skipped.
 */
/*----------------------------------------------------------------------------*/
static int kmos_combine_is_skipped(
        const cpl_bivector  *   skipped, 
        int                     frame_idx,
        int                     ifu_nr) 
{
    const cpl_vector    *   vec_x ;
    const cpl_vector    *   vec_y ;
    double                  val1, val2 ;
    int                     i ;

    /* Check entries */
    if (skipped == NULL) return 0;

    /* Initialise */
    vec_x = cpl_bivector_get_x_const(skipped) ;
    vec_y = cpl_bivector_get_y_const(skipped) ;

    /* Loop */
    for (i=0 ; i<cpl_bivector_get_size(skipped) ; i++) {
        val1 = cpl_vector_get(vec_x, i) ;
        val2 = cpl_vector_get(vec_y, i) ;
        if (fabs(val1-frame_idx)<1e-3 && fabs(val2-ifu_nr)<1e-3) return 1 ; 
    }
    return 0 ;
}

