/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set sw=2 sts=2 et cin: */
/*
 * This file is part of the MUSE Instrument Pipeline
 * Copyright (C) 2005-2014 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., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

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

#define STORE_SLIT_WIDTH
#define STORE_BIN_WIDTH

/*----------------------------------------------------------------------------*
 *                             Includes                                       *
 *----------------------------------------------------------------------------*/
#include <math.h>
#include <string.h>
#include <fenv.h>

#include <cpl.h>

#include "muse_lsf_params.h"
#include "muse_resampling.h"
#include "muse_pfits.h"
#include "muse_quality.h"
#include "muse_instrument.h"
#include "muse_optimize.h"
#include "muse_tracing.h"
#include "muse_utils.h"

/** @addtogroup muse_lsf */
/**@{*/

/*----------------------------------------------------------------------------*/
/**
   @brief Create a new lsf_params structure.
   @param n_sensit Order of polynomial sensitivity parametrization.
   @param n_lsf_width Order of polynomial lsf width parametrization.
   @param n_hermit Order of polynomial parametrization of hermitean coefficients.
   @return Structure to hold the data.

   All polynomial parametrizations are meant as wavelength dependent.
 */
/*----------------------------------------------------------------------------*/
muse_lsf_params *
muse_lsf_params_new(cpl_size n_sensit, cpl_size n_lsf_width, cpl_size n_hermit)
{
  muse_lsf_params *res = cpl_calloc(1, sizeof(muse_lsf_params));
  res->refraction = 1.0;
  res->offset = 0.0;
  res->slit_width = kMuseSliceSlitWidthA;
  res->bin_width = kMuseSpectralSamplingA;
  res->lambda_ref = 7000;
  int i;
  if (n_hermit > 0) {
    for (i = 0; i < MAX_HERMIT_ORDER; i++) {
      res->hermit[i] = cpl_array_new(n_hermit, CPL_TYPE_DOUBLE);
      cpl_array_fill_window_double(res->hermit[i], 0, n_hermit, 0.0);
    }
  }
  res->lsf_width = cpl_array_new(n_lsf_width, CPL_TYPE_DOUBLE);
  if (n_lsf_width > 0) {
    cpl_array_fill_window_double(res->lsf_width, 0, n_lsf_width, 0.0);
    cpl_array_set_double(res->lsf_width, 0, 1.0);
  }
  res->sensitivity = cpl_array_new(n_sensit, CPL_TYPE_DOUBLE);
  if (n_sensit > 0) {
    cpl_array_fill_window_double(res->sensitivity, 0, n_sensit, 0.0);
    cpl_array_set_double(res->sensitivity, 0, 1.0);
  }
  return res;
}

/*----------------------------------------------------------------------------*/
/**
   @brief Count the number of entries in the array.
   @param aParams   LSF parameter array
   @return Number of entries in the array.
 */
/*----------------------------------------------------------------------------*/
cpl_size
muse_lsf_params_get_size(muse_lsf_params **aParams) {
  if (aParams == NULL) {
    return 0;
  }
  cpl_size i;
  for  (i = 0; *aParams != NULL; i++) {
    aParams++;
  }
  return i;
}

/*----------------------------------------------------------------------------*/
/**
   @brief Delete an allocated muse_lsf_params structure.
   @param aParams Structure to delete.
 */
/*----------------------------------------------------------------------------*/
void
muse_lsf_params_delete(muse_lsf_params *aParams) {
  if (aParams != NULL) {
    cpl_array_delete(aParams->sensitivity);
    int i;
    for (i = 0; i < MAX_HERMIT_ORDER; i++) {
      cpl_array_delete(aParams->hermit[i]);
    }
    cpl_array_delete(aParams->lsf_width);
    cpl_free(aParams);
  }
}

/*----------------------------------------------------------------------------*/
/**
   @brief Delete an allocated array of muse_lsf_params structure.
   @param aParams Structure to delete.
 */
/*----------------------------------------------------------------------------*/
void
muse_lsf_params_delete_all(muse_lsf_params **aParams) {
  if (aParams != NULL) {
    muse_lsf_params **det;
    for  (det = aParams; *det != NULL; det++) {
      muse_lsf_params_delete(*det);
    }
    cpl_free(aParams);
  }
}

/*----------------------------------------------------------------------------*/
/**
    @brief Definition of a lsf parameters table

    - <tt>ifu</tt>: IFU number
    - <tt>slice</tt>: slice number within the IFU
    - <tt>sensitivity</tt>sensitivity, relative to the reference
    - <tt>lsf_width</tt>: LSF gauss-hermitean width [Angstrom]
    - <tt>hermit3</tt>: 3rd order hermitean coefficient
    - <tt>hermit4</tt>: 4th order hermitean coefficient
    - <tt>hermit5</tt>: 5th order hermitean coefficient
    - <tt>hermit6</tt>: 6th order hermitean coefficient

*/
/*----------------------------------------------------------------------------*/
const muse_cpltable_def muse_lsfparams_def[] = {
  {"ifu", CPL_TYPE_INT, NULL, "%i", "IFU number", CPL_TRUE},
  {"slice", CPL_TYPE_INT, NULL, "%i", "slice number within the IFU", CPL_TRUE},
  {"sensitivity", CPL_TYPE_DOUBLE | CPL_TYPE_POINTER, NULL, "%e",
   "detector sensitivity, relative to the reference", CPL_TRUE},
  {"offset", CPL_TYPE_DOUBLE, NULL, "%e", "wavelength calibration offset", CPL_TRUE},
  {"refraction", CPL_TYPE_DOUBLE, NULL, "%e", "relative refraction index", CPL_TRUE},
#ifdef STORE_SLIT_WIDTH
  {"slit_width", CPL_TYPE_DOUBLE, "Angstrom", "%e", "slit width", CPL_TRUE},
#endif
#ifdef STORE_BIN_WIDTH
  {"bin_width", CPL_TYPE_DOUBLE, "Angstrom", "%e", "bin width", CPL_TRUE},
#endif
  {"lsf_width", CPL_TYPE_DOUBLE | CPL_TYPE_POINTER, "Angstrom", "%e",
   " LSF gauss-hermitean width", CPL_TRUE},
  {"hermit3", CPL_TYPE_DOUBLE | CPL_TYPE_POINTER, NULL, "%e",
   "3rd order hermitean coefficient", CPL_TRUE},
  {"hermit4", CPL_TYPE_DOUBLE | CPL_TYPE_POINTER, NULL, "%e",
   "4th order hermitean coefficient", CPL_TRUE},
  {"hermit5", CPL_TYPE_DOUBLE | CPL_TYPE_POINTER, NULL, "%e",
   "5th order hermitean coefficient", CPL_TRUE},
  {"hermit6", CPL_TYPE_DOUBLE | CPL_TYPE_POINTER, NULL, "%e",
   "6th order hermitean coefficient", CPL_TRUE},
  { NULL, 0, NULL, NULL, NULL, CPL_FALSE }
};

/*----------------------------------------------------------------------------*/
/**
   @brief Save slice LSF parameters to the extension "slice" on disk.
   @param aLsfParams NULL-terminated array of LSF parameters.
   @param aFile File name (file must exist, is written as extension)
   @retval CPL_ERROR_NONE if everything went OK
   @cpl_ensure_code{aLsfParams != NULL, CPL_ERROR_NULL_INPUT}
   @cpl_ensure_code{*aLsfParams != NULL, CPL_ERROR_DATA_NOT_FOUND}
   @cpl_ensure_code{aFile != NULL, CPL_ERROR_NULL_INPUT}

   The slice parameters are converted to a table structure:

   @copydetails muse_lsfparams_def
 */
/*----------------------------------------------------------------------------*/
cpl_error_code
muse_lsf_params_save(const muse_lsf_params **aLsfParams, const char *aFile) {
  cpl_ensure_code(aLsfParams != NULL, CPL_ERROR_NULL_INPUT);
  cpl_ensure_code(*aLsfParams != NULL, CPL_ERROR_DATA_NOT_FOUND);
  cpl_ensure_code(aFile != NULL, CPL_ERROR_NULL_INPUT);

  cpl_size nrows = 0;
  const muse_lsf_params **det;
  cpl_size sensitivity_order = 1;
  cpl_size lsf_order = 1;
  cpl_size hermit_order[MAX_HERMIT_ORDER];
  int i;
  for (i = 0; i < MAX_HERMIT_ORDER; i++) {
    hermit_order[i] = 1;
  }
  for (det = aLsfParams; *det != NULL; det++, nrows++) {
    sensitivity_order = fmax(sensitivity_order,
                             cpl_array_get_size((*det)->sensitivity));
    lsf_order = fmax(lsf_order, cpl_array_get_size((*det)->lsf_width));
    for (i = 0; i < MAX_HERMIT_ORDER; i++) {
      hermit_order[i] = fmax(hermit_order[i],
                             cpl_array_get_size((*det)->hermit[i]));
    }
  }

  cpl_table *slice_param = cpl_table_new(nrows);
  cpl_table_new_column(slice_param, "ifu", CPL_TYPE_INT);
  cpl_table_new_column(slice_param, "slice", CPL_TYPE_INT);
  cpl_table_new_column_array(slice_param, "sensitivity",
                             cpl_array_get_type(aLsfParams[0]->sensitivity),
                             sensitivity_order);
  cpl_table_new_column(slice_param, "offset", CPL_TYPE_DOUBLE);
  cpl_table_new_column(slice_param, "refraction", CPL_TYPE_DOUBLE);
#ifdef STORE_SLIT_WIDTH
  cpl_table_new_column(slice_param, "slit_width", CPL_TYPE_DOUBLE);
#endif
#ifdef STORE_BIN_WIDTH
  cpl_table_new_column(slice_param, "bin_width", CPL_TYPE_DOUBLE);
#endif
  cpl_table_new_column_array(slice_param, "lsf_width",
                             cpl_array_get_type(aLsfParams[0]->lsf_width),
                             lsf_order);
  cpl_table_new_column_array(slice_param, "hermit3",
                             cpl_array_get_type(aLsfParams[0]->hermit[0]),
                             hermit_order[0]);
  cpl_table_new_column_array(slice_param, "hermit4",
                             cpl_array_get_type(aLsfParams[0]->hermit[1]),
                             hermit_order[1]);
  cpl_table_new_column_array(slice_param, "hermit5",
                             cpl_array_get_type(aLsfParams[0]->hermit[2]),
                             hermit_order[2]);
  cpl_table_new_column_array(slice_param, "hermit6",
                             cpl_array_get_type(aLsfParams[0]->hermit[3]),
                             hermit_order[3]);

  cpl_size iRow = 0;
  for (det = aLsfParams; *det != NULL; det++, iRow++) {
    cpl_table_set(slice_param, "ifu", iRow, (*det)->ifu);
    cpl_table_set(slice_param, "slice", iRow, (*det)->slice);
    cpl_table_set_array(slice_param, "sensitivity", iRow, (*det)->sensitivity);
    cpl_table_set(slice_param, "offset", iRow, (*det)->offset);
    cpl_table_set(slice_param, "refraction", iRow, (*det)->refraction);
#ifdef STORE_SLIT_WIDTH
    cpl_table_set(slice_param, "slit_width", iRow, (*det)->slit_width);
#endif
#ifdef STORE_BIN_WIDTH
    cpl_table_set(slice_param, "bin_width", iRow, (*det)->bin_width);
#endif
    cpl_table_set_array(slice_param, "lsf_width", iRow, (*det)->lsf_width);
    cpl_table_set_array(slice_param, "hermit3", iRow, (*det)->hermit[0]);
    cpl_table_set_array(slice_param, "hermit4", iRow, (*det)->hermit[1]);
    cpl_table_set_array(slice_param, "hermit5", iRow, (*det)->hermit[2]);
    cpl_table_set_array(slice_param, "hermit6", iRow, (*det)->hermit[3]);
  }

  int r = muse_cpltable_append_file(slice_param, aFile, "SLICE_PARAM",
                                    muse_lsfparams_def);
  cpl_table_delete(slice_param);
  return r;
}

/*----------------------------------------------------------------------------*/
/**
   @brief Load slice LSF parameters from the extension "SLICE_PARAM".
   @param aFile File name (file must exist, is written as extension)
   @param aParams NULL-terminated array of LSF parameters to append to,
          or NULL.
   @param aIFU the IFU/channel number, or 0 for all IFUs/channels
   @return NULL-terminated array of LSF parameters.

   The extension "SLICE_PARAM" (or "CHANnn.SLICE_PARAM") is expected to be a
   table with the @ref muse_lsfparams_def "slice dependent LSF parameters".
 */
/*----------------------------------------------------------------------------*/
muse_lsf_params **
muse_lsf_params_load(const char *aFile, muse_lsf_params **aParams, int aIFU)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  cpl_table *lsfTable = muse_cpltable_load(aFile, "SLICE_PARAM",
                                           muse_lsfparams_def);
  if (!lsfTable) {
    /* try to load from channel extensions */
    char *extname = cpl_sprintf("CHAN%02d.SLICE_PARAM", aIFU);
    lsfTable = muse_cpltable_load(aFile, extname, muse_lsfparams_def);
    cpl_free(extname);
    if (!lsfTable) {
      if (aParams == NULL) {
        /* this may be not a LSF table but a LSF cube? reset the error.. */
        cpl_errorstate_set(prestate);
        return NULL;
      }
      /* now display both the older and the new error messages */
      cpl_error_set_message(__func__, cpl_error_get_code(), "Loading LSF data from "
                            "\"%s[SLICE_PARAMS]\" and \"%s[CHAH%02d.SLICE_PARAMS]\" "
                            "failed", aFile, aFile, aIFU);
      return aParams;
    } /* if 2nd load failure */
  } /* if 1st load failure */

  cpl_size n_rows = cpl_table_get_nrow(lsfTable);
  cpl_size n_rows_old = muse_lsf_params_get_size(aParams);
  muse_lsf_params **lsfParams
    = cpl_realloc(aParams, (n_rows + n_rows_old + 1) * sizeof(muse_lsf_params *));
  lsfParams[n_rows + n_rows_old] = NULL;
  cpl_size i_row_new = n_rows_old;
  cpl_size i_row;
  for (i_row = 0; i_row < n_rows; i_row++) {
    int ifu = cpl_table_get(lsfTable, "ifu", i_row, NULL);
    lsfParams[i_row + n_rows_old] = NULL;
    if ((aIFU <= 0) || (ifu == aIFU)) {
      muse_lsf_params *det = muse_lsf_params_new(0,0,0);
      lsfParams[i_row_new] = det;
      i_row_new++;
      det->ifu = ifu;
      det->slice = cpl_table_get(lsfTable, "slice", i_row, NULL);
      cpl_array_delete(det->sensitivity);
      det->sensitivity
        = muse_cpltable_get_array_copy(lsfTable, "sensitivity",i_row);
      det->offset = cpl_table_get(lsfTable, "offset", i_row, NULL);
      det->refraction = cpl_table_get(lsfTable, "refraction", i_row, NULL);
#ifdef STORE_SLIT_WIDTH
      det->slit_width = cpl_table_get(lsfTable, "slit_width", i_row, NULL);
#endif
#ifdef STORE_BIN_WIDTH
      det->bin_width = cpl_table_get(lsfTable, "bin_width", i_row, NULL);
#endif
      cpl_array_delete(det->lsf_width);
      det->lsf_width
        = muse_cpltable_get_array_copy(lsfTable, "lsf_width", i_row);
      cpl_array_delete(det->hermit[0]);
      det->hermit[0]
        = muse_cpltable_get_array_copy(lsfTable, "hermit3", i_row);
      cpl_array_delete(det->hermit[1]);
      det->hermit[1]
        = muse_cpltable_get_array_copy(lsfTable, "hermit4", i_row);
      cpl_array_delete(det->hermit[2]);
      det->hermit[2]
        = muse_cpltable_get_array_copy(lsfTable, "hermit5", i_row);
      cpl_array_delete(det->hermit[3]);
      det->hermit[3]
        = muse_cpltable_get_array_copy(lsfTable, "hermit6", i_row);
    }
  }
  cpl_table_delete(lsfTable);

  return lsfParams;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Load slice LSF parameters
  @param    aProcessing   the processing structure
  @param    aIFU          the IFU/channel number, or 0 for all IFUs/channels
  @return   a pointer to the LSF params or NULL on error

  @error{set CPL_ERROR_NULL_INPUT\, return NULL,
         invalid processing pointer}
  @error{propagate CPL error code\, return NULL,
         LST_TABLE frame not found in input frameset of the processing structure}

   The input files must be tables with the
   @ref muse_lsfparams_def "slice dependent LSF parameters".
 */
/*----------------------------------------------------------------------------*/
muse_lsf_params **
muse_processing_lsf_params_load(muse_processing *aProcessing, int aIFU)
{
  cpl_ensure(aProcessing, CPL_ERROR_NULL_INPUT, NULL);
  cpl_frameset *frames = muse_frameset_find(aProcessing->inframes,
                                            MUSE_TAG_LSF_PROFILE, aIFU,
                                            CPL_FALSE);
  if (frames == NULL) {
    return NULL;
  }
  /* try standard format first: one file per IFU */
  cpl_errorstate state = cpl_errorstate_get();
  cpl_size iframe, nframes = cpl_frameset_get_size(frames);
  muse_lsf_params **lsfParams = NULL;
  for (iframe = 0; iframe < nframes; iframe++) {
    cpl_frame *frame = cpl_frameset_get_position(frames, iframe);
    lsfParams = muse_lsf_params_load(cpl_frame_get_filename(frame),
                                     lsfParams, aIFU);
    if (lsfParams) {
      cpl_msg_info(__func__, "Loaded slice LSF params from \"%s\"",
                   cpl_frame_get_filename(frame));
      muse_processing_append_used(aProcessing, frame, CPL_FRAME_GROUP_CALIB, 1);
    }
  } /* for iframe (all frames) */
  char *errmsg = NULL;
  if (!cpl_errorstate_is_equal(state)) {
    errmsg = cpl_strdup(cpl_error_get_message());
  }
  cpl_errorstate_set(state); /* hide error(s) */

  /* extra loop to support the merged format */
  if (!lsfParams && aIFU == 0 && nframes == 1) {
    cpl_msg_debug(__func__, "No LSF parameters loaded yet, trying merged table "
                  "format.");

    cpl_frame *frame = cpl_frameset_get_position(frames, 0);
    const char *fname = cpl_frame_get_filename(frame);
    state = cpl_errorstate_get();
    unsigned char ifu;
    for (ifu = 1; ifu <= kMuseNumIFUs; ifu++) {
      lsfParams = muse_lsf_params_load(fname, lsfParams, ifu);
    } /* for ifu */
    cpl_errorstate_set(state); /* ignore errors */
    if (lsfParams) {
      cpl_msg_info(__func__, "Loaded (merged) slice LSF params from \"%s\"", fname);
      muse_processing_append_used(aProcessing, frame, CPL_FRAME_GROUP_CALIB, 1);
    }
  } /* if */
  cpl_frameset_delete(frames);

  /* now possible output error message */
  if (errmsg) {
    cpl_msg_debug(__func__, "Loading %ss from input frameset did not succeed: "
                  "%s", MUSE_TAG_LSF_PROFILE, errmsg);
  } /* if still no lsfParams */
  cpl_free(errmsg);
  return lsfParams;
} /* muse_processing_lsf_params_load() */

/*----------------------------------------------------------------------------*/
/**
   @brief Get the slice LSF parameters for one slice.
   @return Pointer to parameter structure.
 */
/*----------------------------------------------------------------------------*/
muse_lsf_params *
muse_lsf_params_get(muse_lsf_params **aParams, int aIFU, int aSlice) {
  if (aParams == NULL) {
    return NULL;
  }
  int i_det;
  for (i_det = 0; aParams[i_det] != NULL; i_det++) {
    if (aParams[i_det]->ifu == aIFU && aParams[i_det]->slice == aSlice) {
      return aParams[i_det];
    }
  }
  return NULL;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Helper function "G" for integrated damped gauss-hermitean function
  @param aX X values array to apply
  @param aCoeffs pointer to five values for the damping polynomial
  @return The return value array.
  @cpl_ensure{aX != NULL, CPL_ERROR_NULL_INPUT, 0.0}
  @cpl_ensure{aCoeffs != NULL, CPL_ERROR_NULL_INPUT, 0.0}

  @pseudocode
R(x) = aCoeffs[0] * x**4 + aCoeffs[1] * x**3
     + aCoeffs[2] * x**2 + aCoeffs[3] * x + aCoeffs[4]

G(x) =  exp(-0.5 * x**2)
      + R(x) * exp(-x**2) / 60
      + x * sqrt(Pi/2) * erf(x * sqrt(0.5))@endpseudocode
*/
/*----------------------------------------------------------------------------*/
static cpl_array *
muse_lsf_G(cpl_array *aX, cpl_array *aCoeffs) {
  cpl_ensure(aX != NULL, CPL_ERROR_NULL_INPUT, NULL);
  cpl_ensure(aCoeffs != NULL, CPL_ERROR_NULL_INPUT, NULL);

  cpl_array *y = cpl_array_duplicate(aX);
  cpl_array_multiply(y, y);
  cpl_array_multiply_scalar(y, -1);  // y = - x**2

  cpl_array *y2 = cpl_array_duplicate(y);
  muse_cplarray_exp(y2);
  cpl_array_multiply_scalar(y2, 1.0/60); // y2 = exp(-x**2)/60

  cpl_array_multiply_scalar(y, 0.5);
  muse_cplarray_exp(y); // y = exp(-0.5 * x**2)

  cpl_array *R = cpl_array_duplicate(aX);
  muse_cplarray_poly1d(R, aCoeffs);
  cpl_array_multiply(y2, R); // y2 = R * exp(-x**2)/60
  cpl_array_delete(R);
  cpl_array_add(y, y2);

  cpl_array_copy_data_double(y2, cpl_array_get_data_double(aX));
  cpl_array_multiply_scalar(y2, sqrt(0.5));
  muse_cplarray_erf(y2);
  cpl_array_multiply_scalar(y2, sqrt(CPL_MATH_PI / 2));
  cpl_array_multiply(y2, aX); // y3 = x * sqrt(Pi/2) * erf(x/sqrt(2))
  cpl_array_add(y, y2);
  cpl_array_delete(y2);

  return y;
}

/*----------------------------------------------------------------------------*/
/**
   @brief Apply the MUSE LSF function to a single line.
   @param aLsfParams LSF parameters
   @param aVal     Array with wavelengths [A]. Will be replaced by the
                   normalized LSF data points
   @param aLambda  Line wavelength reference [A].
   @retval CPL_ERROR_NONE Everything went OK
   @cpl_ensure_code{aLsfParamsImage, CPL_ERROR_NULL_INPUT}
   @cpl_ensure_code{aWCS, CPL_ERROR_NULL_INPUT}
   @cpl_ensure_code{aVal, CPL_ERROR_NULL_INPUT}

  @pseudocode
x = (aVal - aLambda) / aLsfParams.width
slit_width = aLsfParams.slit_width / aLsfParams.width
bin_width  = aLsfParams.bin_width  / aLsfParams.width

aVal = G(x + slit_width/2 + bin_width/2) - G(x - slit_width/2 + bin_width/2)
  -    G(x + slit_width/2 - bin_width/2) + G(x - slit_width/2 - bin_width/2)@endpseudocode

 */
/*----------------------------------------------------------------------------*/
cpl_error_code
muse_lsf_params_apply(const muse_lsf_params *aLsfParams,
                      cpl_array *aVal, double aLambda)
{

  cpl_ensure_code(aVal != NULL, CPL_ERROR_NULL_INPUT);
  cpl_ensure_code(aLsfParams != NULL, CPL_ERROR_NULL_INPUT);

  double slit_width = aLsfParams->slit_width;
  double bin_width = aLsfParams->bin_width;
  double width = muse_cplarray_poly1d_double(aLambda - aLsfParams->lambda_ref,
                                             aLsfParams->lsf_width);
  double h3 = muse_cplarray_poly1d_double(aLambda - aLsfParams->lambda_ref,
                                          aLsfParams->hermit[0]);
  double h4 = muse_cplarray_poly1d_double(aLambda - aLsfParams->lambda_ref,
                                          aLsfParams->hermit[1]);
  double h5 = muse_cplarray_poly1d_double(aLambda - aLsfParams->lambda_ref,
                                          aLsfParams->hermit[2]);
  double h6 = muse_cplarray_poly1d_double(aLambda - aLsfParams->lambda_ref,
                                          aLsfParams->hermit[3]);

  cpl_array *coeff = cpl_array_new(5, CPL_TYPE_DOUBLE);
  cpl_array_set(coeff, 4, 2 * sqrt(5) * h6);
  cpl_array_set(coeff, 3, 2 * sqrt(15) * h5);
  cpl_array_set(coeff, 2, 5 * sqrt(6) * h4 - 6 * sqrt(5) * h6);
  cpl_array_set(coeff, 1, 10 * sqrt(3) * h3 - 3 * sqrt(15) * h5);
  cpl_array_set(coeff, 0, -2.5 * sqrt(6) * h4 + 1.5 * sqrt(5) * h6);

  cpl_array_divide_scalar(aVal, width);
  bin_width /= 2 * width;
  slit_width /= 2 * width;

  cpl_array *x = cpl_array_duplicate(aVal);
  cpl_array *y;
  cpl_array *y1;
  cpl_array_add_scalar(x, slit_width + bin_width);
  y = muse_lsf_G(x, coeff);

  cpl_array_copy_data_double(x, cpl_array_get_data_double(aVal));
  cpl_array_add_scalar(x, slit_width - bin_width);
  y1 = muse_lsf_G(x, coeff);
  cpl_array_subtract(y, y1);
  cpl_array_delete(y1);

  cpl_array_copy_data_double(x, cpl_array_get_data_double(aVal));
  cpl_array_add_scalar(x, -slit_width + bin_width);
  y1 = muse_lsf_G(x, coeff);
  cpl_array_subtract(y, y1);
  cpl_array_delete(y1);

  cpl_array_copy_data_double(x, cpl_array_get_data_double(aVal));
  cpl_array_add_scalar(x, -slit_width - bin_width);
  y1 = muse_lsf_G(x, coeff);
  cpl_array_delete(x);
  cpl_array_add(y, y1);
  cpl_array_delete(y1);

  cpl_array_divide_scalar(y, sqrt(CPL_MATH_PI * 32)
                          * bin_width * slit_width * width * width);
  cpl_array_multiply_scalar(y, width);
  cpl_array_copy_data_double(aVal, cpl_array_get_data_double(y));
  cpl_array_delete(y);
  cpl_array_delete(coeff);

  return CPL_ERROR_NONE;
}

/*----------------------------------------------------------------------------*/
/**
   @brief Create spectrum for a single slice.
   @param aLambda     Wavelength array.
   @param aLines      Sky emission line table
   @param aLsfParams        LSF parameters to use
   return the spectrum as cpl_array

   The values in the returned array correspond to the wavelengths in the
   aLambda parameter.

   @pseudocode
spectrum = 0
for line in aLines:
    spectrum += muse_lsf_line_apply(aLambda - line.lambda, line.lambda) * line.flux
return spectrum@endpseudocode */
/*----------------------------------------------------------------------------*/
cpl_array *
muse_lsf_params_spectrum(const cpl_array *aLambda, cpl_table *aLines,
                         const muse_lsf_params *aLsfParams)
{
  cpl_size n_lines = cpl_table_get_nrow(aLines);
  cpl_size i_line;
  cpl_array *spectrum = cpl_array_new(cpl_array_get_size(aLambda),
                                      CPL_TYPE_DOUBLE);
  cpl_array_fill_window(spectrum, 0, cpl_array_get_size(aLambda), 0.0);
  int errold = errno;
  feclearexcept(FE_UNDERFLOW);
  for (i_line = 0; i_line < n_lines; i_line++) {
    double l_lambda = cpl_table_get(aLines, "lambda", i_line, NULL);
    double l_flux = cpl_table_get(aLines, "flux", i_line, NULL);
    double l_min = -7;
    double l_max = 7;
    cpl_size imin = muse_cplarray_find_sorted(aLambda, l_lambda + l_min);
    cpl_size imax = muse_cplarray_find_sorted(aLambda, l_lambda + l_max);
    if (imax <= imin) {
      continue;
    }
    cpl_array *l0 = cpl_array_extract(aLambda, imin, imax-imin+1);
    cpl_array_subtract_scalar(l0, l_lambda);
    muse_lsf_params_apply(aLsfParams, l0, l_lambda);
    cpl_array_multiply_scalar(l0, l_flux);
    muse_cplarray_add_window(spectrum, imin, l0);
    cpl_array_delete(l0);
  }
  if (fetestexcept(FE_UNDERFLOW)) {
    errno = errold;
    feclearexcept(FE_UNDERFLOW);
  }
  return spectrum;
}

/*----------------------------------------------------------------------------*/
/**
   @brief Create a new fit parameter structure
   @param aOffset Set to CPL_True if the offset is to be fit
   @param aRefraction set to CPL_True if the atmospheric refraction is to be fit
   @param aSensitivity Order of the sensitivity fit parameter
   @param aSlitWidth Order of the slit width fit parameter
   @param aBinWidth Order of the bin width fit parameter
   @param aLSFWidth Order of the LSF width fit parameter
   @param aHermit3 Order of the 3rd order hermitean fit parameter
   @param aHermit4 Order of the 4rd order hermitean fit parameter
   @param aHermit5 Order of the 5rd order hermitean fit parameter
   @param aHermit6 Order of the 6rd order hermitean fit parameter

   @return Pointer to a newly allocates fit parameter structure.
 */

/*----------------------------------------------------------------------------*/

muse_lsf_fit_params *
muse_lsf_fit_params_new(cpl_boolean aOffset, cpl_boolean aRefraction,
                        cpl_size aSensitivity, cpl_size aSlitWidth,
                        cpl_size aBinWidth, cpl_size aLSFWidth,
                        cpl_size aHermit3, cpl_size aHermit4,
                        cpl_size aHermit5, cpl_size aHermit6)
{
  muse_lsf_fit_params *params = cpl_malloc(sizeof(muse_lsf_fit_params));
  params->offset = aOffset;
  params->refraction = aRefraction;
  params->sensitivity = aSensitivity;
  params->slit_width = aSlitWidth;
  params->bin_width = aBinWidth;
  params->lsf_width = aLSFWidth;
  params->hermit[0] = aHermit3;
  params->hermit[1] = aHermit4;
  params->hermit[2] = aHermit5;
  params->hermit[3] = aHermit6;

  params->n_param = params->offset + params->refraction + params->sensitivity +
    params->slit_width + params->bin_width + params->lsf_width;
  cpl_size i;
  for (i = 0; i < MAX_HERMIT_ORDER; i++) {
    params->n_param += params->hermit[i];
  }

  return params;
}

/*----------------------------------------------------------------------------*/
/**
   @brief Delete the fit parameter structure.
   @param params Structure to destroy
*/
/*----------------------------------------------------------------------------*/
void
muse_lsf_fit_params_delete(muse_lsf_fit_params *params) {
  cpl_free(params);
}


/*----------------------------------------------------------------------------*/
/**
   @private
   @brief Return the parametrization values of the first guess.
   @param aFitParams  Structure defining slice parameter fitting flags
   @return An array containing the first guess values.

   This array can be converted to lsf parametern using
   muse_lsf_apply_parametrization().
 */
/*----------------------------------------------------------------------------*/
static cpl_array *
muse_lsf_firstguess(const muse_lsf_fit_params *aFitParams) {
  cpl_array *pars = cpl_array_new(aFitParams->n_param, CPL_TYPE_DOUBLE);
  cpl_size offset = 0;

  // Wavelength offset
  if (aFitParams->offset > 0) {
    cpl_array_set(pars, offset++, 0.0);
  }

  // Relative refraction ratio - 1
  if (aFitParams->refraction > 0) {
    cpl_array_set(pars, offset++, 0.0);
  }

  // Relative sensitivity
  cpl_size j;
  for (j = 0; j < aFitParams->sensitivity; j++) {
    cpl_array_set(pars, offset++, (j == 0)?1.0:0.0);
  }

  // Slit width
  if (aFitParams->slit_width > 0) {
    cpl_array_set(pars, offset++, kMuseSliceSlitWidthA);
  }

  // Bin width
  if (aFitParams->bin_width > 0) {
    cpl_array_set(pars, offset++, kMuseSpectralSamplingA);
  }

  // LSF width
  for (j = 0; j < aFitParams->lsf_width; j++) {
    cpl_array_set(pars, offset++, (j == 0)?0.5:0.0);
  }

  // Hermitean coefficients
  cpl_size i;
  for (i = 0; i < MAX_HERMIT_ORDER; i++) {
    for (j = 0; j < aFitParams->hermit[i]; j++) {
      cpl_array_set(pars, offset++, 0.0);
    }
  }

  if (offset > cpl_array_get_size(pars)) {
    cpl_msg_error(__func__,
                  "inconsistent array: size %ld, filled with %ld values",
                  (long)cpl_array_get_size(pars), (long)offset);
  }
  return pars;
}

/*----------------------------------------------------------------------------*/
/**
   @private
   @brief Return the parametrization values for the specific LSF params
   @param aLsfParams   Slice LSF params.
   @param aFitParams   Structure defining slice parameter fitting flags
   @return An array containing the first guess values.
 */
/*----------------------------------------------------------------------------*/
static cpl_array *
muse_lsf_set_param(muse_lsf_params *aLsfParams,
                             const muse_lsf_fit_params *aFitParams) {
  cpl_array *pars = cpl_array_new(aFitParams->n_param, CPL_TYPE_DOUBLE);
  cpl_size offset = 0;

  // Relative refraction ratio - 1
  if (aFitParams->offset > 0) {
    cpl_array_set(pars, offset++, aLsfParams->offset);
  }
  if (aFitParams->refraction > 0) {
    cpl_array_set(pars, offset++, aLsfParams->refraction -1);
  }
  // relative sensitivity
  cpl_size j;
  cpl_size n = cpl_array_get_size(aLsfParams->sensitivity);
  for (j = 0; j < aFitParams->sensitivity; j++) {
    if (j < n) {
      cpl_msg_debug(__func__, "S[%li]=%f", (long)j,
                    cpl_array_get(aLsfParams->sensitivity, j, NULL));
      cpl_array_set(pars, offset++,
                    cpl_array_get(aLsfParams->sensitivity, j, NULL));
    } else {
      cpl_array_set(pars, offset++, (j == 0)?1.0:0.0);
    }
  }

  if (aFitParams->slit_width > 0) {
    cpl_array_set(pars, offset++, aLsfParams->slit_width);
  }

  if (aFitParams->bin_width > 0) {
    cpl_array_set(pars, offset++, aLsfParams->bin_width);
  }

  // LSF width
  n = cpl_array_get_size(aLsfParams->lsf_width);
  for (j = 0; j < aFitParams->lsf_width; j++) {
    if (j < n) {
      cpl_array_set(pars, offset++,
                    cpl_array_get(aLsfParams->lsf_width, j, NULL));
    } else {
      cpl_array_set(pars, offset++, (j == 0)?1.0:0.0);
    }
  }

  // Hermitean coefficients
  cpl_size i;
  for (i = 0; i < MAX_HERMIT_ORDER; i++) {
    n = cpl_array_get_size(aLsfParams->hermit[i]);
    for (j = 0; j < aFitParams->hermit[i]; j++) {
      if (j < n) {
        cpl_array_set(pars, offset++,
                      cpl_array_get(aLsfParams->hermit[i], j, NULL));
      } else {
        cpl_array_set(pars, offset++, 0.0);
      }
    }
  }

  if (offset > cpl_array_get_size(pars)) {
    cpl_msg_error(__func__,
                  "inconsistent array: size %ld, filled with %ld values",
                  (long)cpl_array_get_size(pars), (long)offset);
  }
  return pars;
}

/*----------------------------------------------------------------------------*/
/**
   @private
   @brief Convert a parametrization to LSF parameters.
   @param aTemplate    Template for parametrization values
   @param aPar         Parametrization values
   @param aFitParams   Structure defining slice parameter fitting flags
   @return Pointer to a newly allocates LSF parameter structure.

   This is the parametrization used in the SLICE procedure.
 */
/*----------------------------------------------------------------------------*/
static muse_lsf_params *
muse_lsf_apply_parametrization(const muse_lsf_params *aTemplate,
                               const cpl_array *aPar,
                               const muse_lsf_fit_params *aFitParams)
{
  cpl_size offset = 0;
  cpl_size j;

  cpl_size max_hermit = 0;
  for (j = 0; j < MAX_HERMIT_ORDER; j++) {
    if (max_hermit < aFitParams->hermit[j]) {
      max_hermit = aFitParams->hermit[j];
    }
  }
  muse_lsf_params *lsf
    = muse_lsf_params_new((aFitParams->sensitivity > 0)?
                          aFitParams->sensitivity:
                          cpl_array_get_size(aTemplate->sensitivity),
                          (aFitParams->lsf_width > 0)?
                          aFitParams->lsf_width:
                          cpl_array_get_size(aTemplate->lsf_width),
                          (max_hermit > 0)?
                          max_hermit:
                          cpl_array_get_size(aTemplate->hermit[0]));
  cpl_array_set(lsf->sensitivity, 0, 1.0);

  if (aFitParams->offset > 0) {
    lsf->offset = cpl_array_get(aPar, offset++, NULL);
  } else {
    lsf->offset = aTemplate->offset;
  }

  if (aFitParams->refraction > 0) {
    lsf->refraction = 1.0 + cpl_array_get(aPar, offset++, NULL);
  } else {
    lsf->refraction = aTemplate->refraction;
  }

  cpl_size n = cpl_array_get_size(lsf->sensitivity);
  if (aFitParams->sensitivity > 0) {
    for (j = 0; j < n; j++) {
      if (j < aFitParams->sensitivity) {
        cpl_array_set(lsf->sensitivity, j,
                      cpl_array_get(aPar, offset++, NULL));
      } else {
      cpl_array_set(lsf->sensitivity, j, 0.0);
      }
    }
  } else {
    for (j = 0; j < n; j++) {
      cpl_array_set(lsf->sensitivity, j,
                    cpl_array_get(aTemplate->sensitivity, j, NULL));
    }
  }

  if (aFitParams->slit_width > 0) {
    lsf->slit_width = cpl_array_get(aPar, offset++, NULL);
  } else {
    lsf->slit_width = aTemplate->slit_width;
  }

  if (aFitParams->bin_width > 0) {
    lsf->bin_width = cpl_array_get(aPar, offset++, NULL);
  } else {
    lsf->bin_width = aTemplate->bin_width;
  }

  n = cpl_array_get_size(lsf->lsf_width);
  if (aFitParams->lsf_width > 0) {
    for (j = 0; j < n; j++) {
      if (j < aFitParams->lsf_width) {
        cpl_array_set(lsf->lsf_width, j,
                      cpl_array_get(aPar, offset++, NULL));
      } else {
        cpl_array_set(lsf->lsf_width, j, 0.0);
      }
    }
  } else {
    for (j = 0; j < n; j++) {
      cpl_array_set(lsf->lsf_width, j,
                    cpl_array_get(aTemplate->lsf_width, j, NULL));
    }
  }

  int i;
  for (i = 0; i < MAX_HERMIT_ORDER; i++) {
    n = cpl_array_get_size(lsf->hermit[i]);
    if (aFitParams->hermit[i] > 0) {
      for (j = 0; j < n; j++) {
        if (j < aFitParams->hermit[i]) {
          cpl_array_set(lsf->hermit[i], j,
                        cpl_array_get(aPar, offset++, NULL));
        } else {
          cpl_array_set(lsf->hermit[i], j, 0.0);
        }
      }
    } else {
      for (j = 0; j < n; j++) {
        cpl_array_set(lsf->hermit[i], j,
                      //cpl_array_get(aTemplate->hermit[i], j, NULL));
                      0.0);
      }
    }
  }

  if (offset > cpl_array_get_size(aPar)) {
    cpl_msg_error(__func__,
                  "inconsistent array: size %ld, read with %ld values",
                  (long)cpl_array_get_size(aPar), (long)offset);
    muse_lsf_params_delete(lsf);
    return NULL;
  }

  return lsf;
}


/*----------------------------------------------------------------------------*/

typedef struct {
  cpl_array *lambda;
  cpl_array *values;
  cpl_array *stat;
  const cpl_table *lines;
  const muse_lsf_fit_params *fit_params;
  muse_lsf_params *firstGuess;
} muse_lsf_fit_struct;

/*----------------------------------------------------------------------------*/
/**
  @private
   @brief Evaluate a given parameter set
   @param aData Data forwarded from the fit algorithm call.
   @param aPar  Current fit parameter.
   @param aRetval Return value vector.
   @return CPL_ERROR_NONE if everything went OK.
 */
/*----------------------------------------------------------------------------*/
static cpl_error_code
muse_lsf_eval(void *aData, cpl_array *aPar, cpl_array *aRetval) {

  muse_lsf_fit_struct *data = aData;
  cpl_size size = cpl_array_get_size(aRetval);

  muse_lsf_params *lsfParam
    = muse_lsf_apply_parametrization(data->firstGuess, aPar, data->fit_params);

  cpl_table *lines = cpl_table_duplicate(data->lines);

  if (!cpl_table_has_column(lines, "flux")) {
    cpl_array *linesFlux = cpl_array_extract(aPar, cpl_array_get_size(aPar)
                                             - cpl_table_get_nrow(data->lines),
                                             cpl_table_get_nrow(data->lines));
    cpl_table_wrap_double(lines, cpl_array_unwrap(linesFlux), "flux");
  }

  cpl_array *simulated
    = muse_lsf_params_spectrum(data->lambda, lines, lsfParam);

  cpl_table_delete(lines);

  muse_lsf_params_delete(lsfParam);
  cpl_array_subtract(simulated, data->values);
  cpl_array_divide(simulated, data->stat);

  cpl_array_fill_window_double(aRetval, 0, size, 0.0);
  memcpy(cpl_array_get_data_double(aRetval),
         cpl_array_get_data_double_const(simulated),
         size * sizeof(double));

  // replace invalid numbers by 0.0
  cpl_size i;
  double *d = cpl_array_get_data_double(aRetval);
  for (i = 0; i < size; i++) {
    if (isnan(d[i])) {
      d[i] = 0.0;
    }
  }

  cpl_array_delete(simulated);
  return CPL_ERROR_NONE;
}

/*----------------------------------------------------------------------------*/
/**
   @brief  Fit all entries of one slice.
   @param  aPixtable   Pixel table created from an arc exposure.
   @param  aLines      @ref muse_sky_lines_lines_def "List of emission lines".
   @param  aMaxIter    Maximum number of iterations.
   @return The fitted LSF parameters or NULL on error.

   As a quality measure, the LSF fitted lines are subtracted from the
   measured spectrum.

   @error{set CPL_ERROR_NULL_INPUT\, return NULL, aPixtable is NULL}
 */
/*----------------------------------------------------------------------------*/
muse_lsf_params *
muse_lsf_params_fit(muse_pixtable *aPixtable, cpl_table *aLines, int aMaxIter)
{
  cpl_ensure(aPixtable, CPL_ERROR_NULL_INPUT, NULL);
  uint32_t origin = (uint32_t)cpl_table_get_int(aPixtable->table,
                                                MUSE_PIXTABLE_ORIGIN,
                                                0, NULL);
  int i_ifu = muse_pixtable_origin_get_ifu(origin);
  int i_slice = muse_pixtable_origin_get_slice(origin);

  cpl_propertylist *order = cpl_propertylist_new();
  cpl_propertylist_append_bool(order, MUSE_PIXTABLE_LAMBDA, CPL_FALSE);
  cpl_table_sort(aPixtable->table, order);
  cpl_propertylist_delete(order);

  // We need a private copy since we will modify the line flux. The fit may
  // run in parallel for all slices.
  cpl_table *lines = cpl_table_duplicate(aLines);

  //
  // Create "lambda", "data", and "stat" fields from the pixel table.
  // They do not contain all pixels, but a certain percentage of them.
  //
  cpl_size reduction = 1; // we use 100% of all pixels for the fit.
  cpl_size size = cpl_table_get_nrow(aPixtable->table)/reduction;
  cpl_array *lambda = cpl_array_new(size, CPL_TYPE_DOUBLE);
  cpl_array *data = cpl_array_new(size, CPL_TYPE_DOUBLE);
  cpl_array *stat = cpl_array_new(size, CPL_TYPE_DOUBLE);

  cpl_msg_info(__func__, "processing slice %2i.%02i"
               " with %"CPL_SIZE_FORMAT" entries",
               i_ifu, i_slice, size);

  cpl_size i;
  for (i = 0; i < size; i++) {
    int iflg = 0;
    cpl_array_set(lambda, i, cpl_table_get(aPixtable->table,
                                           MUSE_PIXTABLE_LAMBDA,
                                           reduction * i, &iflg));
    cpl_array_set(data, i, cpl_table_get(aPixtable->table,
                                         MUSE_PIXTABLE_DATA,
                                         reduction * i, &iflg));
    cpl_array_set(stat, i, sqrt(cpl_table_get(aPixtable->table,
                                              MUSE_PIXTABLE_STAT,
                                              reduction * i, &iflg)));
  }

  muse_lsf_params *firstGuess = muse_lsf_params_new(1, 3, 1);

  int debug = getenv("MUSE_DEBUG_LSF_FIT")
            && atoi(getenv("MUSE_DEBUG_LSF_FIT")) > 0;
  muse_cpl_optimize_control_t ctrl = {
    -1, -1, -1, // default ftol, xtol, gtol
    aMaxIter, debug
  };

  //
  // First minimization step: Fit the lsf width and the fluxes of all lines
  //
  muse_lsf_fit_params *slice_fit_params0 = muse_lsf_fit_params_new
    (
     0, // aParams->slice_fit_offset,
     0, // aParams->slice_fit_refraction,
     0, // sensitivity is not used here
     0, // aParams->slice_fit_slit_width,
     0, // aParams->slice_fit_bin_width,
     3, // aParams->slice_fit_lsf_width + 1,
     0, // aParams->slice_fit_h3 + 1,
     0, // aParams->slice_fit_h4 + 1,
     0, // aParams->slice_fit_h5 + 1,
     0  // aParams->slice_fit_h6 + 1
     );

  muse_lsf_fit_struct fit_data = {
    lambda,
    data,
    stat,
    lines,
    slice_fit_params0,
    firstGuess
  };

  cpl_array *pars = muse_lsf_firstguess(slice_fit_params0);

  // Take the lines fluxes as they come out of the line list as first guess
  cpl_array *lf = muse_cpltable_extract_column(lines, "flux");
  cpl_array *linesFlux = cpl_array_cast(lf, CPL_TYPE_DOUBLE);
  cpl_array_unwrap(lf);
  cpl_array_insert(pars, linesFlux, cpl_array_get_size(pars));
  cpl_table_erase_column(lines, "flux");

  cpl_error_code r
    = muse_cpl_optimize_lvmq(&fit_data, pars, size, muse_lsf_eval, &ctrl);

  if (r != CPL_ERROR_NONE) { // on error: reset to first guess
    cpl_array_delete(pars);
    pars = muse_lsf_firstguess(slice_fit_params0);
    cpl_array_insert(pars, linesFlux, cpl_array_get_size(pars));
  }

  //
  // Second minimization step: Keep the line fluxes constant and fit
  // all shape parameters
  //
  muse_lsf_fit_params *slice_fit_params = muse_lsf_fit_params_new
      (
       0, // aParams->slice_fit_offset,
       0, // aParams->slice_fit_refraction,
       0, // sensitivity is not used here
       1, // aParams->slice_fit_slit_width,
       1, // aParams->slice_fit_bin_width,
       3, // aParams->slice_fit_lsf_width + 1,
       1, // aParams->slice_fit_h3 + 1,
       2, // aParams->slice_fit_h4 + 1,
       1, // aParams->slice_fit_h5 + 1,
       2  // aParams->slice_fit_h6 + 1
       );

  fit_data.fit_params = slice_fit_params;

  cpl_array_delete(linesFlux);
  linesFlux = cpl_array_extract(pars, cpl_array_get_size(pars)
                                - cpl_table_get_nrow(lines),
                                cpl_table_get_nrow(lines));
  cpl_table_wrap_double(lines, cpl_array_unwrap(linesFlux), "flux");

  fit_data.firstGuess = muse_lsf_apply_parametrization(firstGuess, pars,
                                                       slice_fit_params0);
  muse_lsf_fit_params_delete(slice_fit_params0);
  cpl_array_delete(pars);
  pars = muse_lsf_set_param(fit_data.firstGuess, slice_fit_params);

  r = muse_cpl_optimize_lvmq(&fit_data, pars, size, muse_lsf_eval, &ctrl);
  if (r != CPL_ERROR_NONE) { // on error: reset to first guess
    cpl_array_delete(pars);
    pars = muse_lsf_firstguess(slice_fit_params);
  }

  muse_lsf_params *lsfParam
    = muse_lsf_apply_parametrization(firstGuess, pars, slice_fit_params);
  lsfParam->ifu = i_ifu;
  lsfParam->slice = i_slice;

  cpl_msg_debug(__func__, "Slice %2i.%02i: Slit width: %f (%s), bin width: %f (%s)",
                i_ifu, i_slice,
                lsfParam->slit_width, slice_fit_params->slit_width?"fit":"fixed",
                lsfParam->bin_width, slice_fit_params->bin_width?"fit":"fixed");

  cpl_array *simulated = muse_lsf_params_spectrum(lambda, lines, lsfParam);
  cpl_table_wrap_double(aPixtable->table, cpl_array_unwrap(simulated),
                        "simulated");
  cpl_table_subtract_columns(aPixtable->table, MUSE_PIXTABLE_DATA,
                             "simulated");
  cpl_table_erase_column(aPixtable->table, "simulated");


  cpl_array_delete(pars);
  if (cpl_table_has_column(aPixtable->table, "lambda_double")) {
    cpl_table_erase_column(aPixtable->table, "lambda_double");
  }
  cpl_array_delete(fit_data.lambda);
  cpl_array_delete(fit_data.values);
  cpl_array_delete(fit_data.stat);
  muse_lsf_params_delete(fit_data.firstGuess);
  muse_lsf_params_delete(firstGuess);
  muse_lsf_fit_params_delete(slice_fit_params);
  cpl_table_delete(lines);

  return lsfParam;
}

/*----------------------------------------------------------------------------*/
/**
  @brief   Measure the FWHM of an LSF at a given wavelength.
  @param   aDP          the LSF parameters (for one IFU and slice)
  @param   aLambda      wavelength at which to evaluate the LSF
  @param   aSampling    sampling of the spectrum in Angstrom
  @param   aLength      length of spectrum in pixels
  @return  The FWHM of the LSF at this wavelength or 0. on error.

  This function creates a spectrum of given aLength with given resolution
  (aSampling), fills it with a line with a reference flux (1.0) at the given
  wavelength aLambda. This spectrum is then used to measures peak intensity,
  searches for the position where the intensity reaches half the peak, to
  derive the FWHM from this.

  @error{set CPL_ERROR_NULL_INPUT\, return 0., aDP is NULL}
  @error{set CPL_ERROR_ILLEGAL_INPUT\, return 0.,
         aDP contains lsf_width of zero or one}
  @error{set CPL_ERROR_ILLEGAL_OUTPUT\, return 0.,
         FWHM is outside the test spectrum for the given parameters}
 */
/*----------------------------------------------------------------------------*/
double
muse_lsf_fwhm_lambda(const muse_lsf_params *aDP, double aLambda,
                     double aSampling, unsigned int aLength)
{
  cpl_ensure(aDP, CPL_ERROR_NULL_INPUT, 0.);
  /* if the lsf_width was not fitted correctly this value would be useless */
  cpl_ensure(cpl_array_get(aDP->lsf_width, 0, NULL) != 1 &&
             cpl_array_get(aDP->lsf_width, 0, NULL) != 0,
             CPL_ERROR_ILLEGAL_INPUT, 0.);

  cpl_table *line = cpl_table_new(1);
  cpl_table_new_column(line, "lambda", CPL_TYPE_DOUBLE);
  cpl_table_new_column(line, "flux", CPL_TYPE_FLOAT);
  cpl_table_set_double(line, "lambda", 0, aLambda);
  cpl_table_set_float(line, "flux", 0, 1.0);

  cpl_array *lambda = cpl_array_new(aLength, CPL_TYPE_DOUBLE);
  cpl_size i;
  for (i = 0; i < aLength; i++) {
    cpl_array_set_double(lambda, i, (i + 1. - aLength/2) * aSampling + aLambda);
  } /* for i */

  cpl_array *spec = muse_lsf_params_spectrum(lambda, line, aDP);
  cpl_size imax;
  cpl_array_get_maxpos(spec, &imax);
  double max = cpl_array_get_max(spec),
         vl = 0., vr = 0.; /* exact values at half maximum */
  i = imax;
  while (--i >= 0 &&
         (vl = cpl_array_get_double(spec, i, NULL)) > max/2.) ;
  cpl_size il = i; /* first point with value below half max */
  i = imax;
  while (++i < aLength &&
         (vr = cpl_array_get_double(spec, i, NULL)) > max/2.) ;
  cpl_size ir = i;
  /* compute the FWHM that we really want; do not use (ir - il + 1) *
   * because on average we stepped two half pixels too far...       */
  double fwhm = (ir - il) * aSampling;
  cpl_array_delete(spec);
  cpl_array_delete(lambda);
  cpl_table_delete(line);
  cpl_ensure(il > 0 && ir < aLength, CPL_ERROR_ILLEGAL_OUTPUT, 0.);
  return fwhm;
} /* muse_lsf_fwhm_lambda() */

/**@}*/
