/*
 * This file is part of the KMOS Pipeline
 * Copyright (C) 2002,2003 European Southern Observatory
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

/*-----------------------------------------------------------------------------
 *                              Includes
 *----------------------------------------------------------------------------*/

#include <string.h>
#include <math.h>

#include <cpl.h>

#include "kmo_utils.h"
#include "kmo_functions.h"
#include "kmo_priv_reconstruct.h"
#include "kmo_priv_functions.h"
#include "kmo_priv_lcorr.h"
#include "kmo_cpl_extensions.h"
#include "kmo_dfs.h"
#include "kmo_error.h"
#include "kmo_utils.h"
#include "kmo_constants.h"
#include "kmo_debug.h"

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

static int kmo_reconstruct_create(cpl_plugin *);
static int kmo_reconstruct_exec(cpl_plugin *);
static int kmo_reconstruct_destroy(cpl_plugin *);
static int kmo_reconstruct(cpl_parameterlist *, cpl_frameset *);

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

static char kmo_reconstruct_description[] =
"Data with or without noise is reconstructed into a cube using the\n"
"calibration frames XCAL, YCAL and LCAL. XCAL and YCAL are generated using\n"
"recipe kmo_flat, LCAL is generated using recipe kmo_wave_cal.\n"
"The input data can contain noise extensions and will be reconstructed into\n"
"additional extensions.\n"
"If an OH spectrum is given in the SOF file the lambda axis will be corrected\n"
"using the OH lines as reference.\n"
"\n"
"BASIC PARAMETERS:\n"
"-----------------\n"
"--imethod\n"
"The interpolation method used for reconstruction.\n"
"\n"
"--detectorimage\n"
"Specify if the resampled image of the input frame should be generated.\n"
"Therefore all slitlets of all IFUs are aligned one next to the other. This\n"
"frame serves for quality control. One can immediately see if the\n"
"reconstruction was successful.\n"
"\n"
"--file_extension"
"Set to TRUE if OBS_ID (from input frame header) should be appended to the\n"
"output frame.\n"
"\n"
"ADVANCED PARAMETERS\n"
"-------------------\n"
"--flux\n"
"Specify if flux conservation should be applied.\n"
"\n"
"--neighborhoodRange\n"
"Defines the range to search for neighbors during reconstruction\n"
"\n"
"--b_samples\n"
"The number of samples in spectral direction for the reconstructed cube."
"Ideally this number should be greater than 2048, the detector size.\n"
"\n"
"--b_start\n"
"--b_end\n"
"Used to define manually the start and end wavelength for the reconstructed\n"
"cube. By default the internally defined values are used.\n"
"\n"
"--pix_scale\n"
"Change the pixel scale [arcsec]. Default of 0.2\" results into cubes of\n"
"14x14pix, a scale of 0.1\" results into cubes of 28x28pix, etc.\n"
"\n"
"--xcal_interpolation\n"
"If TRUE interpolate the pixel position in the slitlet (xcal) using the two\n"
"closest rotator angles in the calibration file. Otherwise take the values\n"
"of the closest rotator angle\n"
"\n"
"---------------------------------------------------------------------------\n"
"  Input files:\n"
"\n"
"   DO              KMOS                                                    \n"
"   category        Type     Explanation                    Required #Frames\n"
"   --------        -----    -----------                    -------- -------\n"
"   DARK    or      RAW/F2D  data with                          Y       1   \n"
"   FLAT_ON or      RAW/F2D  or without noise                               \n"
"   ARC_ON  or      RAW/F2D                                                 \n"
"   OBJECT  or      RAW                                                     \n"
"   STD     or      RAW                                                     \n"
"   SCIENCE         RAW                                                     \n"
"   XCAL            F2D      x-direction calib. frame           Y       1   \n"
"   YCAL            F2D      y-direction calib. frame           Y       1   \n"
"   LCAL            F2D      Wavelength calib. frame            Y       1   \n"
"   WAVE_BAND       F2L      Table with start-/end-wavelengths  Y       1   \n"
"   OH_SPEC         F1S      Vector holding OH lines            N       1   \n"
"\n"
"  Output files:\n"
"\n"
"   DO                KMOS\n"
"   category          Type     Explanation\n"
"   --------              -----    -----------\n"
"   CUBE_DARK   or    F3I      Reconstructed cube   \n"
"   CUBE_FLAT   or    RAW/F2D  with or without noise\n"
"   CUBE_ARC    or                                  \n"
"   CUBE_OBJECT or                                  \n"
"   CUBE_STD    or                                  \n"
"   CUBE_SCIENCE                                    \n"
"---------------------------------------------------------------------------\n"
"\n";

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

/*----------------------------------------------------------------------------*/
/**
 * @defgroup kmo_reconstruct    Performs the cube reconstruction
 */
/*----------------------------------------------------------------------------*/

/**@{*/

/*----------------------------------------------------------------------------*/
/**
  @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_reconstruct",
                        "Performs the cube reconstruction "
                        "using different interpolation methods.",
                        kmo_reconstruct_description,
                        "Alex Agudo Berbel",
                        "usd-help@eso.org",
                        kmos_get_license(),
                        kmo_reconstruct_create,
                        kmo_reconstruct_exec,
                        kmo_reconstruct_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_reconstruct_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 */
    /* --imethod */
    p = cpl_parameter_new_value("kmos.kmo_reconstruct.imethod", CPL_TYPE_STRING,
            "Method to use for interpolation. [\"NN\" (nearest neighbour), "
            "\"lwNN\" (linear weighted nearest neighbor), "
            "\"swNN\" (square weighted nearest neighbor), "
            "\"MS\" (Modified Shepard's method)"
            "\"CS\" (Cubic spline)]",
            "kmos.kmo_reconstruct", "CS");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "imethod");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --neighborhoodRange */
    p = cpl_parameter_new_value("kmos.kmo_reconstruct.neighborhoodRange",
            CPL_TYPE_DOUBLE,
            "Defines the range to search for neighbors. in pixels",
            "kmos.kmo_reconstruct", 1.001);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "neighborhoodRange");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --flux */
    p = cpl_parameter_new_value("kmos.kmo_reconstruct.flux", CPL_TYPE_BOOL,
            "TRUE: Apply flux conservation. FALSE: otherwise",
            "kmos.kmo_reconstruct", 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);

    /* --detectorimage */
    p = cpl_parameter_new_value("kmos.kmo_reconstruct.detectorimage",
            CPL_TYPE_BOOL,
            "TRUE: if resampled detector frame should be "
            "created, FALSE: otherwise",
            "kmos.kmo_reconstruct", FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "detimg");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --file_extension */
    p = cpl_parameter_new_value("kmos.kmo_reconstruct.file_extension",
            CPL_TYPE_BOOL,
            "TRUE: if OBS_ID keyword should be appended to "
            "output frames, FALSE: otherwise",
            "kmos.kmo_reconstruct", FALSE);
    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);

    /* --pix_scale */
    p = cpl_parameter_new_value("kmos.kmo_reconstruct.pix_scale",
            CPL_TYPE_DOUBLE,
            "Change the pixel scale [arcsec]. "
            "Default of 0.2\" results into cubes of 14x14pix, "
            "a scale of 0.1\" results into cubes of 28x28pix, etc.",
            "kmos.kmo_reconstruct", KMOS_PIX_RESOLUTION);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "pix_scale");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --xcal_interpolation */
    p = cpl_parameter_new_value("kmos.kmo_reconstruct.xcal_interpolation",
            CPL_TYPE_BOOL,
            "TRUE: Interpolate xcal between rotator angles. FALSE: otherwise",
            "kmos.kmo_reconstruct", TRUE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "xcal_interpolation");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* Add parameters for band-definition */
    kmos_band_pars_create(recipe->parameters, "kmos.kmo_reconstruct");
    return 0;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Execute the plugin instance given by the interface
  @param    plugin  the plugin
  @return   0 if everything is ok
 */
/*----------------------------------------------------------------------------*/
static int kmo_reconstruct_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_reconstruct(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_reconstruct_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_reconstruct(cpl_parameterlist *parlist, cpl_frameset *frameset)
{
    int                 ret_val                 = 0,
                        nr_devices              = 0,
                        i                       = 0,
                        j                       = 0,
                        flux                    = FALSE,
                        background              = FALSE,
                        index                   = 0,
                        detectorimage           = 0,
                        *bounds                 = NULL,
                        ifu_nr                  = 0,
                        obs_id                  = 0,
                        file_extension          = FALSE,
                        xcal_interpolation      = FALSE,
                        detImgCube              = FALSE,
                        l = 0, x = 0, y = 0;
    float               *pdet_img_data          = NULL,
                        *pdet_img_noise         = NULL,
                        *slice                  = NULL;
    double              neighborhoodRange       = 1.001,
                        pix_scale               = 0.0;

    const char          *imethod                = NULL,
                        *input_frame_name       = NULL,
                        *output_frame_name      = NULL,
                        *filter_id              = NULL,
                        *filter_id_tmp          = NULL,
                        *tmp_str                = NULL;
    char                *keyword                = NULL,
                        *filename_cube          = NULL,
                        *filename_img           = NULL,
//                        *fn_lut                 = NULL,
                        *suffix                 = NULL,
                        *obs_suffix             = NULL,
                        *my_filter_id           = NULL,
                        *extname                = NULL;
    cpl_image           *lcal                   = NULL,
                        *det_img_data[KMOS_NR_DETECTORS],
                        *det_img_noise[KMOS_NR_DETECTORS],
                        *tmp_img                = NULL;
    cpl_imagelist       *cube_data              = NULL,
                        *cube_noise             = NULL;
    cpl_frame           *rec_frame              = NULL,
                        *xcal_frame             = NULL,
                        *ycal_frame             = NULL,
                        *lcal_frame             = NULL,
                        *ref_spectrum_frame     = NULL;
    cpl_propertylist    *main_header           = NULL,
                        *sub_header            = NULL,
                        *sub_header_orig       = NULL,
                        *actual_sub_header     = NULL,
                        *tmp_header            = NULL;
    cpl_table           *band_table            = NULL;
    gridDefinition      gd;
    main_fits_desc      desc1,
                        desc2;
    cpl_polynomial      *lcorr_coeffs = NULL;

    for (i = 0; i < KMOS_NR_DETECTORS; i++) {
        det_img_data[i] = NULL;
        det_img_noise[i] = 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((cpl_frameset_count_tags(frameset, DARK) == 1) ||
                       (cpl_frameset_count_tags(frameset, FLAT_ON) == 1) ||
                       (cpl_frameset_count_tags(frameset, ARC_ON) == 1) ||
                       (cpl_frameset_count_tags(frameset, OBJECT) == 1) ||
                       (cpl_frameset_count_tags(frameset, STD) == 1) ||
                       (cpl_frameset_count_tags(frameset, SCIENCE) == 1),
                       CPL_ERROR_NULL_INPUT,
                       "A data frame (DARK, FLAT_ON, ARC_ON, OBJECT, STD or SCIENCE) must "
                       "be provided!");

        KMO_TRY_ASSURE(cpl_frameset_count_tags(frameset, XCAL) == 1,
                       CPL_ERROR_FILE_NOT_FOUND,
                       "XCAL frame missing in frameset!!");

        KMO_TRY_ASSURE(cpl_frameset_count_tags(frameset, YCAL) == 1,
                       CPL_ERROR_FILE_NOT_FOUND,
                       "YCAL frame missing in frameset!!");

        KMO_TRY_ASSURE(cpl_frameset_count_tags(frameset, LCAL) == 1,
                       CPL_ERROR_FILE_NOT_FOUND,
                       "LCAL frame missing in frameset!!");

        KMO_TRY_ASSURE(cpl_frameset_count_tags(frameset, WAVE_BAND) == 1,
                       CPL_ERROR_FILE_NOT_FOUND,
                       "WAVE_BAND frame missing in frameset!!");

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

        KMO_TRY_ASSURE(cpl_frameset_count_tags(frameset, OH_SPEC) == 0 ||
                       cpl_frameset_count_tags(frameset, OH_SPEC) == 1,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Only a single reference spectrum can be provided!");

        // --- get parameters ---
        cpl_msg_info("", "--- Parameter setup for kmo_reconstruct ---");

        KMO_TRY_EXIT_IF_NULL(
            imethod = kmo_dfs_get_parameter_string(parlist,
                                              "kmos.kmo_reconstruct.imethod"));

        KMO_TRY_ASSURE((strcmp(imethod, "NN") == 0) ||
                       (strcmp(imethod, "lwNN") == 0) ||
                       (strcmp(imethod, "swNN") == 0) ||
                       (strcmp(imethod, "MS") == 0) ||
                       (strcmp(imethod, "CS") == 0),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "imethod must be either \"NN\", \"lwNN\", "
                       "\"swNN\", \"MS\" or \"CS\"!");

        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist,
                                        "kmos.kmo_reconstruct.imethod"));

        flux = kmo_dfs_get_parameter_bool(parlist,
                                          "kmos.kmo_reconstruct.flux");

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

        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_reconstruct.flux"));

        detectorimage = kmo_dfs_get_parameter_bool(parlist,
                                          "kmos.kmo_reconstruct.detectorimage");

        KMO_TRY_ASSURE((detectorimage == 0) ||
                       (detectorimage == 1),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "detectorimage must be either 0 or 1 !");

        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_reconstruct.detectorimage"));

        neighborhoodRange = kmo_dfs_get_parameter_double(parlist,
                "kmos.kmo_reconstruct.neighborhoodRange");
        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_ASSURE(neighborhoodRange > 0.0,
                CPL_ERROR_ILLEGAL_INPUT,
                "neighborhoodRange must be greater than 0.0");
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist,
                                     "kmos.kmo_reconstruct.neighborhoodRange"));

        kmos_band_pars_load(parlist, "kmos.kmo_reconstruct");

        file_extension = kmo_dfs_get_parameter_bool(parlist,
                                        "kmos.kmo_reconstruct.file_extension");
        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_EXIT_IF_ERROR(
           kmo_dfs_print_parameter_help(parlist,
                                       "kmos.kmo_reconstruct.file_extension"));

        pix_scale = kmo_dfs_get_parameter_double(parlist,
                                        "kmos.kmo_reconstruct.pix_scale");
        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_EXIT_IF_ERROR(
           kmo_dfs_print_parameter_help(parlist,
                                       "kmos.kmo_reconstruct.pix_scale"));
        KMO_TRY_ASSURE((pix_scale >= 0.01) &&
                       (pix_scale <= 0.4),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "pix_scale must be between 0.01 and 0.4 (results in cubes "
                       "with 7x7 to 280x280 pixels)!");

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


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

        // load descriptor and header of data frame to reconstruct
        if (cpl_frameset_count_tags(frameset, DARK) == 1) {
            input_frame_name = DARK;
            output_frame_name = CUBE_DARK;
        } else if (cpl_frameset_count_tags(frameset, FLAT_ON) == 1) {
            input_frame_name = FLAT_ON;
            output_frame_name = CUBE_FLAT;
        } else if (cpl_frameset_count_tags(frameset, ARC_ON) == 1) {
            input_frame_name = ARC_ON;
            output_frame_name = CUBE_ARC;
        } else if (cpl_frameset_count_tags(frameset, OBJECT) == 1) {
            input_frame_name = OBJECT;
            output_frame_name = CUBE_OBJECT;
        } else if (cpl_frameset_count_tags(frameset, STD) == 1) {
            input_frame_name = STD;
            output_frame_name = CUBE_STD;
        } else if (cpl_frameset_count_tags(frameset, SCIENCE) == 1) {
            input_frame_name = SCIENCE;
            output_frame_name = CUBE_SCIENCE;
        }

        // assure that filters, grating and rotation offsets match for
        // XCAL, YCAL, LCAL and for data frame to reconstruct (except DARK
        // frames)
        // check if filter_id and grating_id match for all detectors
        KMO_TRY_EXIT_IF_ERROR(
            kmo_check_frame_setup(frameset, XCAL, YCAL,
                                       TRUE, FALSE, TRUE));
        KMO_TRY_EXIT_IF_ERROR(
            kmo_check_frame_setup(frameset, XCAL, LCAL,
                                       TRUE, FALSE, TRUE));

        if (cpl_frameset_count_tags(frameset, OH_SPEC) > 0) {
            KMO_TRY_EXIT_IF_ERROR(kmo_check_oh_spec_setup(frameset, XCAL));
        }

        if (cpl_frameset_count_tags(frameset, DARK) != 1) {
            // check if filters, gratings and rotator offset match
            // (except for DARK frames)
            KMO_TRY_EXIT_IF_ERROR(
                kmo_check_frame_setup(frameset, XCAL, input_frame_name,
                                           TRUE, FALSE, FALSE));
        }

        KMO_TRY_EXIT_IF_NULL(
            xcal_frame = kmo_dfs_get_frame(frameset, XCAL));
        KMO_TRY_EXIT_IF_NULL(
            rec_frame = kmo_dfs_get_frame(frameset, input_frame_name));
        KMO_TRY_EXIT_IF_NULL(
            suffix = kmo_dfs_get_suffix(rec_frame, TRUE, TRUE));

        KMO_TRY_EXIT_IF_ERROR(
            kmo_check_frame_setup_md5_xycal(frameset));
        KMO_TRY_EXIT_IF_ERROR(
            kmo_check_frame_setup_md5(frameset));

        cpl_msg_info("", "Detected instrument setup:   %s", suffix+1);
        cpl_msg_info("", "(grating 1, 2 & 3, rotation angle)");
        cpl_msg_info("", "-------------------------------------------");

        KMO_TRY_EXIT_IF_NULL(
            main_header = kmo_dfs_load_primary_header(frameset,
                                                      input_frame_name));

        if (cpl_frameset_count_tags(frameset, OH_SPEC) != 0) {
            if (cpl_propertylist_has(main_header, ORIGFILE)) {
                KMO_TRY_EXIT_IF_NULL(
                    tmp_str = cpl_propertylist_get_string(main_header, ORIGFILE));
                if (strstr(tmp_str, "OBS") != NULL) {
                    // we are reconstructing an OBS-frame, allow OH_SPEC correction
                    KMO_TRY_EXIT_IF_NULL(
                        ref_spectrum_frame = kmo_dfs_get_frame(frameset, OH_SPEC));
                } else {
                    cpl_msg_warning("", "Supplied OH_SPEC is ignored since a calibration frame is being reconstructed.");
                }
            } else {
                cpl_msg_warning("", "The supplied frame %s is assumed to be a science frame. If it is a calibration frame, omit OH_SPEC from sof-file", input_frame_name);
                KMO_TRY_EXIT_IF_NULL(
                    ref_spectrum_frame = kmo_dfs_get_frame(frameset, OH_SPEC));
            }
        }

        desc1 = kmo_identify_fits_header(cpl_frame_get_filename(rec_frame));
        KMO_TRY_CHECK_ERROR_STATE();

        KMO_TRY_ASSURE(((desc1.nr_ext == KMOS_NR_DETECTORS) ||
                        ((desc1.nr_ext == 2*KMOS_NR_DETECTORS))) &&
                       (desc1.ex_badpix == FALSE) &&
                       ((desc1.fits_type == raw_fits) ||
                        (desc1.fits_type == f2d_fits)) &&
                       (desc1.frame_type == detector_frame),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "The frame to reconstruct isn't in the correct format!!!"
                       "Exactly 3 frames, or 6 with noise are expected!");

        if (!desc1.ex_noise) {
            nr_devices = desc1.nr_ext;
        } else {
            nr_devices = desc1.nr_ext / 2;
        }

        // compare descriptor of XCAL and data frame to reconstruct
        desc2 = kmo_identify_fits_header(cpl_frame_get_filename(xcal_frame));
        KMO_TRY_CHECK_ERROR_STATE();

        KMO_TRY_ASSURE((desc2.nr_ext % 3 == 0) &&
                       (desc1.ex_badpix == desc2.ex_badpix) &&
                       (desc1.frame_type == desc2.frame_type),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "XCAL isn't in the correct format!!!");

        kmo_free_fits_desc(&desc2);

        // compare descriptor of YCAL and data frame to reconstruct
        kmo_init_fits_desc(&desc2);

        KMO_TRY_EXIT_IF_NULL(
            ycal_frame = kmo_dfs_get_frame(frameset, YCAL));

        desc2 = kmo_identify_fits_header(cpl_frame_get_filename(ycal_frame));
        KMO_TRY_CHECK_ERROR_STATE();

        KMO_TRY_ASSURE((desc2.nr_ext % 3 == 0) &&
                       (desc1.ex_badpix == desc2.ex_badpix) &&
                       (desc1.frame_type == desc2.frame_type),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "YCAL isn't in the correct format!!!");

        kmo_free_fits_desc(&desc2);

        // compare descriptor of LCAL and data frame to reconstruct
        kmo_init_fits_desc(&desc2);

        KMO_TRY_EXIT_IF_NULL(
            lcal_frame = kmo_dfs_get_frame(frameset, LCAL));

        desc2 = kmo_identify_fits_header(cpl_frame_get_filename(lcal_frame));
        KMO_TRY_CHECK_ERROR_STATE();

        KMO_TRY_ASSURE((desc2.nr_ext % 3 == 0) &&
                       (desc1.ex_badpix == desc2.ex_badpix) &&
                       (desc1.frame_type == desc2.frame_type),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "LCAL isn't in the correct format!!!");

        kmo_free_fits_desc(&desc2);

        //
        // --- update & save primary header ---
        //
        KMO_TRY_EXIT_IF_NULL(
            tmp_header = kmo_dfs_load_primary_header(frameset, LCAL));

        // assert that filters have correct IDs and that all detectors of all
        // input frames have the same filter set
        for (i = 1; i <= nr_devices; i++) {
            // ESO INS FILTi ID
            KMO_TRY_EXIT_IF_NULL(
                keyword = cpl_sprintf("%s%d%s", IFU_FILTID_PREFIX, i,
                                      IFU_FILTID_POSTFIX));

            KMO_TRY_EXIT_IF_NULL(
                filter_id = cpl_propertylist_get_string(tmp_header, keyword));

            KMO_TRY_ASSURE((strcmp(filter_id, "IZ") == 0) ||
                           (strcmp(filter_id, "YJ") == 0) ||
                           (strcmp(filter_id, "H") == 0) ||
                           (strcmp(filter_id, "K") == 0) ||
                           (strcmp(filter_id, "HK") == 0),
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Filter ID in primary header of LCAL frame must "
                           "be either \"IZ\", \"YJ\", \"H\", \"K\" or "
                           "\"HK\" !");

            if (strcmp(input_frame_name, DARK) != 0) {
                // dark needn't to be taken with filter!

                KMO_TRY_EXIT_IF_NULL(
                    filter_id_tmp = cpl_propertylist_get_string(main_header,
                                                                keyword));
                KMO_TRY_ASSURE(strcmp(filter_id, filter_id_tmp) == 0,
                               CPL_ERROR_ILLEGAL_INPUT,
                               "Filter IDs must be the same for LCAL frame and "
                               "the frame to reconstruct!"
                               "Detector No.: %d\nLCAL: %s\n%s: %s\n",
                               i, filter_id, input_frame_name, filter_id_tmp);
            }
            cpl_free(keyword); keyword = NULL;
        }
        KMO_TRY_EXIT_IF_NULL(
            my_filter_id = cpl_strdup(filter_id));
        cpl_propertylist_delete(tmp_header); tmp_header = NULL;

        obs_id = cpl_propertylist_get_int(main_header, OBS_ID);
        KMO_TRY_CHECK_ERROR_STATE();

        KMO_TRY_EXIT_IF_NULL(
            filename_cube = cpl_sprintf("%s", output_frame_name));
        KMO_TRY_EXIT_IF_NULL(
            filename_img = cpl_sprintf("%s", DET_IMG_REC));
        if (file_extension) {
            KMO_TRY_EXIT_IF_NULL(
                obs_suffix = cpl_sprintf("%s%d", "_", obs_id));
        } else {
            KMO_TRY_EXIT_IF_NULL(
                obs_suffix = cpl_sprintf("%s", ""));
        }

        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_save_main_header(frameset, filename_cube, obs_suffix,
                                     rec_frame, NULL, parlist, cpl_func));

        // setup grid definition, wavelength start and end points will be set
        // in the detector loop
        KMO_TRY_EXIT_IF_ERROR(
            kmclipm_setup_grid(&gd, imethod, neighborhoodRange, pix_scale, 0.));

        KMO_TRY_EXIT_IF_NULL(
            tmp_header = kmo_dfs_load_primary_header(frameset, XCAL));

        KMO_TRY_EXIT_IF_NULL(
            bounds = kmclipm_extract_bounds(tmp_header));
        cpl_propertylist_delete(tmp_header); tmp_header = NULL;

        if (detectorimage == TRUE) {
            KMO_TRY_EXIT_IF_ERROR(
                kmo_dfs_save_main_header(frameset, filename_img, obs_suffix,
                                         rec_frame, NULL, parlist, cpl_func));
        }

        /* loop through all detectors */
        for (i = 1; i <= nr_devices; i++) {
            cpl_msg_info("","Processing detector No. %d", i);

            // load lcal
            // extract LCAL image close to ROTANGLE 0. assuming that the wavelength range
            // doesn't differ too much with different ROTANGLEs.
            double rotangle_found;
            print_cal_angle_msg_once = FALSE;
            print_xcal_angle_msg_once = FALSE;
            KMO_TRY_EXIT_IF_NULL(
                lcal = kmo_dfs_load_cal_image(frameset, LCAL, i, FALSE, 0.,
                                              FALSE, NULL, &rotangle_found, -1, 0, 0));
            if (i==1) {
                print_cal_angle_msg_once = TRUE;
                print_xcal_angle_msg_once = TRUE;
            }

            KMO_TRY_EXIT_IF_NULL(
                band_table = kmo_dfs_load_table(frameset, WAVE_BAND, 1, FALSE));

            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_setup_grid_band_lcal(&gd, my_filter_id,
                                             band_table));
            cpl_table_delete(band_table); band_table = NULL;

            cpl_image_delete(lcal); lcal = NULL;

            if (detectorimage == TRUE) {
                KMO_TRY_EXIT_IF_NULL(
                    det_img_data[i-1] = cpl_image_new(gd.x.dim*gd.y.dim*KMOS_IFUS_PER_DETECTOR,
                                                      gd.l.dim, CPL_TYPE_FLOAT));
                KMO_TRY_EXIT_IF_NULL(
                    pdet_img_data = cpl_image_get_data_float(det_img_data[i-1]));

                KMO_TRY_EXIT_IF_NULL(
                    det_img_noise[i-1] = cpl_image_new(gd.x.dim*gd.y.dim*KMOS_IFUS_PER_DETECTOR,
                                                      gd.l.dim, CPL_TYPE_FLOAT));
                KMO_TRY_EXIT_IF_NULL(
                    pdet_img_noise = cpl_image_get_data_float(det_img_noise[i-1]));
            }


            for (j = 0; j < KMOS_IFUS_PER_DETECTOR; j++) {
                /* update sub-header */
                ifu_nr = (i-1)*KMOS_IFUS_PER_DETECTOR + j + 1;

                /* load raw image and sub-header*/
                KMO_TRY_EXIT_IF_NULL(
                    sub_header = kmo_dfs_load_sub_header(frameset, input_frame_name,
                                                         i, FALSE));
                KMO_TRY_EXIT_IF_NULL(
                    sub_header_orig = cpl_propertylist_duplicate(sub_header));

                // check if IFU is valid according to main header keywords &
                // calibration files

                if (getenv("KMOS_RECONSTRUCT_ALL") == NULL) {
                    KMO_TRY_EXIT_IF_NULL(
                        keyword = cpl_sprintf("%s%d%s", IFU_VALID_PREFIX, ifu_nr,
                                              IFU_VALID_POSTFIX));
                    KMO_TRY_CHECK_ERROR_STATE();
                    cpl_propertylist_get_string(main_header, keyword);
                    cpl_free(keyword); keyword = NULL;
                } else {
                    // if KMOS_RECONSTRUCT_ALL is set all IFUs should be
                    // reconstructed
                    cpl_propertylist_get_string(main_header, "ggg");
                }

                if ((cpl_error_get_code() == CPL_ERROR_DATA_NOT_FOUND) &&
                    (bounds[2*(ifu_nr-1)] != -1) &&
                    (bounds[2*(ifu_nr-1)+1] != -1))
                {
                    cpl_error_reset();
                    // IFU is valid
                    actual_sub_header = sub_header;

                    //
                    // calc WCS & update subheader
                    //
                    KMO_TRY_EXIT_IF_ERROR(
                        kmo_calc_wcs_gd(main_header, actual_sub_header, ifu_nr, gd));

                    KMO_TRY_EXIT_IF_ERROR(
                        kmclipm_update_property_int(actual_sub_header,
                                                    NAXIS, 3,
                                                    "number of data axes"));
                    KMO_TRY_EXIT_IF_ERROR(
                        kmclipm_update_property_int(actual_sub_header,
                                                    NAXIS1, gd.x.dim,
                                                    "length of data axis 1"));
                    KMO_TRY_EXIT_IF_ERROR(
                        kmclipm_update_property_int(actual_sub_header,
                                                    NAXIS2, gd.y.dim,
                                                    "length of data axis 2"));
                    KMO_TRY_EXIT_IF_ERROR(
                        kmclipm_update_property_int(actual_sub_header,
                                                    NAXIS3, gd.l.dim,
                                                    "length of data axis 3"));

                    // reconstruct data and noise (if available)
//                    if (j == 0) {
//                        sat_mode_msg = FALSE;
//                    } else {
//                        sat_mode_msg = TRUE;
//                    }
                    KMO_TRY_EXIT_IF_ERROR(
                        kmo_reconstruct_sci(ifu_nr,
                                            bounds[2*(ifu_nr-1)],
                                            bounds[2*(ifu_nr-1)+1],
                                            rec_frame,
                                            input_frame_name,
                                            NULL,
                                            NULL,
                                            NULL,
                                            xcal_frame,
                                            ycal_frame,
                                            lcal_frame,
                                            NULL,
                                            NULL,
                                            &gd,
                                            &cube_data,
                                            &cube_noise,
                                            flux,
                                            background,
                                            xcal_interpolation));

                    if (ref_spectrum_frame != NULL && cube_data != NULL) {
                        KMO_TRY_EXIT_IF_NULL(
                            lcorr_coeffs = kmo_lcorr_get(cube_data,
                                                         actual_sub_header,
                                                         ref_spectrum_frame,
                                                         gd,
                                                         my_filter_id,
                                                         ifu_nr));

                        cpl_imagelist_delete(cube_data); cube_data = NULL;
                        if (cube_noise != NULL) {
                            cpl_imagelist_delete(cube_noise); cube_noise = NULL;
                        }
                        KMO_TRY_EXIT_IF_ERROR(
                            kmo_reconstruct_sci(ifu_nr,
                                                bounds[2*(ifu_nr-1)],
                                                bounds[2*(ifu_nr-1)+1],
                                                rec_frame,
                                                input_frame_name,
                                                NULL,
                                                NULL,
                                                NULL,
                                                xcal_frame,
                                                ycal_frame,
                                                lcal_frame,
                                                lcorr_coeffs,
                                                NULL,
                                                &gd,
                                                &cube_data,
                                                &cube_noise,
                                                flux,
                                                background,
                                                xcal_interpolation));
                        cpl_polynomial_delete(lcorr_coeffs); lcorr_coeffs = NULL;

                    }

                    // scale flux according to pixel_scale
                    KMO_TRY_EXIT_IF_NULL(
                        tmp_img = cpl_imagelist_get(cube_data, 0));
                    double scaling = (cpl_image_get_size_x(tmp_img)*cpl_image_get_size_y(tmp_img)) /
                                     (KMOS_SLITLET_X*KMOS_SLITLET_Y);
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_imagelist_divide_scalar(cube_data, scaling));
                    if (cube_noise != NULL) {
                        KMO_TRY_EXIT_IF_ERROR(
                            cpl_imagelist_divide_scalar(cube_noise, scaling));
                    }
                } else {
                    // IFU is invalid
                    actual_sub_header = sub_header_orig;
                    cpl_error_reset();
                } // if ((cpl_error_get_code() == CPL_ERROR_DATA_NOT_FOUND) ..

                if (detectorimage) {
                    if (cube_data != NULL) {
                        for (l = 0; l < gd.l.dim; l++) {
                            KMO_TRY_EXIT_IF_NULL(
                                slice = cpl_image_get_data_float(cpl_imagelist_get(cube_data, l)));
                            for (y = 0; y < gd.y.dim; y++) {
                                for (x = 0; x < gd.x.dim; x++) {
                                    int ix = x +
                                             y * gd.x.dim +
                                             j * gd.x.dim*gd.y.dim +     //IFU offset
                                             l * gd.x.dim*gd.y.dim*KMOS_IFUS_PER_DETECTOR;
                                    pdet_img_data[ix] = slice[x + y*gd.x.dim];
                                }
                            }
                        }
                    }
                    if (cube_noise != NULL) {
                        if (detectorimage) {
                            detImgCube = TRUE;
                        }
                        for (l = 0; l < gd.l.dim; l++) {
                            KMO_TRY_EXIT_IF_NULL(
                                slice = cpl_image_get_data_float(cpl_imagelist_get(cube_noise, l)));
                            for (y = 0; y < gd.y.dim; y++) {
                                for (x = 0; x < gd.x.dim; x++) {
                                    int ix = x +
                                             y * gd.x.dim +
                                             j * gd.x.dim*gd.y.dim +     //IFU offset
                                             l * gd.x.dim*gd.y.dim*KMOS_IFUS_PER_DETECTOR;
                                    pdet_img_noise[ix] = slice[x + y*gd.x.dim];
                                }
                            }
                        }
                    }
                }

                // save output
                KMO_TRY_EXIT_IF_NULL(
                    extname = kmo_extname_creator(ifu_frame, ifu_nr,
                                                  EXT_DATA));

                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_string(actual_sub_header,
                                            EXTNAME,
                                            extname,
                                            "FITS extension name"));

                cpl_free(extname); extname = NULL;

                KMO_TRY_EXIT_IF_ERROR(
                    kmo_dfs_save_cube(cube_data, filename_cube, obs_suffix,
                                      actual_sub_header, 0./0.));

                if (cube_noise != NULL) {
                    KMO_TRY_EXIT_IF_NULL(
                        extname = kmo_extname_creator(ifu_frame, ifu_nr,
                                                      EXT_NOISE));

                    KMO_TRY_EXIT_IF_ERROR(
                        kmclipm_update_property_string(actual_sub_header,
                                                EXTNAME,
                                                extname,
                                                "FITS extension name"));

                    cpl_free(extname); extname = NULL;

                    KMO_TRY_EXIT_IF_ERROR(
                        kmo_dfs_save_cube(cube_noise, filename_cube, obs_suffix,
                                          actual_sub_header, 0./0.));
                }

                cpl_imagelist_delete(cube_data); cube_data = NULL;
                cpl_imagelist_delete(cube_noise); cube_noise = NULL;
                cpl_propertylist_delete(sub_header); sub_header = NULL;
                cpl_propertylist_delete(sub_header_orig); sub_header_orig = NULL;
            } // for j IFUs

            if (detectorimage) {
                index = kmo_identify_index(cpl_frame_get_filename(rec_frame),
                                           i, FALSE);
                KMO_TRY_CHECK_ERROR_STATE();

                KMO_TRY_EXIT_IF_NULL(
                    tmp_header = kmclipm_propertylist_load(
                                         cpl_frame_get_filename(rec_frame), index));
                KMO_TRY_EXIT_IF_ERROR(
                    kmo_save_det_img_ext(det_img_data[i-1], gd, i, filename_img,
                                         obs_suffix, tmp_header, FALSE, FALSE));
                cpl_propertylist_delete(tmp_header); tmp_header = NULL;

                if (detImgCube) {
                    if (desc1.ex_noise) {
                        index = kmo_identify_index(cpl_frame_get_filename(rec_frame),
                                                   i, TRUE);
                        KMO_TRY_CHECK_ERROR_STATE();
                    } else {
                        // use same index as for data frame, since input frame
                        // has just 3 extensions
                    }
                    KMO_TRY_EXIT_IF_NULL(
                        tmp_header = kmclipm_propertylist_load(
                                             cpl_frame_get_filename(rec_frame), index));
                    KMO_TRY_EXIT_IF_ERROR(
                        kmo_save_det_img_ext(det_img_noise[i-1], gd, i, filename_img,
                                             obs_suffix, tmp_header,
                                             FALSE, TRUE));
                    cpl_propertylist_delete(tmp_header); tmp_header = NULL;
                }
            }

            // free memory
            cpl_imagelist_delete(cube_data); cube_data = NULL;
            cpl_imagelist_delete(cube_noise); cube_noise = NULL;
            cpl_propertylist_delete(sub_header); sub_header = NULL;
            cpl_propertylist_delete(sub_header_orig); sub_header_orig = NULL;
        } // for i devices
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_val = -1;
    }

    kmo_free_fits_desc(&desc1);
    kmo_free_fits_desc(&desc2);
    for (i = 0; i < KMOS_NR_DETECTORS; i++) {
        cpl_image_delete(det_img_data[i]); det_img_data[i] = NULL;
        cpl_image_delete(det_img_noise[i]); det_img_noise[i] = NULL;
    }
    cpl_free(my_filter_id); my_filter_id = NULL;
    cpl_free(bounds); bounds = NULL;
    cpl_propertylist_delete(main_header); main_header = NULL;
    cpl_propertylist_delete(sub_header); sub_header = NULL;
    cpl_propertylist_delete(sub_header_orig); sub_header_orig = NULL;
    cpl_imagelist_delete(cube_data); cube_data = NULL;
    cpl_imagelist_delete(cube_noise); cube_noise = NULL;
    cpl_free(obs_suffix); obs_suffix = NULL;
    cpl_free(suffix); suffix = NULL;
    cpl_free(filename_img); filename_img = NULL;
    cpl_free(filename_cube); filename_cube = NULL;

    return ret_val;
}

/**@}*/
