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

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

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

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

#include <cpl.h>

#include "kmo_error.h"
#include "kmo_utils.h"
#include "kmo_debug.h"
#include "kmo_priv_functions.h"
#include "kmo_cpl_extensions.h"
#include "kmo_dfs.h"
#include "kmo_constants.h"

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



/*----------------------------------------------------------------------------*/
/**
    @defgroup kmos_priv_functions   General Helper functions 
 */
/*----------------------------------------------------------------------------*/

/**@{*/

/*----------------------------------------------------------------------------*/
/**
  @brief    Add combine-parameters to parameterlist
  @param pl             The already allocated parameterlist
  @param recipe_name    The name of the recipe in form of "kmo.kmo_xxx"
  @param default_method Default combining method (Applies only when
                         no_method==FALSE)
  @param no_method      If FALSE "cmethod" will be added as parameter
                        If TRUE it isn't added
  @return 0 in case of success, -1 otherwise
  This functions is called when creating the parameters for the recipes which
  do combine frames.
 */
/*----------------------------------------------------------------------------*/
int kmos_combine_pars_create(
        cpl_parameterlist   *   pl,
        const char          *   recipe_name,
        const char          *   default_method,
        int                     no_cmethod)
{
    cpl_parameter   *   p;
    char            *   tmpstr ;

    /* Check entries */
    if (pl == NULL || recipe_name == NULL)  return -1;

    if (no_cmethod == FALSE) {
        tmpstr = cpl_sprintf("%s.%s", recipe_name, "cmethod");
        p = cpl_parameter_new_value(tmpstr, CPL_TYPE_STRING,
        "Apply \"average\", \"median\", \"sum\", \"min_max.\" or \"ksigma\".",
                recipe_name, default_method);
        cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "cmethod");
        cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
        cpl_parameterlist_append(pl, p);
        cpl_free(tmpstr); 
    }

    if ((no_cmethod == FALSE) ||
        ((no_cmethod == TRUE) && (strcmp(default_method, "ksigma") == 0))) {
        /* --cpos_rej */
        tmpstr = cpl_sprintf("%s.%s", recipe_name, "cpos_rej");
        p = cpl_parameter_new_value(tmpstr, CPL_TYPE_DOUBLE,
        "The positive rejection threshold for kappa-sigma-clipping (sigma).",
                recipe_name, DEF_POS_REJ_THRES);
        cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "cpos_rej");
        cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
        cpl_parameterlist_append(pl, p);
        cpl_free(tmpstr); 

        /* --cneg_rej */
        tmpstr = cpl_sprintf("%s.%s", recipe_name, "cneg_rej");
        p = cpl_parameter_new_value(tmpstr, CPL_TYPE_DOUBLE,
        "The negative rejection threshold for kappa-sigma-clipping (sigma).",
                recipe_name, DEF_NEG_REJ_THRES);
        cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "cneg_rej");
        cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
        cpl_parameterlist_append(pl, p);
        cpl_free(tmpstr);

        /* --citer */
        tmpstr = cpl_sprintf("%s.%s", recipe_name, "citer");
        p = cpl_parameter_new_value(tmpstr, CPL_TYPE_INT,
                "The number of iterations for kappa-sigma-clipping.",
                recipe_name, DEF_ITERATIONS);
        cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "citer");
        cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
        cpl_parameterlist_append(pl, p);
        cpl_free(tmpstr);
    }

    if ((no_cmethod == FALSE) ||
        ((no_cmethod == TRUE) && (strcmp(default_method, "min_max") == 0))) {
        /* --cmax */
        tmpstr = cpl_sprintf("%s.%s", recipe_name, "cmax");
        p = cpl_parameter_new_value(tmpstr, CPL_TYPE_INT,
            "The number of maximum pixel values to clip with min/max-clipping.",
                recipe_name, DEF_NR_MAX_REJ);
        cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "cmax");
        cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
        cpl_parameterlist_append(pl, p);
        cpl_free(tmpstr);

        /* --cmin */
        tmpstr = cpl_sprintf("%s.%s", recipe_name, "cmin");
        p = cpl_parameter_new_value(tmpstr, CPL_TYPE_INT,
            "The number of minimum pixel values to clip with min/max-clipping.",
                recipe_name, DEF_NR_MIN_REJ);
        cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "cmin");
        cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
        cpl_parameterlist_append(pl, p);
        cpl_free(tmpstr);
    }

    if (cpl_error_get_code() != CPL_ERROR_NONE) {
        return -1;
    }
    return 0;
}

/*----------------------------------------------------------------------------*/
/**
  @brief  Add band-parameters to parameterlist
  @param  pl             The already allocated parameterlist
  @param  recipe_name    The name of the recipe in form of "kmo.kmo_xxx"
  This functions is called when creating the parameters for the recipes which
  do reconstruct frames
 */
/*----------------------------------------------------------------------------*/
void kmos_band_pars_create(
        cpl_parameterlist   *   pl, 
        const char          *   recipe_name)
{
    cpl_parameter   *   p;
    char            *   tmpstr ;

    /* Check Entries */
    if (pl == NULL || recipe_name == NULL)  return ;

    /* --b_samples */
    tmpstr = cpl_sprintf("%s.%s", recipe_name, "b_samples");
    p = cpl_parameter_new_value(tmpstr, CPL_TYPE_INT,
            "The number of samples in wavelength for the reconstructed cube",
            recipe_name, kmclipm_band_samples);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "b_samples");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(pl, p);
    cpl_free(tmpstr);

    /* --b_start */
    /*
    tmpstr = cpl_sprintf("%s.%s", recipe_name, "b_start");
    p = cpl_parameter_new_value(tmpstr, CPL_TYPE_DOUBLE,
            "The lowest wavelength [um] to use when reconstructing. "
            "Derived by default, depending on the band",
            recipe_name, -1.0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "b_start");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(pl, p);
    cpl_free(tmpstr);
    */

    /* --b_end */
    /*
    tmpstr = cpl_sprintf("%s.%s", recipe_name, "b_end");
    p = cpl_parameter_new_value(tmpstr, CPL_TYPE_DOUBLE,
            "The highest wavelength [um] to use when reconstructing. "
            "Derived by default, depending on the band",
            recipe_name, -1.0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "b_end");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(pl, p);
    cpl_free(tmpstr);
    */

    return ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Load combine parameters from parameterlist
  @param pl             The parameterlist
  @param recipe_name    The name of the recipe in form of "kmo.kmo_xxx"
  @param cmethod        (Output) Will hold the combing method
  @param ccalc_noise    (Output) 1 if noise should be (re)calculated,
                        0 otherwise
  @param cpos_rej       (Output) Will hold the positive rejection factor for
                        ksigma-rejection
  @param cneg_rej       (Output) Will hold the negative rejection factor for
                        ksigma-rejection
  @param citer          (Output) Will hold the number of iterations for
                        ksigma-rejection
  @param cmin           (Output) Will hold the number of min pixels to
                        reject using cmethod "min_max"
  @param cmax           (Output) Will hold the number of max pixels to
                        reject using cmethod "min_max"
  @param no_method      If FALSE "cmethod" will be added as parameter
                        If TRUE it isn't added
  @return CPL_ERROR_NONE in case of success
  This functions is called at the beginning of recipes which
  do combine frames.
  Possible cpl_error_code set in this function:
  @li CPL_ERROR_NULL_INPUT    if any of the needed arguments is NULL
 */
/*----------------------------------------------------------------------------*/
cpl_error_code kmos_combine_pars_load(
        cpl_parameterlist   *   pl,
        const char          *   recipe_name,
        const char          **  cmethod,
        double              *   cpos_rej,
        double              *   cneg_rej,
        int                 *   citer,
        int                 *   cmin,
        int                 *   cmax,
        int                     no_cmethod)
{
    char    *   tmpstr ;

    /* Check Entries */
    if (pl ==NULL || recipe_name == NULL) {
        cpl_msg_error(__func__, "NULL inputs") ;
        cpl_error_set(__func__, CPL_ERROR_NULL_INPUT) ;
        return CPL_ERROR_NULL_INPUT ;
    }

    /* cmethod */
    if (!no_cmethod && cmethod != NULL) {
        tmpstr = cpl_sprintf("%s.%s", recipe_name, "cmethod");
        *cmethod = kmo_dfs_get_parameter_string(pl, tmpstr);
        cpl_free(tmpstr);
        if (strcmp(*cmethod, "ksigma")  && strcmp(*cmethod, "sum") &&
                strcmp(*cmethod, "average") && strcmp(*cmethod, "median") &&
                strcmp(*cmethod, "min_max")) {
            cpl_msg_error(__func__, "Invalid method specified") ;
            cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
            return CPL_ERROR_ILLEGAL_INPUT ;
        }
    }
    
    /* cpos_rej */
    if (cpos_rej != NULL) {
        tmpstr = cpl_sprintf("%s.%s", recipe_name, "cpos_rej");
        *cpos_rej = kmo_dfs_get_parameter_double(pl, tmpstr);
        cpl_free(tmpstr);
        if (*cpos_rej < 0.0) {
            cpl_msg_error(__func__, "cpos_rej must be >= 0") ;
            cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
            return CPL_ERROR_ILLEGAL_INPUT ;
        }
    }

    /* cneg_rej */
    if (cneg_rej != NULL) {
        tmpstr = cpl_sprintf("%s.%s", recipe_name, "cneg_rej");
        *cneg_rej = kmo_dfs_get_parameter_double(pl, tmpstr);
        cpl_free(tmpstr);
        if (*cneg_rej < 0.0) {
            cpl_msg_error(__func__, "cneg_rej must be >= 0") ;
            cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
            return CPL_ERROR_ILLEGAL_INPUT ;
        }
    }

    /* citer */
    if (citer != NULL) {
        tmpstr = cpl_sprintf("%s.%s", recipe_name, "citer");
        *citer = kmo_dfs_get_parameter_int(pl, tmpstr);
        cpl_free(tmpstr);
        if (*citer < 0) {
            cpl_msg_error(__func__, "citer must be >= 0") ;
            cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
            return CPL_ERROR_ILLEGAL_INPUT ;
        }
    }

    /* cmin */
    if (!no_cmethod && cmin != NULL) {
        tmpstr = cpl_sprintf("%s.%s", recipe_name, "cmin");
        *cmin = kmo_dfs_get_parameter_int(pl, tmpstr);
        cpl_free(tmpstr); 
        if (*cmin < 0) {
            cpl_msg_error(__func__, "cmin must be >= 0") ;
            cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
            return CPL_ERROR_ILLEGAL_INPUT ;
        }
    }

    /* cmax */
    if (!no_cmethod && cmax != NULL) {
        tmpstr = cpl_sprintf("%s.%s", recipe_name, "cmax");
        *cmax = kmo_dfs_get_parameter_int(pl, tmpstr);
        cpl_free(tmpstr);
        if (*cmax < 0) {
            cpl_msg_error(__func__, "cmax must be >= 0") ;
            cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
            return CPL_ERROR_ILLEGAL_INPUT ;
        }
    }
    return CPL_ERROR_NONE ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Load band-parameters from parameterlist
  @param pl             The parameterlist
  @param recipe_name    The name of the recipe in form of "kmo.kmo_xxx"
  @return CPL_ERROR_NONE in case of success
  This function collects reconstruction input parameters

  Possible cpl_error_code set in this function:
  @li CPL_ERROR_NULL_INPUT    if any of the needed arguments is NULL.
 */
/*----------------------------------------------------------------------------*/
cpl_error_code kmos_band_pars_load(
        cpl_parameterlist   *   pl,
        const char          *   recipe_name)
{
    char    *   tmpstr ;
   
    /* Check Entries */
    if (pl ==NULL || recipe_name == NULL) {
        cpl_msg_error(__func__, "NULL inputs") ;
        cpl_error_set(__func__, CPL_ERROR_NULL_INPUT) ;
        return CPL_ERROR_NULL_INPUT ;
    }
    
    /* Initialise */

    /* b_samples */
    tmpstr = cpl_sprintf("%s.%s", recipe_name, "b_samples");
    kmclipm_band_samples = kmo_dfs_get_parameter_int(pl, tmpstr);
    cpl_free(tmpstr);
    if (kmclipm_band_samples <= 2) {
        cpl_msg_error(__func__, "b_samples must be greater than 2") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return CPL_ERROR_ILLEGAL_INPUT ;
    }

    /* b_start */
    /*
    tmpstr = cpl_sprintf("%s.%s", recipe_name, "b_start");
    kmclipm_band_start = kmo_dfs_get_parameter_double(pl, tmpstr);
    cpl_free(tmpstr);

    if (!(fabs(kmclipm_band_start + 1.0) < tol)) {
        // Check only if value has been provided
        if (kmclipm_band_start <= 0.0) {
            cpl_msg_error(__func__, "b_start must be greater than 0.0") ;
            cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
            return CPL_ERROR_ILLEGAL_INPUT ;
        }
    }
    */

    /* b_end */
    /*
    tmpstr = cpl_sprintf("%s.%s", recipe_name, "b_end");
    kmclipm_band_end = kmo_dfs_get_parameter_double(pl, tmpstr);
    cpl_free(tmpstr); 
    if (!(fabs(kmclipm_band_end + 1.0) < tol)) {
        // Check only if value has been provided
        if (kmclipm_band_end <= kmclipm_band_start) {
            cpl_msg_error(__func__, "b_end must be greater than b_start") ;
            cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
            return CPL_ERROR_ILLEGAL_INPUT ;
        }
    }
    */
    return CPL_ERROR_NONE;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Creates a vector with wavelength values according to header data.
  @param size     Size of the vector to create (size > 0).
  @param crpix    Reference pixel position (CRPIX3 from FITS-header).
  @param crval    Value of reference pixel (CRVAL3 from FITS-header).
  @param cdelt    Delta between pixels (CDELT3 from FITS-header).
  @return A vector in case of success, NULL otherwise.
  The returned vector has to be deallocated with cpl_vector_delete().
*/
/*----------------------------------------------------------------------------*/
cpl_vector* kmo_create_lambda_vec(
        int     size,
        int     crpix,
        double  crval,
        double  cdelt)
{
    double          *vec_data	= NULL;
    int             i           = 0;
    cpl_vector      *vec_lambda = NULL;

    KMO_TRY
    {
        KMO_TRY_ASSURE(size > 0, CPL_ERROR_ILLEGAL_INPUT,
                "Size must be greater than zero!");
        KMO_TRY_EXIT_IF_NULL(vec_lambda = cpl_vector_new(size));
        KMO_TRY_EXIT_IF_NULL(vec_data = cpl_vector_get_data(vec_lambda));
        for (i = 1; i <= size; i++) {
            vec_data[i-1] = (i - crpix) * cdelt + crval;
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        cpl_vector_delete(vec_lambda); vec_lambda = NULL;
    }
    return vec_lambda;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Check if a value in a vector is within a certain spectral range.
  @param ranges        The vector containing the wavelength-ranges which will
                       be valid. The vector MUST be even-sized. Each pair of
                       values designates the start- and end-point of a valid
                       wavelength-range.
  @param ifu_lambda_in The wavelength-vector of a certain IFU.
  @param index         The index to the value in @c ifu_lambda_in to examine.
                       Starts at zero.
  @return 1 if value is in range, 0 otherwise.
  This function checks if a value at a specified position in @c ifu_lambda_in
  lies inbetween a specified range.
  Possible cpl_error_code set in this function:
  @c CPL_ERROR_NULL_INPUT    Not enough inputs defined
  @c CPL_ERROR_ILLEGAL_INPUT @c ranges is not even-sized
                             or index is larger or smaller than the size of
                             @c ifu_lambda_in
*/
/*----------------------------------------------------------------------------*/
int kmo_is_in_range(
        const cpl_vector    *   ranges,
        const cpl_vector    *   ifu_lambda_in,
        int                     index)
{
    int     size    = 0,
            i       = 0,
            ret_val = 0;
    double  start   = 0.0,
            stop    = 0.0,
            val     = 0.0;

    KMO_TRY
    {
        /* Check inputs */
        KMO_TRY_ASSURE((ranges != NULL) && (ifu_lambda_in != NULL),
                CPL_ERROR_NULL_INPUT, "Not all input data is provided!");
        size = cpl_vector_get_size(ranges);
        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_ASSURE((size % 2) == 0, CPL_ERROR_ILLEGAL_INPUT,
                "Ranges must have an even number of elements!");
        KMO_TRY_ASSURE((index >= 0) && 
                (index < cpl_vector_get_size(ifu_lambda_in)), 
                CPL_ERROR_ILLEGAL_INPUT, "Index < 0 or larger than vector!");

        val  = cpl_vector_get(ifu_lambda_in, index);
        KMO_TRY_CHECK_ERROR_STATE();
        for (i = 0; i < size; i += 2) {
            start = cpl_vector_get(ranges, i);
            stop = cpl_vector_get(ranges, i + 1);
            if ((start <= val) && (val <= stop + 1e-6))     ret_val = 1;
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_val = 0;
    }
    return ret_val;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Identifies spectral slices of a cube to collapse.
  @param ranges         An even sized vector defining the wavelength ranges
                        (optional).
  @param ifu_crpix      Reference pixel position (CRPIX3 from FITS-header).
  @param ifu_crval      Value of reference pixel (CRVAL3 from FITS-header).
  @param ifu_cdelt      Delta between pixels (CDELT3 from FITS-header).
  @param size_ifu       The number of spectral slices of the IFU.
  @return A vector in case of success, NULL otherwise. The vector is of the
          same size as @c size_ifu and contains ones for valid slices and
          zeros for invalid slices.
  This function identifies spectral slices of a cube to collapse. It takes a
  a range-vector created with @c kmo_identify_ranges .
  The returned vector has to be deallocated with cpl_vector_delete().
*/
/*----------------------------------------------------------------------------*/
cpl_vector* kmo_identify_slices(
        const cpl_vector    *   ranges,
        double                  ifu_crpix,
        double                  ifu_crval,
        double                  ifu_cdelt,
        int                     size_ifu)
{
    cpl_vector      *slices           = NULL,
                    *ifu_lambda_in    = NULL;
    double          *data             = NULL;

    int             i                 = 0;
    KMO_TRY
    {
        if (ranges != NULL) {
            /* create lambda-vector for IFU */
            KMO_TRY_EXIT_IF_NULL(
                ifu_lambda_in = kmo_create_lambda_vec(size_ifu, ifu_crpix, 
                    ifu_crval, ifu_cdelt));
        }

        /* allocate output vector */
        KMO_TRY_EXIT_IF_NULL(slices = cpl_vector_new(size_ifu));
        KMO_TRY_EXIT_IF_ERROR(cpl_vector_fill(slices, 0.0));

       /* populate output vector */
        KMO_TRY_EXIT_IF_NULL(data = cpl_vector_get_data(slices));
        for (i = 0; i < size_ifu; i++) {
            if ((ranges == NULL) || kmo_is_in_range(ranges, ifu_lambda_in, i)) {
                data[i] = 1;
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        cpl_vector_delete(slices); slices = NULL;
    }
    cpl_vector_delete(ifu_lambda_in); ifu_lambda_in = NULL;
    return slices;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Parses a text string and extracts pairs of values as ranges.
  @param txt   String to parse.
  @return   A vector in case of success, NULL otherwise.

  The string has to be of following form: "0.8,1.2" or "0.8,0.9;1.0,2.0" etc.
  Start- and end-value of a range have to be separated by a comma.
  Multiple ranges have to be separated with a semi-colon (The decimal point
  has to be a point).
  The returned vector will be even sized. The format is as follows:
  [start range 1, end range 1, start range 2, end range 2, ...]
  The returned vector has to be deallocated with cpl_vector_delete().
  Possible cpl_error_code set in this function:
  @li CPL_ERROR_NULL_INPUT    if @c txt pointer is NULL
  @li CPL_ERROR_ILLEGAL_INPUT if the @c txtpointer doesn't contain a end-value
                              if a start-value has been provided.
*/
/*----------------------------------------------------------------------------*/
cpl_vector* kmo_identify_ranges(const char *txt)
{
    cpl_vector      *ranges         = NULL;
    char            **split_ranges  = NULL,
                    **split_values  = NULL;
    int             i               = 0,
                    j               = 0,
                    g               = 0,
                    size            = 0;
    double          *pranges        = NULL;

    KMO_TRY
    {
        /* Check inputs */
        KMO_TRY_ASSURE(txt != NULL, CPL_ERROR_NULL_INPUT,
                "Not all input data is provided!");

        if (strcmp(txt, "") != 0) {
            KMO_TRY_EXIT_IF_NULL(split_ranges = kmo_strsplit(txt, ";", NULL));

            // get size of vector to create
            while (split_ranges[i] != NULL) {
                KMO_TRY_EXIT_IF_NULL(
                    split_values = kmo_strsplit(split_ranges[i], ",", NULL));

                while (split_values[j] != NULL) {
                    size++;
                    j++;
                }
                if (j != 2) {
                    KMO_TRY_ERROR_SET_MSG(CPL_ERROR_ILLEGAL_INPUT,
                            "Range-string incomplete!");
                    KMO_TRY_EXIT();
                }
                kmo_strfreev(split_values); split_values = NULL;
                i++;
                j = 0;
            }
            if ((size % 2) != 0) {
                KMO_TRY_ERROR_SET_MSG(CPL_ERROR_ILLEGAL_INPUT,
                        "Range-string incomplete!");
                KMO_TRY_EXIT();
            }

            // fill vector with extracted values
            KMO_TRY_EXIT_IF_NULL(ranges = cpl_vector_new(size));
            KMO_TRY_EXIT_IF_NULL(pranges = cpl_vector_get_data(ranges));
            i = 0;
            j = 0;
            while (split_ranges[i] != NULL) {
                KMO_TRY_EXIT_IF_NULL(
                    split_values = kmo_strsplit(split_ranges[i], ",", NULL));
                while (split_values[j] != NULL) {
                    pranges[g] = atof(split_values[j]);
                    j++;
                    g++;
                }
                kmo_strfreev(split_values);
                split_values = NULL;
                i++;
                j = 0;
            }
            KMO_TRY_CHECK_ERROR_STATE();
            kmo_strfreev(split_ranges);
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        cpl_vector_delete(ranges); ranges = NULL;
        if (strcmp(txt, "") != 0) {
            kmo_strfreev(split_ranges);
            kmo_strfreev(split_values);
        }
    }
    return ranges;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Parses a text string and extracts values.
  @param txt   String to parse.
  @return   A vector in case of success, NULL otherwise.

  The string has to be of following form: "0.8" or "0.8;0.9" etc.
  Values have to be separated with a semi-colon (The decimal point
  has to be a point).
  The returned vector has to be deallocated with cpl_vector_delete().
  Possible cpl_error_code set in this function:
  @li CPL_ERROR_NULL_INPUT    if @c txt pointer is NULL
  @li CPL_ERROR_ILLEGAL_INPUT if the @c txtpointer doesn't contain a end-value
                              if a start-value has been provided.
*/
/*----------------------------------------------------------------------------*/
cpl_vector* kmo_identify_values(const char *txt)
{
    cpl_vector      *values         = NULL;
    char            **split_values  = NULL,
                    *tmp            = NULL;
    int             size            = 0;
    double          *pvalues        = NULL;

    KMO_TRY
    {
        /* Check inputs */
        KMO_TRY_ASSURE(txt != NULL, CPL_ERROR_NULL_INPUT,
                "Not all input data is provided!");

        if (strcmp(txt, "") != 0) {
            // check for wrong delimiters
            KMO_TRY_ASSURE((strstr(txt, ",") == NULL) &&
                    (strstr(txt, ":") == NULL), CPL_ERROR_ILLEGAL_INPUT,
                    "',' and ':'aren't allowed in txt!");

            KMO_TRY_EXIT_IF_NULL(split_values = kmo_strsplit(txt, ";", &size));

            // fill vector with extracted values
            KMO_TRY_EXIT_IF_NULL(values = cpl_vector_new(size));
            KMO_TRY_EXIT_IF_NULL(pvalues = cpl_vector_get_data(values));
            size = 0;
            while (split_values[size] != NULL) {
                pvalues[size] = atof(split_values[size]);
                size++;
            }
            KMO_TRY_CHECK_ERROR_STATE();
            kmo_strfreev(split_values);
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        cpl_free(tmp); tmp = NULL;
        cpl_vector_delete(values); values = NULL;
        if (strcmp(txt, "") != 0)   kmo_strfreev(split_values);
    }
    return values;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Calculate the mean of an image considering a bad pixel mask.
  @param data            Image to be averaged.
  @param bad_pix_mask    The bad pixel mask.
  @return   The mean.
  All pixels in @c bad_pix_mask with a value of 0.0 are ignored.
  Possible cpl_error_code set in this function:
  @li CPL_ERROR_NULL_INPUT    if @c data or @c bad_pix_mask is NULL.
  @li CPL_ERROR_ILLEGAL_INPUT if @c data or @c bad_pix_mask don't have the
                              same size.
*/
/*----------------------------------------------------------------------------*/
double kmo_image_get_mean_badpix(
        const cpl_image     *   data,
        const cpl_image     *   bad_pix_mask)
{
    int         nr_valid_pix    = 0,
                nx              = 0,
                ny              = 0,
                i               = 0,
                j               = 0;
    const float *pbad_pix_mask  = NULL,
                *pdata          = NULL;
    double      ret_val         = 0.0,
                sum             = 0.0;

    KMO_TRY
    {
        KMO_TRY_ASSURE((data != NULL) && (bad_pix_mask != NULL),
                CPL_ERROR_NULL_INPUT, "No input data is provided!");
        nx = cpl_image_get_size_x(data);
        ny = cpl_image_get_size_y(data);
        nr_valid_pix = nx * ny;

        KMO_TRY_ASSURE((nx == cpl_image_get_size_x(bad_pix_mask)) &&
                (ny == cpl_image_get_size_y(bad_pix_mask)),
                CPL_ERROR_ILLEGAL_INPUT,
                "Data and bad pixel mask must have same dimensions!");

        KMO_TRY_EXIT_IF_NULL(pdata = cpl_image_get_data_float_const(data));

        KMO_TRY_EXIT_IF_NULL(
            pbad_pix_mask = cpl_image_get_data_float_const(bad_pix_mask));

        for (j = 0; j < ny; j++) {
            for (i = 0; i < nx; i++) {
                if (pbad_pix_mask[i+j*nx] < 0.5)    nr_valid_pix--;
                else                                sum += pdata[i+j*nx];
            }
        }
        ret_val = sum / nr_valid_pix;
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_val = 0.0;
    }
    return ret_val;
}

/*----------------------------------------------------------------------------*/
/**
  @brief  Calculate the median of an image considering a bad pixel mask.
  @param data            Input Image.
  @param bad_pix_mask    The bad pixel mask.
  @return     The median.
  All pixels in @c bad_pix_mask with a value of 0.0 are ignored.
  Possible cpl_error_code set in this function:
  @li CPL_ERROR_NULL_INPUT    if @c data or @c bad_pix_mask is NULL.
  @li CPL_ERROR_ILLEGAL_INPUT if @c data or @c bad_pix_mask don't have the
                              void *  cpl_malloc (size_t nbytes)same size.
*/
/*----------------------------------------------------------------------------*/
double kmo_image_get_median_badpix(
        const cpl_image *   data,
        const cpl_image *   bad_pix_mask)
{
    int             nx              = 0,
                    ny              = 0,
                    i               = 0,
                    j               = 0;
    const float     *pbad_pix_mask  = NULL,
                    *pdata          = NULL;
    double          ret_val         = 0.0;
    kmclipm_vector  *vec            = NULL;

    KMO_TRY
    {
        KMO_TRY_ASSURE((data != NULL) && (bad_pix_mask != NULL),
                CPL_ERROR_NULL_INPUT, "No input data is provided!");
        nx = cpl_image_get_size_x(data);
        ny = cpl_image_get_size_y(data);
        KMO_TRY_ASSURE((nx == cpl_image_get_size_x(bad_pix_mask)) &&
                (ny == cpl_image_get_size_y(bad_pix_mask)),
                CPL_ERROR_ILLEGAL_INPUT,
                "Data and bad pixel mask must have same dimensions!");

        KMO_TRY_EXIT_IF_NULL(vec = kmclipm_vector_new(nx*ny));
        KMO_TRY_EXIT_IF_NULL(pdata = cpl_image_get_data_float_const(data));
        KMO_TRY_EXIT_IF_NULL(
            pbad_pix_mask = cpl_image_get_data_float_const(bad_pix_mask));

        for (j = 0; j < ny; j++) {
            for (i = 0; i < nx; i++) {
                if (pbad_pix_mask[i+j*nx] >= 0.5) {
                    kmclipm_vector_set(vec, i+j*nx, pdata[i+j*nx]);
                    KMO_TRY_CHECK_ERROR_STATE();
                }
            }
        }
        ret_val = kmclipm_vector_get_median(vec, KMCLIPM_ARITHMETIC);
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_val = 0.0;
    }
    return ret_val;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Calculate the stdev of an image considering a bad pixel mask.
  @param data            Input data.
  @param bad_pix_mask    The bad pixel mask.
  @return   The standard deviation of @c data .
  All pixels in @c bad_pix_mask with a value of 0.0 are ignored.
  Possible cpl_error_code set in this function:
  @li CPL_ERROR_NULL_INPUT    if @c data or @c bad_pix_mask is NULL.
  @li CPL_ERROR_ILLEGAL_INPUT if @c data or @c bad_pix_mask don't have the
                              same size.
*/
/*----------------------------------------------------------------------------*/
double kmo_image_get_stdev_badpix(
        const cpl_image *   data,
        const cpl_image *   bad_pix_mask)
{
    int         nr_valid_pix    = 0,
                nx              = 0,
                ny              = 0,
                i               = 0,
                j               = 0;
    const float *pbad_pix_mask  = NULL,
                *pdata          = NULL;
    double      ret_val         = 0.0,
                sum             = 0.0,
                tmp             = 0.0;

    KMO_TRY
    {
        KMO_TRY_ASSURE((data != NULL) && (bad_pix_mask != NULL),
                CPL_ERROR_NULL_INPUT, "No input data is provided!");
        nx = cpl_image_get_size_x(data);
        ny = cpl_image_get_size_y(data);
        nr_valid_pix = nx * ny;
        KMO_TRY_ASSURE((nx == cpl_image_get_size_x(bad_pix_mask)) &&
                (ny == cpl_image_get_size_y(bad_pix_mask)),
                CPL_ERROR_ILLEGAL_INPUT,
                "Data and bad pixel mask must have same dimensions!");

        tmp = kmo_image_get_mean_badpix(data, bad_pix_mask);
        KMO_TRY_CHECK_ERROR_STATE();

        KMO_TRY_EXIT_IF_NULL(pdata = cpl_image_get_data_float_const(data));
        KMO_TRY_EXIT_IF_NULL(
            pbad_pix_mask = cpl_image_get_data_float_const(bad_pix_mask));

        for (j = 0; j < ny; j++) {
            for (i = 0; i < nx; i++) {
                if (pbad_pix_mask[i+j*nx] < 0.5)    nr_valid_pix--;
                else                        sum += pow(pdata[i+j*nx] - tmp, 2);
            }
        }
        ret_val = sqrt(sum / (nr_valid_pix - 1));
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_val = 0.0;
    }
    return ret_val;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Calculate the stdev with median of an image considering a BPM
  @param data            Input data.
  @param bad_pix_mask    The bad pixel mask.
  @return The standard deviation of @c data .
  All pixels in @c bad_pix_mask with a value of 0.0 are ignored.
  Possible cpl_error_code set in this function:
  @li CPL_ERROR_NULL_INPUT    if @c data or @c bad_pix_mask is NULL.
  @li CPL_ERROR_ILLEGAL_INPUT if @c data or @c bad_pix_mask don't have the
                              same size.
*/
/*----------------------------------------------------------------------------*/
double kmo_image_get_stdev_median_badpix(
        const cpl_image *   data,
        const cpl_image *   bad_pix_mask)
{
    int         nr_valid_pix    = 0,
                nx              = 0,
                ny              = 0,
                i               = 0,
                j               = 0;
    const float *pbad_pix_mask  = NULL,
                *pdata          = NULL;
    double      ret_val         = 0.0,
                sum             = 0.0,
                tmp             = 0.0;

    KMO_TRY
    {
        KMO_TRY_ASSURE((data != NULL) && (bad_pix_mask != NULL),
                CPL_ERROR_NULL_INPUT, "No input data is provided!");
        nx = cpl_image_get_size_x(data);
        ny = cpl_image_get_size_y(data);
        nr_valid_pix = nx * ny;

        KMO_TRY_ASSURE((nx == cpl_image_get_size_x(bad_pix_mask)) &&
                (ny == cpl_image_get_size_y(bad_pix_mask)),
                CPL_ERROR_ILLEGAL_INPUT,
                "Data and bad pixel mask must have same dimensions!");

        tmp = kmo_image_get_median_badpix(data, bad_pix_mask);
        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_EXIT_IF_NULL(pdata = cpl_image_get_data_float_const(data));
        KMO_TRY_EXIT_IF_NULL(
            pbad_pix_mask = cpl_image_get_data_float_const(bad_pix_mask));

        for (j = 0; j < ny; j++) {
            for (i = 0; i < nx; i++) {
                if (pbad_pix_mask[i+j*nx] < 0.5)    nr_valid_pix--;
                else                        sum += pow(pdata[i+j*nx] - tmp, 2);
            }
        }
        ret_val = sqrt(sum / (nr_valid_pix - 1));
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_val = 0.0;
    }
    return ret_val;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Calculate the stdev with median of an image.
  @param data            Input data.
  @return   The standard deviation of @c data .
  Rejected values in @c data are taken into account.
  Possible cpl_error_code set in this function:
  @li CPL_ERROR_NULL_INPUT    if @c data is NULL.
*/
/*----------------------------------------------------------------------------*/
double kmo_image_get_stdev_median(const cpl_image *data)
{
    int         nr_valid_pix    = 0,
                nx              = 0,
                ny              = 0;
    const float *pdata          = NULL;
    double      ret_val         = 0.0,
                sum             = 0.0,
                tmp             = 0.0;

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

        nx = cpl_image_get_size_x(data);
        ny = cpl_image_get_size_y(data);
        nr_valid_pix = nx * ny;
        tmp = cpl_image_get_median(data);
        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_EXIT_IF_NULL(pdata = cpl_image_get_data_float_const(data));

        int ix = 0, iy = 0;
        for (iy = 0; iy < ny; iy++) {
            for (ix = 0; ix < nx; ix++) {
                if (cpl_image_is_rejected(data, ix+1, iy+1))    nr_valid_pix--;
                else                    sum += pow(pdata[ix+iy*nx] - tmp, 2);
            }
        }
        ret_val = sqrt(sum / (nr_valid_pix - 1));
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_val = 0.0;
    }
    return ret_val;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Checks consistency of frames regarding grating, filter & rotation.
  @param frameset         The frameset
  @param frame_type       The type of the frames to examine (e.g. FLAT)
  @param check_grating    1: if gratings should be checked, 0 otherwise
  @param check_filter     1: if filters should be checked, 0 otherwise
  @param check_rotation   1: if rotation should be checked, 0 otherwise
  @param rot_tol          Tolerance for rotator offset
  @return   error code

  If @code check_grating is set following keywords will be checked for
  existence and if they match for each detector and each provided frame of
  type @code frame_type:
     - ESO INS GRAT1 ID
     - ESO INS GRAT2 ID
     - ESO INS GRAT3 ID
  If @code check_filter is set, the same tests are performed for keywords:
     - ESO INS FILT1 ID
     - ESO INS FILT2 ID
     - ESO INS FILT3 ID
  If @code check_rotation is set, test is performed to assure that the same
  rotation offset preset is used:
     - ESO OCS ROT NAANGLE
  Possible cpl_error_code set in this function:
      CPL_ERROR_ILLEGAL_INPUT if any of the keywords don't match.
      CPL_ERROR_NULL_INPUT if @code frameset or @code frame_type is empty
*/
/*----------------------------------------------------------------------------*/
cpl_error_code kmo_check_frameset_setup(
        cpl_frameset    *   frameset,
        const char      *   frame_type,
        int                 check_grating,
        int                 check_filter,
        int                 check_rotation)
{
    cpl_error_code ret_error = CPL_ERROR_NONE;
    cpl_frame        *frame1                = NULL,
                     *frame2                = NULL;
    cpl_propertylist *h                     = NULL;
    const char       *tmp_str               = NULL;
    char             *keyword               = NULL;

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

        // load 1st frame
        KMO_TRY_EXIT_IF_NULL(frame1 = kmo_dfs_get_frame(frameset, frame_type));

        if (cpl_frameset_count_tags(frameset, frame_type) == 1)
        {
            h = kmclipm_propertylist_load(cpl_frame_get_filename(frame1), 0);
            if (cpl_error_get_code() != CPL_ERROR_NONE) {
                cpl_msg_error(__func__, "File not found: %s!",
                        cpl_frame_get_filename(frame1));
                KMO_TRY_CHECK_ERROR_STATE();
            }

            if (check_grating == 1) {
                // check grating for detector1
                KMO_TRY_EXIT_IF_NULL(
                    keyword = cpl_sprintf("%s%d%s", IFU_GRATID_PREFIX, 1,
                        IFU_GRATID_POSTFIX));
                tmp_str = cpl_propertylist_get_string(h, keyword);
                KMO_TRY_ASSURE(tmp_str != NULL, CPL_ERROR_ILLEGAL_INPUT,
                        "keyword \n%s\n of frame %s is missing!",
                        keyword, frame_type);
                cpl_free(keyword); keyword = NULL;

                // check grating for detector2
                KMO_TRY_EXIT_IF_NULL(
                    keyword = cpl_sprintf("%s%d%s", IFU_GRATID_PREFIX, 2, 
                        IFU_GRATID_POSTFIX));
                tmp_str = cpl_propertylist_get_string(h, keyword);
                KMO_TRY_ASSURE(tmp_str != NULL, CPL_ERROR_ILLEGAL_INPUT,
                        "keyword \n%s\n of frame %s is missing!",
                        keyword, frame_type);
                cpl_free(keyword); keyword = NULL;

                // check grating for detector3
                KMO_TRY_EXIT_IF_NULL(
                    keyword = cpl_sprintf("%s%d%s", IFU_GRATID_PREFIX, 3, 
                        IFU_GRATID_POSTFIX));
                tmp_str = cpl_propertylist_get_string(h, keyword);
                KMO_TRY_ASSURE(tmp_str != NULL, CPL_ERROR_ILLEGAL_INPUT,
                        "keyword \n%s\n of frame %s is missing!",
                        keyword, frame_type);
                cpl_free(keyword); keyword = NULL;
            }

            if (check_filter == 1) {
                // check filter for detector1
                KMO_TRY_EXIT_IF_NULL(
                    keyword = cpl_sprintf("%s%d%s", IFU_FILTID_PREFIX, 1, 
                        IFU_FILTID_POSTFIX));
                tmp_str = cpl_propertylist_get_string(h, keyword);
                KMO_TRY_ASSURE(tmp_str != NULL, CPL_ERROR_ILLEGAL_INPUT,
                        "keyword \n%s\n of frame %s is missing!",
                        keyword, frame_type);
                cpl_free(keyword); keyword = NULL;

                // check filter for detector2
                KMO_TRY_EXIT_IF_NULL(
                    keyword = cpl_sprintf("%s%d%s", IFU_FILTID_PREFIX, 2, 
                        IFU_FILTID_POSTFIX));
                tmp_str = cpl_propertylist_get_string(h, keyword);
                KMO_TRY_ASSURE(tmp_str != NULL, CPL_ERROR_ILLEGAL_INPUT,
                        "keyword \n%s\n of frame %s is missing!",
                        keyword, frame_type);
                cpl_free(keyword); keyword = NULL;

                // check filter for detector3
                KMO_TRY_EXIT_IF_NULL(
                    keyword = cpl_sprintf("%s%d%s", IFU_FILTID_PREFIX, 3, 
                        IFU_FILTID_POSTFIX));
                tmp_str = cpl_propertylist_get_string(h, keyword);
                KMO_TRY_ASSURE(tmp_str != NULL, CPL_ERROR_ILLEGAL_INPUT,
                        "keyword \n%s\n of frame %s is missing!",
                        keyword, frame_type);
                cpl_free(keyword); keyword = NULL;
            }

            if (check_rotation == 1) {
                // check rotation offset
                KMO_TRY_EXIT_IF_NULL(keyword = cpl_sprintf("%s", ROTANGLE));
                cpl_propertylist_get_double(h, keyword);
                if (cpl_error_get_code() != CPL_ERROR_NONE) {
                    KMO_TRY_ASSURE(1 == 0, CPL_ERROR_ILLEGAL_INPUT,
                            "keyword \n%s\n of frame %s is missing!",
                            keyword, frame_type);
                    cpl_free(keyword); keyword = NULL;
                }
            }
            cpl_propertylist_delete(h); h = NULL;
        } else {
            frame2 = kmo_dfs_get_frame(frameset, NULL);
            KMO_TRY_CHECK_ERROR_STATE();
            while (frame2 != NULL) {
                KMO_TRY_EXIT_IF_ERROR(
                    kmo_priv_compare_frame_setup(frame1, frame2, frame_type, 
                        frame_type,check_grating,check_filter,check_rotation));
                frame2 = kmo_dfs_get_frame(frameset, NULL);
                KMO_TRY_CHECK_ERROR_STATE();
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }
    cpl_propertylist_delete(h); h = NULL;
    cpl_free(keyword); keyword = NULL;
    return ret_error;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Checks consistency of frames regarding grating, filter & rotation.
  @param frameset         The frameset
  @param frame_type1      The type of the 1st frame to examine (e.g. FLAT)
  @param frame_type2      The type of the 2nd frame to examine (e.g. DARK)
  @param check_grating    1: if gratings should be checked, 0 otherwise
  @param check_filter     1: if filters should be checked, 0 otherwise
  @param check_rotation   1: if rotation should be checked, 0 otherwise
  @param rot_tol          Tolerance for rotator offset
  @return   error code
  If @code check_grating is set following keywords will be checked for
  existence and if they match for each detector and each provided frame of
  type @code frame_type:
     - ESO INS GRAT1 ID
     - ESO INS GRAT2 ID
     - ESO INS GRAT3 ID
  If @code check_filter is set, the same tests are performed for keywords:
     - ESO INS FILT1 ID
     - ESO INS FILT2 ID
     - ESO INS FILT3 ID
  If @code check_rotation is set, test is performed to assure that the same
  rotation offset preset is used:
     - ESO OCS ROT NAANGLE
  Possible cpl_error_code set in this function:
      CPL_ERROR_ILLEGAL_INPUT if any of the keywords don't match.
      CPL_ERROR_NULL_INPUT if @code frameset or @code frame_type is empty
*/
/*----------------------------------------------------------------------------*/
cpl_error_code kmo_check_frame_setup(
        cpl_frameset    *   frameset,
        const char      *   frame_type1,
        const char      *   frame_type2,
        int                 check_grating,
        int                 check_filter,
        int                 check_rotation)
{
    cpl_error_code ret_error = CPL_ERROR_NONE;
    cpl_frame        *frame1                = NULL,
                     *frame2                = NULL;
    KMO_TRY
    {
        KMO_TRY_ASSURE((frameset != NULL) && (frame_type1 != NULL) &&
                (frame_type2 != NULL), CPL_ERROR_NULL_INPUT,
                "Not all input data is provided!");

        // get frames
        KMO_TRY_EXIT_IF_NULL(frame1 = kmo_dfs_get_frame(frameset, frame_type1));
        KMO_TRY_EXIT_IF_NULL(frame2 = kmo_dfs_get_frame(frameset, frame_type2));

        KMO_TRY_EXIT_IF_ERROR(
            kmo_priv_compare_frame_setup(frame1, frame2, frame_type1, 
                frame_type2, check_grating, check_filter, check_rotation));
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }
    return ret_error;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Checks consiѕtency of OH_SPEC filter 
  @param frameset         The frameset
  @param frame_type1      The type of the ref frame to examine (e.g.  XCAL)
  @return   error code
  The following keywords
     - ESO INS FILT1 ID
     - ESO INS FILT2 ID
     - ESO INS FILT3 ID
  are checked from the reference frame. Their value must match the
  OH_SPEC keyword ESO FILT ID
  Possible cpl_error_code set in this function:
      CPL_ERROR_ILLEGAL_INPUT if any of the keywords don't match.
      CPL_ERROR_NULL_INPUT if @code frameset or @code frame_type is empty
*/
/*----------------------------------------------------------------------------*/
cpl_error_code kmo_check_oh_spec_setup(
        cpl_frameset    *   frameset,
        const char      *   frame_type)
{
    cpl_error_code ret_error = CPL_ERROR_NONE;
    cpl_frame        *frame1                = NULL,
                     *frame2                = NULL;
    const char       *tmp_str1              = NULL,
                     *tmp_str2              = NULL;
    char             *keyword               = NULL;
    cpl_propertylist *main_header1          = NULL,
                     *main_header2          = NULL;
    int              i ;

    KMO_TRY
    {
        /* Check entries */
        KMO_TRY_ASSURE((frameset != NULL) && (frame_type != NULL),
                CPL_ERROR_NULL_INPUT, "Not all input data is provided!");
   
        /* Get frames */
        KMO_TRY_EXIT_IF_NULL(frame1 = kmo_dfs_get_frame(frameset, frame_type));
        KMO_TRY_EXIT_IF_NULL(frame2 = kmo_dfs_get_frame(frameset, OH_SPEC));
        
        /* Get main header of reference frame */
        main_header1 = kmclipm_propertylist_load(cpl_frame_get_filename(frame1),
                0);
        if (cpl_error_get_code() != CPL_ERROR_NONE) {
            cpl_msg_error(__func__, "File not found: %s!",
                    cpl_frame_get_filename(frame1));
            KMO_TRY_CHECK_ERROR_STATE();
        }

        /* Get Main header of OH_SPEC */
        main_header2 = kmclipm_propertylist_load(cpl_frame_get_filename(frame2),
                0);
        if (cpl_error_get_code() != CPL_ERROR_NONE) {
            cpl_msg_error(__func__, "File not found: %s!",
                    cpl_frame_get_filename(frame2));
            KMO_TRY_CHECK_ERROR_STATE();
        }

        /* Get Filter info from OH_SPEC */
        keyword = cpl_sprintf("ESO FILT ID");
        tmp_str2 = cpl_propertylist_get_string(main_header2, keyword);
        KMO_TRY_ASSURE(tmp_str2 != NULL, CPL_ERROR_ILLEGAL_INPUT,
                "keyword \n%s\n of frame %s is missing!",
                keyword, OH_SPEC);
        cpl_free(keyword); keyword = NULL;

        /* Compare filter for detector 1,2,3 */
        for (i=1 ; i<=3 ; i++) {
            KMO_TRY_EXIT_IF_NULL(
                keyword = cpl_sprintf("%s%d%s", IFU_FILTID_PREFIX, i, 
                    IFU_FILTID_POSTFIX));
            tmp_str1 = cpl_propertylist_get_string(main_header1, keyword);
            KMO_TRY_ASSURE(tmp_str1 != NULL, CPL_ERROR_ILLEGAL_INPUT,
                    "keyword \n%s\n of frame %s is missing!",
                    keyword, frame_type);
            KMO_TRY_ASSURE(strcmp(tmp_str1, tmp_str2) == 0,
                    CPL_ERROR_ILLEGAL_INPUT,
                    "Filter for detector %d of frames %s and %s don't match!",
                    i, frame_type, OH_SPEC);
            cpl_free(keyword); keyword = NULL;
        }
    }

    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }
    cpl_propertylist_delete(main_header1); main_header1 = NULL;
    cpl_propertylist_delete(main_header2); main_header2 = NULL;
    cpl_free(keyword); keyword = NULL;
    return ret_error;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Checks consistency of two frames regarding grating, filter &rotation
  @param frame1           The 1st frame
  @param frame2           The 2nd frame
  @param frame_type1      The type of the 1st frame to examine (e.g. FLAT)
  @param frame_type2      The type of the 2nd frame to examine (e.g. DARK)
  @param check_grating    1: if gratings should be checked, 0 otherwise
  @param check_filter     1: if filters should be checked, 0 otherwise
  @param check_rotation   1: if rotation should be checked, 0 otherwise
  @return   error code
  If @code check_grating is set following keywords will be checked for
  existence and if they match for each detector and each provided frame of
  type @code frame_type:
     - ESO INS GRAT1 ID
     - ESO INS GRAT2 ID
     - ESO INS GRAT3 ID
  If @code check_filter is set, the same tests are performed for keywords:
     - ESO INS FILT1 ID
     - ESO INS FILT2 ID
     - ESO INS FILT3 ID
  If @code check_rotation is set, test is performed to assure that the same
  rotation offset preset is used:
     - ESO OCS ROT NAANGLE
  Possible cpl_error_code set in this function:
      CPL_ERROR_ILLEGAL_INPUT if any of the keywords don't match.
      CPL_ERROR_NULL_INPUT if @code frameset or @code frame_type is empty
*/
/*----------------------------------------------------------------------------*/
cpl_error_code kmo_priv_compare_frame_setup(
        const cpl_frame     *   frame1,
        const cpl_frame     *   frame2,
        const char          *   frame_type1,
        const char          *   frame_type2,
        int                     check_grating,
        int                     check_filter,
        int                     check_rotation)
{
    cpl_error_code          ret_error = CPL_ERROR_NONE;
    const char          *   tmp_str1              = NULL,
                        *   tmp_str2              = NULL;
    char                *   keyword               = NULL;
    cpl_propertylist    *   main_header1          = NULL,
                        *   main_header2          = NULL;
    int                     i ;

    KMO_TRY
    {
        KMO_TRY_ASSURE((frame1 != NULL) && (frame2 != NULL) && 
                (frame_type1 != NULL) && (frame_type2 != NULL),
                CPL_ERROR_NULL_INPUT, "Not all input data is provided!");

        main_header1 = kmclipm_propertylist_load(cpl_frame_get_filename(frame1),
                0);
        if (cpl_error_get_code() != CPL_ERROR_NONE) {
            cpl_msg_error(__func__, "File not found: %s!",
                    cpl_frame_get_filename(frame1));
            KMO_TRY_CHECK_ERROR_STATE();
        }

        main_header2 = kmclipm_propertylist_load(cpl_frame_get_filename(frame2),
                0);
        if (cpl_error_get_code() != CPL_ERROR_NONE) {
            cpl_msg_error(__func__, "File not found: %s!",
                    cpl_frame_get_filename(frame2));
            KMO_TRY_CHECK_ERROR_STATE();
        }

        if (check_grating == 1) {
            // compare grating for detector 1, 2, 3
            for (i=1 ; i<=3 ; i++) {
                KMO_TRY_EXIT_IF_NULL(
                    keyword = cpl_sprintf("%s%d%s", IFU_GRATID_PREFIX, i, 
                        IFU_GRATID_POSTFIX));
                tmp_str1 = cpl_propertylist_get_string(main_header1, keyword);
                KMO_TRY_ASSURE(tmp_str1 != NULL, CPL_ERROR_ILLEGAL_INPUT,
                        "keyword \n%s\n of frame %s is missing!",
                        keyword, frame_type1);
                tmp_str2 = cpl_propertylist_get_string(main_header2, keyword);
                KMO_TRY_ASSURE(tmp_str2 != NULL, CPL_ERROR_ILLEGAL_INPUT,
                        "keyword \n%s\n of frame %s is missing!",
                        keyword, frame_type2);
                KMO_TRY_ASSURE(strcmp(tmp_str1, tmp_str2) == 0,
                        CPL_ERROR_ILLEGAL_INPUT, 
                        "Grating for detector %d frame %s and %s don't match!",
                        i, frame_type1, frame_type2);
                cpl_free(keyword); keyword = NULL;
            }
        }

        if (check_filter == 1) {
            // compare filter for detector 1,2,3
            for (i=1 ; i<=3 ; i++) {
                KMO_TRY_EXIT_IF_NULL(
                    keyword = cpl_sprintf("%s%d%s", IFU_FILTID_PREFIX, i, 
                        IFU_FILTID_POSTFIX));
                tmp_str1 = cpl_propertylist_get_string(main_header1, keyword);
                KMO_TRY_ASSURE(tmp_str1 != NULL, CPL_ERROR_ILLEGAL_INPUT,
                        "keyword \n%s\n of frame %s is missing!",
                        keyword, frame_type1);
                tmp_str2 = cpl_propertylist_get_string(main_header2, keyword);
                KMO_TRY_ASSURE(tmp_str2 != NULL, CPL_ERROR_ILLEGAL_INPUT,
                        "keyword \n%s\n of frame %s is missing!",
                        keyword, frame_type2);
                KMO_TRY_ASSURE(strcmp(tmp_str1, tmp_str2) == 0,
                        CPL_ERROR_ILLEGAL_INPUT,
                        "Filter for detector %d frame %s and %s don't match!",
                        i, frame_type1, frame_type2);
                cpl_free(keyword); keyword = NULL;
            }
        }

        if (check_rotation == 1) {
            // compare rotation offset
            KMO_TRY_EXIT_IF_NULL(
                keyword = cpl_sprintf("%s", ROTANGLE));
            cpl_propertylist_get_double(main_header1, keyword);
            if (cpl_error_get_code() != CPL_ERROR_NONE) {
                KMO_TRY_ASSURE(1 == 0, CPL_ERROR_ILLEGAL_INPUT,
                        "keyword \n%s\n of frame %s is missing!",
                        keyword, frame_type1);
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }
    cpl_propertylist_delete(main_header1); main_header1 = NULL;
    cpl_propertylist_delete(main_header2); main_header2 = NULL;
    cpl_free(keyword); keyword = NULL;
    return ret_error;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Checks consistency of frames regarding MD5 keyword (XCAL, YCAL vs LCAL)
  @param frameset         The frameset
  @return   error code
  Possible cpl_error_code set in this function:
      CPL_ERROR_NULL_INPUT if @code frameset is empty
*/
/*----------------------------------------------------------------------------*/
cpl_error_code kmo_check_frame_setup_md5(cpl_frameset *frameset)
{
    cpl_error_code ret_error = CPL_ERROR_NONE;
    cpl_frame           *tmpFrame           = NULL;
    int                 matchError          = FALSE,
                        ind                 = 1;
    cpl_propertylist    *mainLcal           = NULL,
                        *mainTmp            = NULL;
    char                *keywordName        = NULL,
                        *keywordCatg        = NULL,
                        *keywordMd5         = NULL;
    const char          *valMd5             = NULL,
                        *valCatg            = NULL,
                        *md5Tmp             = NULL;

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

        KMO_TRY_EXIT_IF_NULL(
            mainLcal = kmo_dfs_load_primary_header(frameset, LCAL));

        // get keyword ESO PRO REC1 CAL1 NAME
        KMO_TRY_EXIT_IF_NULL(
            keywordName = cpl_sprintf("ESO PRO REC1 CAL%d NAME", ind));

        while (cpl_propertylist_has(mainLcal, keywordName)) {
            KMO_TRY_EXIT_IF_NULL(
                keywordCatg = cpl_sprintf("ESO PRO REC1 CAL%d CATG", ind));

            KMO_TRY_EXIT_IF_NULL(
                valCatg = cpl_propertylist_get_string(mainLcal, keywordCatg));

            if ((strcmp(valCatg, XCAL) == 0) ||
                (strcmp(valCatg, YCAL) == 0) ||
                (strcmp(valCatg, FLAT_EDGE) == 0) ||
                (strcmp(valCatg, MASTER_FLAT) == 0)) {
                tmpFrame = cpl_frameset_find(frameset, valCatg);
                if (tmpFrame != NULL) {
                    // check if MD5 matches
                    KMO_TRY_EXIT_IF_NULL(
                        keywordMd5 = cpl_sprintf("ESO PRO REC1 CAL%d DATAMD5", 
                            ind));

                    KMO_TRY_EXIT_IF_NULL(
                        valMd5 = cpl_propertylist_get_string(mainLcal, 
                            keywordMd5));
                    cpl_free(keywordMd5); keywordMd5 = NULL;

                    KMO_TRY_EXIT_IF_NULL(
                        mainTmp = kmo_dfs_load_primary_header(frameset,
                            valCatg));

                    KMO_TRY_EXIT_IF_NULL(
                        md5Tmp = cpl_propertylist_get_string(mainTmp, 
                            "DATAMD5"));

                    if (strcmp(valMd5, md5Tmp) != 0) {
                        if (!matchError) {
                            cpl_msg_warning("","**********************************************************************");
                            cpl_msg_warning("","**********************************************************************");
                            matchError = TRUE;
                        }
                        cpl_msg_warning("","***   LCAL has been produced with a different %s frame!          ***", valCatg);
                    }
                    cpl_propertylist_delete(mainTmp); mainTmp = NULL;
                }
            }
            cpl_free(keywordName); keywordName = NULL;
            cpl_free(keywordCatg); keywordCatg = NULL;

            // next keyword ESO PRO REC1 CALx NAME
            ind++;
            KMO_TRY_EXIT_IF_NULL(
                keywordName = cpl_sprintf("ESO PRO REC1 CAL%d NAME", ind));
        }

        if (matchError) {
            cpl_msg_warning("","***                                                                ***");
            cpl_msg_warning("","***        The recipe will be executed, but the                    ***");
            cpl_msg_warning("","***        results should be mistrusted !!!                        ***");
            cpl_msg_warning("","***                                                                ***");
            cpl_msg_warning("","***        Please take care to take XCAL, YCAL and LCAL frame      ***");
            cpl_msg_warning("","***        from the same calibration set !!!                       ***");
            cpl_msg_warning("","**********************************************************************");
            cpl_msg_warning("","**********************************************************************");
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }
    cpl_propertylist_delete(mainLcal); mainLcal = NULL;
    cpl_free(keywordName); keywordName = NULL;
    return ret_error;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Checks consistency of frames regarding MD5 keyword (XCAL, YCAL vs LCAL)
  @param frameset         The frameset
  @return   error code
  Possible cpl_error_code set in this function:
      CPL_ERROR_NULL_INPUT if @code frameset is empty
*/
/*----------------------------------------------------------------------------*/
cpl_error_code kmo_check_frame_setup_md5_xycal(cpl_frameset *frameset)
{
    cpl_error_code ret_error = CPL_ERROR_NONE;
    int                 ind                 = 1;
    cpl_propertylist    *mainXcal           = NULL,
                        *mainYcal           = NULL;
    char                *keywordName        = NULL;
    const char          *valX               = NULL,
                        *valY               = NULL;
    KMO_TRY
    {
        KMO_TRY_ASSURE(frameset != NULL, CPL_ERROR_NULL_INPUT,
                "Not all input data is provided!");

        KMO_TRY_EXIT_IF_NULL(
            mainXcal = kmo_dfs_load_primary_header(frameset, XCAL));
        KMO_TRY_EXIT_IF_NULL(
            mainYcal = kmo_dfs_load_primary_header(frameset, YCAL));

        // get keyword ESO PRO REC1 RAW1 NAME
        KMO_TRY_EXIT_IF_NULL(
            keywordName = cpl_sprintf("ESO PRO REC1 RAW%d NAME", ind));

        int ok = TRUE;
        while (ok) {
            if (cpl_propertylist_has(mainXcal, keywordName) &&
                cpl_propertylist_has(mainYcal, keywordName)) {
                KMO_TRY_EXIT_IF_NULL(
                    valX = cpl_propertylist_get_string(mainXcal, keywordName));
                KMO_TRY_EXIT_IF_NULL(
                    valY = cpl_propertylist_get_string(mainYcal, keywordName));

                if (strcmp(valX, valY) != 0) {
                    cpl_msg_warning("","**********************************************************************");
                    cpl_msg_warning("","**********************************************************************");
                    cpl_msg_warning("","***   XCAL and YCAL originate from different calibration sets!     ***");
                    cpl_msg_warning("","***   %s differs for these files         ***", keywordName);
                    cpl_msg_warning("","***                                                                ***");
                    cpl_msg_warning("","***        The recipe will be executed, but the                    ***");
                    cpl_msg_warning("","***        results should be mistrusted !!!                        ***");
                    cpl_msg_warning("","***                                                                ***");
                    cpl_msg_warning("","***        Please take care to take XCAL, YCAL and LCAL frame      ***");
                    cpl_msg_warning("","***        from the same calibration set !!!                       ***");
                    cpl_msg_warning("","**********************************************************************");
                    cpl_msg_warning("","**********************************************************************");
                    ok = FALSE;
                }
            } else {
                ok = FALSE;
            }
            cpl_free(keywordName); keywordName = NULL;
            // next keyword ESO PRO REC1 CALx NAME
            ind++;
            KMO_TRY_EXIT_IF_NULL(
                keywordName = cpl_sprintf("ESO PRO REC1 RAW%d NAME", ind));
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }
    cpl_propertylist_delete(mainXcal); mainXcal = NULL;
    cpl_propertylist_delete(mainYcal); mainYcal = NULL;
    cpl_free(keywordName); keywordName = NULL;
    return ret_error;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Get the ESO PRO REC1 PARAMx VALUE of a ESO PRO REC1 PARAMx NAME 
  @param header         The header
  @param par_name       The value of the ESO PRO REC1 PARAMx NAME
  @return   error code
  Possible cpl_error_code set in this function:
      CPL_ERROR_NULL_INPUT if @code frameset is empty
*/
/*----------------------------------------------------------------------------*/
const char * kmo_get_pro_keyword_val(
        const cpl_propertylist  *   header,
        const char              *   par_name)
{
    int         found           = FALSE,
                ind             = 1;
    char        *keywordName    = NULL;
    const char  *val            = NULL,
                *keywordNameVal = NULL;

    KMO_TRY
    {
        KMO_TRY_ASSURE((header != NULL) || (par_name != NULL),
                CPL_ERROR_NULL_INPUT, "Not all input data is provided!");
        KMO_TRY_EXIT_IF_NULL(
            keywordName = cpl_sprintf("ESO PRO REC1 PARAM%d NAME", ind));

        while (!found && cpl_propertylist_has(header, keywordName)) {
            KMO_TRY_EXIT_IF_NULL(
                keywordNameVal = cpl_propertylist_get_string(header, 
                    keywordName));

            if (strcmp(keywordNameVal, par_name) == 0) {
                found = TRUE;
                break;
            }

            cpl_free(keywordName); keywordName = NULL;
            // next keyword ESO PRO REC1 CALx NAME
            ind++;
            KMO_TRY_EXIT_IF_NULL(
                keywordName = cpl_sprintf("ESO PRO REC1 PARAM%d NAME", ind));
        }

        if (found) {
            cpl_free(keywordName); keywordName = NULL;
            KMO_TRY_EXIT_IF_NULL(
                keywordName = cpl_sprintf("ESO PRO REC1 PARAM%d VALUE", ind));
            KMO_TRY_EXIT_IF_NULL(
                val = cpl_propertylist_get_string(header, keywordName));
            cpl_free(keywordName); keywordName = NULL;
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        val = NULL;
    }
    cpl_free(keywordName); keywordName = NULL;
    return val;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Checks consistency of frames regarding sampling-keywords
  @param frameset         The frameset
  @return   error code
  Possible cpl_error_code set in this function:
      CPL_ERROR_NULL_INPUT if @code frameset is empty
*/
/*----------------------------------------------------------------------------*/
cpl_error_code  kmo_check_frame_setup_sampling(cpl_frameset *frameset)
{
    cpl_error_code      ret_error       = CPL_ERROR_NONE;
    cpl_propertylist    *mainLcal       = NULL,
                        *mainTell       = NULL,
                        *mainTellGen    = NULL;
    int                 matchError      = FALSE;
    const char          *b_lcal         = NULL,
                        *b_tell         = NULL,
                        *b_tellgen      = NULL;

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

        KMO_TRY_EXIT_IF_NULL(
            mainLcal = kmo_dfs_load_primary_header(frameset, LCAL));

        mainTell = kmo_dfs_load_primary_header(frameset, TELLURIC);
        if (mainTell != NULL) {
            // check if b_samples is equal
            KMO_TRY_EXIT_IF_NULL(
                b_lcal =  kmo_get_pro_keyword_val(mainLcal, "b_samples"));
            KMO_TRY_EXIT_IF_NULL(
                b_tell =  kmo_get_pro_keyword_val(mainTell, "b_samples"));

            if (strcmp(b_lcal, b_tell) != 0) {
                matchError = TRUE;
                cpl_msg_warning("","**********************************************************************");
                cpl_msg_warning("","**********************************************************************");
                cpl_msg_warning("","***  The parameter b_samples isn't the same in LCAL and TELLURIC!  ***");
                cpl_msg_warning("","***     ==> LCAL (%s) and TELLURIC (%s)", b_lcal, b_tell);
            }
        }

        mainTellGen = kmo_dfs_load_primary_header(frameset, TELLURIC_GEN);
        if (mainTellGen != NULL) {
            // check if b_samples is equal
            KMO_TRY_EXIT_IF_NULL(
                b_lcal =  kmo_get_pro_keyword_val(mainLcal, "b_samples"));
            KMO_TRY_EXIT_IF_NULL(
                b_tellgen =  kmo_get_pro_keyword_val(mainTellGen, "b_samples"));

            if (strcmp(b_lcal, b_tellgen) != 0) {
                matchError = TRUE;
                cpl_msg_warning("","**********************************************************************");
                cpl_msg_warning("","**********************************************************************");
                cpl_msg_warning("","***  The parameter b_samples isn't the same in LCAL and TELLURIC_GEN!  ***");
                cpl_msg_warning("","***     ==> LCAL (%s) and TELLURIC_GEN (%s)", b_lcal, b_tellgen);
            }
        }

        if (matchError) {
            cpl_msg_warning("","***                                                                ***");
            cpl_msg_warning("","***        The recipe will be executed, but the                    ***");
            cpl_msg_warning("","***        results should be mistrusted !!!                        ***");
            cpl_msg_warning("","**********************************************************************");
            cpl_msg_warning("","**********************************************************************");
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }
    cpl_propertylist_delete(mainLcal); mainLcal = NULL;
    cpl_propertylist_delete(mainTell); mainTell = NULL;
    cpl_propertylist_delete(mainTellGen); mainTellGen = NULL;
    return ret_error;
}

/** @} */
