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

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

/*----------------------------------------------------------------------------*/
/**
 *                 Typedefs: Structs and enum types
 */
/*----------------------------------------------------------------------------*/

typedef struct {
  cpl_boolean fit;              /* Flag: Fit resolution                                     */
  double      res;              /* Initial value for FWHM                                   */
} mf_kernel;

typedef struct {
  kmos_molecfit_parameter mf;   /* Generic molecfit parameter                                   */
  const char  *process_ifus;    /* IFUs to process. If -1 (default), process all IFUs with data */
  double      ftol;             /* Relative chi-square convergence criterion                    */
  double      xtol;             /* Relative parameter  convergence criterion                    */
  mf_kernel   boxcar;           /* Fit resolution by boxcar LSF                                 */
  mf_kernel   gauss;            /* Fit resolution by Gaussian                                   */
  mf_kernel   lorentz;          /* Fit resolution by Lorentz                                    */
} kmos_molecfit_model_parameter;


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

/* Check the string variables defined by the user (wave_range and molecules definition) */
static cpl_error_code kmos_molecfit_model_check_wave_molec_conf(
    kmos_grating *grating);

/* Fill the internal KMOS configuration file */
static kmos_molecfit_model_parameter * kmos_molecfit_model_conf(
    const cpl_parameterlist *list);

/* Fill the molecfit specific recipe configuration file */
static cpl_parameterlist * kmos_molecfit_model_mf_conf(
    kmos_molecfit_model_parameter *conf, kmos_spectrum *ifu, kmos_grating_type type);

/* Clean variables allocated in the recipe */
static void kmos_molecfit_model_clean(
    kmos_molecfit_model_parameter *conf);

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

#define RECIPE_NAME      KMOS_MOLECFIT_MODEL
#define CONTEXT          "kmos."RECIPE_NAME

static char kmos_molecfit_model_description[] =
    "This recipe runs molecfit on a 1D spectrum and the input data can have category: \n"
    " - STAR_SPEC (24 DATA plus 24 NOISE extensions)\n"
    " - EXTRACT_SPEC (24 DATA extensions, additional 24 NOISE extensions are optional)\n"
    " - SCIENCE (24 DATA extensions, additional 24 NOISE extensions are optional)\n"
    " - SCI_RECONSTRUCTED (24 DATA extensions, additional 24 NOISE extensions are optional)\n"
    "It is not mandatory that all the DATA extension contains data.\n"
    "Molecfit will be run on all the extension that contain data (not noise extensions). \n"
    "The recipe also accepts as input a kernel library, e.g. the information of the spectral resolution for each IFU and each rotator angle.          \n"
    "When the recipe executes molecfit on the i-th IFU, the kernel in the library that matches the IFU number and rotator angle is used.              \n"
    "If no kernel are provided, each time the recipe runs molecfit, the kernel is considered as a free parameter and it will be determined by molecfit itself and stored into the BEST_FIT_PARM output.                            \n"
    "\n"
    "Input files: (It's mandatory to provide 1 file only of type A) \n"
    "\n"
    "   DO                 KMOS                                                  \n"
    "   category           Type   required   Explanation                         \n"
    "   --------           -----  --------   -----------                         \n"
    "   STAR_SPEC          F1I       A       The spectrum (1D spectrum, IMAGE format).\n"
    "   EXTRACT_SPEC       F1I       A       The spectrum (1D spectrum, IMAGE format).\n"
    "   SCIENCE            F3I       A       The spectrum (3D cube,     IMAGE format).\n"
    "   SCI_RECONSTRUCTED  F3I       A       The spectrum (3D cube,     IMAGE format).\n"
    "   KERNEL_LIBRARY     F2I       N       The kernel library; must be the same grating as the other inputs.\n"
    "\n"
    "Output files:                                                               \n"
    "\n"
    "   DO                 KMOS                                                  \n"
    "   category           Type              Explanation                         \n"
    "   --------           -----             -----------                         \n"
    "   ATMOS_PARM         F1L               Atmospheric parameters                   (multiextension fits table).\n"
    "   BEST_FIT_PARM      F1L               Best fitting parameters                  (multiextension fits table).\n"
    "   BEST_FIT_MODEL     F1L               Best fit model and intermediate products (multiextension fits table).\n"
    "\n";

/* Standard CPL recipe definition */
cpl_recipe_define(	kmos_molecfit_model,
                  	KMOS_BINARY_VERSION,
                  	"Jose A. Escartin, Yves Jung",
                  	"usd-help@eso.org",
                  	"2017",
                  	"Run molecfit on set of 1D spectra to compute an atmospheric model.",
                  	kmos_molecfit_model_description);


/*----------------------------------------------------------------------------*/
/**
 * @defgroup kmos_molecfit_model  It runs molecfit on KMOS standard star file
 *                                to compute an atmospheric model.
 */
/*----------------------------------------------------------------------------*/

/**@{*/

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

/*----------------------------------------------------------------------------*/
/**
 * @brief    Interpret the command line options and execute the data processing
 *
 * @param    frameset   the frames list
 * @param    parlist    the parameters list
 *
 * @return   CPL_ERROR_NONE if everything is OK or CPL_ERROR_CODE in other case
 *
 */
/*----------------------------------------------------------------------------*/
static int kmos_molecfit_model(
    cpl_frameset *frameset, const cpl_parameterlist *parlist)
{
  /* Check initial Entries */
  if (kmos_check_and_set_groups(frameset) != CPL_ERROR_NONE) {
      return cpl_error_get_code();
  }

  /* Get initial errorstate */
  cpl_errorstate initial_errorstate = cpl_errorstate_get();

  /* Extract and verify data in the parameters of the recipe */
  cpl_msg_info(cpl_func, "Configuring initial parameters in the recipe ...");
  kmos_molecfit_model_parameter *conf = kmos_molecfit_model_conf(parlist);
  cpl_error_ensure(conf, CPL_ERROR_ILLEGAL_INPUT,
                   return (int)CPL_ERROR_ILLEGAL_INPUT, "Problems with the configuration parameters");
  if (!(conf->mf.fit_wavelenght.fit)) cpl_msg_info(cpl_func, "Not fit wavelenght!");
  if (!(conf->mf.fit_continuum.fit) ) cpl_msg_info(cpl_func, "Not fit continuum!" );

  /* Loading data spectrums */
  if(kmos_molecfit_load_spectrums(frameset, &(conf->mf), RECIPE_NAME) != CPL_ERROR_NONE) {
      kmos_molecfit_model_clean(conf);
      return (int)cpl_error_set_message(cpl_func, CPL_ERROR_FILE_IO,
                                        "Loading spectrums data in the input frameset failed!");
  }

  /* Loading kernels */
  const cpl_frame *frmKernel = cpl_frameset_find(frameset, KERNEL_LIBRARY);
  if (frmKernel && conf->mf.use_input_kernel) {
      if(kmos_molecfit_load_kernels(frmKernel, &(conf->mf)) != CPL_ERROR_NONE) {
          kmos_molecfit_model_clean(conf);
          return (int)cpl_error_set_message(cpl_func, cpl_error_get_code(),
                                            "Loading convolution kernel data in the input frameset failed!");
      }
  } else {
      if (frmKernel) {
          cpl_msg_warning(cpl_func, "Kernel is provided, but use_input_kernel = false !");
      } else if (conf->mf.use_input_kernel){
          cpl_msg_warning(cpl_func, "Kernel isn't provided, but use_input_kernel = true !");
      }
      cpl_msg_info(cpl_func, "Using the default molecfit kernels!");
      cpl_msg_info(cpl_func, "Fit resolution by Boxcar   (--fit_res_box    ) = %d", conf->boxcar.fit );
      cpl_msg_info(cpl_func, "Fit resolution by Gaussian (--fit_res_gauss  ) = %d", conf->gauss.fit  );
      cpl_msg_info(cpl_func, "Fit resolution by Lorentz  (--fit_res_lorentz) = %d", conf->lorentz.fit);
  }

  cpl_msg_info(cpl_func, " +++ All input data loaded successfully! +++");


  /* Saving generic multi-extension output *.fits files */
  cpl_msg_info(cpl_func, "Saving generic multi-extension output fits file ('%s','%s','%s') ...", ATMOS_PARM, BEST_FIT_PARM, BEST_FIT_MODEL);
  cpl_error_code sAtm_e    = kmos_molecfit_save(frameset, parlist, RECIPE_NAME, conf->mf.parms, ATMOS_PARM,     conf->mf.grating.name, NULL);
  cpl_error_code sBParms_e = kmos_molecfit_save(frameset, parlist, RECIPE_NAME, conf->mf.parms, BEST_FIT_PARM,  conf->mf.grating.name, NULL);
  cpl_error_code sBModel_e = kmos_molecfit_save(frameset, parlist, RECIPE_NAME, conf->mf.parms, BEST_FIT_MODEL, conf->mf.grating.name, NULL);
  if (sAtm_e != CPL_ERROR_NONE || sBParms_e != CPL_ERROR_NONE || sBModel_e != CPL_ERROR_NONE) {
      kmos_molecfit_model_clean(conf);
      return (int)cpl_error_set_message(cpl_func, cpl_error_get_code(),
                                        "Saving generic multi-extension output fits files ('%s','%s','%s') failed!", ATMOS_PARM, BEST_FIT_PARM, BEST_FIT_MODEL);
  }

  /* Change the TMPDIR environment variable to force molecfit and calctrans to create its temporary directory in the current working directory.
   * This is to adhere to pipeline recipe standards, which require recipe output of any kind being created only in the current working directory. */
  char *cwd    = kmos_molecfit_cwd_get();
  char *tmpdir = NULL;
  if (cwd) {
      kmos_molecfit_tmpdir_set(cwd, &tmpdir);
      cpl_free(cwd);
      cwd = NULL;
  }

  /* Number of IFUs processed with molecfit */
  cpl_size n_procesed_ifus = 0;

  /* Execute molecfit for each data spectrum (odd extensions) in STAR_SPEC */
  kmos_grating *grating = &(conf->mf.grating);
  for (cpl_size i = 0; i < N_IFUS; i++) {
      kmos_spectrum *ifu = &(conf->mf.ifus[i]);

      /* Initialize variables for molecfit execution */
      cpl_table *atmprof   = NULL;
      cpl_table *res_table = NULL;
      cpl_table *spec_out  = NULL;

      /* Check if the IFU contains data */
      if (ifu->process && ifu->data) {

          /* Building molecfic configuration file */
          cpl_msg_info(cpl_func, "Building molecfict configuration variable in %s ... ", ifu->name);
          cpl_parameterlist *mf_config = kmos_molecfit_model_mf_conf(conf, ifu, grating->type);
          if (!mf_config) {
              kmos_molecfit_model_clean(conf);
              kmos_molecfit_tmpdir_set(tmpdir, NULL);
              cpl_free(tmpdir);
              return (int)cpl_error_set_message(cpl_func, cpl_error_get_code(),
                                                "Building molecfit configuration variable failed!");
          }

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

          /* Initialize the mf_state for molecfit */
          mf_calctrans_state *mf_state = mf_init_calctrans_state();

          /* Running molecfit */
          cpl_msg_info(cpl_func, "Call mf_run_molecfit(...) in %s ...", ifu->name);
          cpl_table *data = cpl_table_duplicate(ifu->data);
          mf_run_molecfit( mf_config,                  /* cpl_parameterlist  *parlist    */
                           conf->mf.header_spectrums,  /* cpl_propertylist   *plist      */
                           &data,                      /* cpl_table          **inspec    */
                           1,                          /* cpl_size           nspec       */
                           grating->molecules,         /* cpl_table          *molectab   */
                           grating->incl_wave_ranges,  /* cpl_table          *wlinclude  */
                           NULL,                       /* cpl_table          *wlexclude  */
                           NULL,                       /* cpl_table          *pixexclude */
                           ifu->kernel.data,           /* cpl_matrix         *kernel     */
                           &atmprof,                   /* cpl_table          **prof_out  */
                           &res_table,                 /* cpl_table          **res_out   */
                           &spec_out,                  /* cpl_table          **spec_out  */
                           &mf_state);                 /* mf_calctrans_state **state     */

          /* Check execution */
          if (cpl_errorstate_is_equal(preState)) {
              cpl_msg_info(cpl_func, "Call mf_run_molecfit(...) run successfully! in %s ...", ifu->name);
          } else {
              cpl_msg_info(cpl_func, "Call mf_run_molecfit(...) failed! in %s -> error: %s", ifu->name, cpl_error_get_message());
              kmos_molecfit_model_clean(conf);
              cpl_parameterlist_delete(mf_config);
              mf_cleanup(mf_state);
              cpl_table_delete(data);
              kmos_molecfit_tmpdir_set(tmpdir, NULL);
              cpl_free(tmpdir);
              return (int)cpl_error_set_message(cpl_func, cpl_error_get_code(),
                                                "Molecfit call (mf_run_molecfit) failed! ...");
          }
          n_procesed_ifus++;

          /* Cleanup molecfic output variables */
          cpl_parameterlist_delete(mf_config);
          mf_cleanup(mf_state);
          cpl_table_delete(data);
      }

      /* Saving data */
      if (!(ifu->data)) {
          cpl_msg_info(cpl_func, "Saving data IFU=%d ...", ifu->num);
      } else if (!(ifu->process)){
          cpl_msg_info(cpl_func, "Saving data IFU=%d ... (data spectrum -> But not processed)", ifu->num);
      } else {
          cpl_msg_info(cpl_func, "Saving data IFU=%d ... (data spectrum -> Molecfit executed)", ifu->num);
      }
      sAtm_e    = kmos_molecfit_save_mf_results(ifu->header_ext_data, ifu->header_ext_noise, ATMOS_PARM,     conf->mf.grating.name, NULL, atmprof,   NULL);
      sBParms_e = kmos_molecfit_save_mf_results(ifu->header_ext_data, ifu->header_ext_noise, BEST_FIT_PARM,  conf->mf.grating.name, NULL, res_table, NULL);
      sBModel_e = kmos_molecfit_save_mf_results(ifu->header_ext_data, ifu->header_ext_noise, BEST_FIT_MODEL, conf->mf.grating.name, NULL, spec_out,  NULL);
      if (atmprof)   cpl_table_delete(atmprof);
      if (res_table) cpl_table_delete(res_table);
      if (spec_out)  cpl_table_delete(spec_out);
      if (sAtm_e != CPL_ERROR_NONE || sBParms_e != CPL_ERROR_NONE || sBModel_e != CPL_ERROR_NONE) {
          kmos_molecfit_model_clean(conf);
          kmos_molecfit_tmpdir_set(tmpdir, NULL);
          cpl_free(tmpdir);
          return (int)cpl_error_set_message(cpl_func, cpl_error_get_code(),
                                            "Saving Molecfit output fits files ('%s','%s','%s') failed!", ATMOS_PARM, BEST_FIT_PARM, BEST_FIT_MODEL);
      }
  }

  /* Cleanup configuration */
  cpl_msg_info(cpl_func,"Cleaning variables ...");
  kmos_molecfit_model_clean(conf);

  /* Restore tmp_directory */
  kmos_molecfit_tmpdir_set(tmpdir, NULL);
  cpl_free(tmpdir);

  /* Check Recipe status and end */
  if (cpl_errorstate_is_equal(initial_errorstate) && cpl_error_get_code() == CPL_ERROR_NONE ) {
      cpl_msg_info(cpl_func,"Recipe successfully!, Number of IFUs processed (with data): %lld", n_procesed_ifus);
  } else {
      /* Dump the error history since recipe execution start.
       * At this point the recipe cannot recover from the error */
      cpl_errorstate_dump(initial_errorstate, CPL_FALSE, NULL);
      cpl_msg_info(cpl_func,"Recipe failed!, error(%d)=%s", cpl_error_get_code(), cpl_error_get_message());
  }

  return (int)cpl_error_get_code();
}

/**@}*/

/*----------------------------------------------------------------------------*/
/**
 * @brief Function needed by cpl_recipe_define to fill the input parameters
 *
 * @param  self   parameterlist where you need put parameters
 *
 * @return cpl_error_code
 *
 */
/*----------------------------------------------------------------------------*/
static cpl_error_code kmos_molecfit_model_fill_parameterlist(
    cpl_parameterlist *self)
{
  /* Add the different default parameters to the recipe */
  cpl_errorstate prestate = cpl_errorstate_get();

  /* Fill the parameters list */
  cpl_error_code e;
  cpl_boolean    range     = CPL_TRUE;
  const void     *dummyMin = NULL;
  const void     *dummyMax = NULL;


  /* --process_ifus */
  const char *process_ifus = "-1";
  e = kmos_molecfit_fill_parameter(RECIPE_NAME, self, "process_ifus",
                                   !range, dummyMin, dummyMax, CPL_TYPE_STRING, (const void *)process_ifus,
                                   "A list of IFUs to process. If set to -1, all the IFUs that have data will be process.", CPL_FALSE);
  if (e != CPL_ERROR_NONE) return (int)e;

  /* --wave_range */
  const char *wave_range = "-1";
  e = kmos_molecfit_fill_parameter(RECIPE_NAME, self, "wave_range",
                                   !range, dummyMin, dummyMax, CPL_TYPE_STRING, (const void *)wave_range,
                                   "A list of numbers defining the wavelength ranges to fit in the grating. "
                                   "If set to -1, grating dependent default values are used (see manual for reference). ", CPL_FALSE);
  if (e != CPL_ERROR_NONE) return (int)e;

  /* --list_molec */
  const char *list_molec = "-1";
  e = kmos_molecfit_fill_parameter(RECIPE_NAME, self, "list_molec",
                                   !range, dummyMin, dummyMax, CPL_TYPE_STRING, (const void *)list_molec,
                                   "A list of molecules to fit in grating IZ. "
                                   "If set to -1, grating dependent default values are used (see manual for reference). ", CPL_FALSE);
  if (e != CPL_ERROR_NONE) return (int)e;

  /* --fit_molec */
  const char *fit_molec = "-1";
  e = kmos_molecfit_fill_parameter(RECIPE_NAME, self, "fit_molec",
                                   !range, dummyMin, dummyMax, CPL_TYPE_STRING, (const void *)fit_molec,
                                   "Flags to fit the column density of the corresponding list_molec in grating. "
                                   "If set to -1, grating dependent default values are used (see manual for reference). ", CPL_FALSE);
  if (e != CPL_ERROR_NONE) return (int)e;

  /* --relcol */
  const char *relcol = "-1";
  e = kmos_molecfit_fill_parameter(RECIPE_NAME, self, "relcol",
                                   !range, dummyMin, dummyMax, CPL_TYPE_STRING, (const void *)relcol,
                                   "Column density relative to atmospheric profile of the corresponding list_molec in grating. "
                                   "If set to -1, grating dependent default values are used (see manual for reference). ", CPL_FALSE);
  if (e != CPL_ERROR_NONE) return (int)e;

  /* --ftol */
  double ftol = 0.01;
  e = kmos_molecfit_fill_parameter(RECIPE_NAME, self, "ftol",
                                   !range, dummyMin, dummyMax, CPL_TYPE_DOUBLE, &ftol,
                                   "Relative chi-square convergence criterion.", CPL_FALSE);
  if (e != CPL_ERROR_NONE) return (int)e;

  /* --xtol */
  double xtol = 0.001;
  e = kmos_molecfit_fill_parameter(RECIPE_NAME, self, "xtol",
                                   !range, dummyMin, dummyMax, CPL_TYPE_DOUBLE, &xtol,
                                   "Relative parameter convergence criterion.", CPL_FALSE);
  if (e != CPL_ERROR_NONE) return (int)e;

  /* --fit_cont */
  cpl_boolean fit_cont = CPL_TRUE;
  e = kmos_molecfit_fill_parameter(RECIPE_NAME, self, "fit_cont",
                                   !range, dummyMin, dummyMax, CPL_TYPE_BOOL, &fit_cont,
                                   "Flag to enable/disable the polynomial fit of the continuum.", CPL_FALSE);
  if (e != CPL_ERROR_NONE) return (int)e;

  /* --cont_n */
  int cont_n = 1;
  e = kmos_molecfit_fill_parameter(RECIPE_NAME, self, "cont_n",
                                   !range, dummyMin, dummyMax, CPL_TYPE_INT, &cont_n,
                                   "Degree of polynomial continuum fit.", CPL_FALSE);
  if (e != CPL_ERROR_NONE) return (int)e;

  /* --fit_wlc */
  cpl_boolean fit_wlc = CPL_TRUE;
  e = kmos_molecfit_fill_parameter(RECIPE_NAME, self, "fit_wlc",
                                   !range, dummyMin, dummyMax, CPL_TYPE_BOOL, &fit_wlc,
                                   "Flag to enable/disable the refinement of the wavelength solution.", CPL_FALSE);
  if (e != CPL_ERROR_NONE) return (int)e;

  /* --wlc_n */
  int wlc_n = 2;
  e = kmos_molecfit_fill_parameter(RECIPE_NAME, self, "wlc_n",
                                   !range, dummyMin, dummyMax, CPL_TYPE_INT, &wlc_n,
                                   "Polynomial degree of the refined wavelength solution.", CPL_FALSE);
  if (e != CPL_ERROR_NONE) return (int)e;

  /* --wlc_const */
  double wlc_const = 0.;
  e = kmos_molecfit_fill_parameter(RECIPE_NAME, self, "wlc_const",
                                   !range, dummyMin, dummyMax, CPL_TYPE_DOUBLE, &wlc_const,
                                   "Initial constant term for wavelength adjustment (shift relative to half wavelength range).", CPL_FALSE);
  if (e != CPL_ERROR_NONE) return (int)e;

  /* --use_input_kernel */
  cpl_boolean use_input_kernel = CPL_TRUE;
  e = kmos_molecfit_fill_parameter(RECIPE_NAME, self, "use_input_kernel",
                                   !range, dummyMin, dummyMax, CPL_TYPE_BOOL, &use_input_kernel,
                                   "The parameters below are ignored if use_input_kernel.", CPL_FALSE);
  if (e != CPL_ERROR_NONE) return (int)e;

  /* --fit_res_box */
  cpl_boolean fit_res_box = CPL_FALSE;
  e = kmos_molecfit_fill_parameter(RECIPE_NAME, self, "fit_res_box",
                                   !range, dummyMin, dummyMax, CPL_TYPE_BOOL, &fit_res_box,
                                   "Fit resolution by Boxcar LSF.", CPL_FALSE);
  if (e != CPL_ERROR_NONE) return (int)e;

  /* --relres_box */
  double relres_box     = 0.;
  double relres_box_Min = 0.;
  double relres_box_Max = 2.;
  e = kmos_molecfit_fill_parameter(RECIPE_NAME, self, "relres_box",
                                   range, &relres_box_Min, &relres_box_Max, CPL_TYPE_DOUBLE, &relres_box,
                                   "Initial value for FWHM of Boxcar rel. to slit width (Range between 0 and 2).", CPL_FALSE);
  if (e != CPL_ERROR_NONE) return (int)e;

  /* --fit_res_gauss */
  cpl_boolean fit_res_gauss = CPL_TRUE;
  e = kmos_molecfit_fill_parameter(RECIPE_NAME, self, "fit_res_gauss",
                                   !range, dummyMin, dummyMax, CPL_TYPE_BOOL, &fit_res_gauss,
                                   "Fit resolution by Gaussian.", CPL_FALSE);
  if (e != CPL_ERROR_NONE) return (int)e;

  /* --res_gauss */
  double res_gauss = -1.;
  e = kmos_molecfit_fill_parameter(RECIPE_NAME, self, "res_gauss",
                                   !range, dummyMin, dummyMax, CPL_TYPE_DOUBLE, &res_gauss,
                                   "Initial value for FWHM of the Gaussian in pixels (Default = -1: IZ=1.84, YJ=1.82, H=1.76, K=1.73, HK=2.06).", CPL_FALSE);
  if (e != CPL_ERROR_NONE) return (int)e;

  /* --fit_res_lorentz */
  cpl_boolean fit_res_lorentz = CPL_FALSE;
  e = kmos_molecfit_fill_parameter(RECIPE_NAME, self, "fit_res_lorentz",
                                   !range, dummyMin, dummyMax, CPL_TYPE_BOOL, &fit_res_lorentz,
                                   "Fit resolution by Lorentz.", CPL_FALSE);
  if (e != CPL_ERROR_NONE) return (int)e;

  /* --res_lorentz */
  double res_lorentz = 0.5;
  e = kmos_molecfit_fill_parameter(RECIPE_NAME, self, "res_lorentz",
                                   !range, dummyMin, dummyMax, CPL_TYPE_DOUBLE, &res_lorentz,
                                   "Initial value for FWHM of the Lorentz in pixels.", CPL_FALSE);
  if (e != CPL_ERROR_NONE) return (int)e;

  /* --kernmode */
  cpl_boolean kernmode = CPL_FALSE;
  e = kmos_molecfit_fill_parameter(RECIPE_NAME, self, "kernmode",
                                   !range, dummyMin, dummyMax, CPL_TYPE_BOOL, &kernmode,
                                   "Voigt profile approx. or independent Gauss and Lorentz.", CPL_FALSE);
  if (e != CPL_ERROR_NONE) return (int)e;

  /* --kernfac */
  double kernfac = 5.;
  e = kmos_molecfit_fill_parameter(RECIPE_NAME, self, "kernfac",
                                   !range, dummyMin, dummyMax, CPL_TYPE_DOUBLE, &kernfac,
                                   "Size of Gaussian/Lorentz/Voigt kernel in FWHM..", CPL_FALSE);
  if (e != CPL_ERROR_NONE) return (int)e;

  /* --varkern */
  cpl_boolean varkern = CPL_FALSE;
  e = kmos_molecfit_fill_parameter(RECIPE_NAME, self, "varkern",
                                   !range, dummyMin, dummyMax, CPL_TYPE_BOOL, &varkern,
                                   "Does the kernel size increase linearly with wavelength?.", CPL_FALSE);
  if (e != CPL_ERROR_NONE) return (int)e;


  /* Check possible errors */
  if (!cpl_errorstate_is_equal(prestate)) {
      return cpl_error_set_message(cpl_func, cpl_error_get_code(),
                                   "kmos_molecfit_model_fill_parameterlist failed!");
  }

  return CPL_ERROR_NONE;
}

/*----------------------------------------------------------------------------*/
/**
 * @brief  Check the string variables defined by the user (wave_range and molecules definition)
 *
 * @param  grating    Struct with the variables to check
 *
 * @return cpl_error_code
 *
 */
/*----------------------------------------------------------------------------*/
static cpl_error_code kmos_molecfit_model_check_wave_molec_conf(
    kmos_grating *grating)
{
  /* Check input */
  cpl_error_ensure(grating, CPL_ERROR_NULL_INPUT,
                   return CPL_ERROR_NULL_INPUT, "grating input is NULL!");

  cpl_error_ensure(grating->wave_range && grating->list_molec && grating->fit_molec && grating->relcol, CPL_ERROR_NULL_INPUT,
                   return CPL_ERROR_NULL_INPUT, "values of grating input are NULL!");

  /* Init variables */
  char **tokens;
  int  n_tokens;


  /*** Check wave_range variable ***/
  if (strcmp(grating->wave_range, "-1") != 0) {

      tokens = kmo_strsplit(grating->wave_range, ",", &n_tokens);

      /* Any token? */
      if (!tokens || n_tokens <= 0) {
          if (tokens) kmo_strfreev(tokens);
          return cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT,
                                       "Unexpected error getting .wave_ranges!");
      }

      /* At least 2 parameters, Check even number of parameters */
      if (n_tokens % 2 != 0) {
          kmo_strfreev(tokens);
          return cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                                       ".wave_range haven't a even number of parameters");
      }

      /* Range of wavelenghts and direction */
      for (cpl_size i = 0; i < n_tokens / 2; i++) {

          cpl_size idx   = i * 2;
          double   start = strtod(tokens[idx],     NULL);
          double   end   = strtod(tokens[idx + 1], NULL);

          /* Check the wavelength range - range of KMOS between 0.8 to 2.5 (um) */
          if (start < KMOS_WAVELENGTH_START || start > KMOS_WAVELENGTH_END || end < KMOS_WAVELENGTH_START || end > KMOS_WAVELENGTH_END) {
              kmo_strfreev(tokens);
              return cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                                           "wavelength in '.wave_range' out of the valid range [%g,%g]", KMOS_WAVELENGTH_START, KMOS_WAVELENGTH_END);
          } else if (start > end) {
              kmo_strfreev(tokens);
              return cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                                           "wavelength in '.wave_range' starts before end , valid range [%g,%g]", KMOS_WAVELENGTH_START, KMOS_WAVELENGTH_END);
          }
      }
      kmo_strfreev(tokens);
  }

  /*** Check molec_list variable ***/
  if (strcmp(grating->list_molec, "-1") != 0 ){

      tokens = kmo_strsplit(grating->list_molec, ",", &n_tokens);

      /* Any token? */
      if (!tokens || n_tokens <= 0) {
          kmo_strfreev(tokens);
          return cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                                       "It's necessary at least one molecule in .list_molec");
      }

      /* Name of molecs */
      for (cpl_size i = 0; i < n_tokens; i++) {

          /* Check the name of the molecules */
          if (strcmp(tokens[i], "H2O") != 0 &&
              strcmp(tokens[i], "CH4") != 0 &&
              strcmp(tokens[i], "CO" ) != 0 &&
              strcmp(tokens[i], "CO2") != 0 &&
              strcmp(tokens[i], "O2" ) != 0 ){
              kmo_strfreev(tokens);
              return cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                                           "Molecule unknown in .list_molec");
          }
      }
      kmo_strfreev(tokens);
  }

  /*** Check fit_molec variable ***/
  if (strcmp(grating->fit_molec, "-1") != 0) {

      tokens = kmo_strsplit(grating->fit_molec, ",", &n_tokens);

      /* Any token? */
      if (!tokens || n_tokens <= 0) {
          kmo_strfreev(tokens);
          return cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                                       "It's necessary the same number of values in .list_molec and .fit_molec");
      }

      /* Boolean value? */
      for (cpl_size i = 0; i < n_tokens; i++) {
          int fit_molec = atoi(tokens[i]);

          if (fit_molec != 0 && fit_molec != 1) {
              kmo_strfreev(tokens);
              return cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                                           "Unexpected value in .fit_molec it'll be boolean, 0 or 1");
          }
      }
      kmo_strfreev(tokens);
  }

  /*** Check relcol variable ***/
  if (strcmp(grating->relcol, "-1") != 0) {

      tokens = kmo_strsplit(grating->relcol, ",", &n_tokens);

      /* Any token? */
      if (!tokens || n_tokens <= 0) {
          kmo_strfreev(tokens);
          return cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                                       "It's necessary the same number of values in .list_molec and .relcol");
      }

      /* Range between 0. to 1. ? */
      for (cpl_size j = 0; j < n_tokens; j++) {
          double relcol = strtod(tokens[j], NULL);
          if (relcol < 0. || relcol > 1.) {
              kmo_strfreev(tokens);
              return cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                                           "Unexpected value in .relcol, it's a fraction. It'll be < 0. or > 1.");
          }
      }
      kmo_strfreev(tokens);
  }


  return CPL_ERROR_NONE;
}

/*----------------------------------------------------------------------------*/
/**
 * @brief  Generate the internal configuration file for the recipe and check values
 *
 * @param  list   parameterlist with the parameters
 *
 * @return configuration file or NULL if exist an error
 *
 */
/*----------------------------------------------------------------------------*/
static kmos_molecfit_model_parameter * kmos_molecfit_model_conf(
    const cpl_parameterlist *list)
{
  /* Check input */
  cpl_error_ensure(list, CPL_ERROR_NULL_INPUT,
                   return NULL, "list input is NULL!");

  /* Get preState */
  cpl_errorstate preState = cpl_errorstate_get();
  const cpl_parameter *p;


  /* Create the configuration parameter */
  kmos_molecfit_model_parameter *conf = (kmos_molecfit_model_parameter *)cpl_malloc(sizeof(kmos_molecfit_model_parameter));
  kmos_molecfit_nullify(&(conf->mf));

  /* Initialize input parameters propertylist */
  conf->mf.parms = cpl_propertylist_new();


  /*** Mapping IFUs in kmos_molecfit_model recipe, correspondence 1 to 1 ***/
  for (cpl_size n_ifu = 0; n_ifu < N_IFUS; n_ifu++) {
      conf->mf.ifus[n_ifu].map = n_ifu + 1;
  }


  /*** What IFUs Do I need to process? ***/
  p = cpl_parameterlist_find_const(list, "process_ifus");
  conf->process_ifus = cpl_parameter_get_string(p);
  if (strcmp(conf->process_ifus, "-1") == 0) {

      /* Process all IFUs by default */
      cpl_msg_info(cpl_func, "Processing all IFUs (by default) ... ");
      for (cpl_size n_ifu = 0; n_ifu < N_IFUS; n_ifu++) {
          conf->mf.ifus[n_ifu].process = CPL_TRUE;
      }

  } else {

      /* Initialize process IFUs to CPL_FALSE */
      for (cpl_size n_ifu = 0; n_ifu < N_IFUS; n_ifu++) {
          conf->mf.ifus[n_ifu].process = CPL_FALSE;
      }

      /* Init variables */
      int  n_tokens;
      char **tokens = kmo_strsplit(conf->process_ifus, ",", &n_tokens);

      /* Any token? */
      if (!tokens || n_tokens <= 0) {
          kmo_strfreev(tokens);
          cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                                "proccess_IFU empty. It's necessary to process some IFU");
          return NULL;
      }

      /* Set process IFUs */
      for (cpl_size i = 0; i < n_tokens; i++) {
          int n_ifu = atoi(tokens[i]);

          /* Number of IFU correct */
          if (n_ifu <1 || n_ifu > 24) {
              kmo_strfreev(tokens);
              cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                                    "Incorrect value in process_ifus. It need to be from 1 to 24");
              return NULL;
          }

          /* Process this IFU */
          cpl_msg_info(cpl_func, "Processing IFU.%02d (assigned by the user) ... ", n_ifu);
          conf->mf.ifus[n_ifu - 1].process = CPL_TRUE;
      }
      kmo_strfreev(tokens);
  }


  /*** Wavelenght ranges & molecules: Defined by the user ****/

  /* Get string iwht the list of wavelenght for the grating defined by the user */
  p = cpl_parameterlist_find_const(list, "wave_range");
  conf->mf.grating.wave_range = cpl_parameter_get_string(p);

  /* Get string with the list of molecules for the grating defined by the user */
  p = cpl_parameterlist_find_const(list, "list_molec");
  conf->mf.grating.list_molec = cpl_parameter_get_string(p);

  /* Get string with the fit list of molecules for this grating */
  p = cpl_parameterlist_find_const(list, "fit_molec");
  conf->mf.grating.fit_molec = cpl_parameter_get_string(p);

  /* Get string with the relcol list of molecules for this grating */
  p = cpl_parameterlist_find_const(list, "relcol");
  conf->mf.grating.relcol = cpl_parameter_get_string(p);

  /* Check values defined by the user */
  if (kmos_molecfit_model_check_wave_molec_conf(&(conf->mf.grating)) != CPL_ERROR_NONE) {
      kmos_molecfit_model_clean(conf);
      return NULL;
  }


  /*** Convergence criterion ***/

  p = cpl_parameterlist_find_const(list, "ftol");
  conf->ftol = cpl_parameter_get_double(p);
  if (conf->ftol < 1.e-10) {
      kmos_molecfit_model_clean(conf);
      cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                            ".ftol out of the valid range");
      return NULL;
  }

  p = cpl_parameterlist_find_const(list, "xtol");
  conf->xtol = cpl_parameter_get_double(p);
  if (conf->xtol < 1.e-10) {
      kmos_molecfit_model_clean(conf);
      cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                            ".xtol out of the valid range");
      return NULL;
  }


  /*** Continuum ***/

  p = cpl_parameterlist_find_const(list, "fit_cont");
  conf->mf.fit_continuum.fit = cpl_parameter_get_bool(p);
  if (cpl_error_get_code() != CPL_ERROR_NONE) {
      kmos_molecfit_model_clean(conf);
      cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                            "Unexpected value in .fit_cont it'll be boolean, 0 or 1");
      return NULL;
  }

  p = cpl_parameterlist_find_const(list, "cont_n");
  conf->mf.fit_continuum.n = cpl_parameter_get_int(p);
  if (conf->mf.fit_continuum.n < 0 || conf->mf.fit_continuum.n > MOLECFIT_MAX_POLY_FIT) {
      kmos_molecfit_model_clean(conf);
      cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                            ".cont_n out of the valid range");
      return NULL;
  }


  /*** Wavelength solution fit/adjustment ***/

  p = cpl_parameterlist_find_const(list, "fit_wlc");
  conf->mf.fit_wavelenght.fit = cpl_parameter_get_bool(p);
  if (cpl_error_get_code() != CPL_ERROR_NONE) {
      kmos_molecfit_model_clean(conf);
      cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                            "Unexpected value in .fit_wlc it'll be boolean, 0 or 1");
      return NULL;
  }

  p = cpl_parameterlist_find_const(list, "wlc_n");
  conf->mf.fit_wavelenght.n = cpl_parameter_get_int(p);
  if (conf->mf.fit_wavelenght.n < 1 || conf->mf.fit_wavelenght.n > MOLECFIT_MAX_POLY_FIT) {
      kmos_molecfit_model_clean(conf);
      cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                            ".wlc_n out of the valid range");
      return NULL;
  }

  p = cpl_parameterlist_find_const(list, "wlc_const");
  conf->mf.fit_wavelenght.const_val = cpl_parameter_get_double(p);
  if (conf->mf.fit_wavelenght.const_val < 0.) {
      kmos_molecfit_model_clean(conf);
      cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                            ".wlc_const out of the valid range");
      return NULL;
  }


  /*** User input kernel ? ***/
  p = cpl_parameterlist_find_const(list, "use_input_kernel");
  conf->mf.use_input_kernel = cpl_parameter_get_bool(p);
  if (cpl_error_get_code() != CPL_ERROR_NONE) {
      kmos_molecfit_model_clean(conf);
      cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                            "Unexpected value in .use_input_kernel it'll be boolean, 0 or 1");
      return NULL;
  }


  /*** Default kernel: Boxcar kernel ***/
  p = cpl_parameterlist_find_const(list, "fit_res_box");
  conf->boxcar.fit = cpl_parameter_get_bool(p);
  if (cpl_error_get_code() != CPL_ERROR_NONE) {
      kmos_molecfit_model_clean(conf);
      cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                            "Unexpected value in .fit_res_box it'll be boolean, 0 or 1");
      return NULL;
  }
  p = cpl_parameterlist_find_const(list, "relres_box");
  conf->boxcar.res = cpl_parameter_get_double(p);
  if (conf->boxcar.res < 0. || conf->boxcar.res > 2.) {
      kmos_molecfit_model_clean(conf);
      cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                            ".relres_box out of the valid range");
      return NULL;
  }


  /*** Default kernel: Gaussian kernel ***/
  p = cpl_parameterlist_find_const(list, "fit_res_gauss");
  conf->gauss.fit = cpl_parameter_get_bool(p);
  if (cpl_error_get_code() != CPL_ERROR_NONE) {
      kmos_molecfit_model_clean(conf);
      cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                            "Unexpected value in .fit_res_gauss it'll be boolean, 0 or 1");
      return NULL;
  }
  p = cpl_parameterlist_find_const(list, "res_gauss");
  conf->gauss.res = cpl_parameter_get_double(p);
  if (conf->gauss.res != -1. && conf->gauss.res < 0.01) {
      kmos_molecfit_model_clean(conf);
      cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                            ".res_gauss out of the valid range");
      return NULL;
  }

  /*** Default kernel: Lorentz kernel ***/
  p = cpl_parameterlist_find_const(list, "fit_res_lorentz");
  conf->lorentz.fit = cpl_parameter_get_bool(p);
  if (cpl_error_get_code() != CPL_ERROR_NONE) {
      kmos_molecfit_model_clean(conf);
      cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                            "Unexpected value in .fit_res_lorentz it'll be boolean, 0 or 1");
      return NULL;
  }
  p = cpl_parameterlist_find_const(list, "res_lorentz");
  conf->lorentz.res = cpl_parameter_get_double(p);
  if (conf->lorentz.res < 0. || conf->lorentz.res > 100.) {
      kmos_molecfit_model_clean(conf);
      cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                            ".res_lorentz out of the valid range");
      return NULL;
  }


  /*** Default kernels: Generic parameters ***/

  p = cpl_parameterlist_find_const(list, "kernmode");
  conf->mf.kernmode = cpl_parameter_get_bool(p);
  if (cpl_error_get_code() != CPL_ERROR_NONE) {
      kmos_molecfit_model_clean(conf);
      cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                            "Unexpected value in .kernmode it'll be boolean, 0 or 1");
      return NULL;
  }

  p = cpl_parameterlist_find_const(list, "kernfac");
  conf->mf.kernfac = cpl_parameter_get_double(p);
  if (conf->mf.kernfac < 3. || conf->mf.kernfac > 300.) {
      kmos_molecfit_model_clean(conf);
      cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                            ".kernfac out of the valid range");
      return NULL;
  }

  p = cpl_parameterlist_find_const(list, "varkern");
  conf->mf.varkern = cpl_parameter_get_bool(p);
  if (cpl_error_get_code() != CPL_ERROR_NONE) {
      kmos_molecfit_model_clean(conf);
      cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                            "Unexpected value in .varkern it'll be boolean, 0 or 1");
      return NULL;
  }


  /* Save parameter in the output propertylist */

  cpl_propertylist_update_string(conf->mf.parms, KMOS_MF_PARAM_RECIPE"PROCESS_IFUS",     conf->process_ifus);

  cpl_propertylist_update_string(conf->mf.parms, KMOS_MF_PARAM_RECIPE"WAVE_RANGE",       conf->mf.grating.wave_range);
  cpl_propertylist_update_string(conf->mf.parms, KMOS_MF_PARAM_RECIPE"LIST_MOLEC",       conf->mf.grating.list_molec);
  cpl_propertylist_update_string(conf->mf.parms, KMOS_MF_PARAM_RECIPE"FIT_MOLEC",        conf->mf.grating.fit_molec);
  cpl_propertylist_update_string(conf->mf.parms, KMOS_MF_PARAM_RECIPE"RECOL",           conf->mf.grating.relcol);

  cpl_propertylist_update_double(conf->mf.parms, KMOS_MF_PARAM_RECIPE"FTOL",             conf->ftol);
  cpl_propertylist_update_double(conf->mf.parms, KMOS_MF_PARAM_RECIPE"XTOL",             conf->xtol);

  cpl_propertylist_update_bool(  conf->mf.parms, KMOS_MF_PARAM_RECIPE"FIT_CONT",         conf->mf.fit_continuum.fit);
  cpl_propertylist_update_int(   conf->mf.parms, KMOS_MF_PARAM_RECIPE"CONT_N",           conf->mf.fit_continuum.n);

  cpl_propertylist_update_bool(  conf->mf.parms, KMOS_MF_PARAM_RECIPE"FIT_WLC",          conf->mf.fit_wavelenght.fit);
  cpl_propertylist_update_int(   conf->mf.parms, KMOS_MF_PARAM_RECIPE"WLC_N",            conf->mf.fit_wavelenght.n);
  cpl_propertylist_update_double(conf->mf.parms, KMOS_MF_PARAM_RECIPE"WLC_CONST",        conf->mf.fit_wavelenght.const_val);

  cpl_propertylist_update_bool(  conf->mf.parms, KMOS_MF_PARAM_RECIPE"USE_INPUT_KERNEL", conf->mf.use_input_kernel);
  cpl_propertylist_update_bool(  conf->mf.parms, KMOS_MF_PARAM_RECIPE"KERNMODE",         conf->mf.kernmode);
  cpl_propertylist_update_double(conf->mf.parms, KMOS_MF_PARAM_RECIPE"KERNFAC",          conf->mf.kernfac);
  cpl_propertylist_update_bool(  conf->mf.parms, KMOS_MF_PARAM_RECIPE"VARKERN",          conf->mf.varkern);

  cpl_propertylist_update_bool(  conf->mf.parms, KMOS_MF_PARAM_RECIPE"FIT_RES_BOX",      conf->boxcar.fit);
  cpl_propertylist_update_double(conf->mf.parms, KMOS_MF_PARAM_RECIPE"RELRES_BOX",       conf->boxcar.res);

  cpl_propertylist_update_bool(  conf->mf.parms, KMOS_MF_PARAM_RECIPE"FIT_RES_GAUSS",    conf->gauss.fit);
  cpl_propertylist_update_double(conf->mf.parms, KMOS_MF_PARAM_RECIPE"RES_GAUSS",        conf->gauss.res);

  cpl_propertylist_update_bool(  conf->mf.parms, KMOS_MF_PARAM_RECIPE"FIT_RES_LORENTZ",  conf->lorentz.fit);
  cpl_propertylist_update_double(conf->mf.parms, KMOS_MF_PARAM_RECIPE"RES_LORENTZ",      conf->lorentz.res);


  /* Check status */
  if (!cpl_errorstate_is_equal(preState)) {
      /* Configuration failed */
      kmos_molecfit_model_clean(conf);
      return NULL;
  } else {
      /* Configuration successfully */
      return conf;
  }
}

/*----------------------------------------------------------------------------*/
/**
 * @brief Function needed to fill the molecfit configuration file
 *
 * @param  conf   Recipe configuration.
 * @param  ifu    Concrete IFU for whitch generate the molecfit configuration
 * @param  type   Concrete grating
 *
 * @return parameterlist with contain the config to molecfit or NULL if error
 *
 */
/*----------------------------------------------------------------------------*/
static cpl_parameterlist * kmos_molecfit_model_mf_conf(
    kmos_molecfit_model_parameter *conf, kmos_spectrum *ifu, kmos_grating_type type)
{
  /* Check inputs */
  cpl_error_ensure(conf && ifu, CPL_ERROR_NULL_INPUT,
                   return NULL, "Any input is NULL!");

  /* Add the config values necessaries to execute molecfit */
  cpl_errorstate prestate = cpl_errorstate_get();


  /*** Building generic configuration molecfic file ***/
  cpl_parameterlist *mf_config = kmos_molecfit_conf_generic(RECIPE_NAME, &(conf->mf));
  if (!mf_config) {
      return NULL;
  }


  /*** Set molecfit configuration with recipe parameters ***/
  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;

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

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

  /* --fit_cont */
  int cont_fit = conf->mf.fit_continuum.fit;
  if(!e) e = kmos_molecfit_fill_parameter(RECIPE_NAME, mf_config, "fit_cont",
                                          range, &boolMin, &boolMax, CPL_TYPE_INT, &cont_fit,
                                          "molecfit", CPL_TRUE);

  /* --cont_n */
  if(!e) e = kmos_molecfit_fill_parameter(RECIPE_NAME, mf_config, "cont_n",
                                          !range, dummyMin, dummyMax, CPL_TYPE_INT, &(conf->mf.fit_continuum.n),
                                          "molecfit", CPL_TRUE);

  /* --cont_const -> Spectrum data dependency, Calculate median (ifu->median) of the input spectrum (ifu->data) */
  double cont_const = ifu->median;
  cpl_msg_info(cpl_func,"--.cont_const = %g", cont_const);
  if(!e) e = kmos_molecfit_fill_parameter(RECIPE_NAME, mf_config, "cont_const",
                                          !range, dummyMin, dummyMax, CPL_TYPE_DOUBLE, &cont_const,
                                          "molecfit", CPL_TRUE);

  /* --fit_wlc */
  int wlc_fit = conf->mf.fit_wavelenght.fit;
  if(!e) e = kmos_molecfit_fill_parameter(RECIPE_NAME, mf_config, "fit_wlc",
                                          range, &boolMin, &boolMax, CPL_TYPE_INT, &wlc_fit,
                                          "molecfit", CPL_TRUE);

  /* --wlc_n */
  if(!e) e = kmos_molecfit_fill_parameter(RECIPE_NAME, mf_config, "wlc_n",
                                          !range, dummyMin, dummyMax, CPL_TYPE_INT, &(conf->mf.fit_wavelenght.n),
                                          "molecfit", CPL_TRUE);

  /* --wlc_const */
  if(!e) e = kmos_molecfit_fill_parameter(RECIPE_NAME, mf_config, "wlc_const",
                                          !range, dummyMin, dummyMax, CPL_TYPE_DOUBLE, &(conf->mf.fit_wavelenght.const_val),
                                          "molecfit", CPL_TRUE);

  /* --fit_res_box */
  int boxcar_fit = conf->boxcar.fit;
  if(!e) e = kmos_molecfit_fill_parameter(RECIPE_NAME, mf_config, "fit_res_box",
                                          range, &boolMin, &boolMax, CPL_TYPE_INT, &boxcar_fit,
                                          "molecfit", CPL_TRUE);

  /* --relres_box */
  if(!e) e = kmos_molecfit_fill_parameter(RECIPE_NAME, mf_config, "relres_box",
                                          !range, dummyMin, dummyMax, CPL_TYPE_DOUBLE, &(conf->boxcar.res),
                                          "molecfit", CPL_TRUE);

  /* --fit_res_gauss */
  int gauss_fit = conf->gauss.fit;
  if(!e) e = kmos_molecfit_fill_parameter(RECIPE_NAME, mf_config, "fit_res_gauss",
                                          range, &boolMin, &boolMax, CPL_TYPE_INT, &gauss_fit,
                                          "molecfit", CPL_TRUE);

  /* --res_gauss -> Grating dependency, set with the define constant values */
  double res_gauss = conf->gauss.res;
  if (res_gauss == -1.) {
      switch(type) {
        case GRATING_IZ: res_gauss = RES_GAUSS_IZ; break;
        case GRATING_YJ: res_gauss = RES_GAUSS_YJ; break;
        case GRATING_H:  res_gauss = RES_GAUSS_H;  break;
        case GRATING_K:  res_gauss = RES_GAUSS_K;  break;
        case GRATING_HK: res_gauss = RES_GAUSS_HK; break;
      }
      cpl_msg_info(cpl_func,"--.res_gauss by default = %g", res_gauss);
  } else {
      cpl_msg_info(cpl_func,"--.res_gauss by the user = %g", res_gauss);
  }
  if(!e) e = kmos_molecfit_fill_parameter(RECIPE_NAME, mf_config, "res_gauss",
                                          !range, dummyMin, dummyMax, CPL_TYPE_DOUBLE, &res_gauss,
                                          "molecfit", CPL_TRUE);

  /* --fit_res_lorentz */
  int lorentz_fit = conf->lorentz.fit;
  if(!e) e = kmos_molecfit_fill_parameter(RECIPE_NAME, mf_config, "fit_res_lorentz",
                                          range, &boolMin, &boolMax, CPL_TYPE_INT, &lorentz_fit,
                                          "molecfit", CPL_TRUE);

  /* --res_lorentz */
  if(!e) e = kmos_molecfit_fill_parameter(RECIPE_NAME, mf_config, "res_lorentz",
                                          !range, dummyMin, dummyMax, CPL_TYPE_DOUBLE, &(conf->lorentz.res),
                                          "molecfit", CPL_TRUE);



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


  /* --trans: spectrum_type (The range is between 0 to 2, KMOS need 1 - TRANSMISSION) */
  int trans     = 1; /* Molecfit default: 1 */
  int trans_min = 0;
  int trans_max = 2;
  if(!e) e = kmos_molecfit_fill_parameter(RECIPE_NAME, mf_config, "trans",
                                          range, &trans_min, &trans_max, CPL_TYPE_INT, &trans,
                                          "molecfit", CPL_TRUE);

  /* --fit_back */
  int fit_back = 0; /* Molecfit default: 1 */
  if(!e) e = kmos_molecfit_fill_parameter(RECIPE_NAME, mf_config, "fit_back",
                                          range, &boolMin, &boolMax, CPL_TYPE_INT, &fit_back,
                                          "molecfit", CPL_TRUE);

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

  /* --flux_unit */
  int flux_unit = 0; /* Molecfit default: 0 */
  if(!e) e = kmos_molecfit_fill_parameter(RECIPE_NAME, mf_config, "flux_unit",
                                          range, &boolMin, &boolMax, CPL_TYPE_INT, &flux_unit,
                                          "molecfit", CPL_TRUE);

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

  /* --ref_atm */
  const char *ref_atm = "equ.atm"; /* Molecfit default: "equ.atm" */
  if(!e) e = kmos_molecfit_fill_parameter(RECIPE_NAME, mf_config, "ref_atm",
                                          !range, dummyMin, dummyMax, CPL_TYPE_STRING, (const void *)ref_atm,
                                          "molecfit", CPL_TRUE);

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

  /* --layers */
  int layers = 1; /* Molecfit default: 1 */
  if(!e) e = kmos_molecfit_fill_parameter(RECIPE_NAME, mf_config, "layers",
                                          range, &boolMin, &boolMax, CPL_TYPE_INT, &layers,
                                          "molecfit", CPL_TRUE);

  /* --emix */
  double emix = 5.; /* Molecfit default: 5.*/
  if(!e) e = kmos_molecfit_fill_parameter(RECIPE_NAME, mf_config, "emix",
                                          !range, dummyMin, dummyMax, CPL_TYPE_DOUBLE, &emix,
                                          "molecfit", CPL_TRUE);

  /* --pwv */
  double pwv = -1.; /* Molecfit default: -1.*/
  if(!e) e = kmos_molecfit_fill_parameter(RECIPE_NAME, mf_config, "pwv",
                                          !range, dummyMin, dummyMax, CPL_TYPE_DOUBLE, &pwv,
                                          "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 configuration variable failed!");
      return NULL;
  }

  return mf_config;
}

/*----------------------------------------------------------------------------*/
/**
 * @brief    Deallocate the given parameter configuration object and its contents
 *
 * @param    conf       The parameter configuration variable in the recipe.
 */
/*----------------------------------------------------------------------------*/
static void kmos_molecfit_model_clean(
    kmos_molecfit_model_parameter *conf)
{
  if (conf) {

      kmos_molecfit_clean(&(conf->mf));

      cpl_free(conf);
  }
}
