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

#define __USE_XOPEN2K
#include <stdlib.h>

#include "kmos_molecfit.h"

#include "kmo_priv_extract_spec.h"

/*----------------------------------------------------------------------------*/
/**
 *                              Defines
 */
/*----------------------------------------------------------------------------*/

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

/* Load STAR_SPEC spectrum */
static cpl_error_code kmos_molecfit_load_spectrum_1D(
    kmos_spectrum *ifu, const char* filename, cpl_boolean ext24);

/* Load STAR_SPEC spectrum */
static cpl_error_code kmos_molecfit_load_spectrum_3D(
    kmos_spectrum *ifu, const char* filename, cpl_boolean ext24, const char *recipe_name, kmos_spectrum *ifus);

/* Parse 1D Spectrum and put the data in ifu */
static cpl_error_code kmos_molecfit_parse_1D_Spectrum(
    cpl_vector *spec, kmos_spectrum *ifu);

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

/*----------------------------------------------------------------------------*/
/**
 * @defgroup kmos_molecfit  Common functions to the kmos_molecfit recipes
 */
/*----------------------------------------------------------------------------*/

/**@{*/

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

/*----------------------------------------------------------------------------*/
/**
 * @brief    Add parameter to the recipe parameterlist
 *
 * @param    recipe        Name of the recipe
 * @param    plist         Propertylist in the recipe for put the parameter
 * @param    param         Name of the parameter
 * @param    range         Flag: If the value is unique or it's a range
 * @param    rMin          In case of range: Min value
 * @param    rMax          In case of range: Max value
 * @param    type          Type of the parameter
 * @param    value         Generic pointer to the value/s of the parameter
 * @param    description   Description of the parameter
 * @param    mf_conf       Flag: If the parameter is for the molecfit configuration
 *
 * @return   CPL_ERROR_NONE if everything is OK and CPL_ERROR_INVALID_TYPE if
 *           the type isn't correct.
 *
 * Description:
 * 		Create a parameter with the input and insert in the recipe parameterlist.
 */
/*----------------------------------------------------------------------------*/
cpl_error_code kmos_molecfit_fill_parameter(
    const char* recipe, cpl_parameterlist *plist, const char* param,
    cpl_boolean range, const void *rMin, const void *rMax,
    cpl_type type, const void *value, const char* description,
    cpl_boolean mf_conf)
{
  cpl_parameter *p;

  if (range) {

      if(type == CPL_TYPE_INT) {

          const int val    = *(const int *)value;
          const int valMin = *(const int *)rMin;
          const int valMax = *(const int *)rMax;
          if(val < valMin || val > valMax) {
              return cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT, "Illegal int value range");
          }
          p = cpl_parameter_new_range(param, type, description, recipe, val, valMin, valMax);

      } else if( type == CPL_TYPE_DOUBLE) {

          const double val     = *(const double *)value;
          const double valMin  = *(const double *)rMin;
          const double valMax  = *(const double *)rMax;
          if(val < valMin || val > valMax) {
              return cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT, "Illegal double value range");
          }
          p = cpl_parameter_new_range(param, type, description, recipe, val, valMin, valMax);

      } else {
          return cpl_error_set_message(cpl_func, CPL_ERROR_INVALID_TYPE, "cpl_type is not correct with ranges");
      }

  } else {

      switch(type) {
        case CPL_TYPE_STRING :
          p = cpl_parameter_new_value(param, type, description, recipe,  (const char *)       value); break;
        case CPL_TYPE_BOOL :
          p = cpl_parameter_new_value(param, type, description, recipe, *(const cpl_boolean *)value); break;
        case CPL_TYPE_INT :
          p = cpl_parameter_new_value(param, type, description, recipe, *(const int *)        value); break;
        case CPL_TYPE_DOUBLE :
          p = cpl_parameter_new_value(param, type, description, recipe, *(const double *)     value); break;
        default :
          return cpl_error_set_message(cpl_func, CPL_ERROR_INVALID_TYPE, "cpl_type is not correct");
      }
  }

  if(!mf_conf) {
      cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, param);
      cpl_parameter_disable(  p, CPL_PARAMETER_MODE_ENV);
  } else {
      cpl_parameter_disable(  p, CPL_PARAMETER_MODE_CLI);
      cpl_parameter_disable(  p, CPL_PARAMETER_MODE_ENV);
      cpl_parameter_disable(  p, CPL_PARAMETER_MODE_CFG);
  }
  cpl_parameterlist_append(plist, p);

  return CPL_ERROR_NONE;
}

/*----------------------------------------------------------------------------*/
/**
 * @brief    Set the grating type that check with grating string
 *
 * @param  name   name of grating in one string
 * @param  type   out: type of grating.
 *
 * @return cpl_error_code
 *
 */
/*----------------------------------------------------------------------------*/
cpl_error_code kmos_molecfit_grating_type(
    const char* name, kmos_grating_type *type)
{
  if      (strcmp(name, "IZ") == 0) *type = GRATING_IZ;
  else if (strcmp(name, "YJ") == 0) *type = GRATING_YJ;
  else if (strcmp(name, "H" ) == 0) *type = GRATING_H;
  else if (strcmp(name, "K" ) == 0) *type = GRATING_K;
  else if (strcmp(name, "HK") == 0) *type = GRATING_HK;
  else {
      return cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                                   "name of grating unknown!");
  }
  return CPL_ERROR_NONE;
}

/*----------------------------------------------------------------------------*/
/**
 * @brief    Fill the configuration grating by default, depending of the type
 *
 * @param  grating  Grating to fill
 * @param  type     Specific grating
 *
 * @return cpl_error_code
 *
 */
/*----------------------------------------------------------------------------*/
cpl_error_code kmos_molecfit_grating_by_default(
    kmos_grating *grating, kmos_grating_type type)
{
  cpl_error_ensure(grating, CPL_ERROR_NULL_INPUT,
                   return CPL_ERROR_NULL_INPUT, "kmos_molecfit_calctrans_grating_by_default grating input is NULL!");

  cpl_boolean wave_range_default = (strcmp(grating->wave_range, "-1") == 0) ? CPL_TRUE : CPL_FALSE;
  cpl_boolean list_molec_default = (strcmp(grating->list_molec, "-1") == 0) ? CPL_TRUE : CPL_FALSE;
  cpl_boolean fit_molec_default  = (strcmp(grating->fit_molec,  "-1") == 0) ? CPL_TRUE : CPL_FALSE;
  cpl_boolean relcol_default     = (strcmp(grating->relcol,     "-1") == 0) ? CPL_TRUE : CPL_FALSE;

  if (type == GRATING_IZ) {

      if (wave_range_default) grating->wave_range = "0.815,0.830,0.894,0.899,0.914,0.919,0.929,0.940,0.972,0.986";
      if (list_molec_default) grating->list_molec = "H2O";
      if (fit_molec_default ) grating->fit_molec  = "1";
      if (relcol_default    ) grating->relcol     = "1.";

  } else if (type == GRATING_YJ) {

      if (wave_range_default) grating->wave_range = "1.106,1.116,1.075,1.083,1.131,1.137,1.139,1.149,1.155,1.166,1.177,1.189,1.201,1.209,1.263,1.276,1.294,1.303,1.312,1.336";
      if (list_molec_default) grating->list_molec = "H2O,CO2,CH4,O2";
      if (fit_molec_default ) grating->fit_molec  = "1,0,0,1";
      if (relcol_default    ) grating->relcol     = "1.,1.06,1.,1.";

  } else if (type == GRATING_H ) {

      if (wave_range_default) grating->wave_range = "1.482,1.491,1.500,1.512,1.559,1.566,1.598,1.605,1.575,1.583,1.622,1.629,1.646,1.671,1.699,1.711,1.721,1.727,1.746,1.758,1.764,1.767,1.773,1.780,1.789,1.794";
      if (list_molec_default) grating->list_molec = "H2O,CO2,CO,CH4";
      if (fit_molec_default ) grating->fit_molec  = "1,1,0,1";
      if (relcol_default    ) grating->relcol     = "1.,1.06,1.,1.";

  } else if (type == GRATING_K ) {

      if (wave_range_default) grating->wave_range = "1.975,1.987,1.993,2.010,2.041,2.060,2.269,2.291,2.308,2.335,2.360,2.379,2.416,2.440,2.445,2.475";
      if (list_molec_default) grating->list_molec = "H2O,CO2,CH4";
      if (fit_molec_default ) grating->fit_molec  = "1,1,1";
      if (relcol_default    ) grating->relcol     = "1.,1.06,1.";

  } else if (type == GRATING_HK) {

      if (wave_range_default) grating->wave_range = "1.575,1.584,1.594,1.606,1.646,1.671,1.756,1.771,1.781,1.811,1.945,1.969,1.975,1.987,1.993,2.030,2.043,2.089,2.242,2.294,2.308,2.335,2.360,2.379";
      if (list_molec_default) grating->list_molec = "H2O,CO2,CH4";
      if (fit_molec_default ) grating->fit_molec  = "1,1,1";
      if (relcol_default    ) grating->relcol     = "1.,1.06,1.";
  }


  /*** Check same number in the list refered to the molecules ***/
  char **tokens;

  int  n_tokens1;
  tokens = kmo_strsplit(grating->list_molec, ",", &n_tokens1);
  kmo_strfreev(tokens);

  int  n_tokens2;
  tokens = kmo_strsplit(grating->fit_molec,  ",", &n_tokens2);
  kmo_strfreev(tokens);

  int  n_tokens3;
  tokens = kmo_strsplit(grating->relcol,     ",", &n_tokens3);
  kmo_strfreev(tokens);

  if (n_tokens1 != n_tokens2 || n_tokens1 != n_tokens3 ){
      return cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT,
                                   "--list_molec, --fit_molec,and  --relcol must contain the same number of elements.");
  }


  /*** Show info about the grating configuration ***/

  if (wave_range_default) cpl_msg_info(cpl_func, "Configuring wavelength ranges   in grating('%s') defined by default  : %s", grating->name, grating->wave_range);
  else                    cpl_msg_info(cpl_func, "Configuring wavelength ranges   in grating('%s') defined by the user : %s", grating->name, grating->wave_range);

  if (list_molec_default) cpl_msg_info(cpl_func, "Configuring list   of molecules in grating('%s') defined by default  : %s", grating->name, grating->list_molec);
  else                    cpl_msg_info(cpl_func, "Configuring list   of molecules in grating('%s') defined by the user : %s", grating->name, grating->list_molec);

  if (fit_molec_default ) cpl_msg_info(cpl_func, "Configuring fit    of molecules in grating('%s') defined by default  : %s", grating->name, grating->fit_molec);
  else                    cpl_msg_info(cpl_func, "Configuring fit    of molecules in grating('%s') defined by the user : %s", grating->name, grating->fit_molec);

  if (relcol_default    ) cpl_msg_info(cpl_func, "Configuring relcol of molecules in grating('%s') defined by default  : %s", grating->name, grating->relcol);
  else                    cpl_msg_info(cpl_func, "Configuring relcol of molecules in grating('%s') defined by the user : %s", grating->name, grating->relcol);

  return CPL_ERROR_NONE;
}

/*----------------------------------------------------------------------------*/
/**
 * @brief    Generate wave_ranges and molecules cpl_table's in the conf file
 *
 * @param  conf    Configuration file in the recipe
 *
 * @return cpl_error_code
 *
 */
/*----------------------------------------------------------------------------*/
cpl_error_code kmos_molecfit_generate_wave_molec_tables(
    kmos_molecfit_parameter *conf)
{
  cpl_error_ensure(conf, CPL_ERROR_NULL_INPUT,
                   return CPL_ERROR_NULL_INPUT, "conf input is NULL!");

  /* Get prestate */
  cpl_errorstate preState = cpl_errorstate_get();


  /*** Check if it's necessary to fill the grating variables with the default configuration ***/
  kmos_grating      *grating = &(conf->grating);
  kmos_grating_type type     = conf->grating.type;

  /* Put variables by default if it's necessary */
  if (kmos_molecfit_grating_by_default(grating, type) != CPL_ERROR_NONE) {
      return cpl_error_get_code();
  }


  /*** Wavelength ranges ***/
  int n_wavelength;
  char **wave_ranges = kmo_strsplit(grating->wave_range, ",", &n_wavelength);

  /* Create wave_ranges cpl_table */
  int n_wave_ranges = n_wavelength / 2;
  grating->incl_wave_ranges = cpl_table_new(n_wave_ranges);
  cpl_table_new_column(grating->incl_wave_ranges, MF_WAVELENGTH_COLUMN_LOWER, CPL_TYPE_DOUBLE);
  cpl_table_new_column(grating->incl_wave_ranges, MF_WAVELENGTH_COLUMN_UPPER, CPL_TYPE_DOUBLE);

  for (cpl_size i = 0; i < n_wavelength / 2; i++) {
      cpl_size idx   = i * 2;
      double   start = strtod(wave_ranges[idx],     NULL);
      double   end   = strtod(wave_ranges[idx + 1], NULL);
      cpl_table_set_double(grating->incl_wave_ranges, MF_WAVELENGTH_COLUMN_LOWER, i, start);
      cpl_table_set_double(grating->incl_wave_ranges, MF_WAVELENGTH_COLUMN_UPPER, i, end);

      cpl_msg_info(cpl_func, "wave_range[%lld]={%g,%g}", i, start, end);
  }


  /*** Molecules ***/
  int n_molec;
  char **list_molecs = kmo_strsplit(grating->list_molec, ",", &n_molec);
  char **fit_molecs  = kmo_strsplit(grating->fit_molec,  ",", &n_molec);
  char **relcols     = kmo_strsplit(grating->relcol,     ",", &n_molec);

  /* Create Molecules cpl_table */
  grating->molecules = cpl_table_new(n_molec);
  cpl_table_new_column(grating->molecules, MF_MOLECULES_COLUMN_LIST, CPL_TYPE_STRING);
  cpl_table_new_column(grating->molecules, MF_MOLECULES_COLUMN_FIT,  CPL_TYPE_INT   );
  cpl_table_new_column(grating->molecules, MF_MOLECULES_COLUMN_RELCOL,     CPL_TYPE_DOUBLE);

  /* Insert molecules properties in the cpl_table */
  for (cpl_size i = 0; i < n_molec; i++) {
      int    fit    = atoi(fit_molecs[i]);
      double relcol = strtod(relcols[i], NULL);

      cpl_table_set_string(grating->molecules, MF_MOLECULES_COLUMN_LIST, i, list_molecs[i]);
      cpl_table_set_int(   grating->molecules, MF_MOLECULES_COLUMN_FIT,  i, fit);
      cpl_table_set_double(grating->molecules, MF_MOLECULES_COLUMN_RELCOL,     i, relcol);

      cpl_msg_info(cpl_func, "Molec[%s] -> %d:{%g}", list_molecs[i], fit, relcol);
  }


  /*** Cleanup values ***/
  kmo_strfreev(wave_ranges);
  kmo_strfreev(list_molecs);
  kmo_strfreev(fit_molecs);
  kmo_strfreev(relcols);


  /* Check execution */
  if (!cpl_errorstate_is_equal(preState)) {
      return cpl_error_get_code();
  } else {
      return CPL_ERROR_NONE;
  }
}

/*----------------------------------------------------------------------------*/
/**
 * @brief    Function needed to fill the molecfit generic configuration file
 *
 * @param  recipe Name of the recipe
 * @param  conf   Recipe configuration. The variable conf->recipe_parameters is
 *                  modified in order to include the hardcode values
 *
 * @return parameterlist with contain the config to molecfit or NULL if error
 *
 */
/*----------------------------------------------------------------------------*/
cpl_parameterlist * kmos_molecfit_conf_generic(
    const char* recipe, kmos_molecfit_parameter *conf)
{
  /* Add the config values necessaries to execute molecfit */
  cpl_errorstate prestate = cpl_errorstate_get();

  /* Fill the parameters list */
  cpl_error_code e         = CPL_ERROR_NONE;
  cpl_boolean    range     = CPL_TRUE;
  const void     *dummyMin = NULL;
  const void     *dummyMax = NULL;
  int            boolMin   = 0;
  int            boolMax   = 1;


  /*** Create a new parematerlist ***/
  cpl_parameterlist *mf_config = cpl_parameterlist_new();


  /* --kernmode */
  if(!e) e = kmos_molecfit_fill_parameter(recipe, mf_config, "kernmode",
                                          range, &boolMin, &boolMax, CPL_TYPE_INT, &(conf->kernmode),
                                          "molecfit", CPL_TRUE);

  /* --kernfac */
  if(!e) e = kmos_molecfit_fill_parameter(recipe, mf_config, "kernfac",
                                          !range, dummyMin, dummyMax, CPL_TYPE_DOUBLE, &(conf->kernfac),
                                          "molecfit", CPL_TRUE);

  /* --varkern */
  if(!e) e = kmos_molecfit_fill_parameter(recipe, mf_config, "varkern",
                                          range, &boolMin, &boolMax, CPL_TYPE_INT, &(conf->varkern),
                                          "molecfit", CPL_TRUE);


  /*** PARAMETERS NOT INCLUDED IN THE RECIPE: HARD-CODED ***/

  /* --col_lam */
  const char *col_lam = MF_SPECTRUM_COLUMN_WLEN; /* Molecfit default: "undef" */
  if(!e) e = kmos_molecfit_fill_parameter(recipe, mf_config, "col_lam",
                                          !range, dummyMin, dummyMax, CPL_TYPE_STRING, (const void *)col_lam,
                                          "molecfit", CPL_TRUE);

  /* --col_flux */
  const char *col_flux = MF_SPECTRUM_COLUMN_FLUX; /* Molecfit default: "undef" */
  if(!e) e = kmos_molecfit_fill_parameter(recipe, mf_config, "col_flux",
                                          !range, dummyMin, dummyMax, CPL_TYPE_STRING, (const void *)col_flux,
                                          "molecfit", CPL_TRUE);

  /* --col_dflux */
  const char *col_dflux = "NULL"; /* Molecfit default: "undef" */
  if(!e) e = kmos_molecfit_fill_parameter(recipe, mf_config, "col_dflux",
                                          !range, dummyMin, dummyMax, CPL_TYPE_STRING, (const void *)col_dflux,
                                          "molecfit", CPL_TRUE);

  /* --col_mask */
  const char *col_mask = "NULL"; /* Molecfit default: "undef" */
  if(!e) e = kmos_molecfit_fill_parameter(recipe, mf_config, "col_mask",
                                          !range, dummyMin, dummyMax, CPL_TYPE_STRING, (const void *)col_mask,
                                          "molecfit", CPL_TRUE);

  /* --wlgtomicron -- Conversion factor to microns (1., KMOS work in micros) */
  double wlgtomicron = 1.; /* Molecfit default: 1. */
  if(!e) e = kmos_molecfit_fill_parameter(recipe, mf_config, "wlgtomicron",
                                          !range, dummyMin, dummyMax, CPL_TYPE_DOUBLE, &wlgtomicron,
                                          "molecfit", CPL_TRUE);

  /* --vac_air */
  const char *vac_air = "vac"; /* Molecfit default: "vac" */
  if(!e) e = kmos_molecfit_fill_parameter(recipe, mf_config, "vac_air",
                                          !range, dummyMin, dummyMax, CPL_TYPE_STRING, (const void *)vac_air,
                                          "molecfit", CPL_TRUE);


  /* --slitw  */
  double slitw = 0.4; /* Molecfit default: 0.4 */
  if(!e) e = kmos_molecfit_fill_parameter(recipe, mf_config, "slitw",
                                          !range, dummyMin, dummyMax, CPL_TYPE_DOUBLE, &slitw,
                                          "molecfit", CPL_TRUE);

  /* --slitw_key */
  const char *slitw_key = "ANY"; /* Molecfit default: "ESO INS SLIT1 WID" */
  if(!e) e = kmos_molecfit_fill_parameter(recipe, mf_config, "slitw_key",
                                          !range, dummyMin, dummyMax, CPL_TYPE_STRING, (const void *)slitw_key,
                                          "molecfit", CPL_TRUE);

  /* --pixsc  */
  double pixsc = 0.2; /* Molecfit default: 0.086 */
  if(!e) e = kmos_molecfit_fill_parameter(recipe, mf_config, "pixsc",
                                          !range, dummyMin, dummyMax, CPL_TYPE_DOUBLE, &pixsc,
                                          "molecfit", CPL_TRUE);

  /* --pixsc_key */
  const char *pixsc_key = "NONE"; /* Molecfit default: "NONE" */
  if(!e) e = kmos_molecfit_fill_parameter(recipe, mf_config, "pixsc_key",
                                          !range, dummyMin, dummyMax, CPL_TYPE_STRING, (const void *)pixsc_key,
                                          "molecfit", CPL_TRUE);


  /*** Check possible errors ***/
  if (!cpl_errorstate_is_equal(prestate) || e != CPL_ERROR_NONE) {
      cpl_parameterlist_delete(mf_config);
      cpl_error_set_message(cpl_func, cpl_error_get_code(),
                            "Building molecfit generic configuration variable failed!");
      return NULL;
  }

  return mf_config;
}


/*----------------------------------------------------------------------------*/
/**
 * @brief    Get the data input frame in the frameset file
 *
 * @param frameset   Set of input frames
 *
 * @return   The cpl_frame that contains the frame with data
 */
/*----------------------------------------------------------------------------*/
cpl_frame * kmos_molecfit_get_frame_spectrums(
    cpl_frameset *frameset)
{
  /*** Get frame, header and check input data ***/
  cpl_errorstate preStateLoadData = cpl_errorstate_get();
  cpl_msg_info(cpl_func, "Loading header, input data frame ...");
  cpl_frame *frm = cpl_frameset_find(frameset, STAR_SPEC);
  if (!frm) {
      cpl_errorstate_set(preStateLoadData);
      frm = cpl_frameset_find(frameset, EXTRACT_SPEC);
      if (!frm) {
          cpl_errorstate_set(preStateLoadData);
          frm = cpl_frameset_find(frameset, SCIENCE);
          if (!frm) {
              cpl_errorstate_set(preStateLoadData);
              frm = cpl_frameset_find(frameset, RECONSTRUCTED_CUBE);
              if (!frm) {
                  cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
                                        "Frame with input data not found in frameset ('%s','%s','%s','%s') !",
                                        STAR_SPEC, EXTRACT_SPEC, SCIENCE, RECONSTRUCTED_CUBE);
                  return NULL;
              }
          }
      }
  }

  return frm;
}


/*----------------------------------------------------------------------------*/
/**
 * @brief    Load the data input spectrums in the frameset file
 *
 * @param frameset     Set of input frames
 * @param conf         Generic kmos_molecfit configuration
 * @param recipe_name  Name of recipe
 *
 * @return   cpl_error
 *
 * Description:
 *    Loads the spectrum properties and the spectrums table from the extensions with data.
 */
/*----------------------------------------------------------------------------*/
cpl_error_code kmos_molecfit_load_spectrums(
    cpl_frameset *frameset, kmos_molecfit_parameter *conf, const char *recipe_name)
{
  /*** CHARGE ALL OF THE IFU EXTENSIONS WITH DATA ***/

  /* KMOS works with wavelengths units in microns like molecfit.
   * It's not necessary to check nor do any conversion or special management */

  /* Load the input spectrum of the standard used to derive the atmospheric
   * column densities, and update the molecfit configuration with the
   * input spectrum specific values. */

  /* Get frame, filename, tag and number of extensions, ext24=1 (24ext), ext24=2 (48ext) */
  cpl_frame *frm = kmos_molecfit_get_frame_spectrums(frameset);
  cpl_error_ensure(frm, CPL_ERROR_DATA_NOT_FOUND,
                   return CPL_ERROR_DATA_NOT_FOUND, "Raw data not found in input frameset!");
  const char *fileData  = cpl_frame_get_filename(frm);
  const char* tag       = cpl_frame_get_tag(frm);
  cpl_size n_extensions = cpl_fits_count_extensions(fileData);
  cpl_msg_info(cpl_func, "Load spectrums in %s -> n_extensions = %lld", fileData, n_extensions);
  cpl_size    ext24     = (N_IFUS == n_extensions) ? 1 : 2;


  /* Get primary header and show PRO.CATG */
  conf->header_spectrums = cpl_propertylist_load(fileData, 0);
  cpl_error_ensure(conf->header_spectrums, cpl_error_get_code(),
                   return cpl_error_get_code(), "Cannot load raw primary header propertylist from '%s'!", fileData);
  cpl_msg_info(cpl_func, "Input data fits file: PRO.CATG = %s", cpl_propertylist_get_string(conf->header_spectrums, "ESO PRO CATG"));

  /* Get unique grating using in all of data in this STAR_SPEC file {0,60,120,180,240,300} */
  conf->grating.name = cpl_propertylist_get_string(conf->header_spectrums, "ESO INS GRAT1 ID");
  kmos_molecfit_grating_type(conf->grating.name, &(conf->grating.type));

  /* How now I know the type of grating --> Generate cpl_tables by the user/default configuration */
  if (kmos_molecfit_generate_wave_molec_tables(conf) != CPL_ERROR_NONE) {
      return cpl_error_get_code();
  }

  /* Get rotator angle: NAANGLE and convert to more close string(num) in the range {0,60,120,180,240,300}*/
  int rot_angle_close;
  conf->rot_angle = cpl_propertylist_get_float(conf->header_spectrums, "ESO OCS ROT NAANGLE");
  cpl_error_ensure(conf->rot_angle >= -360. && conf->rot_angle <= 360., CPL_ERROR_ILLEGAL_INPUT,
                   return CPL_ERROR_ILLEGAL_INPUT, "The rotator angle in '%s' is out of range=%g!", fileData, conf->rot_angle);
  if (conf->rot_angle >= 0) {
      rot_angle_close = (int)(            60. * floorf(conf->rot_angle / 60. + 0.5))  % 360;
  } else {
      rot_angle_close = (int)(360. - fabs(60. * ceilf( conf->rot_angle / 60. - 0.5))) % 360;
  }

  /* In KMOS IFU STAR_SPEC = {DATA,NOISE}. The odd extension are .DATA (1,3,...,N_EXTENSIONS-1) and the even extension are .NOISE (2,4,...,N_EXTENSIONS) */
  cpl_msg_info(cpl_func, "Loading ('%s') from the file: '%s', rot_angle=%f (more close=%d)",
               tag, fileData, conf->rot_angle, rot_angle_close);
  for (cpl_size n_ifu = 0; n_ifu < N_IFUS; n_ifu++) {

      /* Get and initialize a specific ifu */
      kmos_spectrum *ifu = &(conf->ifus[n_ifu]);
      ifu->name                     = NULL;
      ifu->num                      = n_ifu + 1;
      ifu->ext                      = (n_ifu * ext24) + 1;
      ifu->kernel.header_ext        = NULL;
      ifu->kernel.data              = NULL;
      ifu->kernel.ext               = (n_ifu * 6) + (rot_angle_close / 60) + 1;

      /* Load data */
      if (   strcmp(tag, STAR_SPEC   ) == 0
          || strcmp(tag, EXTRACT_SPEC) == 0 ){

          if (kmos_molecfit_load_spectrum_1D(ifu, fileData, (ext24 == 1)) != CPL_ERROR_NONE) {
              return cpl_error_get_code();
          }

      } else if (   strcmp(tag, SCIENCE           ) == 0
                 || strcmp(tag, RECONSTRUCTED_CUBE) == 0 ){

          if (kmos_molecfit_load_spectrum_3D(ifu, fileData, (ext24 == 1), recipe_name, conf->ifus) != CPL_ERROR_NONE) {
              return cpl_error_get_code();
          }

      } else {

          return cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT, "Unexpected tag ...");
      }
  }

  return CPL_ERROR_NONE;
}

/*----------------------------------------------------------------------------*/
/**
 * @brief    Load 1D spectrum
 *
 * @param ifu        Concrete IFU in KMOS
 * @param filename   Name of file that contains the data spectrum
 * @param ext24      cpl_boolean that explain if the fits input file have 24ext
 *
 * @return   cpl_error
 *
 */
/*----------------------------------------------------------------------------*/
static cpl_error_code kmos_molecfit_load_spectrum_1D(
    kmos_spectrum *ifu, const char* filename, cpl_boolean ext24) {

  /* Get propertylist data/header */
  ifu->header_ext_data = cpl_propertylist_load(filename, ifu->ext);

  /* Get extension name */
  ifu->name = cpl_sprintf("%s", cpl_propertylist_get_string(ifu->header_ext_data, EXTNAME));

  /* Check 24 or 48 extensions */
  if (ext24) {
      /* Add Fake noise extension */
      ifu->header_ext_noise = cpl_propertylist_duplicate(ifu->header_ext_data);
      char *noise_name = cpl_sprintf("IFU.%d.NOISE", ifu->num);
      cpl_propertylist_update_string(ifu->header_ext_noise, EXTNAME, noise_name);
      cpl_free(noise_name);
  } else {
      ifu->header_ext_noise = cpl_propertylist_load(filename, ifu->ext + 1);
  }

  /* Check headers */
  cpl_error_ensure(ifu->header_ext_data && ifu->header_ext_noise, cpl_error_get_code(),
                   return cpl_error_get_code(), "Cannot load data propertylist from IFU='%d'", ifu->num);


  /* Load the data spectrum in 1D, it's an image 1D (vector) */
  cpl_errorstate preState = cpl_errorstate_get();
  cpl_vector *data = cpl_vector_load(filename, ifu->ext);
  if (!data) {

      /* The extension doesn't have data spectrum */
      cpl_errorstate_set(preState);
      ifu->header_1D_data = NULL;
      ifu->data           = NULL;

      cpl_msg_info(cpl_func, "IFU.%02d - Extension: %02d:%s --> Properties charged (No data).",	ifu->num, ifu->ext, ifu->name);

  } else {

      /* Parse from cpl_vector to 1D spectrum: Don't need to convert property form 3D Datacube to 1D spectrum. Copy ifu->header_ext because is 1D */
      ifu->header_1D_data = cpl_propertylist_duplicate(ifu->header_ext_data);
      if (kmos_molecfit_parse_1D_Spectrum(data, ifu) != CPL_ERROR_NONE) {
          if (data) cpl_vector_delete(data);
          return cpl_error_get_code();
      }
      cpl_vector_delete(data);

      cpl_msg_info(cpl_func, "IFU.%02d - Extension: %02d:%s --> Properties charged and loaded data spectrum!",
                   ifu->num, ifu->ext, ifu->name);
  }

  return CPL_ERROR_NONE;
}

/*----------------------------------------------------------------------------*/
/**
 * @brief    Load 3D spectrum
 *
 * @param ifu          Concrete IFU in KMOS
 * @param filename     Name of file that contains the data spectrum
 * @param ext24        cpl_boolean that explain if the fits input file have 24ext
 * @param recipe_name  Name of recipe
 * @param ifus         All of configuration that contain the ifu mapping.
 *
 * @return   cpl_error
 *
 */
/*----------------------------------------------------------------------------*/
static cpl_error_code kmos_molecfit_load_spectrum_3D(
    kmos_spectrum *ifu, const char* filename, cpl_boolean ext24, const char *recipe_name, kmos_spectrum *ifus) {

  int ext_map;
  if (ext24) ext_map =  ifu->map;
  else       ext_map = (ifu->map * 2) - 1;

  /* Get propertylist header of IFU */
  ifu->header_ext_data = cpl_propertylist_load(filename, ext_map);

  /* Update extension name -> Change IFU.Y.DATA by IFU.X-Y.DATA */
  ifu->name = cpl_sprintf("IFU.%d.DATA", ifu->num);
  char *noise_name = cpl_sprintf("IFU.%d.NOISE", ifu->num);
  cpl_propertylist_update_string(ifu->header_ext_data, EXTNAME, ifu->name);

  /* Check 24 or 48 extensions */
  if (ext24) {
      /* Add Fake noise extension */
      ifu->header_ext_noise = cpl_propertylist_duplicate(ifu->header_ext_data);
      cpl_propertylist_update_string(ifu->header_ext_noise, EXTNAME, noise_name);
  } else {
      ifu->header_ext_noise = cpl_propertylist_load(filename, ifu->ext + 1);
  }

  /* Check headers */
  if (!(ifu->header_ext_data) || !(ifu->header_ext_noise)) {
      cpl_free(noise_name);
      return cpl_error_set_message(cpl_func, cpl_error_get_code(), "Cannot load data propertylist from IFU='%d', ext=%02d", ifu->num, ext_map);
  }

  /* Check if actual IFU is mapping */
  if (ifu->num != ifu->map) {

      /* It's not an IFU mapping */
      ifu->data = NULL;
      cpl_msg_info(cpl_func, "IFU.%02d - Extension[%02d]=%s (Mapping IFU.%02d) --> Only properties charged (Not match the mapping).",
                   ifu->num, ifu->ext, ifu->name, ifu->map);

  } else {

      /* It's an IFU_Y: Load DataCube 3D and extract 1D spectrum */

      /* Search a valid 1D spectrum in SCIENCE file */
      cpl_propertylist *header_convert1D = NULL;
      cpl_vector       *data             = NULL;

      if (strcmp(recipe_name, KMOS_MOLECFIT_MODEL) ==0) {

          /* Recipe kmos_recipe_model */
          int ext = ifu->ext;

          header_convert1D = cpl_propertylist_load(filename, ext);
          if (!header_convert1D) {
              cpl_free(noise_name);
              return cpl_error_set_message(cpl_func, cpl_error_get_code(), "Cannot load data propertylist from file=%s, ext=%d", filename, ext);
          }

          /* Extract 1D spectrum from extension in filename (FITS file) */
          cpl_errorstate preState = cpl_errorstate_get();
          data = kmos_molecfit_extract_spec_from_datacube(filename, ext, header_convert1D);
          if (!data) {

              /* The extension doesn't have data spectrum */
              cpl_errorstate_set(preState);
              ifu->header_1D_data = NULL;
              ifu->data           = NULL;

              cpl_msg_info(cpl_func, "IFU.%02d - Extension[%02d]=%s (Mapping IFU.%02d) --> Properties charged.",
                           ifu->num, ifu->ext, ifu->name, ifu->map);

          } else {

              /* Parse from cpl_vector to 1D spectrum: Need to storage cpl_propertylist form 3D Datacube to 1D spectrumn */
              ifu->header_1D_data = cpl_propertylist_duplicate(header_convert1D);
              cpl_propertylist_delete(header_convert1D);
              if (kmos_molecfit_parse_1D_Spectrum(data, ifu) != CPL_ERROR_NONE) {
                  cpl_free(noise_name);
                  if (data) cpl_vector_delete(data);
                  return cpl_error_get_code();
              }
              cpl_vector_delete(data);

              cpl_msg_info(cpl_func, "IFU.%02d - Extension[%02d]=%s (Mapping IFU.%02d) --> Properties charged in extension: %02d and 1D data spectrum extracted!)",
                           ifu->num, ifu->ext, ifu->name, ifu->map, ext);
          }

      } else {

          /* Recipe kmos_recipe_calctrans */
          int ext_data = -1;

          /* Get errorstate preState */
          cpl_errorstate preState       = cpl_errorstate_get();
          cpl_boolean    right_detector = CPL_TRUE;

          /* Get the first data spectrum that match with the map of my ifu */
          for (cpl_size n_ifu = 0; n_ifu < N_IFUS && !data; n_ifu++) {

              if (ext24) ext_data =  n_ifu      + 1;
              else       ext_data = (n_ifu * 2) + 1;


              /* IFU_X = ifu->num : Get the first with data spectrum */
              if (ifus[n_ifu].map == ifu->num) {

                  /* Cleanup header, if it was storage in a previous loop */
                  if (header_convert1D) cpl_propertylist_delete(header_convert1D);

                  header_convert1D = cpl_propertylist_load(filename, ext_data);
                  if (!header_convert1D) {
                      cpl_free(noise_name);
                      return cpl_error_set_message(cpl_func, cpl_error_get_code(),
                                                   "Cannot load data propertylist from file=%s, ext=%02d",
                                                   filename, ext_data);
                  }

                  /* Extract 1D spectrum from extension in filename (FITS file) */
                  data = kmos_molecfit_extract_spec_from_datacube(filename, ext_data, header_convert1D);
                  if (!data && cpl_error_get_code() == CPL_ERROR_FILE_IO) {
                      /* This extension doesn't contain datacube information */
                      cpl_errorstate_set(preState);
                  }

                  /* Check prestate */
                  if (!cpl_errorstate_is_equal(preState)) {
                      cpl_free(noise_name);
                      cpl_propertylist_delete(header_convert1D);
                      if (data) cpl_vector_delete(data);
                      return cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                                                   "Load raw spectrum and the STAR_SPEC from IFU.%02d failed!",
                                                   ifu->num);
                  }
              }
          }

          /* If no data check in all of the input data science file */
          if (!data) {

              /* Get the first data spectrum with data */
              for (cpl_size n_ifu = 0; n_ifu < N_IFUS && !data; n_ifu++) {

                  if (ext24) ext_data =  n_ifu      + 1;
                  else       ext_data = (n_ifu * 2) + 1;


                  /* IFU_X != ifu->num : Get the first with data spectrum */
                  if (ifus[n_ifu].map != ifu->num) {

                      /* Cleanup header, if it was storage in a previous loop */
                      if (header_convert1D) cpl_propertylist_delete(header_convert1D);

                      header_convert1D = cpl_propertylist_load(filename, ext_data);
                      if (!header_convert1D) {
                          cpl_free(noise_name);
                          return cpl_error_set_message(cpl_func, cpl_error_get_code(),
                                                       "Cannot load data propertylist from file=%s, ext=%02d",
                                                       filename, ext_data);
                      }

                      /* Extract 1D spectrum from extension in filename (FITS file) */
                      data = kmos_molecfit_extract_spec_from_datacube(filename, ext_data, header_convert1D);
                      if (!data && cpl_error_get_code() == CPL_ERROR_FILE_IO){
                          cpl_errorstate_set(preState);
                      }

                      /* Check prestate */
                      if (!cpl_errorstate_is_equal(preState)) {
                          cpl_free(noise_name);
                          cpl_propertylist_delete(header_convert1D);
                          if (data) cpl_vector_delete(data);
                          return cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                                                       "Load raw spectrum and the STAR_SPEC from IFU.%02d failed!",
                                                       ifu->num);
                      }
                  }
              }

              /* Check that all of IFU_Y have data */
              if (data) {
                  right_detector = CPL_FALSE;
              } else {
                  cpl_free(noise_name);
                  if (header_convert1D) cpl_propertylist_delete(header_convert1D);
                  return cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT,
                                               "IFU_X.%02d - Extension[%02d]=%s (Mapping IFU_Y.%02d) --> Not founded any data science IFU_X datacube spectrum to load in %s",
                                               ifu->num, ifu->ext, ifu->name, ifu->map, filename);
              }
          }

          /* Parse from cpl_vector to 1D spectrum: Need to storage cpl_propertylist form 3D Datacube to 1D spectrumn */
          ifu->header_1D_data = cpl_propertylist_duplicate(header_convert1D);
          cpl_propertylist_delete(header_convert1D);
          if (kmos_molecfit_parse_1D_Spectrum(data, ifu) != CPL_ERROR_NONE) {
              cpl_free(noise_name);
              if (data) cpl_vector_delete(data);
              return cpl_error_get_code();
          }
          cpl_vector_delete(data);

          if (right_detector) {
              cpl_msg_info(cpl_func, "IFU.%02d - Extension[%02d]=%s (Mapping IFU.%02d) --> First IFU_X with data [IFU_X.%02d] : Properties charged and 1D data spectrum extracted from a his cube data!)",
                           ifu->num, ifu->ext, ifu->name, ifu->map, ext_data);
          } else {
              cpl_msg_warning(cpl_func, "IFU.%02d - Detector doesn't contain datacube spectrum --> Taken from another detector! All his mapping group take this other data spectrum",  ifu->num);
              cpl_msg_warning(cpl_func, "IFU.%02d - Extension[%02d]=%s (Mapping IFU.%02d) --> First IFU_X with data [IFU_X.%02d] : Properties charged and 1D data spectrum extracted from a his cube data!)",
                                         ifu->num, ifu->ext, ifu->name, ifu->map, ext_data);
          }
      }

      /* If data, turn coherente the headers */
      if (ifu->header_1D_data) {

          /* Data header */
          cpl_propertylist_delete(ifu->header_ext_data);
          ifu->header_ext_data = cpl_propertylist_duplicate(ifu->header_1D_data);
          cpl_propertylist_update_string(ifu->header_ext_data, EXTNAME, ifu->name);

          /* Noise header */
          cpl_propertylist_delete(ifu->header_ext_noise);
          ifu->header_ext_noise = cpl_propertylist_duplicate(ifu->header_1D_data);
          cpl_propertylist_update_string(ifu->header_ext_noise, EXTNAME, noise_name);
      }
  }

  /* Cleanup */
  cpl_free(noise_name);

  return CPL_ERROR_NONE;
}

/*----------------------------------------------------------------------------*/
/**
 * @brief    Parse 1D Spectrum and put the data in ifu
 *
 * @param  spc   cpl_vector that contains the 1D spectrum
 * @param  ifu   Concrete IFU in KMOS
 *
 * @return   cpl_error
 *
 */
/*----------------------------------------------------------------------------*/
static cpl_error_code kmos_molecfit_parse_1D_Spectrum(
    cpl_vector *spec, kmos_spectrum *ifu) {

  cpl_error_ensure(spec && ifu, CPL_ERROR_NULL_INPUT,
                   return CPL_ERROR_NULL_INPUT, "Null inputs parse spectrum");

  /* Get preState */
  cpl_errorstate preState = cpl_errorstate_get();

  /* Create structure */
  double n_wave  = cpl_vector_get_size(spec);
  ifu->data = cpl_table_new(n_wave);
  cpl_table_new_column(ifu->data, MF_SPECTRUM_COLUMN_WLEN, CPL_TYPE_DOUBLE);
  cpl_table_new_column(ifu->data, MF_SPECTRUM_COLUMN_FLUX, CPL_TYPE_DOUBLE);

  /* Get Wavelength values: CRVAL1 (initial wavelength) and CDELT1 (step wavelength) */
  ifu->header_CRVAL1  = cpl_propertylist_get_double(ifu->header_1D_data, CRVAL1);
  double wave_ini = ifu->header_CRVAL1;
  if (wave_ini < 0. || wave_ini > 4.) {
      return cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                                   CRVAL1"=%g invalid reading propertylist in %s", wave_ini, ifu->name);
  }
  ifu->header_CDELT1 = cpl_propertylist_get_double(ifu->header_1D_data, CDELT1);
  double wave_step = ifu->header_CDELT1;
  if (wave_step <= 0 || wave_ini + (n_wave * wave_step) > 4.) {
      return cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                                   CDELT1"=%g invalid reading propertylist in %s", wave_ini, ifu->name);
  }

  /* Set waveleght */
  double wavelength = wave_ini;
  for (cpl_size i = 0; i < n_wave; i++) {
      cpl_table_set_double(ifu->data, MF_SPECTRUM_COLUMN_WLEN, i, wavelength);
      wavelength += wave_step;
  }

  /* Set flux */
  cpl_table_copy_data_double(ifu->data, MF_SPECTRUM_COLUMN_FLUX, cpl_vector_get_data(spec));

  /* Set median (It need non_null values) */
  cpl_size n_nonnull = 0;
  cpl_size n_pos = cpl_vector_get_size(spec);
  for (cpl_size i = 0; i < n_pos; i++) {
      if (!isnan(cpl_vector_get(spec, i))) n_nonnull++;
  }
  cpl_vector *data_nonnull = cpl_vector_new(n_nonnull);
  cpl_size j = 0;
  for (cpl_size i = 0; i < n_pos; i++) {
      double d = cpl_vector_get(spec, i);
      if (!isnan(d)) {
          cpl_vector_set(data_nonnull, j, d);
          j++;
      }
  }
  ifu->median = cpl_vector_get_median(data_nonnull);
  cpl_vector_delete(data_nonnull);


  /* Check preState */
  if (!cpl_errorstate_is_equal(preState)) {
      return cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                                   "Parse 1D spectrum from IFU.%02d failed!", ifu->num);
  }

  return CPL_ERROR_NONE;
}

/*----------------------------------------------------------------------------*/
/**
 * @brief    Extract a 1D spectrum from a extension datacube data in one FITS file
 *
 * @param    filename  filename with the FITS file that conteins the datacube
 * @param    ext       Extension that conteins the datacube
 * @param    header    out: header of extension with the changes need for conver 3D in 1D
 *
 * @return   cpl_vector *  Spectrum extracted from the extension datacube data
 *                         or NULL in case of error.
 */
/*----------------------------------------------------------------------------*/
cpl_vector * kmos_molecfit_extract_spec_from_datacube(
    const char *filename, int ext, cpl_propertylist *header)
{
  /* load data */
  cpl_imagelist *imgList = cpl_imagelist_load(filename, CPL_TYPE_FLOAT, ext);
  if (!imgList) {
      cpl_error_set_message(cpl_func, CPL_ERROR_FILE_IO,
                            "Cannot load datacube from %s, ext=%d", filename, ext);
      return NULL;
  }

  cpl_vector *data = NULL;
  if (cpl_imagelist_get_size(imgList) > 0) {

      /*** Extract 1D spectrum from the input imagelist datacube ***/
      kmo_priv_extract_spec(imgList, NULL, NULL, &data, NULL);
      cpl_imagelist_delete(imgList);
      if (!data) {
          cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
                                "Cannot extract 1D spectrum from datacube in %s, ext=%d", filename, ext);
          return NULL;
      }

      /* Check datacube header properties */
      if( !cpl_propertylist_has(header, CRPIX3) ||
          !cpl_propertylist_has(header, CRVAL3) ||
          !cpl_propertylist_has(header, CDELT3) ||
          !cpl_propertylist_has(header, CTYPE3) ){
          cpl_vector_delete(data);
          cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT, "Not all input data are in the header!");
          return NULL;
      }

      /*** Change WCS here (CRPIX3 goes to CRPIX1 etc...) ***/
      cpl_errorstate prestate = cpl_errorstate_get();

      if (cpl_propertylist_has(header, CTYPE1)) {
          const char *key = cpl_propertylist_get_string(header, CTYPE1);
          if (strcmp(key, "WAVE") != 0) cpl_propertylist_update_string(header, CTYPE1, "WAVE");
      } else {
          cpl_propertylist_set_string(header, CTYPE1, "WAVE");
      }
      if (cpl_propertylist_has(header, CTYPE2)) cpl_propertylist_erase(header, CTYPE2);

      if (cpl_propertylist_has(header, CUNIT1)) {
          const char *unit = cpl_propertylist_get_string(header, CUNIT1);
          if(strcmp(unit, "um") != 0) cpl_propertylist_update_string(header, CUNIT1, "um");
      } else {
          cpl_propertylist_append_string(header, CUNIT1, "um");
      }
      if (cpl_propertylist_has(header, CUNIT2)) cpl_propertylist_erase(header, CUNIT2);
      if (cpl_propertylist_has(header, CUNIT3)) cpl_propertylist_erase(header, CUNIT3);

      cpl_propertylist_set_double(header, CRPIX1, cpl_propertylist_get_double(header, CRPIX3));
      cpl_propertylist_erase(     header, CRPIX2);
      cpl_propertylist_erase(     header, CRPIX3);

      cpl_propertylist_set_double(header, CRVAL1, cpl_propertylist_get_double(header, CRVAL3));
      cpl_propertylist_erase(     header, CRVAL2);
      cpl_propertylist_erase(     header, CRVAL3);

      cpl_propertylist_set_double(header, CDELT1, cpl_propertylist_get_double(header, CDELT3));
      cpl_propertylist_erase(     header, CDELT2);
      cpl_propertylist_erase(     header, CDELT3);

      cpl_propertylist_set_string(header, CTYPE1, cpl_propertylist_get_string(header, CTYPE3));
      cpl_propertylist_erase(     header, CTYPE2);
      cpl_propertylist_erase(     header, CTYPE3);

      cpl_propertylist_erase(     header, CD1_1);
      cpl_propertylist_erase(     header, CD1_2);
      cpl_propertylist_erase(     header, CD1_3);

      cpl_propertylist_erase(     header, CD2_1);
      cpl_propertylist_erase(     header, CD2_2);
      cpl_propertylist_erase(     header, CD2_3);

      cpl_propertylist_erase(     header, CD3_1);
      cpl_propertylist_erase(     header, CD3_2);
      cpl_propertylist_erase(     header, CD3_3);

      if (!cpl_errorstate_is_equal(prestate)) {
          cpl_vector_delete(data);
          cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT,
                                "Cannot update the header to convert 3D datacube header en 1D spectrum header from  %s, ext=%d", filename, ext);
          return NULL;
      }
  }

  return data;
}

/*----------------------------------------------------------------------------*/
/**
 * @brief    Load and setup a convolution kernel provided by the user
 *
 * @param    frameset  Input frameset list
 * @param    conf      The parameter configuration variable in the recipe
 *
 * @return   cpl_error_code
 *
 * Description:
 *    Load the convolution kernel library from a file and cast their kernels to a cpl_matrix for use in molecfit.
 */
/*----------------------------------------------------------------------------*/
cpl_error_code kmos_molecfit_load_kernels(
    const cpl_frame *frm, kmos_molecfit_parameter *conf)
{
  /* Check frame */
  cpl_error_ensure(frm, CPL_ERROR_DATA_NOT_FOUND,
                   return CPL_ERROR_DATA_NOT_FOUND, "Kernel library not found in input frameset!");

  /* Get and verify input parameters in the header */
  const char *filename = cpl_frame_get_filename(frm);

  /* Get primary header */
  conf->header_kernels = cpl_propertylist_load(filename, 0);
  cpl_error_ensure(conf->header_kernels, cpl_error_get_code(),
                   return cpl_error_get_code(), "Cannot load kernels library primary header propertylist from '%s'!", filename);

  /* Check PRO.CATG */
  if (strcmp(cpl_propertylist_get_string(conf->header_kernels, "ESO PRO CATG"), "KERNEL_LIBRARY") != 0) {
      cpl_msg_warning(cpl_func, "Input kernel library PRO.CATG isn't %s, is %s",
                      KERNEL_LIBRARY, cpl_propertylist_get_string(conf->header_kernels, "ESO PRO CATG"));
  }

  /* Load kernel for each IFU with data. The DATA are in the odd extension */
  cpl_msg_info(cpl_func, "Loading convolution kernel library ('%s') from the file '%s' ...", KERNEL_LIBRARY, filename);
  for (cpl_size i = 0; i < N_IFUS; i++) {
      kmos_spectrum *ifu     = &(conf->ifus[i]);
      kmos_spectrum *ifu_map = &(conf->ifus[ifu->map - 1]);
      kmos_kernel   *kernel  = &(ifu->kernel);

      /* ifu = ifu_map in kmos_molecfit_model and it can be different in kmos_molecfit_calctrans */
      if (ifu_map->data) {

          /* Load kernel */
          if (kmos_molecfit_load_kernel(filename, kernel) != CPL_ERROR_NONE) {
              return cpl_error_get_code();
          }

          /* If data spectrum loaded, resampling kernel (When it's necessary) */
          if (kmos_molecfit_resample_kernel(ifu, ifu_map->header_CRVAL1, ifu_map->header_CDELT1, cpl_table_get_nrow(ifu_map->data)) != CPL_ERROR_NONE) {
              return cpl_error_get_code();
          }
      }
  }

  return CPL_ERROR_NONE;
}

/*----------------------------------------------------------------------------*/
/**
 * @brief    Load and setup one convolution kernel provided by the kernel library
 *
 * @param    filename   Kernel library file (including path)
 * @param    kernel     out: variable to fill, but it contains the extension to load
 *
 * @return   cpl_error_code
 *
 * Description:
 *    Load the convolution kernel from the kernelibrary and cast it to a cpl_matrix
 */
/*----------------------------------------------------------------------------*/
cpl_error_code kmos_molecfit_load_kernel(
    const char *filename, kmos_kernel *kernel)
{
  cpl_error_ensure(filename && kernel, CPL_ERROR_NULL_INPUT,
                   return CPL_ERROR_NULL_INPUT, "Null inputs");

  /* Get header of extension */
  kernel->header_ext = cpl_propertylist_load(filename, kernel->ext);
  cpl_error_ensure(kernel->header_ext, cpl_error_get_code(),
                   return cpl_error_get_code(), "Cannot load convolution kernel library extension propertylist from '%s', ext=%d!", filename, kernel->ext);

  kernel->name = cpl_propertylist_get_string(kernel->header_ext, EXTNAME);
  cpl_error_ensure(kernel->name, cpl_error_get_code(),
                   return cpl_error_get_code(), "Cannot get the extension name in propertylist from '%s', ext=%d!", filename, kernel->ext);

  /* Check molec_list variable*/
  int n_tokens;
  char **tokens = kmo_strsplit(kernel->name, ".", &n_tokens);
  if (n_tokens != 4) {
      kmo_strfreev(tokens);
      return cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                                   "Unexpected error in the name of kernel library extension");
  } else {
      kernel->num = atoi(tokens[1]);
      if (kernel->num < 1 || kernel->num > N_IFUS) {
          return cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                                       "Unexpected error in the ifu name of kernel library extension");
      }
  }
  kmo_strfreev(tokens);


  /* Load kernel */
  cpl_msg_info(cpl_func, "Loading    kernel - IFU[%02d], Extension[%03d]: %s ...", kernel->num, kernel->ext, kernel->name);
  cpl_image *imgKernel = cpl_image_load(filename, CPL_TYPE_DOUBLE, 0, kernel->ext);
  cpl_error_ensure(imgKernel, cpl_error_get_code(),
                   return cpl_error_get_code(), "Cannot load kernel library extension=%d!", kernel->ext);


  /* Create the needed matrix version of the convolution kernel from the input kernel image. */
  cpl_errorstate e_state = cpl_errorstate_get();
  cpl_matrix *tmp = cpl_matrix_wrap( cpl_image_get_size_y(imgKernel),
                                     cpl_image_get_size_x(imgKernel),
                                     cpl_image_get_data_double(imgKernel));
  kernel->data = cpl_matrix_duplicate(tmp);
  cpl_matrix_unwrap(tmp);
  cpl_image_delete(imgKernel);
  if (!cpl_errorstate_is_equal(e_state)) {
      return cpl_error_set_message(cpl_func, cpl_error_get_code(),
                                   "Setup of convolution kernel failed!");
  }

  /* Get Wavelength values in kernel : CRVAL1 (initial wavelength) and CD2_2 (step wavelength) */
  kernel->header_CRVAL1  = cpl_propertylist_get_double(kernel->header_ext, CRVAL1);
  double wave_ini = kernel->header_CRVAL1;
  if (wave_ini < 0. || wave_ini > 4.) {
      return cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                                   CRVAL1"=%g invalid reading propertylist in %s", wave_ini, kernel->name);
  }
  kernel->header_CD2_2 = cpl_propertylist_get_double(kernel->header_ext, CD2_2);
  double wave_step = kernel->header_CD2_2;
  if (wave_step <= 0 || wave_ini + (cpl_matrix_get_nrow(kernel->data) * wave_step) > 4.) {
      return cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                                   CD2_2"=%g invalid reading propertylist in %s", wave_ini, kernel->name);
  }

  return CPL_ERROR_NONE;
}

/*----------------------------------------------------------------------------*/
/**
 * @brief    Get a kernel->resampled with the number of rows indicate in kernel->n_rows_resampled
 *
 * @param    ifu          Struct variable with all info about the IFU
 * @param    header_map   cpl_propertylist of the reference map IFU (It's possible that the header_map = ifu->header_ext_data)
 *
 * @return   cpl_error_code
 */
/*----------------------------------------------------------------------------*/
cpl_error_code kmos_molecfit_resample_kernel(
    kmos_spectrum *ifu, double spec_lambda_ini, double spec_lambda_delta, cpl_size spec_n_lambdas)
{
  /* Check ifu */
  cpl_error_ensure(ifu, CPL_ERROR_NULL_INPUT,
                   return CPL_ERROR_NULL_INPUT, "Null input spectrum structure");

  /* Check pointers in ifu */
  cpl_error_ensure(ifu->kernel.header_ext && ifu->kernel.data, CPL_ERROR_NULL_INPUT,
                   return CPL_ERROR_NULL_INPUT, "Null inputs kernel data");

  /* Get KERNEL header values */
  double   kernel_lambda_ini   = cpl_propertylist_get_double(ifu->kernel.header_ext, CRVAL1);
  double   kernel_lambda_delta = cpl_propertylist_get_double(ifu->kernel.header_ext, CD2_2 );
  cpl_size kernel_n_lambdas    = cpl_propertylist_get_int(   ifu->kernel.header_ext, NAXIS2);
  cpl_size kernel_n_points     = cpl_propertylist_get_int(   ifu->kernel.header_ext, NAXIS1);

  /* Check values in the headers */
  cpl_error_ensure(spec_lambda_ini   > 0 && kernel_lambda_ini   > 0 &&
                   spec_lambda_delta > 0 && kernel_lambda_delta > 0 &&
                   spec_n_lambdas    > 0 && kernel_n_lambdas    > 0 &&
                   kernel_n_points   > 0, CPL_ERROR_ILLEGAL_INPUT,
                   return CPL_ERROR_ILLEGAL_INPUT, "Illegal value in the inputs headers");

  double diff_lambda_ini   = fabs(spec_lambda_ini   - kernel_lambda_ini  );
  double diff_lambda_delta = fabs(spec_lambda_delta - kernel_lambda_delta);

  /* Check if it's necessary resample the kernel */
  if ( spec_n_lambdas    != kernel_n_lambdas ||
      diff_lambda_ini    > 1.e-4            ||
      diff_lambda_delta  > 1.e-4            ){

      cpl_msg_info(cpl_func, "Resampling kernel - IFU[%02d], Extension[%03d]: %s ... n_lambdas(SPEC=%lld, KERNEL=%lld), diff_lambda_ini=%g, diff_lambda_delta=%g",
                   ifu->kernel.num, ifu->kernel.ext, ifu->kernel.name, spec_n_lambdas, kernel_n_lambdas, diff_lambda_ini, diff_lambda_delta);

      /* Save original kernel and create the new kernel */
      cpl_matrix *k_new = ifu->kernel.data;
      cpl_matrix *k_old = cpl_matrix_duplicate(k_new);
      cpl_matrix_delete(k_new);
      k_new = cpl_matrix_new(spec_n_lambdas, kernel_n_points);

      /* Get access to rows in the matrix */
      double* k_old_d = cpl_matrix_get_data(k_old);
      double* k_new_d = cpl_matrix_get_data(k_new);

      /* Loop about the lambdas */
      double   lambdaSpec = spec_lambda_ini;
      cpl_size k_old_idx  = 0;
      for (cpl_size i = 0; i < spec_n_lambdas; i++) {

          /* Initialize index */
          cpl_size k_old_idx_i = k_old_idx;
          cpl_size k_old_idx_j = k_old_idx;

          /* Get index kernels: top/down */
          double lambdaKernel_ini = kernel_lambda_ini + k_old_idx_i * kernel_lambda_delta;
          double lambdaKernel_end = lambdaKernel_ini;
          while(lambdaKernel_end < lambdaSpec) {
              if (lambdaKernel_end + kernel_lambda_delta < lambdaSpec) {
                  k_old_idx_i++;
              }
              k_old_idx_j++;
              lambdaKernel_end += kernel_lambda_delta;
          }

          /* The last position doesn't match between ifu and kernel, Not resampling for avoid access beyond the boundaries */
          if (k_old_idx_j + 1 > kernel_n_lambdas) {

              k_old_idx_j      = k_old_idx_i;
              lambdaKernel_end = lambdaKernel_ini;
          }

          /* Update index */
          k_old_idx = k_old_idx_i;

          /* Get Row and high resolution signal */
          cpl_size k_new_index   =           i * kernel_n_points;
          cpl_size k_old_index_1 = k_old_idx_i * kernel_n_points;
          cpl_size k_old_index_2 = k_old_idx_j * kernel_n_points;

          /* Check if (i==j) --> (init/end cases) */
          if (k_old_idx_i == k_old_idx_j) {

              /* Copy values related with k_old_index_1 */
              for (cpl_size j = 0; j < kernel_n_points; j++) {
                  k_new_d[j + k_new_index] = k_old_d[j + k_old_index_1];
              }

          } else {

              /* Apply linear interpolation */
              double x  = lambdaSpec;
              double x1 = lambdaKernel_ini;
              double x2 = lambdaKernel_end;
              for (cpl_size j = 0; j < kernel_n_points; j++) {
                  double y1 = k_old_d[j + k_old_index_1];
                  double y2 = k_old_d[j + k_old_index_2];

                  k_new_d[j + k_new_index] = y1 + (x - x1) * (y2 - y1) / (x2 - x1);
              }
          }

          /* Next lambda */
          lambdaSpec += spec_lambda_delta;
      }

      /* Cleanup */
      cpl_matrix_delete(k_old);
  }

  return CPL_ERROR_NONE;
}

/*----------------------------------------------------------------------------*/
/**
 * @brief    Read library kernel from FITS file and save to ASCII file
 *
 * @param    refFitsFile    FITS  file path (including absolute path) of kernel library
 * @param    target_dir     ASCII file name (including absolute path) of kernel library
 *
 * @return   cpl_error_code
 */
/*----------------------------------------------------------------------------*/
cpl_error_code kmos_molecfit_kernelLibrary_FITS_2_ASCII(
    const char *refFitsFile, const char *target_dir) {

  struct stat st = {0};
  if (stat(target_dir, &st) == -1) {
      mkdir(target_dir, 0700);
  }

  /* Load kernel in each extension */
  cpl_msg_info(cpl_func, "Loading kernel library ('%s') from the file '%s' ...", KERNEL_LIBRARY, refFitsFile);
  for (cpl_size i = 0; i < N_KERNEL_LIBRARY_EXTENSIONS; i = i + 1) {

      int ext = i + 1;

      /* Get header of extension */
      cpl_propertylist *header = cpl_propertylist_load(refFitsFile, ext);
      cpl_error_ensure(header, cpl_error_get_code(),
                       return cpl_error_get_code(), "Cannot load convolution kernel library extension propertylist from '%s', ext=%d!", refFitsFile, ext);

      /* Get name extension */
      const char *name = cpl_propertylist_get_string(header, EXTNAME);
      if (!name) {
          cpl_propertylist_delete(header);
          return cpl_error_set_message(cpl_func, cpl_error_get_code(), "Cannot get the extension name in propertylist from '%s', ext=%d!", refFitsFile, ext);
      }

      /* Get data in the extension */
      cpl_image *imgKernel = cpl_image_load(refFitsFile, CPL_TYPE_DOUBLE, 0, ext);
      if (!imgKernel) {
          cpl_propertylist_delete(header);
          return cpl_error_set_message(cpl_func, cpl_error_get_code(), "Cannot load kernel library extension=%d!", ext);
      }

      /* Open output file */
      char *str = cpl_sprintf("%s/%s.dat", target_dir, name);
      FILE *stream = fopen(str, "w");
      cpl_free(str);
      if (!stream) {
          cpl_propertylist_delete(header);
          cpl_image_delete(imgKernel);
          return cpl_error_set_message(cpl_func, CPL_ERROR_FILE_IO, "Path problem with the kernel library extension (%s)", name);
      }

      /* Get error state */
      cpl_errorstate preState = cpl_errorstate_get();

      /* Dump to file in matrix format */
      cpl_matrix *mat = cpl_matrix_wrap( cpl_image_get_size_y(imgKernel),
                                         cpl_image_get_size_x(imgKernel),
                                         cpl_image_get_data_double(imgKernel));
      cpl_matrix_dump(mat, stream);
      cpl_matrix_unwrap(mat);

      /* Cleanup */
      cpl_propertylist_delete(header);
      cpl_image_delete(imgKernel);
      fclose(stream);

      /* Check errorstate */
      if (!cpl_errorstate_is_equal(preState)) {
          return cpl_error_set_message(cpl_func, cpl_error_get_code(),
                                       "Dump kernel library extension (%s) failed!", name);
      }
  }

  return CPL_ERROR_NONE;
}

/*----------------------------------------------------------------------------*/
/**
 * @brief    Read library kernel from ASCII file and save to FITS file
 *
 * @param    refFitsFile  Reference fits file (for to get the header and add only the kernels)
 * @param    target_dir   folder (absolute path) that contains the kernel library files in ASCII
 * @param    target_fits  FITS file path (including absolute path) of new FITS kernel library
 *
 * @return   cpl_error_code
 */
/*----------------------------------------------------------------------------*/
cpl_error_code kmos_molecfit_kernelLibrary_ASCII_2_FITS(
    const char *refFitsFile, const char *target_dir, const char *target_fits) {

  cpl_error_ensure(strcmp(refFitsFile, target_fits) != 0, CPL_ERROR_ILLEGAL_INPUT,
                   return CPL_ERROR_ILLEGAL_INPUT, "Source and target are equal!");

  /* Get header of extension */
  cpl_propertylist *pl_header = cpl_propertylist_load(refFitsFile, 0);
  cpl_error_ensure(pl_header, cpl_error_get_code(),
                   return cpl_error_get_code(), "Cannot load propertylist from '%s'!", refFitsFile);
  cpl_propertylist_save(pl_header, target_fits, CPL_IO_CREATE);
  cpl_propertylist_delete(pl_header);

  /* Load every kernel extension */
  for (cpl_size idx = 0; idx < N_KERNEL_LIBRARY_EXTENSIONS; idx++) {
      int ext = idx + 1;

      /* Get header of extension */
      cpl_propertylist *ext_header = cpl_propertylist_load(refFitsFile, ext);
      cpl_error_ensure(ext_header, cpl_error_get_code(),
                       return cpl_error_get_code(), "Cannot load convolution kernel library extension propertylist from '%s', ext=%d!", refFitsFile, ext);

      /* get file name of extension */
      const char* name = cpl_propertylist_get_string(ext_header, EXTNAME);

      /* Load orignal data from the fits file */
      cpl_image *imgKernel = cpl_image_load(refFitsFile, CPL_TYPE_DOUBLE, 0, ext);
      cpl_error_ensure(imgKernel, cpl_error_get_code(),
                       return cpl_error_get_code(), "Cannot load kernel library extension=%d!", ext);

      cpl_matrix *mat = cpl_matrix_wrap( cpl_image_get_size_y(imgKernel),
                                         cpl_image_get_size_x(imgKernel),
                                         cpl_image_get_data_double(imgKernel));

      cpl_size n_rows = cpl_matrix_get_nrow(mat);
      cpl_size n_cols = cpl_matrix_get_ncol(mat);

      /* Initialize with a know value */
      for (cpl_size i = 0; i < n_rows; i++) {
          for (cpl_size j = 0; j < n_cols; j++){
              cpl_matrix_set(mat, i, j, DBL_MIN);
          }
      }

      /* Open input dump file */
      char *filename = cpl_sprintf("%s/%s.dat", target_dir, name);
      FILE *stream = fopen(filename, "r");
      if (!stream) {
          cpl_propertylist_delete(ext_header);
          cpl_matrix_unwrap(mat);
          cpl_image_delete(imgKernel);
          cpl_free(filename);
          return cpl_error_set_message(cpl_func, CPL_ERROR_FILE_IO, "Cannot open file dump of kernel library extension (%s)", name);
      }

      /* Read and set cpl_image */
      char line[1024];
      cpl_size n_lines = -1;
      while(fgets(line, 1024 - 1, stream)) {
          n_lines++;

          /* In order to jump the col index and the last empty line */
          cpl_size row = n_lines - 1;
          if (n_lines > 0 && row < n_rows) {

              int n_tokens;
              char **tokens = kmo_strsplit(line, " ", &n_tokens);

              /* Copy nonempty at the beginning of array */
              int n_tokens_nonempty = -1;
              for (cpl_size token = 0; token < n_tokens; token++) {
                  if(strcmp(tokens[token],"") != 0) {
                      /* Jump the row index in the file */
                      if (n_tokens_nonempty >= 0) {
                          strcpy(tokens[n_tokens_nonempty],tokens[token]);
                      }
                      n_tokens_nonempty++;
                  }
              }

              /* Check loading data in file dump */
              if (n_tokens_nonempty != n_cols) {
                  cpl_propertylist_delete(ext_header);
                  cpl_matrix_unwrap(mat);
                  cpl_image_delete(imgKernel);
                  cpl_free(filename);
                  kmo_strfreev(tokens);
                  return cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                                               "File dump no compatible with the kernel library extension (%s), different number of data colums", name);
              }

              /* Set values in the matrix */
              cpl_size col = 0;
              for (cpl_size token = 0; token < n_tokens_nonempty; token++){
                  double value = strtod(tokens[token], NULL);
                  cpl_matrix_set(mat, row, col, value);
                  col++;
              }

              /* Cleanup array */
              kmo_strfreev(tokens);
          }
      }
      cpl_msg_info(cpl_func,"File dump %s load and restored!", filename);
      fclose(stream);

      /* Save extension and cleanup*/
      cpl_matrix_unwrap(mat);
      cpl_image_save(imgKernel, target_fits, CPL_TYPE_FLOAT, ext_header, CPL_IO_EXTEND);
      cpl_propertylist_delete(ext_header);
      cpl_image_delete(imgKernel);
      cpl_free(filename);
  }

  return CPL_ERROR_NONE;
}

/*----------------------------------------------------------------------------*/
/**
 * @brief    Save to disk a new *.fits output file
 *
 * @param  frameset     Input frameset       in the recipe.
 * @param  parlist      Input parameter list in the recipe.
 * @param  recipe       Name of the recipe
 * @param  list         Propertylist with the recipe input parameters
 * @param  tag          Tag in the ESO PRO CATG property.
 * @param  gratingname  Name of grating, for if filename is NULL
 * @param  filename     Name of the output *.fits file, if NULL the funtion compose the name with the tag
 *
 * @return   cpl_error_code
 */
/*----------------------------------------------------------------------------*/
cpl_error_code kmos_molecfit_save(
    cpl_frameset            *frameset,
    const cpl_parameterlist *parlist,
    const char              *recipe,
    cpl_propertylist        *list,
    const char              *tag,
    const char              *gratingname,
    cpl_boolean             suppress_extension,
    const char              *filename)
{
  /* Check inputs */
  cpl_error_ensure(frameset && parlist &&	strcmp(recipe, "") != 0 && list	&& strcmp(tag, "") != 0,
                   CPL_ERROR_NULL_INPUT, return CPL_ERROR_NULL_INPUT, "Null inputs in save function");

  /*** Save the base files ***/
  cpl_errorstate preState = cpl_errorstate_get();

  /* Set applist with the input recipe parameters */
  cpl_propertylist *applist = cpl_propertylist_duplicate(list);
  cpl_propertylist_update_string(applist, CPL_DFS_PRO_CATG, tag);
  if (strcmp(tag, SINGLE_SPECTRA) == 0 || strcmp(tag, SINGLE_CUBES) == 0 ){
      cpl_propertylist_update_string(applist, CPL_DFS_PRO_TECH,    "IFU");
      cpl_propertylist_update_bool(  applist, CPL_DFS_PRO_SCIENCE, CPL_TRUE);
  }

  /* Get filename: If filename not NULL get this, if NULL create with tag */
  char *tag_fits;
  if (filename) {
      tag_fits = cpl_sprintf("%s", filename);
  } else if (gratingname && !suppress_extension){
      tag_fits = cpl_sprintf("%s_%s%s%s.fits", tag, gratingname, gratingname, gratingname);
  } else {
      tag_fits = cpl_sprintf("%s.fits", tag);
  }

  /* Save to disk the fits file */
  cpl_dfs_save_propertylist(  frameset, NULL, parlist, frameset, NULL,
                              recipe, applist, NULL, PACKAGE "/" PACKAGE_VERSION,
                              tag_fits);
  cpl_free(tag_fits);

  /* Cleanup */
  cpl_propertylist_delete(applist);

  /* Check possible errors */
  if (!cpl_errorstate_is_equal(preState)) {
      return cpl_error_get_code();
  }

  return CPL_ERROR_NONE;
}

/*----------------------------------------------------------------------------*/
/**
 * @brief    Save IFU output cpl_table data to disk: Only add data to extension
 *
 * @param    header_data   IFU header of data
 * @param    header_noise  IFU header of noise
 * @param    tag           Name of the output *.fits file.
 * @param    gratingname   Name of grating, for if filename is NULL
 * @param    filename      Name of the output *.fits file, if NULL the funtion compose the name with the tag
 * @param    table         cpl_table data to save in one concrete IFU (only one table or spec, if exist both only the table).
 * @param    vec           1D vector spectrum data to save in one concrete IFU (only one table or spec, if exist both only the table).
 *
 * @return   cpl_error_code
 */
/*----------------------------------------------------------------------------*/
cpl_error_code kmos_molecfit_save_mf_results(
    cpl_propertylist *header_data,
    cpl_propertylist *header_noise,
    const char       *tag,
    const char       *gratingname,
    cpl_boolean      suppress_extension,
    const char       *filename,
    cpl_table        *table,
    cpl_vector       *vec)
{
  /* Check inputs */
  cpl_error_ensure(header_data && header_noise && tag,
                   CPL_ERROR_NULL_INPUT, return CPL_ERROR_NULL_INPUT, "Null inputs in IFU molecfit save data execution");

  /*** Save the IFU extension files {DATA,NOISE} ***/
  cpl_errorstate preState = cpl_errorstate_get();

  /* Get filename: If filename not NULL get this, if NULL create with tag */
  char *tag_fits;
  if (filename) {
      tag_fits = cpl_sprintf("%s",             filename);
  } else if (gratingname && !suppress_extension){
      tag_fits = cpl_sprintf("%s_%s%s%s.fits", tag, gratingname, gratingname, gratingname);
  } else {
      tag_fits = cpl_sprintf("%s.fits",        tag);
  }

  /* Save to disk */
  if (table) {
      cpl_table_save(table, NULL, header_data,  tag_fits, CPL_IO_EXTEND);
  }else if (vec) {
      cpl_vector_save(vec, tag_fits, CPL_TYPE_DOUBLE, header_data, CPL_IO_EXTEND);
  } else {
      cpl_propertylist_save(header_data, tag_fits, CPL_IO_EXTEND);
  }
  cpl_propertylist_save(header_noise, tag_fits, CPL_IO_EXTEND);
  cpl_free(tag_fits);

  /* Check possible errors */
  if (!cpl_errorstate_is_equal(preState)) {
      return cpl_error_get_code();
  }

  return CPL_ERROR_NONE;
}

/*----------------------------------------------------------------------------*/
/**
 * @brief    Deallocate the given parameter generic configuration struct and its contents
 *
 * @param    conf       The parameter configuration variable in the recipe.
 */
/*----------------------------------------------------------------------------*/
void kmos_molecfit_clean(
    kmos_molecfit_parameter *conf)
{
  if (conf) {

      if (conf->parms)            cpl_propertylist_delete(conf->parms);
      if (conf->header_spectrums) cpl_propertylist_delete(conf->header_spectrums);

      for (cpl_size i = 0; i < N_IFUS; i++) {
          kmos_spectrum *ifu = &(conf->ifus[i]);
          kmos_molecfit_clean_spectrum(ifu);
      }

      kmos_grating *grating = &(conf->grating);
      kmos_molecfit_clean_graing(grating);

      if (conf->header_kernels)   cpl_propertylist_delete(conf->header_kernels);
  }
}

/*----------------------------------------------------------------------------*/
/**
 * @brief    Deallocate the given parameter IFU spectrum configuration struct and its contents
 *
 * @param    ifu       One parameter spectrum in the recipe.
 */
/*----------------------------------------------------------------------------*/
void kmos_molecfit_clean_spectrum(
    kmos_spectrum *ifu) {
  if (ifu->name)                  cpl_free(               ifu->name);
  if (ifu->header_ext_data)       cpl_propertylist_delete(ifu->header_ext_data);
  if (ifu->header_ext_noise)      cpl_propertylist_delete(ifu->header_ext_noise);
  if (ifu->header_1D_data)        cpl_propertylist_delete(ifu->header_1D_data);
  if (ifu->data)                  cpl_table_delete(       ifu->data);
  kmos_kernel *kernel = &(ifu->kernel);
  kmos_molecfit_clean_kernel(kernel);
}

/*----------------------------------------------------------------------------*/
/**
 * @brief    Deallocate the given parameter kernel configuration struct and its contents
 *
 * @param    kernel       One parameter kernel in the recipe.
 */
/*----------------------------------------------------------------------------*/
void kmos_molecfit_clean_kernel(
    kmos_kernel *kernel) {
  if (kernel->header_ext)         cpl_propertylist_delete(kernel->header_ext);
  if (kernel->data)               cpl_matrix_delete(      kernel->data);
}

/*----------------------------------------------------------------------------*/
/**
 * @brief    Deallocate the given parameter grating configuration struct and its contents
 *
 * @param    grating      One parameter grating in the recipe.
 */
/*----------------------------------------------------------------------------*/
void kmos_molecfit_clean_graing(
    kmos_grating *grating) {
  if (grating->incl_wave_ranges) cpl_table_delete(       grating->incl_wave_ranges);
  if (grating->molecules)        cpl_table_delete(       grating->molecules);
}

/*----------------------------------------------------------------------------*/
/**
 * @brief    Nullify the given parameter generic configuration object and its contents
 *
 * @param    conf       The parameter configuration variable in the recipe.
 */
/*----------------------------------------------------------------------------*/
void kmos_molecfit_nullify(
    kmos_molecfit_parameter *conf)
{
  if (conf) {

      conf->parms               = NULL;
      conf->header_spectrums    = NULL;

      for (cpl_size i = 0; i < N_IFUS; i++) {

          kmos_spectrum* ifu    = &(conf->ifus[i]);
          ifu->name             = NULL;
          ifu->header_ext_data  = NULL;
          ifu->header_ext_noise = NULL;
          ifu->header_1D_data   = NULL;
          ifu->data             = NULL;

          kmos_kernel* kernel   = &(ifu->kernel);
          kernel->header_ext    = NULL;
          kernel->data          = NULL;
      }

      kmos_grating *grating     = &(conf->grating);
      grating->incl_wave_ranges = NULL;
      grating->molecules        = NULL;

      conf->header_kernels      = NULL;
  }
}

/*----------------------------------------------------------------------------*/
/**
 * @brief    Get the directory of execution
 *
 * @return   Path of execution
 *
 */
/*----------------------------------------------------------------------------*/
char * kmos_molecfit_cwd_get(void)
{
  char cwd[4096];
  if (!getcwd(cwd, sizeof cwd)) {
      return NULL;
  }

  char *path = cpl_sprintf("%s", cwd);
  return path;
}

/*----------------------------------------------------------------------------*/
/**
 * @brief    Set the temporary directory variable with path
 *
 * @param path     A valid path
 * @param oldpath  Previous temporary path
 *
 * @return   cpl_error_code
 */
/*----------------------------------------------------------------------------*/
cpl_error_code kmos_molecfit_tmpdir_set(const char *path, char **oldpath)
{
  if (oldpath && (*oldpath != NULL)) {
      return -1;
  }

  const char *tmpdir = getenv("TMPDIR");

  if (!path) {
      unsetenv("TMPDIR");
  } else {
      if (setenv("TMPDIR", path, 1)) {
          return CPL_ERROR_INCOMPATIBLE_INPUT;
      }
  }

  if (oldpath) {
      *oldpath = cpl_strdup(tmpdir);
  }

  return CPL_ERROR_NONE;
}

/**@}*/
