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

#include "kmo_debug.h"
#include "kmo_utils.h"
#include "kmo_dfs.h"
#include "kmo_error.h"
#include "kmo_priv_arithmetic.h"
#include "kmo_constants.h"

static int kmo_arithmetic_create(cpl_plugin *);
static int kmo_arithmetic_exec(cpl_plugin *);
static int kmo_arithmetic_destroy(cpl_plugin *);
static int kmo_arithmetic(cpl_parameterlist *, cpl_frameset *);

static char kmo_arithmetic_description[] =
"With this recipe simple arithmetic operations, like addition, subtraction,\n"
"multiplication, divison and raising to a power can be performed.\n"
"Since FITS files formatted as F1I, F2I and F3I can contain data (and eventually\n"
"noise) of either just one IFU or of all 24 IFUs, kmo_arithmetic behaves diffe-\n"
"rently in these cases.\n"
"When the number of IFUs is the same for both operands, the first IFU of the\n"
"first operand is processed with the first IFU of the second operand.\n"
"When the second operand has only one IFU while the first operand has more IFUs,\n"
"then the all the IFUs of the first operand are processed individually which the\n"
"IFU of the second operand.\n"
"If an operand contains noise and the other doesn't, the noise will not be\n"
"processed.\n"
"\n"
"Noise is only propagated if both operand contain noise extensions. If the\n"
"second operator is a scalar noise is also propagated, of course.\n"
"\n"
"If two cubes are given as operands, they will be combined according to the \n"
"given operator.If a cube is given as first operand and an image as second,\n"
"then it operates on each slice of the cube; similarly if a spectrum is given\n"
"as the second operand, it operates on each spectrum of the cube; and a number\n"
"as the second operand operates on each pixel of the cube.\n"
"\n"
"BASIC PARAMETERS:\n"
"-----------------\n"
"--operator\n"
"Any of the following operations to perform: '+', '-', '*' or '/' (also '^' \n"
"when the 2nd operand is a scalar\n"
"\n"
"--scalar\n"
"To be provided if a frame should be processed together with a scalar\n"
"\n"
"--file_extension\n"
"Define a string to append to the product filename ARITHMETIC in order to get\n"
"an unique filename\n"
"\n"
"-------------------------------------------------------------------------------\n"
"  Input files:\n"
"\n"
"   DO                    KMOS                                                  \n"
"   category              Type   Explanation                    Required #Frames\n"
"   --------              -----  -----------                    -------- -------\n"
"   <none or any>         F3I or Data with or                      Y        1   \n"
"                         F2I or without noise frame                            \n"
"                         F1I or                                                \n"
"                         F2D or                                                \n"
"                         RAW                                                   \n"
"   <none or any>         F3I or Data with or                      N       0,1  \n"
"                         F2I or without noise frame                            \n"
"                         F1I or                                                \n"
"                         F2D or                                                \n"
"                         RAW                                                   \n"
"\n"
"  Output files:\n"
"\n"
"   DO                    KMOS\n"
"   category              Type   Explanation\n"
"   --------              -----  -----------\n"
"   ARITHMETIC            F3I or                                                \n"
"                         F2I or                                                \n"
"                         F1I or                                                \n"
"                         F2D                                                   \n"
"-------------------------------------------------------------------------------\n"
"\n";

/**
 * @defgroup kmo_arithmetic kmo_arithmetic Perform arithmetics on KMOS frames
 *
 * 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_arithmetic",
                        "Perform basic arithmetic on cubes",
                        kmo_arithmetic_description,
                        "Alex Agudo Berbel",
                        "usd-help@eso.org",
                        kmos_get_license(),
                        kmo_arithmetic_create,
                        kmo_arithmetic_exec,
                        kmo_arithmetic_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_arithmetic_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 */
    /* --operator */
    p = cpl_parameter_new_value("kmos.kmo_arithmetic.operator",
                                CPL_TYPE_STRING,
                                "The operator ('+', '-', '*', '/' or '^')",
                                "kmos.kmo_arithmetic",
                                "");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "operator");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --scalar */
    p = cpl_parameter_new_value("kmos.kmo_arithmetic.scalar",
                                CPL_TYPE_DOUBLE,
                                "The scalar operand",
                                "kmos.kmo_arithmetic",
                                -DBL_MAX);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "scalar");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --file_extension */
    p = cpl_parameter_new_value("kmos.kmo_arithmetic.file_extension",
                                CPL_TYPE_STRING,
                                "String to add to product filename.",
                                "kmos.kmo_arithmetic",
                                "");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "file_extension");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    return 0;
}

/**
  @brief    Execute the plugin instance given by the interface
  @param    plugin  the plugin
  @return   0 if everything is ok
*/
static int kmo_arithmetic_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_arithmetic(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_arithmetic_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_arithmetic(cpl_parameterlist *parlist, cpl_frameset *frameset)
{
    const char       *op                 = NULL,
                     *file_extension     = NULL;
    char             *fn_out             = NULL;
    cpl_imagelist    *op1_3d             = NULL,
                     *op2_3d             = NULL,
                     *op1_noise_3d       = NULL,
                     *op2_noise_3d       = NULL;
    cpl_image        *op1_2d             = NULL,
                     *op2_2d             = NULL,
                     *op1_noise_2d       = NULL,
                     *op2_noise_2d       = NULL;
    kmclipm_vector   *op1_1d             = NULL,
                     *op2_1d             = NULL,
                     *op1_noise_1d       = NULL,
                     *op2_noise_1d       = NULL;
    double           op2_scalar          = -DBL_MAX;
    int              ret_val             = 0,
                     nr_devices          = 0,
                     i                   = 0,
                     single_ifu          = FALSE,
                     calc_f3i            = FALSE,
                     calc_f2i            = FALSE,
                     calc_f1i            = FALSE,
                     devnr1              = 0,
                     devnr2              = 0;
    cpl_propertylist *main_header        = NULL,
                     *sub_header_data    = NULL,
                     *sub_header_noise   = NULL;
    main_fits_desc   desc1,
                     desc2;
    cpl_frame        *op1_frame          = NULL,
                     *op2_frame          = NULL;

    KMO_TRY
    {
        kmo_init_fits_desc(&desc1);
        kmo_init_fits_desc(&desc2);

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

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

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

        op2_scalar = kmo_dfs_get_parameter_double(parlist,
                                          "kmos.kmo_arithmetic.scalar");
        KMO_TRY_CHECK_ERROR_STATE();

        KMO_TRY_ASSURE((cpl_frameset_get_size(frameset) == 2) ||
                       ((cpl_frameset_get_size(frameset) == 1) &&
                       (op2_scalar != -DBL_MAX)),
                       CPL_ERROR_NULL_INPUT,
                       "Two fits-files or one fits-file and one "
                       "scalar must be provided!");

        if (cpl_frameset_get_size(frameset) == 1) {
            KMO_TRY_EXIT_IF_ERROR(
                kmo_dfs_print_parameter_help(parlist, "kmos.kmo_arithmetic.scalar"));
        } else {
            KMO_TRY_EXIT_IF_NULL(
                op2_frame = kmo_dfs_get_frame(frameset, "1"));
        }
        KMO_TRY_EXIT_IF_NULL(
            op1_frame = kmo_dfs_get_frame(frameset, "0"));

        KMO_TRY_EXIT_IF_NULL(
            op = kmo_dfs_get_parameter_string(parlist,
                                               "kmos.kmo_arithmetic.operator"));
        KMO_TRY_ASSURE((strcmp(op, "+") == 0) ||
                       (strcmp(op, "-") == 0) ||
                       (strcmp(op, "*") == 0) ||
                       (strcmp(op, "/") == 0) ||
                       (strcmp(op, "^") == 0),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Operator not valid! Has it been provided?");

        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_arithmetic.operator"));

        file_extension = kmo_dfs_get_parameter_string(parlist,
                                                      "kmos.kmo_arithmetic.file_extension");
        KMO_TRY_CHECK_ERROR_STATE();

        KMO_TRY_EXIT_IF_ERROR(
           kmo_dfs_print_parameter_help(parlist, "kmos.kmo_arithmetic.file_extension"));

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

        if (strcmp(file_extension, "") == 0) {
            fn_out = cpl_sprintf("%s", ARITHMETIC);
        } else {
            fn_out = cpl_sprintf("%s_%s", ARITHMETIC, file_extension);
        }

        // load descriptor, header and data of first operand
        desc1 = kmo_identify_fits_header(
                        cpl_frame_get_filename(op1_frame));
        KMO_TRY_CHECK_ERROR_STATE_MSG("Provided fits file doesn't seem to be "
                                      "in KMOS-format!");

        KMO_TRY_ASSURE((desc1.fits_type == f3i_fits) ||
                       (desc1.fits_type == f2i_fits) ||
                       (desc1.fits_type == f1i_fits) ||
                       (desc1.fits_type == f2d_fits) ||
                       (desc1.fits_type == raw_fits),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "First input file hasn't correct data type "
                       "(KMOSTYPE must be F3I, F2I, F1I, F2D or RAW)!");

        // load descriptor, header of second operand
        if (op2_scalar == -DBL_MAX) {
            desc2 = kmo_identify_fits_header(
                            cpl_frame_get_filename(op2_frame));
            KMO_TRY_CHECK_ERROR_STATE_MSG("Provided fits file doesn't seem to "
                                          "be in KMOS-format!");

            if (desc1.fits_type == f3i_fits) {
                KMO_TRY_ASSURE((desc2.fits_type == f3i_fits) ||
                               (desc2.fits_type == f2i_fits)||
                               (desc2.fits_type == f1i_fits),
                               CPL_ERROR_ILLEGAL_INPUT,
                               "For a F3I frame, the 2nd frame must be a "
                               "F3I, F2I or a F1I frame!");

                if (desc2.fits_type == f3i_fits) {
                    KMO_TRY_ASSURE((desc1.naxis1 == desc2.naxis1) &&
                                   (desc1.naxis2 == desc2.naxis2) &&
                                   (desc1.naxis3 == desc2.naxis3),
                                   CPL_ERROR_INCOMPATIBLE_INPUT,
                                   "The dimensions of the two operands do "
                                   "not match!");
                } else if (desc2.fits_type == f2i_fits) {
                    KMO_TRY_ASSURE((desc1.naxis1 == desc2.naxis1) &&
                                   (desc1.naxis2 == desc2.naxis2),
                                   CPL_ERROR_INCOMPATIBLE_INPUT,
                                   "The dimensions of the two operands do "
                                   "not match!");
                } else if (desc2.fits_type == f1i_fits) {
                    KMO_TRY_ASSURE((desc1.naxis3 == desc2.naxis1),
                                   CPL_ERROR_INCOMPATIBLE_INPUT,
                                   "The dimensions of the two operands do "
                                   "not match!");
                }
            } else if (desc1.fits_type == f2i_fits) {
                KMO_TRY_ASSURE(desc2.fits_type == f2i_fits,
                               CPL_ERROR_ILLEGAL_INPUT,
                               "For a F2I frame, the 2nd frame must be a "
                               "F2I frame!");
                KMO_TRY_ASSURE((desc1.naxis1 == desc2.naxis1) &&
                               (desc1.naxis2 == desc2.naxis2),
                               CPL_ERROR_INCOMPATIBLE_INPUT,
                               "The dimensions of the two operands do "
                               "not match!");
            } else if (desc1.fits_type == f1i_fits) {
                KMO_TRY_ASSURE(desc2.fits_type == f1i_fits,
                               CPL_ERROR_ILLEGAL_INPUT,
                               "For a F1I frame, the 2nd frame must be a "
                               "F1I frame!");
                KMO_TRY_ASSURE(desc1.naxis1 == desc2.naxis1,
                               CPL_ERROR_INCOMPATIBLE_INPUT,
                               "The dimensions of the two operands do "
                               "not match!");
            } else if (desc1.fits_type == f2d_fits) {
                KMO_TRY_ASSURE((desc2.fits_type == f2d_fits) ||
                               ((desc2.fits_type == raw_fits) && (desc1.ex_noise == FALSE)),
                               CPL_ERROR_ILLEGAL_INPUT,
                               "For a F2D frame, the 2nd frame must be a "
                               "F2D frame!");
                KMO_TRY_ASSURE((desc1.naxis1 == desc2.naxis1) &&
                               (desc1.naxis2 == desc2.naxis2),
                               CPL_ERROR_INCOMPATIBLE_INPUT,
                               "The dimensions of the two operands do "
                               "not match!");
            } else if (desc1.fits_type == raw_fits) {
                KMO_TRY_ASSURE((desc2.fits_type == raw_fits) ||
                               ((desc2.fits_type == f2d_fits) && (desc2.ex_noise == FALSE)),
                               CPL_ERROR_ILLEGAL_INPUT,
                               "For a RAW frame, the 2nd frame must be a "
                               "RAW frame!");
                KMO_TRY_ASSURE((desc1.naxis1 == desc2.naxis1) &&
                               (desc1.naxis2 == desc2.naxis2),
                               CPL_ERROR_INCOMPATIBLE_INPUT,
                               "The dimensions of the two operands do "
                               "not match!");
            }

            if (((desc2.nr_ext == 1) &&
                 (desc2.sub_desc[0].valid_data == TRUE))
                ||
                ((desc2.nr_ext == 2) &&
                 (desc2.ex_noise == TRUE) &&
                 (desc2.sub_desc[0].valid_data == TRUE) &&
                 (desc2.sub_desc[1].valid_data == TRUE))) {
                single_ifu = TRUE;
            } else {
                if (desc1.ex_noise == desc2.ex_noise) {
                    KMO_TRY_ASSURE(desc1.nr_ext == desc2.nr_ext,
                                  CPL_ERROR_INCOMPATIBLE_INPUT,
                                  "The number of IFUs of the two operands do "
                                  "not match!");
                } else {
                    KMO_TRY_ASSURE(desc1.nr_ext == desc2.nr_ext * 2,
                                   CPL_ERROR_INCOMPATIBLE_INPUT,
                                   "The number of IFUs of the two operands do "
                                   "not match!");
                }
            }
        }

        // --- load, update & save primary header ---
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_save_main_header(frameset, fn_out, "", op1_frame, NULL,
                                     parlist, cpl_func));

        //
        // load data
        //
        if (desc1.ex_noise == TRUE) {
            nr_devices = desc1.nr_ext / 2;
        } else {
            nr_devices = desc1.nr_ext;
        }

        if ((single_ifu == TRUE) &&
            (desc2.sub_desc[0].valid_data == TRUE))
        {
            switch (desc2.fits_type) {
                case f3i_fits:

                    KMO_TRY_EXIT_IF_NULL(
                        op2_3d = kmo_dfs_load_cube(frameset, "1",
                                                   desc2.sub_desc[0].device_nr,
                                                   FALSE));

                    if ((desc1.ex_noise == TRUE) && (desc2.ex_noise == TRUE)) {
                        KMO_TRY_EXIT_IF_NULL(
                            op2_noise_3d = kmo_dfs_load_cube(frameset, "1",
                                                   desc2.sub_desc[0].device_nr,
                                                   TRUE));
                    }
                    break;
                case f2i_fits:
                    KMO_TRY_EXIT_IF_NULL(
                        op2_2d = kmo_dfs_load_image(frameset, "1",
                                                   desc2.sub_desc[0].device_nr,
                                                   FALSE, FALSE, NULL));

                    if ((desc1.ex_noise == TRUE) && (desc2.ex_noise == TRUE)) {
                        KMO_TRY_EXIT_IF_NULL(
                            op2_noise_2d = kmo_dfs_load_image(frameset,
                                                   "1",
                                                   desc2.sub_desc[0].device_nr,
                                                   TRUE, FALSE, NULL));
                    }
                    break;
                case f1i_fits:
                    KMO_TRY_EXIT_IF_NULL(
                        op2_1d = kmo_dfs_load_vector(frameset, "1",
                                                   desc2.sub_desc[0].device_nr,
                                                   FALSE));

                    if ((desc1.ex_noise == TRUE) && (desc2.ex_noise == TRUE)) {
                        KMO_TRY_EXIT_IF_NULL(
                            op2_noise_1d = kmo_dfs_load_vector(frameset,
                                                   "1",
                                                   desc2.sub_desc[0].device_nr,
                                                   TRUE));
                    }
                    break;
                default:
                    break;
            }
        }

        for (i = 1; i <= nr_devices; i++) {
            if (desc1.ex_noise == FALSE) {
                devnr1 = desc1.sub_desc[i - 1].device_nr;
            } else {
                devnr1 = desc1.sub_desc[2 * i - 1].device_nr;
            }

            if (op2_scalar == -DBL_MAX) {
                if (desc2.ex_noise == FALSE) {
                    devnr2 = desc2.sub_desc[i - 1].device_nr;
                } else {
                    devnr2 = desc2.sub_desc[2 * i - 1].device_nr;
                }
            }

            KMO_TRY_EXIT_IF_NULL(
                sub_header_data = kmo_dfs_load_sub_header(frameset, "0",
                                                          devnr1,
                                                          FALSE));
            switch (desc1.fits_type) {
                case raw_fits:
                    // load data 1st operand
                    KMO_TRY_EXIT_IF_NULL(
                        op1_2d = kmo_dfs_load_image(frameset, "0", i,
                                                    FALSE, TRUE, NULL));
                    //
                    // process RAW & RAW
                    // process RAW & F2D
                    //
                    if ((desc2.fits_type == raw_fits) ||
                        (desc2.fits_type == f2d_fits))
                    {
                        /* load data 2nd operand */
                        KMO_TRY_EXIT_IF_NULL(
                            op2_2d = kmo_dfs_load_image(frameset, "1", i,
                                                    FALSE, TRUE, NULL));

                        KMO_TRY_EXIT_IF_ERROR(
                            kmo_arithmetic_2D_2D(op1_2d, op2_2d, NULL, NULL, op));
                    }

                    //
                    // process RAW & scalar
                    //
                    else if (op2_scalar != -DBL_MAX) {
                        KMO_TRY_EXIT_IF_ERROR(
                            kmo_arithmetic_2D_scalar(op1_2d, op2_scalar, NULL,
                                                     op));
                    }

// keep EXTNAME from op1 (e.g. CHIP1.INT1 instead of creating DET.1.DATA)
//                    KMO_TRY_EXIT_IF_ERROR(
//                        kmo_update_sub_keywords(sub_header_data, FALSE, FALSE,
//                                                detector_frame, i));

                    KMO_TRY_EXIT_IF_ERROR(
                        kmo_dfs_save_image(op1_2d, fn_out, "",
                                           sub_header_data, 0./0.));
                    break;
                case f2d_fits:
                    KMO_TRY_EXIT_IF_NULL(
                        op1_2d = kmo_dfs_load_image(frameset, "0", i,
                                                    FALSE, FALSE, NULL));
                    //
                    // process F2D & F2D
                    // process F2D & RAW
                    //
                    if ((desc2.fits_type == f2d_fits) ||
                        (desc2.fits_type == raw_fits))
                    {
                        KMO_TRY_EXIT_IF_NULL(
                            op2_2d = kmo_dfs_load_image(frameset, "1", i,
                                                        FALSE, FALSE, NULL));
                        // load noise
                        if ((desc1.ex_noise == TRUE) &&
                            (desc2.ex_noise == TRUE)) {
                            KMO_TRY_EXIT_IF_NULL(
                                op1_noise_2d = kmo_dfs_load_image(frameset,
                                                         "0", i, TRUE, FALSE, NULL));

                            KMO_TRY_EXIT_IF_NULL(
                                op2_noise_2d = kmo_dfs_load_image(frameset,
                                                         "1", i, TRUE, FALSE, NULL));
                        }
                        KMO_TRY_EXIT_IF_ERROR(
                            kmo_arithmetic_2D_2D(op1_2d, op2_2d,
                                                 op1_noise_2d, op2_noise_2d,
                                                 op));
                    }
                    //
                    // process F2D & scalar
                    //
                    else if (op2_scalar != -DBL_MAX) {
                        // process data & noise
                        if (desc1.ex_noise == TRUE) {
                            KMO_TRY_EXIT_IF_NULL(
                                op1_noise_2d = kmo_dfs_load_image(frameset,
                                                         "0", i, TRUE, FALSE, NULL));
                        }
                        KMO_TRY_EXIT_IF_ERROR(
                            kmo_arithmetic_2D_scalar(op1_2d,
                                                           op2_scalar,
                                                           op1_noise_2d,
                                                           op));
                    }

                    // save data (and noise)
                    KMO_TRY_EXIT_IF_ERROR(
                        kmo_dfs_save_image(op1_2d, fn_out, "",
                                           sub_header_data, 0./0.));

                    if (op1_noise_2d != NULL) {
                        KMO_TRY_EXIT_IF_NULL(
                            sub_header_noise = kmo_dfs_load_sub_header(frameset,
                                                            "0", i, TRUE));
                        KMO_TRY_EXIT_IF_ERROR(
                            kmo_dfs_save_image(op1_noise_2d, fn_out, "",
                                               sub_header_noise, 0./0.));

                        cpl_propertylist_delete(sub_header_noise);
                        sub_header_noise = NULL;
                    }
                    break;
                case f3i_fits:
                    calc_f3i = FALSE;

                    // check if IFUs are valid
                    if (desc1.ex_noise == FALSE) {
                        if (desc1.sub_desc[i - 1].valid_data == TRUE) {
                            if (op2_scalar != -DBL_MAX) {
                                calc_f3i = TRUE;
                            } else if (((single_ifu == TRUE) &&
                                        (desc2.sub_desc[0].valid_data == TRUE))
                                       ||
                                       (desc2.sub_desc[i - 1].valid_data == TRUE))
                            {
                                calc_f3i = TRUE;
                            }
                        }
                    }
                    if (desc1.ex_noise == TRUE) {
                        if (desc1.sub_desc[2 * i - 1].valid_data == TRUE) {
                            calc_f3i = TRUE;
                        }
                    }
                    if ((desc1.ex_noise == TRUE) && (desc2.ex_noise == TRUE)) {
                        if (desc1.sub_desc[2 * i - 1].valid_data == TRUE) {
                            if (((single_ifu == TRUE) &&
                                 (desc2.sub_desc[1].valid_data == TRUE))
                                ||
                                (desc2.sub_desc[2 * i - 1].valid_data == TRUE))
                            {
                                calc_f3i = TRUE;
                            }
                        }
                    }
                    if ((single_ifu == TRUE) && (desc1.ex_noise == FALSE)) {
                        if ((desc1.sub_desc[i - 1].valid_data == TRUE) &&
                            (desc2.sub_desc[0].valid_data == TRUE)) {
                            calc_f3i = TRUE;
                        }
                    }

                    if (calc_f3i == TRUE)
                    {
                        KMO_TRY_EXIT_IF_NULL(
                            op1_3d = kmo_dfs_load_cube(frameset, "0",
                                                       devnr1, FALSE));
                        //
                        // process F3I & F3I
                        //
                        if (desc2.fits_type == f3i_fits) {
                            if (single_ifu == FALSE) {
                                KMO_TRY_EXIT_IF_NULL(
                                    op2_3d = kmo_dfs_load_cube(frameset,
                                                               "1", devnr2,
                                                               FALSE));
                            }

                            if ((desc1.ex_noise == TRUE) &&
                                (desc2.ex_noise == TRUE))
                            {
                                KMO_TRY_EXIT_IF_NULL(
                                    op1_noise_3d = kmo_dfs_load_cube(frameset,
                                                               "0", devnr1,
                                                               TRUE));

                                if (single_ifu == FALSE) {
                                    KMO_TRY_EXIT_IF_NULL(
                                        op2_noise_3d = kmo_dfs_load_cube(
                                                               frameset,
                                                               "1", devnr2,
                                                               TRUE));
                                }
                            }

                            KMO_TRY_EXIT_IF_ERROR(
                                kmo_arithmetic_3D_3D(op1_3d, op2_3d,
                                                     op1_noise_3d, op2_noise_3d,
                                                     op));
                        }

                        //
                        // process F3I & F2I
                        //
                        else if (desc2.fits_type == f2i_fits) {
                            if (single_ifu == FALSE) {
                                KMO_TRY_EXIT_IF_NULL(
                                    op2_2d = kmo_dfs_load_image(frameset,
                                                               "1", devnr2,
                                                               FALSE, FALSE, NULL));
                            }

                            if ((desc1.ex_noise == TRUE) &&
                                (desc2.ex_noise == TRUE))
                            {
                                KMO_TRY_EXIT_IF_NULL(
                                    op1_noise_3d = kmo_dfs_load_cube(frameset,
                                                               "0", devnr1,
                                                               TRUE));

                                if (single_ifu == FALSE) {
                                    KMO_TRY_EXIT_IF_NULL(
                                        op2_noise_2d = kmo_dfs_load_image(
                                                               frameset,
                                                               "1", devnr2,
                                                               TRUE, FALSE, NULL));
                                }
                            }

                            KMO_TRY_EXIT_IF_ERROR(
                                kmo_arithmetic_3D_2D(op1_3d, op2_2d,
                                                     op1_noise_3d, op2_noise_2d,
                                                     op));
                        }
                        //
                        // process F3I & F1I
                        //
                        else if (desc2.fits_type == f1i_fits) {
                            if (single_ifu == FALSE) {
                                KMO_TRY_EXIT_IF_NULL(
                                    op2_1d = kmo_dfs_load_vector(frameset,
                                                               "1", devnr2,
                                                               FALSE));
                            }

                            if ((desc1.ex_noise == TRUE) &&
                                (desc2.ex_noise == TRUE))
                            {
                                KMO_TRY_EXIT_IF_NULL(
                                    op1_noise_3d = kmo_dfs_load_cube(frameset,
                                                               "0", devnr1,
                                                               TRUE));

                                if (single_ifu == FALSE) {
                                    KMO_TRY_EXIT_IF_NULL(
                                        op2_noise_1d = kmo_dfs_load_vector(
                                                               frameset,
                                                               "1", devnr2,
                                                               TRUE));
                                }
                            }

                            KMO_TRY_EXIT_IF_ERROR(
                                kmo_arithmetic_3D_1D(op1_3d, op2_1d,
                                                     op1_noise_3d, op2_noise_1d,
                                                     op));
                        }
                        //
                        // process F3I & scalar
                        //
                        else if (op2_scalar != -DBL_MAX) {
                            if (desc1.ex_noise == TRUE) {
                                KMO_TRY_EXIT_IF_NULL(
                                    op1_noise_3d = kmo_dfs_load_cube(frameset,
                                                               "0", devnr1,
                                                               TRUE));
                            }

                            KMO_TRY_EXIT_IF_ERROR(
                                kmo_arithmetic_3D_scalar(op1_3d,
                                                         op2_scalar,
                                                         op1_noise_3d,
                                                         op));
                        }

                        // save data (and noise)
                        KMO_TRY_EXIT_IF_ERROR(
                            kmo_dfs_save_cube(op1_3d, fn_out, "",
                                              sub_header_data, 0./0.));

                        if (op1_noise_3d != NULL) {
                            KMO_TRY_EXIT_IF_NULL(
                                sub_header_noise = kmo_dfs_load_sub_header(
                                                               frameset,
                                                               "0", devnr1,
                                                               TRUE));

                            KMO_TRY_EXIT_IF_ERROR(
                                kmo_dfs_save_cube(op1_noise_3d, fn_out, "",
                                                  sub_header_noise, 0./0.));

                            cpl_propertylist_delete(sub_header_noise);
                            sub_header_noise = NULL;
                        }
                    } else {
                        //
                        // invalid IFU, just save sub_header
                        //
                        KMO_TRY_EXIT_IF_ERROR(
                            kmo_dfs_save_sub_header(fn_out, "",
                                                    sub_header_data));

                        // save noise if it has been calculated
                        if ((desc1.ex_noise == TRUE) &&
                            ((((desc2.fits_type == f3i_fits) ||
                               (desc2.fits_type == f2i_fits) ||
                               (desc2.fits_type == f1i_fits)
                              ) &&
                                (desc2.ex_noise == TRUE)
                             ) ||
                                (op2_scalar != -DBL_MAX)
                            )
                           )
                        {

                            KMO_TRY_EXIT_IF_NULL(
                                sub_header_noise = kmo_dfs_load_sub_header(
                                                               frameset,
                                                               "0", devnr1,
                                                               TRUE));
                            KMO_TRY_EXIT_IF_ERROR(
                                kmo_dfs_save_sub_header(fn_out, "",
                                                        sub_header_noise));

                            cpl_propertylist_delete(sub_header_noise);
                            sub_header_noise = NULL;
                        }
                    }
                    break;
                case f2i_fits:
                    calc_f2i = FALSE;

                    // check if IFUs are valid
                    if (desc1.ex_noise == FALSE) {
                        if (desc1.sub_desc[i - 1].valid_data == TRUE) {
                            if (op2_scalar != -DBL_MAX) {
                                calc_f2i = TRUE;
                            } else if (((single_ifu == TRUE) &&
                                        (desc2.sub_desc[0].valid_data == TRUE))
                                       ||
                                       (desc2.sub_desc[i - 1].valid_data == TRUE))
                            {
                                calc_f2i = TRUE;
                            }
                        }
                    }
                    if (desc1.ex_noise == TRUE) {
                        if (desc1.sub_desc[2 * i - 1].valid_data == TRUE) {
                            calc_f2i = TRUE;
                        }
                    }
                    if ((desc1.ex_noise == TRUE) && (desc2.ex_noise == TRUE)) {
                        if (desc1.sub_desc[2 * i - 1].valid_data == TRUE) {
                            if (((single_ifu == TRUE) &&
                                 (desc2.sub_desc[1].valid_data == TRUE))
                                ||
                                (desc2.sub_desc[2 * i - 1].valid_data == TRUE))
                            {
                                calc_f2i = TRUE;
                            }
                        }
                    }
                    if ((single_ifu == TRUE) && (desc1.ex_noise == FALSE)) {
                        if ((desc1.sub_desc[i - 1].valid_data == TRUE) &&
                            (desc2.sub_desc[0].valid_data == TRUE)) {
                            calc_f2i = TRUE;
                        }
                    }

                    if (calc_f2i == TRUE)
                    {
                        KMO_TRY_EXIT_IF_NULL(
                            op1_2d = kmo_dfs_load_image(frameset, "0",
                                                        devnr1, FALSE, FALSE, NULL));
                        //
                        // process F2I & F2I
                        //
                        if (desc2.fits_type == f2i_fits) {
                            if (single_ifu == FALSE) {
                                KMO_TRY_EXIT_IF_NULL(
                                    op2_2d = kmo_dfs_load_image(frameset, "1",
                                                                devnr2, FALSE, FALSE, NULL));
                            }

                            if ((desc1.ex_noise == TRUE) &&
                                (desc2.ex_noise == TRUE))
                            {
                                KMO_TRY_EXIT_IF_NULL(
                                    op1_noise_2d = kmo_dfs_load_image(
                                                            frameset, "0",
                                                            devnr1, TRUE, FALSE, NULL));

                                if (single_ifu == FALSE) {
                                    KMO_TRY_EXIT_IF_NULL(
                                        op2_noise_2d = kmo_dfs_load_image(
                                                            frameset, "1",
                                                            devnr2, TRUE, FALSE, NULL));
                                }
                            }

                            KMO_TRY_EXIT_IF_ERROR(
                                kmo_arithmetic_2D_2D(op1_2d, op2_2d,
                                                     op1_noise_2d, op2_noise_2d,
                                                     op));
                        }
                        //
                        // process F2I & scalar
                        //
                        else if (op2_scalar != -DBL_MAX) {
                            if (desc1.ex_noise == TRUE) {
                                KMO_TRY_EXIT_IF_NULL(
                                    op1_noise_2d = kmo_dfs_load_image(
                                                                frameset, "0",
                                                                devnr1, TRUE, FALSE, NULL));
                            }

                            KMO_TRY_EXIT_IF_ERROR(
                                kmo_arithmetic_2D_scalar(op1_2d,
                                                         op2_scalar,
                                                         op1_noise_2d,
                                                         op));
                        }

                        // save data (and noise)
                        KMO_TRY_EXIT_IF_ERROR(
                            kmo_dfs_save_image(op1_2d, fn_out, "",
                                               sub_header_data, 0./0.));

                        if (op1_noise_2d != NULL) {
                            KMO_TRY_EXIT_IF_NULL(
                                sub_header_noise = kmo_dfs_load_sub_header(
                                                                frameset, "0",
                                                                devnr1, TRUE));

                            KMO_TRY_EXIT_IF_ERROR(
                                kmo_dfs_save_image(op1_noise_2d, fn_out, "",
                                                   sub_header_noise, 0./0.));

                            cpl_propertylist_delete(sub_header_noise);
                            sub_header_noise = NULL;
                        }
                    } else {
                        //
                        // invalid IFU, just save sub_header
                        //
                        KMO_TRY_EXIT_IF_ERROR(
                            kmo_dfs_save_sub_header(fn_out, "", sub_header_data));

                        // save noise if it has been calculated
                        if ((desc1.ex_noise == TRUE)
                            &&
                            (((desc2.fits_type == f2i_fits) && (desc2.ex_noise == TRUE)) ||
                             (op2_scalar != -DBL_MAX)))
                        {

                            KMO_TRY_EXIT_IF_NULL(
                                sub_header_noise = kmo_dfs_load_sub_header(
                                                               frameset,
                                                               "0", devnr1,
                                                               TRUE));
                            KMO_TRY_EXIT_IF_ERROR(
                                kmo_dfs_save_sub_header(fn_out, "", sub_header_noise));

                            cpl_propertylist_delete(sub_header_noise);
                            sub_header_noise = NULL;
                        }
                    }
                    break;
                case f1i_fits:
                    calc_f1i = FALSE;

                    // check if IFUs are valid
                    if (desc1.ex_noise == FALSE) {
                        if (desc1.sub_desc[i - 1].valid_data == TRUE) {
                            if (op2_scalar != -DBL_MAX) {
                                calc_f1i = TRUE;
                            } else if (((single_ifu == TRUE) &&
                                        (desc2.sub_desc[0].valid_data == TRUE))
                                       ||
                                       (desc2.sub_desc[i - 1].valid_data == TRUE))
                            {
                                calc_f1i = TRUE;
                            }
                        }
                    }
                    if (desc1.ex_noise == TRUE) {
                        if (desc1.sub_desc[2 * i - 1].valid_data == TRUE) {
                            calc_f1i = TRUE;
                        }
                    }
                    if ((desc1.ex_noise == TRUE) && (desc2.ex_noise == TRUE)) {
                        if (desc1.sub_desc[2 * i - 1].valid_data == TRUE) {
                            if (((single_ifu == TRUE) &&
                                 (desc2.sub_desc[1].valid_data == TRUE))
                                ||
                                (desc2.sub_desc[2 * i - 1].valid_data == TRUE))
                            {
                                calc_f1i = TRUE;
                            }
                        }
                    }
                    if ((single_ifu == TRUE) && (desc1.ex_noise == FALSE)) {
                        if ((desc1.sub_desc[i - 1].valid_data == TRUE) &&
                            (desc2.sub_desc[0].valid_data == TRUE)) {
                            calc_f1i = TRUE;
                        }
                    }

                    if (calc_f1i == TRUE)
                    {
                        KMO_TRY_EXIT_IF_NULL(
                            op1_1d = kmo_dfs_load_vector(frameset, "0",
                                                         devnr1, FALSE));
                        //
                        // process F1I & F1I
                        //
                        if (desc2.fits_type == f1i_fits) {
                            if (single_ifu == FALSE) {
                                KMO_TRY_EXIT_IF_NULL(
                                    op2_1d = kmo_dfs_load_vector(frameset, "1",
                                                                 devnr2, FALSE));
                            }

                            if ((desc1.ex_noise == TRUE) &&
                                (desc2.ex_noise == TRUE))
                            {
                                KMO_TRY_EXIT_IF_NULL(
                                    op1_noise_1d = kmo_dfs_load_vector(
                                                                frameset, "0",
                                                                devnr1, TRUE));

                                if (single_ifu == FALSE) {
                                    KMO_TRY_EXIT_IF_NULL(
                                        op2_noise_1d = kmo_dfs_load_vector(
                                                                frameset, "1",
                                                                devnr2, TRUE));
                                }
                            }

                            KMO_TRY_EXIT_IF_ERROR(
                                kmo_arithmetic_1D_1D(op1_1d, op2_1d,
                                                     op1_noise_1d, op2_noise_1d,
                                                     op));
                        }
                        //
                        // process F1I & scalar
                        //
                        else if (op2_scalar != -DBL_MAX) {
                            if (desc1.ex_noise == TRUE) {
                                KMO_TRY_EXIT_IF_NULL(
                                    op1_noise_1d = kmo_dfs_load_vector(
                                                                frameset, "0",
                                                                devnr1, TRUE));
                            }

                            KMO_TRY_EXIT_IF_ERROR(
                                kmo_arithmetic_1D_scalar(op1_1d,
                                                         op2_scalar,
                                                         op1_noise_1d,
                                                         op));
                        }

                        // save data (and noise)
                        KMO_TRY_EXIT_IF_ERROR(
                            kmo_dfs_save_vector(op1_1d, fn_out, "",
                                                sub_header_data, 0./0.));

                        if (op1_noise_1d != NULL) {
                            KMO_TRY_EXIT_IF_NULL(
                                sub_header_noise = kmo_dfs_load_sub_header(
                                                                frameset, "0",
                                                                devnr1, TRUE));

                            KMO_TRY_EXIT_IF_ERROR(
                                kmo_dfs_save_vector(op1_noise_1d, fn_out, "",
                                                    sub_header_noise, 0./0.));

                            cpl_propertylist_delete(sub_header_noise);
                            sub_header_noise = NULL;
                        }
                    } else {
                        //
                        // invalid IFU, just save sub_header
                        //
                        KMO_TRY_EXIT_IF_ERROR(
                            kmo_dfs_save_sub_header(fn_out, "", sub_header_data));

                        // save noise if it has been calculated
                        if ((desc1.ex_noise == TRUE)
                            &&
                            (((desc2.fits_type == f2i_fits) && (desc2.ex_noise == TRUE)) ||
                             (op2_scalar != -DBL_MAX)))
                        {

                            KMO_TRY_EXIT_IF_NULL(
                                sub_header_noise = kmo_dfs_load_sub_header(
                                                               frameset,
                                                               "0", devnr1,
                                                               TRUE));
                            KMO_TRY_EXIT_IF_ERROR(
                                kmo_dfs_save_sub_header(fn_out, "",
                                                        sub_header_noise));

                            cpl_propertylist_delete(sub_header_noise);
                            sub_header_noise = NULL;
                        }
                    }
                    break;
                default:
                    break;
            }

            cpl_propertylist_delete(sub_header_data); sub_header_data = NULL;

            cpl_image_delete(op1_2d); op1_2d = NULL;
            cpl_imagelist_delete(op1_3d); op1_3d = NULL;

            cpl_image_delete(op1_noise_2d); op1_noise_2d = NULL;
            cpl_imagelist_delete(op1_noise_3d); op1_noise_3d = NULL;

            if (single_ifu == FALSE) {
                kmclipm_vector_delete(op2_1d); op2_1d = NULL,
                cpl_image_delete(op2_2d); op2_2d = NULL;
                cpl_imagelist_delete(op2_3d); op2_3d = NULL;

                kmclipm_vector_delete(op2_noise_1d); op2_noise_1d = NULL,
                cpl_image_delete(op2_noise_2d); op2_noise_2d = NULL;
                cpl_imagelist_delete(op2_noise_3d); op2_noise_3d = NULL;
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_val = -1;
    }

    kmo_free_fits_desc(&desc1);
    kmo_free_fits_desc(&desc2);
    cpl_free(fn_out); fn_out = NULL;
    cpl_propertylist_delete(main_header); main_header = NULL;
    cpl_propertylist_delete(sub_header_data); sub_header_data = NULL;
    cpl_propertylist_delete(sub_header_noise); sub_header_noise = NULL;
    cpl_image_delete(op1_2d); op1_2d = NULL;
    cpl_imagelist_delete(op1_3d); op1_3d = NULL;
    kmclipm_vector_delete(op2_1d); op2_1d = NULL;
    cpl_image_delete(op2_2d); op2_2d = NULL;
    cpl_imagelist_delete(op2_3d); op2_3d = NULL;
    cpl_image_delete(op1_noise_2d); op1_noise_2d = NULL;
    cpl_imagelist_delete(op1_noise_3d); op1_noise_3d = NULL;
    kmclipm_vector_delete(op2_noise_1d); op2_noise_1d = NULL;
    cpl_image_delete(op2_noise_2d); op2_noise_2d = NULL;
    cpl_imagelist_delete(op2_noise_3d); op2_noise_3d = NULL;

    return ret_val;
}

/**@}*/
