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

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

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

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

#include <cpl.h>

#include "kmclipm_math.h"

#include "kmo_priv_std_star.h"
#include "kmo_priv_functions.h"
#include "kmo_functions.h"
#include "kmo_cpl_extensions.h"
#include "kmo_priv_fit_profile.h"
#include "kmo_dfs.h"
#include "kmo_constants.h"
#include "kmo_debug.h"
#include "kmo_error.h"

#ifdef __USE_XOPEN
#include <time.h>
#else
#define __USE_XOPEN // to get the definition for strptime in time.h
#include <time.h>
#undef __USE_XOPEN
#endif

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

int print_warning_once_stdstar = TRUE;
int cnter = 0;

typedef struct {
//    long long int ts;   /* timestamp in microseconds  */
    time_t ts;
    cpl_frame *frame;
    char type [KMOS_NR_IFUS];
    int containsObjects;
} tmpFrameTableStruct;


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



/*----------------------------------------------------------------------------*/
/**
  @defgroup kmos_priv_std_star     Helper functions for recipe kmo_std_star.
 */
/*----------------------------------------------------------------------------*/

/**@{*/

/*----------------------------------------------------------------------------*/
/**
  @brief    Get the star temperature
  @param    fset        The frame set
  @param    spec_type   The user specified spec type (or "")
  @param    star_type   The computed star type

  The input string must be something like 'G2V' odr 'A9III'.
  The 1st letter can either be: O, B, A, F, G, K.
  The 2nd letter can range from 0 to 9 
  The remaning letters specify the lum. class in roman letters:
  I, II, III, IV, V
  @return   the temperature or -1.0 if it could not be derived
  Possible cpl_error_code set in this function:
      @li CPL_ERROR_NULL_INPUT     if any of the inputs is NULL.
      @li CPL_ERROR_ILLEGAL_INPUT  if they provided star type is wrong.
*/
/*----------------------------------------------------------------------------*/
double kmos_get_temperature(
        cpl_frameset        *   fset,
        const char          *   spec_type,
        char                *   star_type)
{
    cpl_frame           *   std_frame ;
    cpl_propertylist    *   main_h ;
    char                *   spec_type_loc ;
    double                  star_temperature ;
    cpl_table           *   spec_table ;
    char                    lum_class[8] ;
    int                     ind_row, d ;

    /* Check Entries */
    if (fset == NULL || spec_type == NULL || star_type == NULL) {
        cpl_msg_error(__func__, "NULL inputs") ;
        cpl_error_set(__func__, CPL_ERROR_NULL_INPUT) ;
        return -1.0 ;
    }

    /* If the passed spec_type is empty, use the header */
    if (!strcmp(spec_type, "")) {
        std_frame = kmo_dfs_get_frame(fset, STD);
        main_h=kmclipm_propertylist_load(cpl_frame_get_filename(std_frame),0);

        // check for startype-keyword
        if ((cpl_propertylist_has(main_h, STDSTAR_TYPE)) &&
            (cpl_propertylist_get_type(main_h, STDSTAR_TYPE)==CPL_TYPE_STRING)){
            spec_type_loc = cpl_strdup(
                    cpl_propertylist_get_string(main_h, STDSTAR_TYPE));
        } else {
            spec_type_loc = NULL ;
        }
        cpl_propertylist_delete(main_h) ;
    } else {
        spec_type_loc = cpl_strdup(spec_type) ;
    }

    /* No Spectral type */
    if (spec_type_loc == NULL) {
        cpl_msg_error(__func__, "Cannot determine the spectral type") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1.0 ;
    }

    /* Upper case */
    kmo_strupper(spec_type_loc);

    /* Check the spectral type */
    if (spec_type_loc[0] != 'O' && spec_type_loc[0] != 'B' &&
            spec_type_loc[0] != 'A' && spec_type_loc[0] != 'F' &&
            spec_type_loc[0] != 'G' && spec_type_loc[0] != 'K') {
        cpl_free(spec_type_loc) ;
        cpl_msg_error(__func__, "O, B, A, F, G, K stars are supported") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1.0 ;
    }
    *star_type = spec_type_loc[0] ;

    if (spec_type_loc[0] == 'K' && spec_type_loc[1] != '0') {
        cpl_free(spec_type_loc) ;
        cpl_msg_error(__func__, "For K the 2nd letter must be 0") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1.0 ;
    }
    if (spec_type_loc[0] == 'O' && spec_type_loc[1] == '0') {
        cpl_free(spec_type_loc) ;
        cpl_msg_error(__func__, "For O the 2nd letter cannot be 0") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1.0 ;
    }
    if (spec_type_loc[1] != '0' && spec_type_loc[1] != '1' &&
            spec_type_loc[1] != '2' && spec_type_loc[1] != '3' &&
            spec_type_loc[1] != '4' && spec_type_loc[1] != '5' &&
            spec_type_loc[1] != '6' && spec_type_loc[1] != '7' &&
            spec_type_loc[1] != '8' && spec_type_loc[1] != '9') {
        cpl_free(spec_type_loc) ;
        cpl_msg_error(__func__, "The 2nd letter must be a digit 0-9") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1.0 ;
    }

    /* Load the SPEC_TYPE_LOOKUP table */
    spec_table = kmo_dfs_load_table(fset, SPEC_TYPE_LOOKUP, 1, 0);
    if (spec_table == NULL) {
        cpl_free(spec_type_loc) ;
        cpl_msg_error(__func__, "Cannot load the spec type lookup table") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1.0 ;
    }
    if (cpl_table_get_ncol(spec_table) != 5 || 
            cpl_table_get_nrow(spec_table) != 50) {
        cpl_free(spec_type_loc) ;
        cpl_table_delete(spec_table) ;
        cpl_msg_error(__func__, "Spec type lookup table bad dimensions") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1.0 ;
    }

    /* Get Row index */
    ind_row = 0 ;
    if (spec_type_loc[0] == 'O')    ind_row = -1;
    if (spec_type_loc[0] == 'B')    ind_row = 9;
    if (spec_type_loc[0] == 'A')    ind_row = 19;
    if (spec_type_loc[0] == 'F')    ind_row = 29;
    if (spec_type_loc[0] == 'G')    ind_row = 39;
    if (spec_type_loc[0] == 'K')    ind_row = 49;

    if (spec_type_loc[1] == '1')    ind_row += 1; 
    if (spec_type_loc[1] == '2')    ind_row += 2; 
    if (spec_type_loc[1] == '3')    ind_row += 3; 
    if (spec_type_loc[1] == '4')    ind_row += 4; 
    if (spec_type_loc[1] == '5')    ind_row += 5; 
    if (spec_type_loc[1] == '6')    ind_row += 6; 
    if (spec_type_loc[1] == '7')    ind_row += 7; 
    if (spec_type_loc[1] == '8')    ind_row += 8; 
    if (spec_type_loc[1] == '9')    ind_row += 9; 

    /* Check lum_class */
    strcpy(lum_class, spec_type_loc + 2);

    /* Get the temperature from the table */
    star_temperature = 1000 * cpl_table_get(spec_table, lum_class, ind_row, &d);
    cpl_free(spec_type_loc) ;
    cpl_table_delete(spec_table) ;

    if (cpl_error_get_code() != CPL_ERROR_NONE) {
        cpl_error_reset();
        star_temperature = -1.0 ;
    }
    return star_temperature ;
}

/**
    @brief
        Interpolates a vector to given WCS.

    If the given output abscissa is shorter than the provided spectrum it will
    be shortened accordingly.

    @param frame   The frame of the input vector to interpolate.
    @param x_out   The vector containing the abscissa of the interpolated
                   vector. Must have the desired length and must contain
                   wavelength values

    @return The interpolated vector (ordinate).
            The returned object must be deleted.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT     if any of the inputs is NULL.
*/
cpl_vector* kmo_interpolate_vector_wcs(const cpl_frame *frame,
                                       cpl_vector *x_out)
{
    kmclipm_vector      *data_in_km     = NULL;
    cpl_vector          *data_in        = NULL,
                        *data_out       = NULL,
                        *x_in           = NULL,
                        *data_in_new    = NULL;
    cpl_bivector        *bi_in          = NULL,
                        *bi_out         = NULL;
    cpl_propertylist    *header         = NULL;
    double              crpix_in        = 0.0,
                        crval_in        = 0.0,
                        cdelt_in        = 0.0,
                        *px_in          = NULL,
                        *pdata_in_new   = NULL,
                        *pdata_in       = NULL,
                        *pdata_out       = NULL,
                        delt_in         = 0,
                        lambda_min_in   = 0.,
                        lambda_max_in   = 0.,
                        lambda_min_out  = 0.,
                        lambda_max_out  = 0.;
    int                 size_in         = 0,
                        nr_new_min      = 0,
                        nr_new_max      = 0,
                        i               = 0;
    KMO_TRY {
        KMO_TRY_ASSURE((frame != NULL) &&
                       (x_out != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data is provided!");

        KMO_TRY_EXIT_IF_NULL(
            data_out = cpl_vector_new(cpl_vector_get_size(x_out)));

        // get WCS
        KMO_TRY_EXIT_IF_NULL(
            header = kmclipm_propertylist_load(cpl_frame_get_filename(frame), 1));

        KMO_TRY_ASSURE(cpl_propertylist_has(header, CRPIX1) &&
                       cpl_propertylist_has(header, CRVAL1) &&
                       cpl_propertylist_has(header, CDELT1),
                       CPL_ERROR_NULL_INPUT,
                       "CRPIX1, CRVAL1 or CDELT1 is missing in input vector!");

        switch (cpl_propertylist_get_type(header, CRPIX1)) {
        case CPL_TYPE_INT:
            crpix_in = cpl_propertylist_get_int(header, CRPIX1);
            break;
        case CPL_TYPE_DOUBLE:
            crpix_in = cpl_propertylist_get_double(header, CRPIX1);
            break;
        case CPL_TYPE_FLOAT:
            crpix_in = cpl_propertylist_get_float(header, CRPIX1);
            break;
        default:
            KMO_TRY_ASSURE(1 == 0,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "CRPIX1 is of wrong type!");
        }
        crval_in = cpl_propertylist_get_double(header, CRVAL1);
        cdelt_in = cpl_propertylist_get_double(header, CDELT1);
        KMO_TRY_CHECK_ERROR_STATE();

        // load data to interpolate
        KMO_TRY_EXIT_IF_NULL(
            data_in_km = kmclipm_vector_load(cpl_frame_get_filename(frame), 1));
        KMO_TRY_EXIT_IF_NULL(
            data_in = kmclipm_vector_create_non_rejected(data_in_km));
        kmclipm_vector_delete(data_in_km); data_in_km = NULL;

        size_in = cpl_vector_get_size(data_in);
        KMO_TRY_CHECK_ERROR_STATE();

        //
        // check if range of data_in is larger than the range of x_out
        //
        lambda_min_in = (1 - crpix_in) * cdelt_in + crval_in;
        lambda_max_in = (size_in + 1 - crpix_in) * cdelt_in + crval_in;
        lambda_min_out = cpl_vector_get(x_out, 0);
        lambda_max_out = cpl_vector_get(x_out, cpl_vector_get_size(x_out)-1);

        KMO_TRY_ASSURE((lambda_max_in > lambda_min_out) &&
                       (lambda_max_out > lambda_min_in),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "The desired wavelength range (%g-%g) isn't "
                       "overlapping at all with the one from frame %s (%g-%g)!",
                       lambda_min_out, lambda_max_out,
                       cpl_frame_get_tag(frame),
                       lambda_min_in, lambda_max_in);

        if ((lambda_min_in > lambda_min_out) ||
            (lambda_max_in < lambda_max_out))
        {
            //
            // x_out has larger extend than data_in
            //
            double  tmp1 = lambda_min_out,
                    tmp2 = lambda_max_out;

            delt_in = (lambda_max_in-lambda_min_in) / (size_in-1);

            if (lambda_min_in > lambda_min_out) {
                // extend data_in at lower end with zero values
                nr_new_min = (lambda_min_in-lambda_min_out) / delt_in + 10;

                KMO_TRY_EXIT_IF_NULL(
                    data_in_new = cpl_vector_new(size_in+nr_new_min));
                KMO_TRY_EXIT_IF_NULL(
                    pdata_in_new = cpl_vector_get_data(data_in_new));
                for (i = 0; i < nr_new_min; i++) {
                    pdata_in_new[i] = 0.;
                }
                KMO_TRY_EXIT_IF_NULL(
                    pdata_in = cpl_vector_get_data(data_in));
                int j = nr_new_min;
                for (i = 0; i < size_in; i++) {
                    pdata_in_new[j++] = pdata_in[i];
                }

                // tmp for warning
                tmp1 = lambda_min_in;

                size_in = size_in + nr_new_min;
                cpl_vector_delete(data_in);
                data_in = data_in_new;
            }


            if (lambda_max_in < lambda_max_out) {
                // extend data_in at upper end with zero values
                nr_new_max = (lambda_max_out-lambda_max_in) / delt_in + 10;

                KMO_TRY_EXIT_IF_NULL(
                    data_in_new = cpl_vector_new(size_in+nr_new_max));
                KMO_TRY_EXIT_IF_NULL(
                    pdata_in_new = cpl_vector_get_data(data_in_new));

                KMO_TRY_EXIT_IF_NULL(
                    pdata_in = cpl_vector_get_data(data_in));
                for (i = 0; i < size_in; i++) {
                    pdata_in_new[i] = pdata_in[i];
                }

                for (i = size_in; i < size_in+nr_new_max; i++) {
                    pdata_in_new[i] = 0.;
                }

                // tmp for warning
                tmp2 = lambda_max_in;

                size_in = size_in + nr_new_max;
                cpl_vector_delete(data_in);
                data_in = data_in_new;
            }

            cpl_msg_warning("", "   The desired wavelength range (%g-%g) is "
                            "larger than the one from frame %s (%g-%g)! The "
                            "values outside the range (%g-%g) are set to zero!",
                            lambda_min_out, lambda_max_out,
                            cpl_frame_get_tag(frame),
                            lambda_min_in, lambda_max_in,
                            tmp1, tmp2);
        }

        // calculate input abcissa vector
        KMO_TRY_EXIT_IF_NULL(
            x_in = cpl_vector_new(size_in));
        KMO_TRY_EXIT_IF_NULL(
            px_in = cpl_vector_get_data(x_in));

        if (lambda_min_in > lambda_min_out) {
            // extend data_in at lower end with zero values
            for (i = 0; i < size_in; i++) {
                px_in[i] = ((i+1-nr_new_min) - crpix_in) * cdelt_in + crval_in;
            }
        } else {
            for (i = 0; i < size_in; i++) {
                px_in[i] = ((i+1) - crpix_in) * cdelt_in + crval_in;
            }
        }

        // interpolate
        KMO_TRY_EXIT_IF_NULL(
            bi_in = cpl_bivector_wrap_vectors(x_in, data_in));
        KMO_TRY_EXIT_IF_NULL(
            bi_out = cpl_bivector_wrap_vectors(x_out, data_out));

        KMO_TRY_EXIT_IF_ERROR(
            cpl_bivector_interpolate_linear(bi_out, bi_in));

        cpl_bivector_unwrap_vectors(bi_in);
        cpl_bivector_unwrap_vectors(bi_out);

        // set all zeros to NaN (in order than cpl_vector_divide() does divide afterwards)
        KMO_TRY_EXIT_IF_NULL(
            pdata_out = cpl_vector_get_data(data_out));
        for (i = 0; i < cpl_vector_get_size(data_out); i++) {
            if (fabs(pdata_out[i]) < 0.001) {
                pdata_out[i] = 0./0.;
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        cpl_vector_delete(data_out); data_out = NULL;
    }

    cpl_vector_delete(x_in); x_in = NULL;
    cpl_propertylist_delete(header); header = NULL;

    // unwrap again
    cpl_vector_delete(data_in); data_in = NULL;

    return data_out;
}

/**
    @brief
        Divides the blackbody.

    @param ydata        (Input/Output) The data values.
    @param xdata        (Input) The abscissa of the data in microns.
    @param temperature  The star temperature.

                                1
         B(l) = ----------------------------------
                  (14387.7/l*temperature)        5
                (e                        -1) * l

    @return             CPL_ERROR_NONE if the blackbody has been divided
                        successfully, an error otherwise.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT     if any of the inputs is NULL.
    @li CPL_ERROR_ILLEGAL_INPUT  if the abscissa and ordinate haven't the
                                 same length.
*/
cpl_error_code kmo_divide_blackbody(cpl_vector *ydata,
                                    const cpl_vector *xdata,
                                    double temperature)
{
    cpl_error_code  ret_err = CPL_ERROR_NONE;

    cpl_error_code  error_code;
    cpl_vector      *blackbody = NULL;
    cpl_vector      *spectrum = NULL;
    cpl_vector      *input_data = NULL;

    KMO_TRY
    {
        KMO_TRY_ASSURE((ydata != NULL) &&
                (xdata != NULL),
                CPL_ERROR_NULL_INPUT,
                "Not all input data is provided!");

        KMO_TRY_ASSURE((cpl_vector_get_size(xdata) == cpl_vector_get_size(ydata)),
                CPL_ERROR_ILLEGAL_INPUT,
                "Both input vectors must have the same size");

        KMO_TRY_EXIT_IF_NULL(
                blackbody = cpl_vector_new(cpl_vector_get_size(xdata)));

        KMO_TRY_EXIT_IF_NULL(
                spectrum = cpl_vector_duplicate(xdata));

        KMO_TRY_EXIT_IF_ERROR(
                error_code = cpl_vector_multiply_scalar(spectrum, 1.e-6));

        KMO_TRY_EXIT_IF_ERROR(
                error_code = cpl_photom_fill_blackbody(
                        blackbody, CPL_UNIT_LESS, spectrum, CPL_UNIT_LENGTH, temperature));

        if (cpl_msg_get_level() == CPL_MSG_DEBUG) {
            KMO_TRY_EXIT_IF_NULL(
                    input_data = cpl_vector_duplicate(ydata));
            /*
            if (cpl_msg_get_level() == CPL_MSG_DEBUG) {
                cpl_bivector    *plot;
                KMO_TRY_EXIT_IF_NULL(
                        plot = cpl_bivector_wrap_vectors(spectrum, blackbody));
                KMO_TRY_EXIT_IF_ERROR(
                        error_code = cpl_plot_bivector(
                                "set datafile missing 'nan';"
                                "set title 'blackbody data';",
                                "w l",
                                "",
                                plot));
             }
             */
        }

        KMO_TRY_EXIT_IF_ERROR(
                cpl_vector_divide(ydata,blackbody)
        );

        if (cpl_msg_get_level() == CPL_MSG_DEBUG) {
            cpl_bivector    *plots[2];
            KMO_TRY_EXIT_IF_NULL(
                    plots[0] = cpl_bivector_wrap_vectors((cpl_vector*)xdata,
                                                         input_data));
            KMO_TRY_EXIT_IF_NULL(
                    plots[1] = cpl_bivector_wrap_vectors((cpl_vector*)xdata,
                                                         ydata));
            char *options[2] = {"w l t 'input'",
                                "w l t 'divided by blackbody'"};
            KMO_TRY_EXIT_IF_ERROR(
                    error_code = cpl_plot_bivectors(
                            "set datafile missing 'nan';"
                            "set title 'blackbody division';",
                            (const char**)options,
                            "",
                            (const cpl_bivector**)plots, 2));
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_err = cpl_error_get_code();
//        cpl_vector_delete(ydata); ydata = NULL;
    }

    if (blackbody != NULL) {
        cpl_vector_delete(blackbody); blackbody = NULL;
    }
    if (spectrum != NULL) {
        cpl_vector_delete(spectrum); spectrum = NULL;
    }
    if (input_data != NULL) {
        cpl_vector_delete(input_data); input_data = NULL;
    }

    return ret_err;
}

/*----------------------------------------------------------------------------*/
/**
  @brief  	Removes arbitrary line
  @param spectrum      (Input/Output) The data values.
  @param lambda        The abscissa of the data in microns.
  @param atmos_model   The atmospheric model sharing the above abscissa
  @param line_center   The spectral position of the line to remove
  @param line_width    The width of the line to remove
  @return   CPL_ERROR_NONE if the line has been removed successfully, 
            an error otherwise.
 */
/*----------------------------------------------------------------------------*/
cpl_error_code kmo_remove_line(
        cpl_vector          *   spectrum,
        const cpl_vector    *   lambda,
        const cpl_vector    *   atmos_model,
        double                  line_center,
        double                  line_width)
{
    cpl_vector          *   lambda_range ;
    cpl_vector          *   spectrum_range ;
    cpl_vector          *   atmos_range ;
    cpl_vector          *   fit ;
    cpl_vector          *   base ;
    cpl_vector          *   fit_par ;
    double              *   pfit ;
    double              *   pspectrum ;
    double              *   pspectrum_range ;
    double              *   pbase ;
    double                  x_array[1] ;
    double                  y_array[1] ;
    double                  min ;
    int                     lowIndex, highIndex, i ;

    /* Check entries */
    if (spectrum == NULL || lambda == NULL || atmos_model == NULL) {
        cpl_msg_error(__func__, "Not all input data is provided") ;
        cpl_error_set(__func__, CPL_ERROR_NULL_INPUT) ;
        return CPL_ERROR_NULL_INPUT ;
    }
    if (line_center <= 0.0 || line_width <= 0.0) {
        cpl_msg_error(__func__, "line_center and line_width must be > 0") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return CPL_ERROR_ILLEGAL_INPUT ;
    }
    if (cpl_vector_get_size(lambda) != cpl_vector_get_size(spectrum) ||
            cpl_vector_get_size(lambda) != cpl_vector_get_size(atmos_model)) {
        cpl_msg_error(__func__, "Input vectors must have the same size") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return CPL_ERROR_ILLEGAL_INPUT ;
    }

    /* Find the line  */
    lowIndex = cpl_vector_find(lambda, line_center - line_width/2);
    highIndex = cpl_vector_find(lambda, line_center + line_width/2);
    if (lowIndex == -1 || highIndex == -1) {
        cpl_msg_error(__func__, "Cannot find the line to remove") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return CPL_ERROR_ILLEGAL_INPUT ;
    }
    
    /* Extract the line from the 3 input vectorѕ */
    lambda_range = cpl_vector_extract(lambda, lowIndex, highIndex, 1);
    spectrum_range = cpl_vector_extract(spectrum, lowIndex, highIndex, 1);
    atmos_range = cpl_vector_extract(atmos_model, lowIndex, highIndex, 1);

    /* Allocate */
    fit = cpl_vector_new(cpl_vector_get_size(spectrum_range));
    base = cpl_vector_duplicate(spectrum_range);

    /* Accessors */
    pspectrum = cpl_vector_get_data(spectrum);
    pspectrum_range = cpl_vector_get_data(spectrum_range);
    pbase = cpl_vector_get_data(base);
    pfit = cpl_vector_get_data(fit);

    /* Divide by atmos */
    cpl_vector_divide(spectrum_range, atmos_range);

    /*
    if (line_center == 0.88635) {
        cpl_plot_vector("set grid;", "t 'line to fit' w lines", "",
                spectrum_range);
    }
    */

    min = cpl_vector_get_min(spectrum_range);
    if ((fit_par = kmo_vector_fit_lorentz(lambda_range, spectrum_range, NULL, 
            line_center, min, NULL, 1)) == NULL) {
        cpl_msg_warning("","   Couldn't identify the line at %g [um]", 
                line_center);
        cpl_error_reset();
    } else {
        for (i = 0; i < cpl_vector_get_size(fit); i++) {
            x_array[0] = cpl_vector_get(lambda_range, i);
            kmo_priv_lorentz1d_fnc(x_array, cpl_vector_get_data(fit_par), 
                    y_array);
            pfit[i] = y_array[0];
            pbase[i] = cpl_vector_get(fit_par, 0) + 
                x_array[0] * cpl_vector_get(fit_par, 4);
            pspectrum_range[i] += pbase[i] - pfit[i] ;
            pspectrum[lowIndex+i] += pbase[i] - pfit[i] ;
        }
        cpl_vector_delete(fit_par) ;

        /*
        if (line_center == 0.88635) {
            cpl_plot_vector("set grid;", "t 'line to fit' w lines", "",
                    spectrum_range);
            cpl_plot_vector("set grid;", "t 'Fitted' w lines", "",
                    fit);
        }
        */ 

        cpl_vector_multiply(spectrum_range, atmos_range);
        cpl_msg_info(__func__, "Removed line at %g [um]", line_center);
    }

    /* Deallocate */
    cpl_vector_delete(lambda_range) ;
    cpl_vector_delete(spectrum_range) ;
    cpl_vector_delete(atmos_range) ;
    cpl_vector_delete(fit) ;
    cpl_vector_delete(base) ;
    return CPL_ERROR_NONE;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Calculates the counts/sec of a spectrum.
  @param data        The spectral data.
  @param filter_id   The filter used.
  @param crpix       CRPIX1.
  @param crval       CRVAL1.
  @param cdelt       CDELT1.
  @return The counts/sec in a certain interval of the input spectrum.
  Possible cpl_error_code set in this function:
      @li CPL_ERROR_NULL_INPUT     if any of the inputs is NULL.
      @li CPL_ERROR_ILLEGAL_INPUT  if an invalid filter is provided.
*/
/*----------------------------------------------------------------------------*/
cpl_error_code kmo_calc_counts(
        const cpl_vector    *   data,
        const char          *   filter_id,
        double                  crpix,
        double                  crval,
        double                  cdelt,
        double              *   counts_band1,
        double              *   counts_band2)
{
    cpl_vector      *data_l     = NULL;
    const double    *pdata      = NULL;
    double          *pdata_l    = NULL,
                    min_l       = 0.0,
                    max_l       = 0.0;
    int             size        = 0,
                   i           = 0;
 
    /* Check Entries */
    if (data==NULL||filter_id==NULL||counts_band1==NULL||counts_band2==NULL) {
        cpl_msg_error(__func__, "NULL inputs") ;
        cpl_error_set(__func__, CPL_ERROR_NULL_INPUT) ;
        return CPL_ERROR_NULL_INPUT ;
    }
    if (strcmp(filter_id, "K") && strcmp(filter_id, "H") &&
            strcmp(filter_id, "HK") && strcmp(filter_id, "IZ") &&
            strcmp(filter_id, "YJ")) {
        cpl_msg_error(__func__, "Wrong filter provided") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return CPL_ERROR_ILLEGAL_INPUT ;
    }

    /* Initialise */
    *counts_band1 = *counts_band2 = 0.0 ;

    /* Calculate lambda range */
    if (!strcmp(filter_id, "K")) {
        min_l = LAMBDA_MIN_K;
        max_l = LAMBDA_MAX_K;
    } else if (!strcmp(filter_id, "H") || !strcmp(filter_id, "HK")) {
        min_l = LAMBDA_MIN_H;
        max_l = LAMBDA_MAX_H;
    } else if (!strcmp(filter_id, "IZ")) {
        min_l = LAMBDA_MIN_Z;
        max_l = LAMBDA_MAX_Z;
    } else if (!strcmp(filter_id, "YJ")) {
        min_l = LAMBDA_MIN_J;
        max_l = LAMBDA_MAX_J;
    }

    /* Setup vectors & data pointers to them */
    size = cpl_vector_get_size(data);
    pdata = cpl_vector_get_data_const(data);
    data_l = kmo_create_lambda_vec(size, crpix, crval, cdelt);
    pdata_l = cpl_vector_get_data(data_l);

    /* Calculate counts for 1st band */
    for (i = 0; i < size; i++) {
        if ((pdata_l[i] >= min_l) && (pdata_l[i] <= max_l) &&
            !kmclipm_is_nan_or_inf(pdata[i])) 
            *counts_band1 += pdata[i];
    }

    /* Calculate counts for 2nd band */
    if (strcmp(filter_id, "HK") == 0) {
        min_l = LAMBDA_MIN_K;
        max_l = LAMBDA_MAX_K;

        /* Calculate counts for second band */
        for (i = 0; i < size; i++) {
            if ((pdata_l[i] >= min_l) && (pdata_l[i] <= max_l) &&
                !kmclipm_is_nan_or_inf(pdata[i]))
                *counts_band2 += pdata[i];
        }
    }
    cpl_vector_delete(data_l); 
    return CPL_ERROR_NONE ;
}

/**
    @brief
        Calculates the zeropoint.

    @param magnitude1    The magnitude of the star in 1st band.
    @param magnitude2    The magnitude of the star in 2nd band (HK only).
    @param counts_band1  The counts/sec of the spectrum for 1st bandpass.
    @param counts_band2  The counts/sec of the spectrum for 2nd bandpass.
    @param cdelt         The CDELT.
    @param filter_id     The filter ID.

    zerpoint = magnitude + 2.5 * log(counts)

    @return The zeropoint.
*/
double kmo_calc_zeropoint(double magnitude1,
                          double magnitude2,
                          double counts_band1,
                          double counts_band2,
                          double cdelt,
                          const char *filter_id)
{
    double  bandwidth   = 0.,
            zp1         = 0.,
            zp2         = 0.;

    KMO_TRY
    {
        KMO_TRY_ASSURE(filter_id != NULL,
                       CPL_ERROR_NULL_INPUT,
                       "No filter id provided!");

        // calculate lambda range
        if (strcmp(filter_id, "K") == 0) {
            bandwidth = LAMBDA_MAX_K - LAMBDA_MIN_K;
        } else if ((strcmp(filter_id, "H") == 0) || (strcmp(filter_id, "HK") == 0)) {
            bandwidth = LAMBDA_MAX_H - LAMBDA_MIN_H;
        } else if (strcmp(filter_id, "IZ") == 0) {
            bandwidth = LAMBDA_MAX_Z - LAMBDA_MIN_Z;
        } else if (strcmp(filter_id, "YJ") == 0) {
            bandwidth = LAMBDA_MAX_J - LAMBDA_MIN_J;
        } else {
            KMO_TRY_ASSURE(1 == 0,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Wrong filter provided!");
        }

        // calculate zeropoint for 1st band
        zp1 = magnitude1 + 2.5*log10(counts_band1 * cdelt / bandwidth);

        // calculate zeropoint for 2nd band
        if (strcmp(filter_id, "HK") == 0) {
            bandwidth = LAMBDA_MAX_K - LAMBDA_MIN_K;
            zp2 = magnitude2 + 2.5*log10(counts_band2 * cdelt / bandwidth);
            cpl_msg_info("", "   Zeropoint in H: %g", zp1);
            cpl_msg_info("", "   Zeropoint in K: %g", zp2);

            // take average of zeropoints for QC keyword
            zp1 = (zp1 + zp2) / 2;
            cpl_msg_info("", "   Avg. zeropoint: %g (to be stored as QC parameter)", zp1);
        } else {
            cpl_msg_info("", "   Zeropoint: %g", zp1);
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        zp1 = 0.0;
    }

    // calculate zeropoint
    return  zp1;
}

/**
    @brief
        Calculates the throughput.

    @param magnitude1    The magnitude of the star in 1st band.
    @param magnitude2    The magnitude of the star in 2nd band (HK only).
    @param counts_band1  The counts/sec of the spectrum for 1st bandpass.
    @param counts_band2  The counts/sec of the spectrum for 2nd bandpass.
    @param gain          The gain.
    @param filter_id     The filter ID.

    throughput = number of photons detected / number of photons expected

    @return The throughput.
*/
double kmo_calc_throughput(double magnitude1,
                           double magnitude2,
                           double counts_band1,
                           double counts_band2,
                           double gain,
                           const char *filter_id)
{
    double  throughput          = 0.0,
            n_photons_det       = 0.0,
            n_photons_expect    = 0.0,
            flux                = 0.0,
            bandwidth           = 0.;

    KMO_TRY
    {
        KMO_TRY_ASSURE(filter_id != NULL,
                       CPL_ERROR_NULL_INPUT,
                       "No filter id provided!");

        KMO_TRY_ASSURE((strcmp(filter_id, "K") == 0) ||
                       (strcmp(filter_id, "H") == 0) ||
                       (strcmp(filter_id, "HK") == 0) ||
                       (strcmp(filter_id, "IZ") == 0) ||
                       (strcmp(filter_id, "YJ") == 0),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Wrong filter ID provided (%s)!", filter_id);

        // calculate lambda range
        if (strcmp(filter_id, "K") == 0) {
            flux = NPH_LAMBDA_K;
            bandwidth = LAMBDA_MAX_K - LAMBDA_MIN_K;
        } else if ((strcmp(filter_id, "H") == 0) || (strcmp(filter_id, "HK") == 0)) {
            flux = NPH_LAMBDA_H;
            bandwidth = LAMBDA_MAX_H - LAMBDA_MIN_H;
        } else if (strcmp(filter_id, "IZ") == 0) {
            flux = NPH_LAMBDA_Z;
            bandwidth = LAMBDA_MAX_Z - LAMBDA_MIN_Z;
        } else if (strcmp(filter_id, "YJ") == 0) {
            flux = NPH_LAMBDA_J;
            bandwidth = LAMBDA_MAX_J - LAMBDA_MIN_J;
        } else {
            KMO_TRY_ASSURE(1 == 0,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Wrong filter provided!");
        }

        // calculate number of photons detected
        n_photons_det = counts_band1 * gain / bandwidth / (double)MIRROR_AREA;

        // calculate number of photons expected
        n_photons_expect = pow(10, -magnitude1/2.5) * flux;

        // do the same for band2
        if (strcmp(filter_id, "HK") == 0) {
            flux = NPH_LAMBDA_K;
            bandwidth = LAMBDA_MAX_K - LAMBDA_MIN_K;

            n_photons_det += counts_band2 * gain / bandwidth / (double)MIRROR_AREA;
            n_photons_expect += pow(10, -magnitude2/2.5) * flux;
        }

        KMO_TRY_ASSURE(n_photons_expect != 0.0,
                       CPL_ERROR_DIVISION_BY_ZERO,
                       "Attempted division by zero when "
                       "calculating throughput!");

        // calculate throughput ratio
        throughput = n_photons_det / n_photons_expect;
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        throughput = 0.0;
    }

    return throughput;
}

/**
    @brief
        Calculates the mean and stddev of throughput.

    @param throughput      The data array holding the throughputs of the IFUs.
                           IFUs not containing an object/sky pair are expected
                           to contain a -1 at this place
    @param size_throughput The size of @c throughput (expected to be 24).
    @param mean            (Output) The mean value.
    @param stdev           (Output) The stdev value.

    Calculates the mean and stddev of throughput for all IFUs containing a
    standard star with a matching sky frame.

    @return  Any of the errors described below or CPL_ERROR_NONE otherwise.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT     if any of the inputs is NULL.
    @li CPL_ERROR_ILLEGAL_INPUT  if the size isn't positive.
*/
cpl_error_code kmo_calc_mean_throughput(double *throughput,
                                        int size_throughput,
                                        double *mean,
                                        double *stdev)
{
    cpl_error_code  ret_err = CPL_ERROR_NONE;

    int             size    = 0,
                    j       = 0,
                    i       = 0;
    cpl_vector      *vec    = NULL;

    double          *pvec   = NULL;

    KMO_TRY
    {
        KMO_TRY_ASSURE(throughput != NULL,
                       CPL_ERROR_NULL_INPUT,
                       "Not all inputs provided!");

        KMO_TRY_ASSURE(size_throughput > 0,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "size_throughput must be positive!");

        // get number of object/sky pairs
        for (i = 0; i < size_throughput; i++) {
            if (throughput[i] > 0.0) {
                size++;
            }
        }

        if (size > 0) {
            KMO_TRY_EXIT_IF_NULL(
                vec = cpl_vector_new(size));

            KMO_TRY_EXIT_IF_NULL(
                pvec = cpl_vector_get_data(vec));

            // put corresponding throughput values into a vector
            for (i = 0; i < size_throughput; i++) {
                if (throughput[i] > 0.0) {
                    pvec[j] = throughput[i];
                    j++;
                }
            }

            // calc mean & stdev
            if (cpl_vector_get_size(vec) > 0) {
                if (mean != NULL)  {
                    *mean = kmo_vector_get_mean_old(vec);
                    KMO_TRY_CHECK_ERROR_STATE();
                }

                if (cpl_vector_get_size(vec) > 1) {
                    if (stdev != NULL) {
                        *stdev = cpl_vector_get_stdev(vec);
                        KMO_TRY_CHECK_ERROR_STATE();
                    }
                }
            }
        } else {
            if (mean != NULL)  { *mean = 0.0; }
            if (stdev != NULL) { *stdev = 0.0; }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_err = cpl_error_get_code();
        if (mean != NULL)  { *mean = 0.0; }
        if (stdev != NULL) { *stdev = 0.0; }
    }

    cpl_vector_delete(vec); vec = NULL;

    return ret_err;
}

/**
    @brief
        Calculate standard trace

    Therefore two sections of the cube (300:400 and 1600:1700) are collapsed
    with median and a profile is fitted to them. The stdandard trace is the
    distance between the two centers.

    @param cube         The data cube
    @param fmethod      Fit method, whatever is supported by the function
                        kmo_fit_profile_2D
    @param std_trace    The returned standard deviation

    @return             CPL_ERROR_NONE, an error otherwise.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if any of the inputs is NULL or fmethod is
                                empty or cube is empty.
*/
cpl_error_code kmo_calculate_std_trace(const cpl_imagelist *cube,
                                       const char *fmethod,
                                       double *std_trace)
{
    cpl_imagelist       *cube1      = NULL,
                        *cube2      = NULL;
    cpl_image           *img1       = NULL,
                        *img2       = NULL,
                        *tmp_img    = NULL;
    cpl_vector          *vec1       = NULL,
                        *vec2       = NULL;
    cpl_error_code      ret_err     = CPL_ERROR_NONE;
    int                 size        = 0,
                        cnt         = 0;
    double              dx          = 0.,
                        dy          = 0.;
    cpl_size            i           = 0;

    KMO_TRY
    {
        KMO_TRY_ASSURE((cube != NULL) &&
                       (fmethod != NULL) &&
                       (strlen(fmethod) != 0) &&
                       (std_trace != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data is provided!");

        size = cpl_imagelist_get_size(cube);
        KMO_TRY_CHECK_ERROR_STATE();

        KMO_TRY_ASSURE(size > 1700,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Input cube must have size of >1700!");

        // extract slices 300:400 and 1600:1700 from input cube
        // and collapse with median
        KMO_TRY_EXIT_IF_NULL(
            cube1 = cpl_imagelist_new());
        KMO_TRY_EXIT_IF_NULL(
            cube2 = cpl_imagelist_new());

        cnt = 0;
        for (i = 300; i <=400; i++) {
            KMO_TRY_EXIT_IF_NULL(
                tmp_img = cpl_imagelist_get((cpl_imagelist*)cube, i));
            KMO_TRY_EXIT_IF_ERROR(
                cpl_imagelist_set(cube1, cpl_image_duplicate(tmp_img), cnt++));
        }

        cnt = 0;
        for (i = 1600; i <=1700; i++) {
            KMO_TRY_EXIT_IF_NULL(
                tmp_img = cpl_imagelist_get((cpl_imagelist*)cube, i));
            KMO_TRY_EXIT_IF_ERROR(
                cpl_imagelist_set(cube2, cpl_image_duplicate(tmp_img), cnt++));
        }

        KMO_TRY_EXIT_IF_NULL(
            img1 = cpl_imagelist_collapse_median_create(cube1));
        KMO_TRY_EXIT_IF_NULL(
            img2 = cpl_imagelist_collapse_median_create(cube2));

        cpl_imagelist_delete(cube1); cube1 = NULL;
        cpl_imagelist_delete(cube2); cube2 = NULL;
        KMO_TRY_CHECK_ERROR_STATE();

        // fit profile
        KMO_TRY_EXIT_IF_NULL(
            vec1 = kmo_fit_profile_2D(img1, NULL, fmethod, NULL, NULL));
        KMO_TRY_EXIT_IF_NULL(
            vec2 = kmo_fit_profile_2D(img2, NULL, fmethod, NULL, NULL));

        cpl_image_delete(img1); img1 = NULL;
        cpl_image_delete(img2); img2 = NULL;
        KMO_TRY_CHECK_ERROR_STATE();

        // calculate distance between center of both images
        dx = cpl_vector_get(vec1, 2) - cpl_vector_get(vec2, 2);
        dy = cpl_vector_get(vec1, 3) - cpl_vector_get(vec2, 3);

        cpl_vector_delete(vec1); vec1 = NULL;
        cpl_vector_delete(vec2); vec2 = NULL;
        KMO_TRY_CHECK_ERROR_STATE();

        if (std_trace != NULL) {
            *std_trace = sqrt(dx*dx + dy*dy);
        }
    } KMO_CATCH {
        KMO_CATCH_MSG();
        ret_err = cpl_error_get_code();
        if (std_trace != NULL) {
            *std_trace = -1;
        }
    }

    return ret_err;
}

/**
    @brief
        Calculate mean of a vector in a specific bandpass

    @param pl           The propertylist
    @param data         The data vector
    @param noise        (Optional) The noise vector
    @param mean_data    (Output) The calculated data mean value
    @param mean_data    (Optinal output) The calculated noise mean value

    @return             CPL_ERROR_NONE or error otherwise.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT     if any of the inputs is NULL.
*/
cpl_error_code kmo_calc_band_mean(const cpl_propertylist *pl,
                                  const char *filter_id,
                                  const cpl_vector *data,
                                  const cpl_vector *noise,
                                  double *mean_data,
                                  double *mean_noise)
{
    double          crpix1              = 0.,
                    crval1              = 0.,
                    cdelt1              = 0.,
                    min_l               = 0.,
                    max_l               = 0.,
                    min_l2              = 0.,
                    max_l2              = 0.;
    const double    *ptmp_spec_data     = NULL,
                    *ptmp_spec_noise    = NULL,
                    *pdata_lambda       = NULL;
    int             size                = 0,
                    cnt                 = 0,
                    i                   = 0;
    cpl_vector      *data_lambda        = NULL;
    cpl_error_code  ret_err             = CPL_ERROR_NONE;

    KMO_TRY
    {
        KMO_TRY_ASSURE((pl != NULL) &&
                       (filter_id != NULL) &&
                       (data != NULL) &&
                       (mean_data != 0),
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data is provided!");

        crpix1 = cpl_propertylist_get_double(pl, CRPIX1);
        crval1 = cpl_propertylist_get_double(pl, CRVAL1);
        cdelt1 = cpl_propertylist_get_double(pl, CDELT1);
        KMO_TRY_CHECK_ERROR_STATE();

        if (strcmp(filter_id, "K") == 0) {
            min_l = LAMBDA_MIN_K;
            max_l = LAMBDA_MAX_K;
        } else if ((strcmp(filter_id, "H") == 0) || (strcmp(filter_id, "HK") == 0)) {
            min_l = LAMBDA_MIN_H;
            max_l = LAMBDA_MAX_H;
        } else if (strcmp(filter_id, "IZ") == 0) {
            min_l = LAMBDA_MIN_Z;
            max_l = LAMBDA_MAX_Z;
        } else if (strcmp(filter_id, "YJ") == 0) {
            min_l = LAMBDA_MIN_J;
            max_l = LAMBDA_MAX_J;
        }
        // for HK
        min_l2 = LAMBDA_MIN_K;
        max_l2 = LAMBDA_MAX_K;

        //
        // calculate mean data
        //
        size = cpl_vector_get_size(data);
        KMO_TRY_EXIT_IF_NULL(
            ptmp_spec_data = cpl_vector_get_data_const(data));
        KMO_TRY_EXIT_IF_NULL(
            data_lambda = kmo_create_lambda_vec(size, crpix1, crval1, cdelt1));
        KMO_TRY_EXIT_IF_NULL(
            pdata_lambda = cpl_vector_get_data_const(data_lambda));

        // calculate counts for 1st band
        *mean_data = 0;
        cnt = 0;
        for (i = 0; i < size; i++) {
            if ((pdata_lambda[i] >= min_l) && (pdata_lambda[i] <= max_l) &&
                !kmclipm_is_nan_or_inf(ptmp_spec_data[i]))
            {
                *mean_data += ptmp_spec_data[i];
                cnt++;
            }
        }

        if (strcmp(filter_id, "HK") == 0) {
            // calculate counts for 2nd band
            for (i = 0; i < size; i++) {
                if ((pdata_lambda[i] >= min_l2) && (pdata_lambda[i] <= max_l2) &&
                    !kmclipm_is_nan_or_inf(ptmp_spec_data[i]))
                {
                    *mean_data += ptmp_spec_data[i];
                    cnt++;
                }
            }
        }
        cpl_vector_delete(data_lambda); data_lambda = NULL;

        *mean_data /= cnt;

        //
        // calculate mean noise
        //
        if ((noise != NULL) && (mean_noise != NULL)) {
            size = cpl_vector_get_size(noise);
            KMO_TRY_EXIT_IF_NULL(
                ptmp_spec_noise = cpl_vector_get_data_const(noise));
            KMO_TRY_EXIT_IF_NULL(
                data_lambda = kmo_create_lambda_vec(size, crpix1, crval1, cdelt1));
            KMO_TRY_EXIT_IF_NULL(
                pdata_lambda = cpl_vector_get_data_const(data_lambda));

            // calculate counts for 1st band
            *mean_noise = 0;
            cnt = 0;
            for (i = 0; i < size; i++) {
                if ((pdata_lambda[i] >= min_l) && (pdata_lambda[i] <= max_l) &&
                    !kmclipm_is_nan_or_inf(ptmp_spec_noise[i]))
                {
                    *mean_noise += ptmp_spec_noise[i];
                    cnt++;
                }
            }

            if (strcmp(filter_id, "HK") == 0) {
                // calculate counts for 2nd band
                for (i = 0; i < size; i++) {
                    if ((pdata_lambda[i] >= min_l2) && (pdata_lambda[i] <= max_l2) &&
                        !kmclipm_is_nan_or_inf(ptmp_spec_noise[i]))
                    {
                        *mean_noise += ptmp_spec_noise[i];
                        cnt++;
                    }
                }
            }
            cpl_vector_delete(data_lambda); data_lambda = NULL;

            *mean_noise /= cnt;

        }
    } KMO_CATCH {
        KMO_CATCH_MSG();
        ret_err = cpl_error_get_code();
        *mean_data = 0.;
        if ((noise != NULL) && (mean_noise != NULL)) {
            *mean_noise = 0.;
        }
    }

    return ret_err;
}

/**
    @brief
        Creates a skySkyStruct containing pairs of sky frames

    @param frameset     The frameset only containing STD frames

    @return             The skySkyStruct

*/
skySkyStruct* kmo_create_skySkyStruct(cpl_frameset *frameset)
{
    int                 nrIfu               = 0,
                        nrFrame             = 0;
    char                *tmp_str            = NULL;
    const char          *filename1          = NULL,
                        *filename2          = NULL,
                        *type1              = NULL,
                        *type2              = NULL;
    cpl_frame           *frame1             = NULL,
                        *frame2             = NULL;
    cpl_propertylist    *header1            = NULL,
                        *header2            = NULL;
    skySkyStruct        *sky_sky_struct     = NULL;
    objSkyStruct        *tmp_obj_sky_struct = NULL;

    KMO_TRY
    {
        KMO_TRY_ASSURE((frameset != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data is provided!");

        KMO_TRY_EXIT_IF_NULL(
            sky_sky_struct = (skySkyStruct*)cpl_calloc(KMOS_NR_IFUS, sizeof(skySkyStruct)));
        for (nrIfu = 0; nrIfu < KMOS_NR_IFUS; nrIfu++) {
            sky_sky_struct[nrIfu].nrSkyPairs = 0;
            sky_sky_struct[nrIfu].skyPairs = NULL;
        }

        // creating a temporary objSkyStruct which holds all STD frames
        KMO_TRY_EXIT_IF_NULL(
            tmp_obj_sky_struct = kmo_create_objSkyStruct(frameset, STD, TRUE));

        // loop first all IFUs
        for (nrIfu = 0; nrIfu < KMOS_NR_IFUS; nrIfu++) {
            // loop all frames
            for (nrFrame = 0; nrFrame < tmp_obj_sky_struct->size; nrFrame++) {
                frame1 = tmp_obj_sky_struct->table[nrFrame].objFrame;
                KMO_TRY_EXIT_IF_NULL(
                    filename1 = cpl_frame_get_filename(frame1));
                if (nrFrame+1 < tmp_obj_sky_struct->size) {
                    // got 2 subsequent frames
                    frame2 = tmp_obj_sky_struct->table[nrFrame+1].objFrame;
                    KMO_TRY_EXIT_IF_NULL(
                        filename2 = cpl_frame_get_filename(frame2));

                    // load headers and check if 2 skies present in their IFU
                    KMO_TRY_EXIT_IF_NULL(
                        header1 = kmclipm_propertylist_load(filename1, 0));
                    KMO_TRY_EXIT_IF_NULL(
                        header2 = kmclipm_propertylist_load(filename2, 0));

                    KMO_TRY_EXIT_IF_NULL(
                        tmp_str = cpl_sprintf("%s%d%s", IFU_TYPE_PREFIX, nrIfu+1, IFU_TYPE_POSTFIX));

                    if (cpl_propertylist_has(header1, tmp_str) && cpl_propertylist_has(header2, tmp_str)) {
                        KMO_TRY_EXIT_IF_NULL(
                            type1 = cpl_propertylist_get_string(header1, tmp_str));
                        KMO_TRY_EXIT_IF_NULL(
                            type2 = cpl_propertylist_get_string(header2, tmp_str));

                        if ((strcmp(type1, "S") == 0) && (strcmp(type1, type2) == 0)) {
                            // both contain skies, store them in new skyPair
                            int my_size = sky_sky_struct[nrIfu].nrSkyPairs;
                            KMO_TRY_EXIT_IF_NULL(
                                sky_sky_struct[nrIfu].skyPairs = cpl_realloc(sky_sky_struct[nrIfu].skyPairs, (my_size+1) * sizeof(skySkyPair)));

                            sky_sky_struct[nrIfu].skyPairs[my_size].skyFrame1 = frame1;
                            sky_sky_struct[nrIfu].skyPairs[my_size].skyFrame2 = frame2;

                            sky_sky_struct[nrIfu].nrSkyPairs++;

                            // successfully found a 2nd sky, increment nrFrame twice!
                            nrFrame++;
                        }
                    }
                    cpl_propertylist_delete(header1); header1 = NULL;
                    cpl_propertylist_delete(header2); header2 = NULL;
                    cpl_free(tmp_str); tmp_str = NULL;
                } else {
                    // only one frame left, ignore it
                }
            } // end for (nrFrame)
        } // end for (nrIfu)
    } KMO_CATCH {
        KMO_CATCH_MSG();
        sky_sky_struct = NULL;
    }

    kmo_delete_objSkyStruct(tmp_obj_sky_struct);

    return sky_sky_struct;
}

/**
    @brief
        Delete an skySkyStruct properly

    @param skySkyStruct   The structure to delete
*/
void kmo_delete_skySkyStruct(skySkyStruct *sky_sky_struct)
{
    int i = 0;

    if (sky_sky_struct != NULL) {
        for (i = 0; i < KMOS_NR_IFUS; i++) {
            cpl_free(sky_sky_struct[i].skyPairs); sky_sky_struct[i].skyPairs = NULL;
        }
        cpl_free(sky_sky_struct); sky_sky_struct = NULL;
    }
}

/** @{ */
