/*
 * 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"
#include "kmos_oscan.h"

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

static int kmos_reconstruct_check_inputs(cpl_frameset *, const char *);

static int kmos_reconstruct_create(cpl_plugin *);
static int kmos_reconstruct_exec(cpl_plugin *);
static int kmos_reconstruct_destroy(cpl_plugin *);
static int kmos_reconstruct(cpl_parameterlist *, cpl_frameset *);

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

static char kmos_reconstruct_description[] =
"Data with or without noise is reconstructed into a cube using X/Y/LCAL, YCAL\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"
"---------------------------------------------------------------------------\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 kmos_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,
                        "kmos_reconstruct",
                        "Performs the cube reconstruction",
                        kmos_reconstruct_description,
                        "Alex Agudo Berbel, Y. Jung",
                        "usd-help@eso.org",
                        kmos_get_license(),
                        kmos_reconstruct_create,
                        kmos_reconstruct_exec,
                        kmos_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 kmos_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.kmos_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.kmos_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.kmos_reconstruct.neighborhoodRange",
            CPL_TYPE_DOUBLE,
            "Defines the range to search for neighbors. in pixels",
            "kmos.kmos_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.kmos_reconstruct.flux", CPL_TYPE_BOOL,
            "TRUE: Apply flux conservation. FALSE: otherwise",
            "kmos.kmos_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.kmos_reconstruct.detectorimage",
            CPL_TYPE_BOOL,
            "TRUE: if resampled detector frame should be "
            "created, FALSE: otherwise",
            "kmos.kmos_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.kmos_reconstruct.file_extension",
            CPL_TYPE_BOOL,
            "TRUE: if OBS_ID keyword should be appended to "
            "output frames, FALSE: otherwise",
            "kmos.kmos_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.kmos_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.kmos_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.kmos_reconstruct.xcal_interpolation",
            CPL_TYPE_BOOL,
            "TRUE: Interpolate xcal between rotator angles. FALSE: otherwise",
            "kmos.kmos_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);

    /* --oscan */
    p = cpl_parameter_new_value("kmos.kmos_reconstruct.oscan",
            CPL_TYPE_BOOL, "Apply Overscan Correction",
            "kmos.kmos_reconstruct", TRUE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "oscan");
    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.kmos_reconstruct");
    return 0;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Execute the plugin instance given by the interface
  @param    plugin  the plugin
  @return   0 if everything is ok
 */
/*----------------------------------------------------------------------------*/
static int kmos_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 kmos_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 kmos_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 kmos_reconstruct(cpl_parameterlist *parlist, cpl_frameset *frameset)
{
    const cpl_parameter *   par ;
    const char          *   imethod ;
    int                     flux, detectorimage, xcal_interpolation,
                            file_extension, oscan ;
    double                  neighborhoodRange, pix_scale, scaling ;
    cpl_frame           *   input_frame ;
    cpl_frame           *   xcal_frame ;
    cpl_frame           *   ycal_frame ;
    cpl_frame           *   lcal_frame ;
    cpl_frame           *   ref_spectrum_frame ;
    float               *   pdet_img_data ;
    float               *   pdet_img_noise ;
    float               *   slice ;
    const char          *   input_frame_name ;
    const char          *   output_frame_name ;
    const char          *   filter_id ;
    char                *   keyword ;
    char                *   suffix ;
    char                *   obs_suffix ;
    char                *   extname ;
    int                 *   bounds ;
    cpl_image           *   det_img_data ;
    cpl_image           *   det_img_noise;
    cpl_image           *   tmp_img ;
    cpl_imagelist       *   cube_data ;
    cpl_imagelist       *   cube_noise ;
    cpl_propertylist    *   main_header ;
    cpl_propertylist    *   sub_header ;
    cpl_propertylist    *   tmp_header ;
    cpl_table           *   band_table ;
    gridDefinition          gd ;
    cpl_polynomial      *   lcorr_coeffs ;
    main_fits_desc          desc1 ;
    int                     i, j, index, ifu_nr, detImgCube, l, x, y ;

    /* Initialise */
    detImgCube = FALSE ;

    /* Check Entries */
    if (parlist == NULL || frameset == NULL) {
        cpl_msg_error(__func__, "Null Inputs") ;
        cpl_error_set(__func__, CPL_ERROR_NULL_INPUT) ;
        return -1 ;
    }

    /* Get parameters */
    par = cpl_parameterlist_find_const(parlist,"kmos.kmos_reconstruct.imethod");
    imethod = cpl_parameter_get_string(par) ;
    par = cpl_parameterlist_find_const(parlist, "kmos.kmos_reconstruct.flux");
    flux = cpl_parameter_get_bool(par);
    par = cpl_parameterlist_find_const(parlist, 
            "kmos.kmos_reconstruct.detectorimage");
    detectorimage = cpl_parameter_get_bool(par);
    par = cpl_parameterlist_find_const(parlist,
            "kmos.kmos_reconstruct.neighborhoodRange");
    neighborhoodRange = cpl_parameter_get_double(par) ;
    kmos_band_pars_load(parlist, "kmos.kmos_reconstruct");
    par = cpl_parameterlist_find_const(parlist, 
            "kmos.kmos_reconstruct.file_extension");
    file_extension = cpl_parameter_get_bool(par);
    par = cpl_parameterlist_find_const(parlist, 
            "kmos.kmos_reconstruct.pix_scale");
    pix_scale = cpl_parameter_get_double(par) ;
    par = cpl_parameterlist_find_const(parlist,
            "kmos.kmos_reconstruct.xcal_interpolation");
    xcal_interpolation = cpl_parameter_get_bool(par);
    par = cpl_parameterlist_find_const(parlist, "kmos.kmos_reconstruct.oscan");
    oscan = cpl_parameter_get_bool(par);

    /* Check Parameters */
    if (strcmp(imethod, "NN") && strcmp(imethod, "lwNN") &&
            strcmp(imethod, "swNN") && strcmp(imethod, "MS") &&
            strcmp(imethod, "CS")) {
        cpl_msg_error(__func__,
                "imethod must be 'NN','lwNN','swNN','MS' or 'CS'") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }
    if (neighborhoodRange <= 0.0) {
        cpl_msg_error(__func__,
                "neighborhoodRange must be greater than 0.0") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }
    if (pix_scale < 0.01 || pix_scale > 0.4) {
        cpl_msg_error(__func__, "pix_scale must be between 0.01 and 0.4");
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }

    /* Identify the RAW and CALIB frames in the input frameset */
    if (kmo_dfs_set_groups(frameset) != 1) {
        cpl_msg_error(__func__, "Cannot identify RAW and CALIB frames") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }

    /* IO File names */
    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;
    } else {
        cpl_msg_error(__func__, "Missing Inputs") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }

    /* Check the inputs consistency */
    if (kmos_reconstruct_check_inputs(frameset, input_frame_name) != 1) {
        cpl_msg_error(__func__, "Input frameset is not consistent") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }

    /* Instrument setup */
    input_frame = kmo_dfs_get_frame(frameset, input_frame_name);
    suffix = kmo_dfs_get_suffix(input_frame, TRUE, TRUE);
    cpl_msg_info(__func__, "Detected instrument setup:   %s", suffix+1);
    cpl_free(suffix);

    /* Get frames */
    xcal_frame = kmo_dfs_get_frame(frameset, XCAL);
    ycal_frame = kmo_dfs_get_frame(frameset, YCAL) ;
    lcal_frame = kmo_dfs_get_frame(frameset, LCAL) ;
    ref_spectrum_frame = kmo_dfs_get_frame(frameset, OH_SPEC) ;

    /* Get input frame primary header */
    main_header = kmo_dfs_load_primary_header(frameset, input_frame_name);

    /* Use the OBS ID in the file name */
    if (file_extension) {
        obs_suffix = cpl_sprintf("_%d",
                cpl_propertylist_get_int(main_header, OBS_ID));
    } else {
        obs_suffix = cpl_sprintf("%s", "");
    }

    /* Save Product (reconstructed) Main Header */
    kmo_dfs_save_main_header(frameset, output_frame_name, obs_suffix,
            input_frame, NULL, parlist, cpl_func);

    /* Save Product (detector image) Main Header */
    if (detectorimage == TRUE) 
        kmo_dfs_save_main_header(frameset, DET_IMG_REC, obs_suffix,
                input_frame, NULL, parlist, cpl_func);
            
    /* Grid definition, wavelength start and end are set later */
    kmclipm_setup_grid(&gd, imethod, neighborhoodRange, pix_scale, 0.);

    /* READ Wavelength bounds */
    tmp_header = kmo_dfs_load_primary_header(frameset, XCAL);
    bounds = kmclipm_extract_bounds(tmp_header);
    cpl_propertylist_delete(tmp_header);

    kmo_init_fits_desc(&desc1);
    desc1 = kmo_identify_fits_header(cpl_frame_get_filename(input_frame));

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

        /* Load LCAL image close to ROTANGLE 0. assuming that the  */
        /* wavelength range doesn't differ with different ROTANGLEs. */
        print_cal_angle_msg_once = FALSE;
        print_xcal_angle_msg_once = FALSE;
        if (i==1) {
            print_cal_angle_msg_once = TRUE;
            print_xcal_angle_msg_once = TRUE;
        }

        /* Read Filter ID */
        keyword = cpl_sprintf("%s%d%s", IFU_FILTID_PREFIX,i,IFU_FILTID_POSTFIX);
        filter_id = cpl_propertylist_get_string(main_header, keyword);
        cpl_free(keyword);
        
        /* Load Band Info */
        band_table = kmo_dfs_load_table(frameset, WAVE_BAND, 1, FALSE);
        kmclipm_setup_grid_band_lcal(&gd, filter_id, band_table);
        cpl_table_delete(band_table); 

        /* Create empty detector images */
        if (detectorimage == TRUE) {
            det_img_data = cpl_image_new(
                    gd.x.dim*gd.y.dim*KMOS_IFUS_PER_DETECTOR, gd.l.dim, 
                    CPL_TYPE_FLOAT);
            pdet_img_data = cpl_image_get_data_float(det_img_data);

            det_img_noise = cpl_image_new(
                    gd.x.dim*gd.y.dim*KMOS_IFUS_PER_DETECTOR, gd.l.dim, 
                    CPL_TYPE_FLOAT);
                pdet_img_noise = cpl_image_get_data_float(det_img_noise);
        }

        /* Loop on IFUs */
        for (j = 0; j < KMOS_IFUS_PER_DETECTOR; j++) {
            ifu_nr = (i-1)*KMOS_IFUS_PER_DETECTOR + j + 1;

            /* Load sub-header*/
            sub_header = kmo_dfs_load_sub_header(frameset, input_frame_name,
                    i, FALSE);

            /* Check if IFU is valid according to main header keywords */
            if (bounds[2*(ifu_nr-1)] != -1 && bounds[2*(ifu_nr-1)+1] != -1) {
                /* IFU valid */

                /* calc WCS & update subheader */
                kmo_calc_wcs_gd(main_header, sub_header, ifu_nr, gd);
                kmclipm_update_property_int(sub_header, NAXIS, 3,
                        "number of data axes");
                kmclipm_update_property_int(sub_header, NAXIS1, 
                        gd.x.dim, "length of data axis 1");
                kmclipm_update_property_int(sub_header, NAXIS2, 
                        gd.y.dim, "length of data axis 2");
                kmclipm_update_property_int(sub_header, NAXIS3, 
                        gd.l.dim, "length of data axis 3");

                /* Reconstruct data and noise (if available) */
                kmo_reconstruct_sci(ifu_nr, bounds[2*(ifu_nr-1)],
                        bounds[2*(ifu_nr-1)+1], input_frame, input_frame_name,
                        NULL, NULL, NULL, xcal_frame, ycal_frame, lcal_frame,
                        NULL, NULL, &gd, &cube_data, &cube_noise, flux,
                        0, xcal_interpolation, oscan);

                /* Reconstruct again using OH_SPEC */
                if (ref_spectrum_frame != NULL && cube_data != NULL) {
                    lcorr_coeffs = kmo_lcorr_get(cube_data, sub_header,
                            ref_spectrum_frame, gd, filter_id, ifu_nr);
                    cpl_imagelist_delete(cube_data);
                    if (cube_noise != NULL) cpl_imagelist_delete(cube_noise);
                    kmo_reconstruct_sci(ifu_nr, bounds[2*(ifu_nr-1)],
                            bounds[2*(ifu_nr-1)+1], input_frame, 
                            input_frame_name, NULL, NULL, NULL, xcal_frame, 
                            ycal_frame, lcal_frame, lcorr_coeffs, NULL, &gd, 
                            &cube_data, &cube_noise, flux, 0, 
                            xcal_interpolation, oscan);
                    cpl_polynomial_delete(lcorr_coeffs);
                }

                /* Scale flux according to pixel_scale */
                tmp_img = cpl_imagelist_get(cube_data, 0);
                scaling = (cpl_image_get_size_x(tmp_img)*
                        cpl_image_get_size_y(tmp_img)) /
                    (KMOS_SLITLET_X*KMOS_SLITLET_Y);
                cpl_imagelist_divide_scalar(cube_data, scaling);
                if (cube_noise != NULL) 
                    cpl_imagelist_divide_scalar(cube_noise, scaling);
            } else {
                /* IFU invalid */
                cube_data = cube_noise = NULL ;
            }

            /* Fill detector images */
            if (detectorimage) {
                if (cube_data != NULL) {
                    for (l = 0; l < gd.l.dim; l++) {
                        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) {
                    detImgCube = TRUE;
                    for (l = 0; l < gd.l.dim; l++) {
                        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 Product (reconstructed) Data Cube */
            extname = kmo_extname_creator(ifu_frame, ifu_nr, EXT_DATA);
            kmclipm_update_property_string(sub_header, EXTNAME, extname,
                    "FITS extension name");
            cpl_free(extname);
            kmo_dfs_save_cube(cube_data, output_frame_name, obs_suffix,
                    sub_header, 0./0.);

            /* Save Product (reconstructed) Noise Cube */
            if (cube_noise != NULL) {
                extname = kmo_extname_creator(ifu_frame, ifu_nr, EXT_NOISE);
                kmclipm_update_property_string(sub_header, EXTNAME,
                        extname, "FITS extension name");
                cpl_free(extname);
                kmo_dfs_save_cube(cube_noise, output_frame_name, obs_suffix,
                        sub_header, 0./0.);
            }

            cpl_imagelist_delete(cube_data);
            if (cube_noise != NULL) cpl_imagelist_delete(cube_noise);
            cpl_propertylist_delete(sub_header);
        } // IFUs loop

        if (detectorimage) {
            /* Save Product (detector image) Data Cube */
            index = kmo_identify_index(cpl_frame_get_filename(input_frame), i,
                    FALSE);
            tmp_header = kmclipm_propertylist_load(
                    cpl_frame_get_filename(input_frame), index);
            kmo_save_det_img_ext(det_img_data, gd, i, DET_IMG_REC,
                    obs_suffix, tmp_header, FALSE, FALSE);
            cpl_propertylist_delete(tmp_header);
            cpl_image_delete(det_img_data);

            if (detImgCube) {
                /* Save Product (detector image) Noise Cube */
                /* Index changes (*2) if Noise extensions are there */
                if (desc1.ex_noise) {
                    index = kmo_identify_index(
                            cpl_frame_get_filename(input_frame), i, TRUE);
                }
                tmp_header = kmclipm_propertylist_load(
                        cpl_frame_get_filename(input_frame), index);
                kmo_save_det_img_ext(det_img_noise, gd, i, DET_IMG_REC,
                        obs_suffix, tmp_header, FALSE, TRUE);
                cpl_propertylist_delete(tmp_header); 
            }
            cpl_image_delete(det_img_noise);
        }
    } // Detectors loop
    cpl_propertylist_delete(main_header);
    cpl_free(obs_suffix);
    kmo_free_fits_desc(&desc1);
    cpl_free(bounds);

    return 0;
}

/**@}*/

/*----------------------------------------------------------------------------*/
/**
  @brief    Make consistency checks
  @param    frameset            Set of frames with ARC_OFF and several ARC_ON
  @param    input_frame_name     
  @return   1 if consistent, 0 if not, -1 in error case
 */
/*----------------------------------------------------------------------------*/
static int kmos_reconstruct_check_inputs(
        cpl_frameset            *   frameset,
        const char              *   input_frame_name)
{
    cpl_propertylist    *   lcal_header ;
    cpl_propertylist    *   input_header ;
    int                     nb_xcal, nb_ycal, nb_lcal, nb_oh_spec ;
    char                *   keyword ;
    const char          *   filter_id ;
    const char          *   filter_id_tmp ;
    int                     i ;

    /* Check entries */
    if (frameset == NULL || input_frame_name == NULL) return -1;

    /* Count frames */
    if (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_msg_error(__func__, "1 data frame must be provided") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return 0 ;
    }
    nb_xcal = cpl_frameset_count_tags(frameset, XCAL) ;
    nb_ycal = cpl_frameset_count_tags(frameset, YCAL) ;
    nb_lcal = cpl_frameset_count_tags(frameset, LCAL) ;
    if (nb_xcal != 1 || nb_ycal != 1 || nb_lcal != 1) {
        cpl_msg_error(__func__, "Exactly 1 XCAL/YCAL/LCAL expected") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return 0 ;
    }
    if (cpl_frameset_count_tags(frameset, WAVE_BAND) != 1) {
        cpl_msg_error(__func__, "Missing WAVE_BAND") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return 0 ;
    }
    nb_oh_spec = cpl_frameset_count_tags(frameset, OH_SPEC) ;
    if (nb_oh_spec != 0 && nb_oh_spec != 1) {
        cpl_msg_error(__func__, "Only 1 reference spectrum can be provided") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return 0 ;
    }

    /* Check filters, rotation consistencies */
    kmo_check_frame_setup(frameset, XCAL, YCAL, TRUE, FALSE, TRUE);
    kmo_check_frame_setup(frameset, XCAL, LCAL, TRUE, FALSE, TRUE);
    if (nb_oh_spec == 1)    kmo_check_oh_spec_setup(frameset, XCAL);

    if (cpl_frameset_count_tags(frameset, DARK) != 1) {
        kmo_check_frame_setup(frameset, XCAL, input_frame_name, TRUE, FALSE, 
                FALSE);
    }
    kmo_check_frame_setup_md5_xycal(frameset);
    kmo_check_frame_setup_md5(frameset);

    /* Check filters */
    input_header = kmo_dfs_load_primary_header(frameset, input_frame_name);
    lcal_header = kmo_dfs_load_primary_header(frameset, LCAL);
    for (i = 1; i <= KMOS_NR_DETECTORS; i++) {
        // ESO INS FILTi ID
        keyword = cpl_sprintf("%s%d%s", IFU_FILTID_PREFIX,i,IFU_FILTID_POSTFIX);
        filter_id = cpl_propertylist_get_string(lcal_header, keyword);
        if (strcmp(filter_id, "IZ") && strcmp(filter_id, "YJ") &&
                strcmp(filter_id, "H") && strcmp(filter_id, "K") &&
                strcmp(filter_id, "HK")) {
            cpl_free(keyword) ;
            cpl_propertylist_delete(input_header) ;
            cpl_propertylist_delete(lcal_header) ;
            cpl_msg_error(__func__,"LCAL Filter ID must be IZ, YJ, H, HK or K");
            cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
            return 0 ;
        }

        /* Dark not taken with filter */
        if (!strcmp(input_frame_name, DARK)) {
            filter_id_tmp = cpl_propertylist_get_string(input_header, keyword);
            if (strcmp(filter_id, filter_id_tmp)) {
                cpl_free(keyword) ;
                cpl_propertylist_delete(input_header) ;
                cpl_propertylist_delete(lcal_header) ;
                cpl_msg_error(__func__,"Filter mismatch");
                cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
                return 0 ;
            }
        }
        cpl_free(keyword);
    }
    cpl_propertylist_delete(input_header) ;
    cpl_propertylist_delete(lcal_header) ;
    
    return 1 ;
}

