/*
 * 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 <math.h>
#include <string.h>

#include <cpl.h>

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

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

static int kmo_fits_strip_create(cpl_plugin *);
static int kmo_fits_strip_exec(cpl_plugin *);
static int kmo_fits_strip_destroy(cpl_plugin *);
static int kmo_fits_strip(cpl_parameterlist *, cpl_frameset *);

static char kmo_fits_strip_description[] =
"With this recipe KMOS fits frames can be stripped in following way:\n"
"\n"
"--noise\n"
"All noise extensions will be removed. Only the data extensions remain.\n"
"\n"
"--angle\n"
"Applies only to calibration products from kmo_flat and kmo_wave_cal.\n"
"All extensions matching provided angle are kept, the others are removed.\n"
"Supply a single integer value.\n"
"\n"
"--empty\n"
"All empty extensions will be removed.\n"
"\n"
"--extension\n"
"Supply a comma-separated string with integer values indicating the extensions\n"
"to keep. The other extensions are removed (any data or noise information is\n"
"disregarded, the values are interpreted absolutely)\n"
"\n"
"The parameters --noise, --angle and --empty can be combined.\n"
"When --extension is specified, all other parameters are ignored.\n"
"When no parameter is provided, no output will be generated.\n"
"\n"
"-------------------------------------------------------------------------------\n"
"  Input files:\n"
"\n"
"   DO                    KMOS                                                  \n"
"   category              Type   Explanation                    Required #Frames\n"
"   --------              -----  -----------                    -------- -------\n"
"   <none or any>         F2D or frame to strip                     Y       1   \n"
"                         F3I or\n"
"                         F2I or\n"
"                         F1I or\n"
"\n"
"  Output files:\n"
"\n"
"   DO                    KMOS\n"
"   category              Type   Explanation\n"
"   --------              -----  -----------\n"
"   STRIP                 F2D or Stripped frame\n"
"                         F3I or \n"
"                         F2I or \n"
"                         F1I    \n"
"-------------------------------------------------------------------------------\n"
"\n";

/**
 * @defgroup kmo_fits_strip kmo_fits_strip Create noise map.
 *
 * 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_fits_strip",
                        "Strip noise, rotator and/or empty extensions from a "
                        "processed KMOS fits frame",
                        kmo_fits_strip_description,
                        "Alex Agudo Berbel",
                        "usd-help@eso.org",
                        kmos_get_license(),
                        kmo_fits_strip_create,
                        kmo_fits_strip_exec,
                        kmo_fits_strip_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_fits_strip_create(cpl_plugin *plugin)
{
    cpl_recipe *recipe = NULL;
    cpl_parameter *p = NULL;

    /* 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 */
    /* --empty */
    p = cpl_parameter_new_value("kmos.kmo_fits_strip.empty",
                                CPL_TYPE_BOOL,
                                "TRUE: if empty extensions shall be removed,"
                                " FALSE: otherwise",
                                "kmos.kmo_fits_strip",
                                FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "empty");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --noise */
    p = cpl_parameter_new_value("kmos.kmo_fits_strip.noise",
                                CPL_TYPE_BOOL,
                                "TRUE: if noise extensions shall be removed,"
                                " FALSE: otherwise",
                                "kmos.kmo_fits_strip",
                                FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "noise");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --angle */
    p = cpl_parameter_new_value("kmos.kmo_fits_strip.angle",
                                CPL_TYPE_INT,
                                "All extensions not matching provided angle "
                                "are stripped.",
                                "kmos.kmo_fits_strip",
                                -1);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "angle");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --extension */
    p = cpl_parameter_new_value("kmos.kmo_fits_strip.extension",
                                CPL_TYPE_STRING,
                                "Comma-separated string with integers. "
                                "All extensions matching these values are stripped.",
                                "kmos.kmo_fits_strip",
                                NULL);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "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_fits_strip_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_fits_strip(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_fits_strip_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 ;
}

// kmo_dfs_save_main_header() doesn't allow to change ESO OCS ROT NAANGLE
cpl_error_code kmo_save_header_fits_strip(cpl_propertylist *header,
                                          cpl_parameterlist *parlist,
                                          cpl_frameset *frameset,
                                          const char *filename)
{
#define PRO_REC_PARAMi_NAME         "ESO PRO REC1 PARAM%d NAME"
#define PRO_REC_PARAMi_VALUE        "ESO PRO REC1 PARAM%d VALUE"

    cpl_error_code      err                 = CPL_ERROR_NONE;
    char                *ggg                = NULL,
                        cval[1024];
    const char          *kname              = NULL;
    const cpl_parameter *param              = NULL;
    cpl_frame           *product_frame      = NULL;
    int                 npar                = 0;

    KMO_TRY
    {
        // setup DFS manually (no MD5 calculated...)
        KMO_TRY_EXIT_IF_ERROR(
            cpl_propertylist_update_string(header, "PIPEFILE", "strip.fits"));
        KMO_TRY_EXIT_IF_ERROR(
            cpl_propertylist_update_string(header, CPL_DFS_PRO_CATG, STRIP));
        KMO_TRY_EXIT_IF_ERROR(
            cpl_propertylist_update_string(header, "ESO PRO REC1 ID", cpl_func));
        ggg = cpl_sprintf("cpl-%d.%d.%d", cpl_version_get_major(), cpl_version_get_minor(), cpl_version_get_micro());
        KMO_TRY_EXIT_IF_ERROR(
            cpl_propertylist_update_string(header, "ESO PRO REC1 DRS ID", ggg));
        cpl_free(ggg); ggg = NULL;
        KMO_TRY_EXIT_IF_ERROR(
            cpl_propertylist_update_string(header, "ESO PRO REC1 PIPE ID", VERSION));
        KMO_TRY_EXIT_IF_ERROR(
            cpl_propertylist_update_string(header, "ESO PRO REC1 CAL1 NAME", kmos_get_base_name(filename)));
        KMO_TRY_EXIT_IF_ERROR(
            cpl_propertylist_update_string(header, "ESO PRO REC1 CAL1 CATG", COMMANDLINE));
        KMO_TRY_EXIT_IF_ERROR(
            cpl_propertylist_update_string(header, "ESO PRO REC1 CAL1 DATAMD5", cpl_propertylist_get_string(header, "DATAMD5")));
        KMO_TRY_CHECK_ERROR_STATE();

        while (param != NULL) {
            char *pval, *dval;
            ++npar;

            kname = cpl_parameter_get_alias(param, CPL_PARAMETER_MODE_CLI);
            const char * comment = cpl_parameter_get_help(param);
            switch (cpl_parameter_get_type(param)) {
            case CPL_TYPE_BOOL:
                pval = cpl_strdup(cpl_parameter_get_bool(param) == 1 ? "true" : "false");
                dval = cpl_sprintf("Default: %s", cpl_parameter_get_default_bool(param) == 1 ? "true" : "false");
                break;
            case CPL_TYPE_INT:
                pval = cpl_sprintf("%d", cpl_parameter_get_int(param));
                dval = cpl_sprintf("Default: %d", cpl_parameter_get_default_int(param));
                break;
            case CPL_TYPE_DOUBLE:
                pval = cpl_sprintf("%g", cpl_parameter_get_double(param));
                dval = cpl_sprintf("Default: %g", cpl_parameter_get_default_double(param));
                break;
            case CPL_TYPE_STRING:
                pval = cpl_strdup(cpl_parameter_get_string(param));
                dval = cpl_sprintf("Default: '%s'", cpl_parameter_get_default_string(param));
                break;
            default:
                /* Theoretically impossible to get here */
                KMO_TRY_ASSURE(1==0,
                               CPL_ERROR_UNSPECIFIED,
                               "what?");
            }
            snprintf(cval, 1024, PRO_REC_PARAMi_NAME, npar);
            cpl_propertylist_update_string(header, cval, kname);
            cpl_propertylist_set_comment(header, cval, comment);

            snprintf(cval, 1024, PRO_REC_PARAMi_VALUE, npar);
            cpl_propertylist_update_string(header, cval, pval);
            cpl_propertylist_set_comment(header, cval, dval);

            cpl_free((void*)pval);
            cpl_free((void*)dval);

            param = cpl_parameterlist_get_next_const(parlist);
        }
        KMO_TRY_CHECK_ERROR_STATE();

        cpl_msg_info(cpl_func, "Writing FITS %s product(%s): %s", "propertylist", "STRIP", "strip.fits");
        KMO_TRY_EXIT_IF_NULL(
            product_frame = cpl_frame_new());
        KMO_TRY_EXIT_IF_ERROR(
            cpl_frame_set_filename(product_frame, "strip.fits"));
        KMO_TRY_EXIT_IF_ERROR(
            cpl_frame_set_tag(product_frame, "STRIP"));
        KMO_TRY_EXIT_IF_ERROR(
            cpl_frame_set_type(product_frame, CPL_FRAME_TYPE_ANY));
        KMO_TRY_EXIT_IF_ERROR(
            cpl_frame_set_group(product_frame, CPL_FRAME_GROUP_PRODUCT));
        KMO_TRY_EXIT_IF_ERROR(
            cpl_frame_set_level(product_frame, CPL_FRAME_LEVEL_FINAL));
        KMO_TRY_EXIT_IF_ERROR(
            cpl_frameset_insert(frameset, product_frame));

        KMO_TRY_EXIT_IF_ERROR(
            cpl_propertylist_save(header, "strip.fits", CPL_IO_CREATE));
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        err = cpl_error_get_code();
    }

    return err;
}

/**
  @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_NULL_INPUT         if no input file is defined,
                                     if the frameset is empty,
                                     if second operand not valid
    @li CPL_ERROR_ILLEGAL_INPUT      if einput fits-file isn't of type RAW,
                                     F2D or F2I,
                                     if noise is already present in input data
    @li CPL_ERROR_DATA_NOT_FOUND     if GAIN- or RON-keyword is missing in
                                     fits-header

*/
static int kmo_fits_strip(cpl_parameterlist *parlist, cpl_frameset *frameset)
{
    int                 ret_val             = 0,
                        nr_devices          = 0,
                        remove_noise        = FALSE,
                        remove_empty        = FALSE,
                        remove_angle        = FALSE,
                        found_angle         = FALSE,
                        isCalFrame          = 0,
                        isMasterFlat        = 0,
                        actDetNr            = 0,
                        cal_device_nr       = 0,
                        dummy               = 0,
                        index               = 0;
    double              angle               = 0.,
                        tmp_angle           = 0.,
                        ret_angle           = 0.,
                        secClosestAng       = 0.,
                        ocsRotAngle         = 0.,
                        proRotAngle         = 0.,
                        *pextension         = NULL;
    cpl_propertylist    *header             = NULL,
                        *sub_header         = NULL;
    cpl_imagelist       *cube               = NULL;
    cpl_image           *img                = NULL;
    cpl_vector          *vec                = NULL,
                        *extension          = NULL;
    main_fits_desc      desc;
    cpl_frame           *frame              = NULL;
    const char          *filename           = NULL,
                        *extension_txt      = NULL;
    char                **split             = NULL;
    kmclipm_vector      *kv                 = NULL;

    KMO_TRY
    {
        kmo_init_fits_desc(&desc);

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

        KMO_TRY_ASSURE(cpl_frameset_get_size(frameset) == 1,
                       CPL_ERROR_NULL_INPUT,
                       "A fits-file must be provided!");

        KMO_TRY_EXIT_IF_NULL(
            frame = kmo_dfs_get_frame(frameset, "0"));

        KMO_TRY_EXIT_IF_NULL(
            filename = cpl_frame_get_filename(frame));

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

        KMO_TRY_ASSURE((desc.fits_type == f2d_fits) ||
                       (desc.fits_type == f3i_fits) ||
                       (desc.fits_type == f2i_fits) ||
                       (desc.fits_type == f1i_fits),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Input data hasn't correct data type "
                       "(KMOSTYPE must be F2D, F3I, F2I or F1I)!");

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

        cpl_msg_info("", "--- Parameter setup for kmo_fits_strip ----");
        remove_empty = kmo_dfs_get_parameter_bool(parlist,
                                                  "kmos.kmo_fits_strip.empty");
        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_ASSURE((remove_empty == TRUE) ||
                       (remove_empty == FALSE),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "empty must be TRUE or FALSE!");
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_fits_strip.empty"));

        remove_noise = kmo_dfs_get_parameter_bool(parlist,
                                                  "kmos.kmo_fits_strip.noise");
        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_ASSURE((remove_noise == TRUE) ||
                       (remove_noise == FALSE),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "noise must be TRUE or FALSE!");
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_fits_strip.noise"));

        angle = kmo_dfs_get_parameter_int(parlist,
                                          "kmos.kmo_fits_strip.angle");
        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_fits_strip.angle"));

        if (angle >= 0) {
            remove_angle = TRUE;
        }

        KMO_TRY_ASSURE(!remove_angle || ((angle >=0) && (angle < 360)),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "angle must be between 0 and 360 degrees!");

        extension_txt = kmo_dfs_get_parameter_string(parlist,
                                                     "kmos.kmo_fits_strip.extension");
        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_fits_strip.extension"));

        if (strcmp(extension_txt, "") != 0) {

            // extract values from string
            KMO_TRY_EXIT_IF_NULL(
                split = kmo_strsplit(extension_txt, ",", NULL));

            int i = 0;
            while (split[i] != NULL) {
                i++;
            }

            KMO_TRY_EXIT_IF_NULL(
                extension = cpl_vector_new(i));
            KMO_TRY_EXIT_IF_NULL(
                pextension = cpl_vector_get_data(extension));
            i = 0;
            while (split[i] != NULL) {
                pextension[i] = atof(split[i]);
                i++;
            }
            KMO_TRY_CHECK_ERROR_STATE();

            kmo_strfreev(split); split = NULL;

            // sort vector and remove double entries
            KMO_TRY_EXIT_IF_ERROR(
                cpl_vector_sort(extension, CPL_SORT_ASCENDING));

            kv = kmclipm_vector_create(extension);

            for (i = 0; i < kmclipm_vector_get_size(kv)-1; i++) {
                if (kmclipm_vector_get(kv, i, NULL) == kmclipm_vector_get(kv, i+1, NULL)) {
                    kmclipm_vector_reject(kv, i+1);
                }
            }
            extension = kmclipm_vector_create_non_rejected(kv);
            kmclipm_vector_delete(kv); kv = NULL;

            KMO_TRY_ASSURE(cpl_vector_get_max(extension) <= desc.nr_ext,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "The input frame has less extensions than specified in 'extensions'-parameter!");
            KMO_TRY_ASSURE(cpl_vector_get_max(extension) >= 1,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "All values in 'extensions'-parameter must be > 1 !");
        }

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

        if ((!remove_empty) && (!remove_noise) && (!remove_angle) && (extension == NULL)) {
            // do nothing
            cpl_msg_info("","No action has been specified (angle-, noise- or empty-parameter),"
                            " therefore no output is generated");
        } else if (extension != NULL) {
            //
            // --- save primary extension ---
            //
            // load data and save it away again
            KMO_TRY_EXIT_IF_NULL(
                header = kmo_dfs_load_primary_header(frameset, "0"));
            KMO_TRY_EXIT_IF_ERROR(
                kmo_save_header_fits_strip(header, parlist, frameset, filename));
            cpl_propertylist_delete(header); header = NULL;

            //
            // loop extensions
            //
            KMO_TRY_EXIT_IF_NULL(
                pextension = cpl_vector_get_data(extension));
            for (int i = 0; i < cpl_vector_get_size(extension); i++) {
                KMO_TRY_EXIT_IF_NULL(
                    sub_header = cpl_propertylist_load(filename, pextension[i]));
                if ((desc.fits_type == f2d_fits) ||
                    (desc.fits_type == f2i_fits))
                {
                    img = cpl_image_load(filename,CPL_TYPE_FLOAT, 0, pextension[i]);
                    if (CPL_ERROR_NONE != cpl_error_get_code()) {
                        cpl_error_reset();
                    }
                    KMO_TRY_EXIT_IF_ERROR(
                        kmo_dfs_save_image(img, STRIP, "", sub_header, 0./0.));
                    cpl_image_delete(img); img = NULL;
                } else if (desc.fits_type == f3i_fits) {
                    cube = cpl_imagelist_load(filename,CPL_TYPE_FLOAT, pextension[i]);
                    if (CPL_ERROR_NONE != cpl_error_get_code()) {
                        cpl_error_reset();
                    }
                    KMO_TRY_EXIT_IF_ERROR(
                        kmo_dfs_save_cube(cube, STRIP, "", sub_header, 0./0.));
                    cpl_imagelist_delete(cube); cube = NULL;
                } else if (desc.fits_type == f1i_fits) {
                    vec = cpl_vector_load(filename, pextension[i]);
                    if (CPL_ERROR_NONE != cpl_error_get_code()) {
                        cpl_error_reset();
                    }
                    KMO_TRY_EXIT_IF_NULL(
                        kv = kmclipm_vector_create(vec));
                    KMO_TRY_EXIT_IF_ERROR(
                        kmo_dfs_save_vector(kv, STRIP, "", sub_header, 0./0.));
                    kmclipm_vector_delete(kv); kv = NULL;
                }
                cpl_propertylist_delete(sub_header); sub_header = NULL;
            }
        } else {
            // check if it is a multi-angle-calibration file
            isCalFrame = FALSE;
            KMO_TRY_EXIT_IF_NULL(
                sub_header = kmo_dfs_load_sub_header(frameset, "0", 1, FALSE));
            if (cpl_propertylist_has(sub_header, CAL_ROTANGLE)) {
                isCalFrame = TRUE;
            }
            cpl_propertylist_delete(sub_header); sub_header = NULL;

            //
            // --- save primary extension ---
            //
            // load data and save it away again
            KMO_TRY_EXIT_IF_NULL(
                header = kmo_dfs_load_primary_header(frameset, "0"));

            if (strcmp(MASTER_FLAT, cpl_propertylist_get_string(header, CPL_DFS_PRO_CATG))==0) {
                isMasterFlat = TRUE;
            }

            if (remove_angle && isCalFrame) {
                // update OCS.ROT.NAANGLE if it differs
                ocsRotAngle = cpl_propertylist_get_double(header, ROTANGLE);
                ocsRotAngle = kmclipm_strip_angle(&ocsRotAngle);
                proRotAngle = kmclipm_cal_propertylist_find_angle(filename, 1, FALSE, angle,
                        &dummy, &secClosestAng);
                KMO_TRY_CHECK_ERROR_STATE();
                if (fabs(ocsRotAngle-proRotAngle) > 0.1) {
                    cpl_msg_warning("", "In the product the original ESO OCS ROT NAANGLE keyword "
                                        "has been updated with the chosen "
                                        "ESO PRO ROT NAANGLE (was: %g, is: %g)!", ocsRotAngle, proRotAngle);
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_propertylist_update_double(header, ROTANGLE, proRotAngle));
                }
            }

            KMO_TRY_EXIT_IF_ERROR(
                kmo_save_header_fits_strip(header, parlist, frameset, filename));
            cpl_propertylist_delete(header); header = NULL;

            if (!isCalFrame) {
                if (!desc.ex_noise) {
                    nr_devices = desc.nr_ext;
                } else {
                    nr_devices = desc.nr_ext / 2;
                }
            } else {
                nr_devices = 3;
            }

            if (isCalFrame) {
                if (isMasterFlat) {
                    remove_noise = !remove_noise;
                }
            } else {
                if (desc.ex_noise) {
                    remove_noise = !remove_noise;
                }
            }

            actDetNr = 1;
            for (int i = 0; i < nr_devices; i++) {

                // either loop noise or not
                for (int n = FALSE; n <= remove_noise; n++) {

                    if (isCalFrame) {
                        KMO_TRY_EXIT_IF_NULL(
                            header = kmclipm_cal_propertylist_load(filename, i+1, n, angle, &ret_angle));
                    } else {
                        KMO_TRY_EXIT_IF_NULL(
                            header = kmo_dfs_load_sub_header(frameset, "0", i + 1, n));
                    }

                    if (remove_angle) {
                        // examine angle
                        if (cpl_propertylist_has(header, CAL_ROTANGLE)) {
                            tmp_angle = cpl_propertylist_get_double(header, CAL_ROTANGLE);
                            KMO_TRY_CHECK_ERROR_STATE();

                            if (fabs(angle - tmp_angle) < 0.01) {
                                found_angle = TRUE;
                                isCalFrame = TRUE;
                            } else {
                                found_angle = FALSE;
                            }
                        } else {
                            // frame doesn't seem to have the CAL_ROTANGLE keyword
                            // process all extensions
                            found_angle = TRUE;
                        }
                    } else {
                        found_angle = TRUE;
                    }

                    index = kmo_identify_index(filename, i+1, n);
                    if (found_angle) {
                        if ((desc.fits_type == f2d_fits) ||
                            (desc.fits_type == f2i_fits))
                        {
                            if (isCalFrame) {
                                cal_device_nr = cpl_propertylist_get_int(header, CHIPINDEX);
                                KMO_TRY_CHECK_ERROR_STATE();
                                if (cal_device_nr == actDetNr) {
                                    KMO_TRY_EXIT_IF_ERROR(
                                        kmo_update_sub_keywords(header,
                                                                n,
                                                                FALSE,
                                                                desc.frame_type,
                                                                actDetNr));
                                    print_cal_angle_msg_once = FALSE;
                                    print_xcal_angle_msg_once = FALSE;
                                    KMO_TRY_EXIT_IF_NULL(
                                        img = kmo_dfs_load_cal_image(frameset, "0",
                                                                     i+1, n, angle,
                                                                     FALSE, NULL,
                                                                     &ret_angle, -1, 0, 0));

                                    if (fabs(angle-ret_angle) > 0.01) {
                                        cpl_msg_warning("","Angle provided: %g, angle found: %g", angle, ret_angle);
                                    }
                                    if (n == remove_noise) {
                                        actDetNr++;
                                    }
                                }
                                if (img != NULL) {
                                    KMO_TRY_EXIT_IF_ERROR(
                                        kmo_dfs_save_image(img, STRIP, "", header, 0./0.));
                                }
                            } else {
                                img = cpl_image_load(filename,CPL_TYPE_FLOAT, 0, index);
                                if (CPL_ERROR_NONE != cpl_error_get_code()) {
                                    cpl_error_reset();
                                    if (!remove_empty) {
                                        KMO_TRY_EXIT_IF_ERROR(
                                            kmo_dfs_save_image(img, STRIP, "", header, 0./0.));
                                    }
                                } else {
                                    KMO_TRY_EXIT_IF_ERROR(
                                        kmo_dfs_save_image(img, STRIP, "", header, 0./0.));
                                }
                            }
                            cpl_image_delete(img); img = NULL;
                        } else if (desc.fits_type == f3i_fits) {
                            cube = cpl_imagelist_load(filename,CPL_TYPE_FLOAT, index);
                            if (CPL_ERROR_NONE != cpl_error_get_code()) {
                                cpl_error_reset();
                                if (!remove_empty) {
                                    KMO_TRY_EXIT_IF_ERROR(
                                        kmo_dfs_save_cube(cube, STRIP, "", header, 0./0.));
                                }
                            } else {
                                KMO_TRY_EXIT_IF_ERROR(
                                    kmo_dfs_save_cube(cube, STRIP, "", header, 0./0.));
                            }
                            cpl_imagelist_delete(cube); cube = NULL;
                        } else if (desc.fits_type == f1i_fits) {
                            vec = cpl_vector_load(filename, index);
                            if (CPL_ERROR_NONE != cpl_error_get_code()) {
                                cpl_error_reset();
                                if (!remove_empty) {
                                    KMO_TRY_EXIT_IF_ERROR(
                                        kmo_dfs_save_vector(NULL, STRIP, "", header, 0./0.));
                                }
                            } else {
                                KMO_TRY_EXIT_IF_NULL(
                                    kv = kmclipm_vector_create(vec));
                                KMO_TRY_EXIT_IF_ERROR(
                                    kmo_dfs_save_vector(kv, STRIP, "", header, 0./0.));
                            }
                            kmclipm_vector_delete(kv); kv = NULL;
                        }
                    } // if (found_angle)
                    cpl_propertylist_delete(header); header = NULL;
                } // for (n)
            } // for (i = nr_devices)
        } // if ((!remove_empty) && (!remove_noise) && (!remove_angle))
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();

        ret_val = -1;
    }

    cpl_propertylist_delete(header); header = NULL;
    cpl_imagelist_delete(cube); cube = NULL;
    cpl_image_delete(img); img = NULL;
    kmclipm_vector_delete(kv); kv = NULL;
    kmo_free_fits_desc(&desc);
    cpl_vector_delete(extension); extension = NULL;

    return ret_val;
}

/**@}*/
