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

#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"

static int kmo_combine_create(cpl_plugin *);
static int kmo_combine_exec(cpl_plugin *);
static int kmo_combine_destroy(cpl_plugin *);
static int kmo_combine(cpl_parameterlist *, cpl_frameset *);

static char kmo_combine_description[] =
"This recipe shifts several exposures of an object and combines them. The diffe-\n"
"rent methods to match the exposures are described below (--method parameter).\n"
"The output cube is larger than the input cubes, according to the shifts to be\n"
"applied. Additionally a border of NaN values is added. The WCS is the same as\n"
"for the first exposure.\n"
"For each spatial/spectral pixel a new value will be calculated (according the\n"
"--cmethod parameter) and written into the output cube.\n"
"Only exposures with equal orientation regarding the WCS can be combined (except\n"
"-–method=”none”), north must point to the same direction. It is recommended to\n"
"apply any rotation possibly after combining.\n"
"\n"
"The behavior of the selection of IFUs to combine differs for some templates and\n"
"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 into\n"
"a single map by default (like in recipe kmo_sci_red). If just the area of a\n"
"specific IFU should be combined, the parameter --ifus can be specified, or more\n"
"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 into\n"
"several output frames by default. The input IFUs are grouped according their\n"
"targeted object name stored in the keywords ESO OCS ARMx NAME. If just a\n"
"specific object should be combined, its name can be specified with parameter\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 all input data cubes IFUs\n"
"pointing to that object.\n"
"\n"
"BASIC PARAMETERS:\n"
"-----------------\n"
"--name\n"
"--ifus\n"
"Since an object can be present only once per exposure and since it can be\n"
"located in different IFUs for the existing exposures, there are two modes to\n"
"identify the objects:\n"
"   * Combine by object names (default)\n"
"   In this case the object name must be provided via the --name parameter. The\n"
"   object name will be searched for in all primary headers of all provided frames\n"
"   in the keyword ESO OCS ARMx NAME.\n"
"\n"
"   * Combine by index (advanced)\n"
"   In this case the --ifus parameter must be provided. The parameter must have\n"
"   the same number of entries as frames are provided, e.g. \"3;1;24\" for 3 expo-\n"
"   sures. The index doesn't reference the extension in the frame but the real\n"
"   index of the IFU as defined in the EXTNAME keyword (e.g. 'IFU.3.DATA').\n"
"\n"
"--method\n"
"There are following sources to get the shift parameters from:\n"
"   * 'none' (default)\n"
"   The cubes are directly recombined, not shifting at all. The ouput frame will\n"
"   have the same dimensions as the input cubes.\n"
"   If the size differs a warning will be emitted and the cubes will be aligned\n"
"   to the lower left corner. If the orientation differs a warning will be emit-\n"
"   ted, but the cubes are combined anyway.\n"
"\n"
"   * 'header'\n"
"   The shifts are calculated according to the WCS information stored in the\n"
"   header of every IFU. The output frame will get larger, except the object is\n"
"   at the exact same position for all exposures. The size of the exposures can\n"
"   differ, but the orientation must be the same for all exposures.\n"
"\n"
"   * 'center'\n"
"   The shifts are calculated using a centering algorithm. The cube will be col-\n"
"   lapsed and a 2D profile will be fitted to it to identify the centre. With \n"
"   the parameter --fmethod the function to fit can be provided. The size of the\n"
"   exposures can differ, but the orientation must be the same for all exposures.\n"
"\n"
"   * 'user'\n"
"   Read the shifts from a user specified file. The path of the file must be pro-\n"
"   vided using the --filename parameter. For every exposure (except the first one)\n"
"   two shift values are expected per line, they have to be separated with simple\n"
"   spaces. The values indicate pixel shifts and are referenced to the first\n"
"   frame. The 1st value is the shift in x-direction to the left, the 2nd the\n"
"   shift in y-direction upwards. The size of the exposures can differ, but the\n"
"   orientation must be the same for all exposures.\n"
"\n"
"--cmethod\n"
"Following methods of frame combination are available:\n"
"   * 'ksigma' (Default)\n"
"   An iterative sigma clipping. For each position all pixels in the spectrum\n"
"   are examined. If they deviate significantly, they will be rejected according\n"
"   to the conditions:\n"
"       val > mean + stdev * cpos_rej\n"
"   and\n"
"       val < mean - stdev * cneg_rej\n"
"   where --cpos_rej, --cneg_rej and --citer are the corresponding configuration\n"
"   parameters. In the first iteration median and percentile level are used.\n"
"\n"
"   * 'median'\n"
"   At each pixel position the median is calculated.\n"
"\n"
"   * 'average'\n"
"   At each pixel position the average is calculated.\n"
"\n"
"   * 'sum'\n"
"   At each pixel position the sum is calculated.\n"
"\n"
"   * 'min_max'\n"
"   The specified number of minimum and maximum pixel values will be rejected.\n"
"   --cmax and --cmin apply to this method.\n"
"\n"
"ADVANCED PARAMETERS\n"
"-------------------\n"
"--edge_nan\n"
"Set borders of two sides of the cubes to NaN before combining them. This minimises\n"
"unwanted border effects when dithering.\n"
"\n"
"--fmethod\n"
"see --method='center'\n"
"The type of function that should be fitted spatially to the collapsed image.\n"
"This fit is used to create a mask to extract the spectrum of the object. Valid\n"
"values are “gauss” and “moffat”.\n"
"\n"
"--filename\n"
"see --method='user'\n"
"\n"
"--cpos_rej\n"
"--cneg_rej\n"
"--citer\n"
"see --cmethod='ksigma'\n"
"\n"
"--cmax\n"
"--cmin\n"
"see --cmethod='min_max'\n"
"\n"
"--flux\n"
"Specify if flux conservation should be applied.\n"
"\n"
"--suppress_extension\n"
"If set to TRUE, the arbitrary filename extensions are supressed. If multiple\n"
"products with the same category are produced, they will be numered consecutively\n"
"starting from 0.\n"
"\n"
"-------------------------------------------------------------------------------\n"
"  Input files:\n"
"\n"
"   DO                      KMOS                                                \n"
"   category                Type   Explanation                  Required #Frames\n"
"   --------                -----  -----------                  -------- -------\n"
"   <none or any>           F3I    data frame                       Y      2-n  \n"
"\n"
"  Output files:\n"
"\n"
"   DO                      KMOS\n"
"   category                Type   Explanation\n"
"   --------                -----  -----------\n"
"   COMBINE_<ESO PRO CATG>  F3I    Combined data cube\n"
"   EXP_MASK_<ESO PRO CATG> F3I    Exposure time mask\n"
"-------------------------------------------------------------------------------\n"
"\n";

/**
 * @defgroup kmo_combine kmo_combine Combine cubes
 *
 * See recipe description for details.
 */

/**@{*/

/**
  @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,
                        "kmo_combine",
                        "Combine reconstructed cubes",
                        kmo_combine_description,
                        "Alex Agudo Berbel",
                        "usd-help@eso.org",
                        kmos_get_license(),
                        kmo_combine_create,
                        kmo_combine_exec,
                        kmo_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 kmo_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.kmo_combine.name",
                                CPL_TYPE_STRING,
                                "Name of the object to combine.",
                                "kmos.kmo_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.kmo_combine.ifus",
                                CPL_TYPE_STRING,
                                "The indices of the IFUs to combine. "
                                "\"ifu1;ifu2;...\"",
                                "kmos.kmo_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.kmo_combine.method",
                                CPL_TYPE_STRING,
                                "The shifting method:   "
                                "'none': no shifting, combined directly "
                                                                  "(default), "
                                "'header': shift according to WCS, "
                                "'center': centering algorithm, "
                                "'user': read shifts from file",
                                "kmos.kmo_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.kmo_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.kmo_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.kmo_combine.filename",
                                CPL_TYPE_STRING,
                                "The path to the file with the shift vectors."
                                "(Applies only to method='user')",
                                "kmos.kmo_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.kmo_combine.flux",
                                CPL_TYPE_BOOL,
                                "Apply flux conservation: "
                                "(TRUE (apply) or "
                                "FALSE (don't apply)",
                                "kmos.kmo_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.kmo_combine.edge_nan",
                                CPL_TYPE_BOOL,
                                "Set borders of cubes to NaN before combining them."
                                "(TRUE (apply) or "
                                "FALSE (don't apply)",
                                "kmos.kmo_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);

    /* --suppress_extension */
    p = cpl_parameter_new_value("kmos.kmo_combine.suppress_extension",
                                CPL_TYPE_BOOL,
                                "Suppress arbitrary filename extension."
                                "(TRUE (apply) or FALSE (don't apply)",
                                "kmos.kmo_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);

    return kmos_combine_pars_create(recipe->parameters,
                                   "kmos.kmo_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 kmo_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 kmo_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 kmo_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 kmo_combine(cpl_parameterlist *parlist, cpl_frameset *frameset)
{
    int              ret_val             = 0,
                     nr_frames           = 0,
                     index               = 0,
                     data_cube_counter   = 0,
                     noise_cube_counter  = 0,
                     citer               = 0,
                     cmin                = 0,
                     cmax                = 0,
                     flux                = FALSE,
                     edge_nan            = FALSE,
                     name_vec_size       = 0,
                     found               = 0,
                     suppress_extension  = FALSE,
                     suppress_index      = 0,
                     i                   = 0,
                     j                   = 0,
                     ifu_nr              = 0,
                     nv                  = 0;
    double           cpos_rej            = 0.0,
                     cneg_rej            = 0.0;
    char             *tmp_str            = NULL,
                     *fn_combine         = NULL,
                     *fn_mask            = NULL,
                     *mapping_mode       = NULL,
                     *name               = NULL,
                     **name_vec          = NULL;
    const char       *method             = NULL,
                     *cmethod            = NULL,
                     *fmethod            = NULL,
                     *filename           = NULL,
                     *frame_filename     = NULL,
                     *ifus_txt           = NULL,
                     *tmp_strc           = NULL;
    cpl_vector       *ifus               = NULL;
    cpl_image        *exp_mask           = NULL;
    cpl_imagelist    **data_cube_list    = NULL,
                     **noise_cube_list   = NULL,
                     *cube_combined_data = NULL,
                     *cube_combined_noise= NULL;
    cpl_propertylist *main_header        = NULL,
                     **data_header_list  = NULL,
                     **noise_header_list = NULL,
                     *tmp_header         = NULL;
    cpl_frame        *frame              = NULL;
    cpl_size         ci                  = 0;
    main_fits_desc   desc;
    enum extrapolationType extrapol_enum = NONE_CLIPPING;

    KMO_TRY
    {
        /* --- check input --- */
        KMO_TRY_ASSURE((parlist != NULL) &&
                       (frameset != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data is provided!");

        nr_frames = cpl_frameset_get_size(frameset);

        KMO_TRY_ASSURE(nr_frames >= 2,
                       CPL_ERROR_NULL_INPUT,
                       "At least two frames must be provided to combine!");

        KMO_TRY_ASSURE(kmo_dfs_set_groups(frameset, "kmo_combine") == 1,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Cannot identify RAW and CALIB frames!");

        cpl_msg_info("", "--- Parameter setup for kmo_combine -------");

        KMO_TRY_EXIT_IF_NULL(
            method = kmo_dfs_get_parameter_string(parlist,
                                           "kmos.kmo_combine.method"));

        KMO_TRY_EXIT_IF_NULL(
            fmethod = kmo_dfs_get_parameter_string(parlist,
                                           "kmos.kmo_combine.fmethod"));

        KMO_TRY_ASSURE((strcmp(method, "none") == 0) ||
                       (strcmp(method, "header") == 0) ||
                       (strcmp(method, "center") == 0) ||
                       (strcmp(method, "user") == 0),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Following shift methods are available : 'none', "
                       "'header', 'center' or 'user'");

        if (strcmp(method, "user") == 0) {
            filename = kmo_dfs_get_parameter_string(parlist,
                                                   "kmos.kmo_combine.filename");
            KMO_TRY_CHECK_ERROR_STATE();

            KMO_TRY_ASSURE(strcmp(filename, "") != 0,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "path of file with shift information must be "
                           "provided!");

            KMO_TRY_EXIT_IF_ERROR(
                kmo_dfs_print_parameter_help(parlist,
                                             "kmos.kmo_combine.filename"));
        }

        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_combine.method"));

        ifus_txt = kmo_dfs_get_parameter_string(parlist,
                                                  "kmos.kmo_combine.ifus");
        KMO_TRY_CHECK_ERROR_STATE();

        name = (char*)kmo_dfs_get_parameter_string(parlist, "kmos.kmo_combine.name");
        KMO_TRY_CHECK_ERROR_STATE();

        if (strcmp(ifus_txt, "") != 0) {
            KMO_TRY_ASSURE(strcmp(name, "") == 0,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "name parameter must be NULL if IFU indices are "
                           "provided!");

            KMO_TRY_EXIT_IF_NULL(
                ifus = kmo_identify_values(ifus_txt));

            KMO_TRY_ASSURE(cpl_vector_get_size(ifus) == nr_frames,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "ifus parameter must have the same number of values "
                           "than frames provided ) (%lld!=%d)",
                           cpl_vector_get_size(ifus), nr_frames);
        }

        if (strcmp(name, "") != 0) {
            KMO_TRY_ASSURE(strcmp(ifus_txt, "") == 0,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "ifus parameter must be NULL if name is provided!");
        }

        flux = kmo_dfs_get_parameter_bool(parlist,
                                          "kmos.kmo_combine.flux");
        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_combine.flux"));

        KMO_TRY_ASSURE((flux == TRUE) || (flux == FALSE),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "flux must be TRUE or FALSE!");

        edge_nan = kmo_dfs_get_parameter_bool(parlist,
                                          "kmos.kmo_combine.edge_nan");
        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_combine.edge_nan"));

        KMO_TRY_ASSURE((edge_nan == TRUE) || (edge_nan == FALSE),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "edge_nan must be TRUE or FALSE!");

        suppress_extension = kmo_dfs_get_parameter_bool(parlist,
                                          "kmos.kmo_combine.suppress_extension");
        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_combine.suppress_extension"));

        KMO_TRY_ASSURE((suppress_extension == TRUE) || (suppress_extension == FALSE),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "suppress_extension must be TRUE or FALSE!");

        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_combine.ifus"));

        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_combine.name"));

        KMO_TRY_EXIT_IF_ERROR(
            kmos_combine_pars_load(parlist,
                                  "kmos.kmo_combine",
                                  &cmethod,
                                  &cpos_rej,
                                  &cneg_rej,
                                  &citer,
                                  &cmin,
                                  &cmax,
                                  FALSE));

        cpl_msg_info("", "-------------------------------------------");

        // load data and noise
        KMO_TRY_EXIT_IF_NULL(
            data_cube_list = cpl_calloc(nr_frames*KMOS_NR_IFUS,
                                        sizeof(cpl_imagelist*)));

        KMO_TRY_EXIT_IF_NULL(
            data_header_list = cpl_calloc(nr_frames*KMOS_NR_IFUS,
                                          sizeof(cpl_propertylist*)));

        KMO_TRY_EXIT_IF_NULL(
            noise_cube_list = cpl_calloc(nr_frames*KMOS_NR_IFUS,
                                         sizeof(cpl_imagelist*)));

        KMO_TRY_EXIT_IF_NULL(
            noise_header_list = cpl_calloc(nr_frames*KMOS_NR_IFUS,
                                           sizeof(cpl_propertylist*)));

        //
        // check for mapping mode
        //
        cpl_size fs_size = cpl_frameset_get_size(frameset);
        KMO_TRY_CHECK_ERROR_STATE();

        for (ci = 0; ci < fs_size; ci++) {
            KMO_TRY_EXIT_IF_NULL(
                frame = cpl_frameset_get_position(frameset, ci));

            KMO_TRY_EXIT_IF_NULL(
                tmp_header = kmclipm_propertylist_load(cpl_frame_get_filename(frame), 0));
            if (cpl_propertylist_has(tmp_header, TPL_ID)) {
                KMO_TRY_EXIT_IF_NULL(
                    tmp_strc = cpl_propertylist_get_string(tmp_header, TPL_ID));
                if (mapping_mode == NULL) {
                    if (strcmp(tmp_strc, MAPPING8) == 0)
                    {
                        mapping_mode = cpl_sprintf("%s", tmp_strc);
                    }
                    if (strcmp(tmp_strc, MAPPING24) == 0)
                    {
                        mapping_mode = cpl_sprintf("%s", tmp_strc);
                    }
                } else {
                    if (strcmp(tmp_strc, mapping_mode) != 0)
                    {
                        cpl_msg_warning("","There are different TPL IDs present in "
                                           "the set of frames: %s and %s",
                                           tmp_strc, mapping_mode);
                    }
                }
            }
            cpl_propertylist_delete(tmp_header); tmp_header = NULL;
        }

        if (mapping_mode != NULL) {
            if ((strcmp(ifus_txt, "") == 0)  && (strcmp(name, "") == 0)) {
                cpl_msg_info("","**************************************************");
                cpl_msg_info("","*  A map containing all IFUs will be generated!  *");
                cpl_msg_info("","**************************************************");
                extrapol_enum = BCS_NATURAL;
            } else {
                cpl_msg_info("","The frames aren't combined into a map although they originate "
                                "from a mapping template. But since the name- or ifu-parameter "
                                "has been specified, the default behaviour is overridden.");
                cpl_free(mapping_mode); mapping_mode = NULL;
            }
        }

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

        if ((strcmp(ifus_txt, "") == 0) &&
            (strcmp(name, "") == 0) &&
            (mapping_mode == NULL))
        {
            // all available names should be combined in one go
            name_vec_size = 0;
            for (i = 0; i < nr_frames; i++) {
                KMO_TRY_EXIT_IF_NULL(
                    tmp_str = cpl_sprintf("%d", i));
                KMO_TRY_EXIT_IF_NULL(
                    frame = kmo_dfs_get_frame(frameset, tmp_str));
                cpl_free(tmp_str); tmp_str = NULL;

                for (ifu_nr = 1; ifu_nr <= KMOS_NR_IFUS; ifu_nr++) {
                    found = 0;
                    tmp_str = kmo_get_name_from_ocs_ifu(frame, ifu_nr);
                    KMO_TRY_CHECK_ERROR_STATE();

                    if (tmp_str != NULL) {
                        for (j = 0; j < name_vec_size; j++) {
                            if (strcmp(name_vec[j], tmp_str) == 0) {
                                found = TRUE;
                                break;
                            }
                        }
                        if (!found) {
                            name_vec[name_vec_size++] = tmp_str;
                        } else {
                            cpl_free(tmp_str); tmp_str = NULL;
                        }
                    }
                }
            }
        } else {
            // standard behavior: either ifu_nr- or name- or mapping-case
            name_vec_size = 1;
            if (mapping_mode != NULL) {
                KMO_TRY_EXIT_IF_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) {
                        KMO_TRY_EXIT_IF_NULL(
                            tmptmp = kmo_shorten_ifu_string(ifus_txt));
                        cpl_msg_info("", "Because of lengthy ifus-parameter, filenames of products and their EXTNAME keywords will be truncated to ...ifu%s...", tmptmp);
                    } else {
                        KMO_TRY_EXIT_IF_NULL(
                            tmptmp = cpl_sprintf("%s", ifus_txt));
                    }
                    KMO_TRY_EXIT_IF_NULL(
                        name_vec[0] = cpl_sprintf("IFU%s", tmptmp));
                    ifus_txt = "";
                    cpl_free(tmptmp); tmptmp = NULL;
                } else {
                    KMO_TRY_EXIT_IF_NULL(
                        name_vec[0] = cpl_sprintf("%s", name));
                }
            }
        }

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

            data_cube_counter = 0;
            noise_cube_counter = 0;
            for (i = 0; i < nr_frames; i++) {
                KMO_TRY_EXIT_IF_NULL(
                    tmp_str = cpl_sprintf("%d", i));

                KMO_TRY_EXIT_IF_NULL(
                    frame = kmo_dfs_get_frame(frameset, tmp_str));

                KMO_TRY_EXIT_IF_NULL(
                    frame_filename = cpl_frame_get_filename(frame));

                kmo_init_fits_desc(&desc);

                desc = kmo_identify_fits_header(frame_filename);
                KMO_TRY_CHECK_ERROR_STATE_MSG("Provided fits file doesn't seem to "
                                              "be in KMOS-format!");

                KMO_TRY_ASSURE(desc.fits_type == f3i_fits,
                               CPL_ERROR_ILLEGAL_INPUT,
                               "Frame No. %d hasn't correct data type "
                               "(must be of type F3I)!", i+1);

                if (mapping_mode != NULL) {
                    // we are in mapping mode
                    for (j = 1; j <= KMOS_NR_IFUS; j++) {
                        //loop over all IFUs
                        if (desc.sub_desc[j-1].valid_data == TRUE) {
                            // load data frames
                            override_err_msg = TRUE;
                            data_cube_list[data_cube_counter] =
                                kmo_dfs_load_cube(frameset, tmp_str, j, FALSE);
                            override_err_msg = FALSE;
                            if (data_cube_list[data_cube_counter] == NULL) {
                                // no data found for this IFU
                                cpl_error_reset();
                            } else {
                                if (edge_nan) {
                                    KMO_TRY_EXIT_IF_ERROR(
                                        kmo_edge_nan(data_cube_list[data_cube_counter], j));
                                }
                                KMO_TRY_EXIT_IF_NULL(
                                    data_header_list[data_cube_counter] =
                                        kmo_dfs_load_sub_header(frameset, 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);
                                data_cube_counter++;
                            }

                            // load noise frames
                            override_err_msg = TRUE;
                            noise_cube_list[noise_cube_counter] =
                                kmo_dfs_load_cube(frameset, 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_TRY_EXIT_IF_ERROR(
                                        kmo_edge_nan(noise_cube_list[noise_cube_counter], j));
                                }
                                KMO_TRY_EXIT_IF_NULL(
                                    noise_header_list[noise_cube_counter] =
                                        kmo_dfs_load_sub_header(frameset, tmp_str, j, TRUE));
                                noise_cube_counter++;
                            }

                            // check for every iteration if number of data and noise
                            // frames is the same
                            if (noise_cube_counter > 0) {
                                KMO_TRY_ASSURE(data_cube_counter == noise_cube_counter,
                                               CPL_ERROR_ILLEGAL_INPUT,
                                               "Frame No. %d (%s) has no noise frame "
                                               "while the preceeding ones had!",
                                               i+1, frame_filename);
                            }
                        } // end if valid_data
                    }
                } else {
                    // we are in name/ifu mode (single)
                    if (ifus != NULL) {
                        ifu_nr = cpl_vector_get(ifus, i);
                        KMO_TRY_CHECK_ERROR_STATE();
                    } else {
                        ifu_nr = kmo_get_index_from_ocs_name(frame, name);
                        KMO_TRY_CHECK_ERROR_STATE();
                    }

                    if (ifu_nr > 0) {
                        index = kmo_identify_index(frame_filename, ifu_nr , FALSE);
                        KMO_TRY_CHECK_ERROR_STATE();

                        if (desc.sub_desc[index-1].valid_data == TRUE) {
                            // load data frames
                            override_err_msg = TRUE;
                            data_cube_list[data_cube_counter] =
                                kmo_dfs_load_cube(frameset, 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, "Frame No. %d (%s) "
                                                    "doesn't contain IFU No. %d!", i+1,
                                                    frame_filename, ifu_nr);
                                } else {
                                    cpl_msg_warning(cpl_func, "Frame No. %d (%s) "
                                                    "doesn't contain IFU with object "
                                                    "name '%s'!", i+1,
                                                    frame_filename, name);
                                }
                            } else {
                                if (edge_nan) {
                                    KMO_TRY_EXIT_IF_ERROR(
                                        kmo_edge_nan(data_cube_list[data_cube_counter], ifu_nr));
                                }

                                KMO_TRY_EXIT_IF_NULL(
                                    data_header_list[data_cube_counter] =
                                        kmo_dfs_load_sub_header(frameset, 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);
                                data_cube_counter++;
                            }

                            // load noise frames
                            override_err_msg = TRUE;
                            noise_cube_list[noise_cube_counter] =
                                kmo_dfs_load_cube(frameset, 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_TRY_EXIT_IF_ERROR(
                                        kmo_edge_nan(noise_cube_list[noise_cube_counter], ifu_nr));
                                }

                                KMO_TRY_EXIT_IF_NULL(
                                    noise_header_list[noise_cube_counter] =
                                        kmo_dfs_load_sub_header(frameset, tmp_str,
                                                                ifu_nr, TRUE));
                                noise_cube_counter++;
                            }

                            // check for every iteration if number of data and noise
                            // frames is the same
                            if (noise_cube_counter > 0) {
                                KMO_TRY_ASSURE(data_cube_counter == noise_cube_counter,
                                               CPL_ERROR_ILLEGAL_INPUT,
                                               "Frame No. %d (%s) has no noise frame "
                                               "while the preceeding ones had!",
                                               i+1, frame_filename);
                            }
                        } // end if valid_data
                    } // end if (ifu_nr > 0)
                }

                kmo_free_fits_desc(&desc);
                cpl_free(tmp_str); tmp_str = NULL;
            } // for i = nr_frames
            KMO_TRY_CHECK_ERROR_STATE();

            //
            // combine data
            //
            KMO_TRY_EXIT_IF_ERROR(
                kmo_priv_combine(data_cube_list,
                                 noise_cube_list,
                                 data_header_list,
                                 noise_header_list,
                                 data_cube_counter,
                                 noise_cube_counter,
                                 name,
                                 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));

            //
            // save data
            //
            /* save data and noise (if existing) */
            // --- load, update & save primary header ---


            if (!suppress_extension) {
                // setup output category COMBINE + ESO PRO CATG
                KMO_TRY_EXIT_IF_NULL(
                    main_header = kmo_dfs_load_primary_header(frameset, "0"));
                KMO_TRY_EXIT_IF_NULL(
                    fn_combine = cpl_sprintf("%s_%s_%s",
                                             COMBINE,
                                             cpl_propertylist_get_string(main_header, CPL_DFS_PRO_CATG),
                                             name_vec[nv]));
                KMO_TRY_EXIT_IF_NULL(
                    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); main_header = NULL;
            } else {
                KMO_TRY_EXIT_IF_NULL(
                    fn_combine = cpl_sprintf("%s_%d", COMBINE, suppress_index));
                KMO_TRY_EXIT_IF_NULL(
                    fn_mask = cpl_sprintf("%s_%d", EXP_MASK, suppress_index++));
            }

            frame = cpl_frameset_find(frameset, NULL);
            KMO_TRY_EXIT_IF_ERROR(
                kmo_dfs_save_main_header(frameset, fn_combine, "", frame, NULL, parlist, cpl_func));
            KMO_TRY_EXIT_IF_ERROR(
                kmo_dfs_save_main_header(frameset, fn_mask, "", frame, NULL, parlist, cpl_func));

            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");
                }
            }
            KMO_TRY_CHECK_ERROR_STATE();

            KMO_TRY_EXIT_IF_ERROR(
                kmo_dfs_save_cube(cube_combined_data, fn_combine, "", data_header_list[0], 0./0.));
            KMO_TRY_EXIT_IF_ERROR(
                kmo_dfs_save_cube(cube_combined_noise, fn_combine, "", noise_header_list[0], 0./0.));
            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_TRY_EXIT_IF_ERROR(
                kmo_dfs_save_image(exp_mask, fn_mask, "", data_header_list[0], 0./0.));

            cpl_imagelist_delete(cube_combined_data); cube_combined_data = NULL;
            cpl_imagelist_delete(cube_combined_noise); cube_combined_noise = NULL;
            cpl_image_delete(exp_mask); exp_mask = NULL;

            if (data_cube_list != NULL) {
                for (i = 0; i < nr_frames*KMOS_NR_IFUS; i++) {
                    cpl_imagelist_delete(data_cube_list[i]); data_cube_list[i] = NULL;
                }
            }
            if (noise_cube_list != NULL) {
                for (i = 0; i < nr_frames*KMOS_NR_IFUS; i++) {
                    cpl_imagelist_delete(noise_cube_list[i]); noise_cube_list[i] = NULL;
                }
            }
            if (data_header_list != NULL) {
                for (i = 0; i < nr_frames*KMOS_NR_IFUS; i++) {
                    cpl_propertylist_delete(data_header_list[i]);
                    data_header_list[i] = NULL;
                }
            }
            if (noise_header_list != NULL) {
                for (i = 0; i < nr_frames*KMOS_NR_IFUS; i++) {
                    cpl_propertylist_delete(noise_header_list[i]);
                    noise_header_list[i] = NULL;
                }
            }
            cpl_free(fn_combine); fn_combine = NULL;
            cpl_free(fn_mask); fn_mask = NULL;
        } // end for (nv = 0; nv < name_vec_size; nv++)
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_val = -1;
    }

    cpl_propertylist_delete(main_header); main_header = NULL;
    cpl_vector_delete(ifus); ifus = NULL;
    cpl_imagelist_delete(cube_combined_data); cube_combined_data = NULL;
    cpl_imagelist_delete(cube_combined_noise); cube_combined_noise = NULL;
    cpl_image_delete(exp_mask); exp_mask = NULL;

    if (data_cube_list != NULL) {
        for (i = 0; i < nr_frames*KMOS_NR_IFUS; i++) {
            cpl_imagelist_delete(data_cube_list[i]); data_cube_list[i] = NULL;
        }
        cpl_free(data_cube_list); data_cube_list = NULL;
    }

    if (noise_cube_list != NULL) {
        for (i = 0; i < nr_frames*KMOS_NR_IFUS; i++) {
            cpl_imagelist_delete(noise_cube_list[i]); noise_cube_list[i] = NULL;
        }
        cpl_free(noise_cube_list); noise_cube_list = NULL;
    }

    if (data_header_list != NULL) {
        for (i = 0; i < nr_frames*KMOS_NR_IFUS; i++) {
            cpl_propertylist_delete(data_header_list[i]);
            data_header_list[i] = NULL;
        }
        cpl_free(data_header_list); data_header_list = NULL;
    }

    if (noise_header_list != NULL) {
        for (i = 0; i < nr_frames*KMOS_NR_IFUS; i++) {
            cpl_propertylist_delete(noise_header_list[i]);
            noise_header_list[i] = NULL;
        }
        cpl_free(noise_header_list); noise_header_list = NULL;
    }

    if (name_vec != NULL) {
        for (i = 0; i < nr_frames*KMOS_NR_IFUS; i++) {
            cpl_free(name_vec[i]); name_vec[i] = NULL;
        }
        cpl_free(name_vec); name_vec = NULL;
    }
    cpl_free(mapping_mode);mapping_mode = NULL;

    return ret_val;
}

/**@}*/
