/* $Id: kmo_utils.c,v 1.29 2013-10-21 13:44:54 aagudo Exp $
 *
 * 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
 */

/*
 * $Author: aagudo $
 * $Date: 2013-10-21 13:44:54 $
 * $Revision: 1.29 $
 * $Name: not supported by cvs2svn $
 */

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

/*-----------------------------------------------------------------------------
 *                                 Includes
 *----------------------------------------------------------------------------*/
#include <string.h>
#include <ctype.h>

#include <cpl.h>

#include <kmo_utils.h>
#include <kmo_error.h>
#include <kmo_debug.h>
#include <kmo_dfs.h>
#include <kmo_constants.h>
#include <kmo_cpl_extensions.h>

#include <math.h>

/*----------------------------------------------------------------------------*/
/**
    @defgroup kmos_utils     Miscellaneous Utilities

    @{
 */
/*----------------------------------------------------------------------------*/

/**
  @brief    Get the pipeline copyright and license

  @return   The copyright and license string

  The function returns a pointer to the statically allocated license string.
  This string should not be modified using the returned pointer.
 */
const char * kmos_get_license(void)
{
    const char  *   kmos_license = 
        "This file is part of the KMOS Instrument Pipeline\n"
        "Copyright (C) 2002,2003 European Southern Observatory\n"
        "\n"
        "This program is free software; you can redistribute it and/or modify\n"
        "it under the terms of the GNU General Public License as published by\n"
        "the Free Software Foundation; either version 2 of the License, or\n"
        "(at your option) any later version.\n"
        "\n"
        "This program is distributed in the hope that it will be useful,\n"
        "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
        "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
        "GNU General Public License for more details.\n"
        "\n"
        "You should have received a copy of the GNU General Public License\n"
        "along with this program; if not, write to the Free Software\n"
        "Foundation, 51 Franklin Street, Suite 500, Boston, MA  02110-1335  USA";

    return kmos_license;
}

/**
    @brief
        Cut leading and trailing -1 of a vector.

    @param vec    (Input)  Vector to trim.
                  (Output) Trimmed vector, if cut is TRUE.
    @param begin  NULL or variable to hold determined beginning
                  position (zero based). Set to zero in case of error.
    @param end    NULL or variable to hold determined ending
                  position (zero based). Set to zero in case of error.
    @param cut    TRUE if @c vec should be trimmed,
                  FALSE if just the start and end positions should be determined.
                  Setting @c begin and @c end to NULL and @c cut to FALSE is
                  senseless.

    @return
        The function returns CPL_ERROR_NONE on success or a CPL error code
        otherwise.

    This function only cuts -1. Other negative numbers are classified as ok.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if @c vec is NULL.
*/
cpl_error_code kmo_cut_endings(cpl_vector** vec, int *begin, int *end, int cut)
{
    cpl_error_code  ret_error   = CPL_ERROR_NONE;

    int             min         = 0,
                    max         = 0,
                    i           = 0;

    double          *pvec       = NULL;

    cpl_vector      *tmp_vec    = NULL;

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

        KMO_TRY_EXIT_IF_NULL(
            pvec = cpl_vector_get_data(*vec));

        min = 0;  // min_pos
        max = cpl_vector_get_size(*vec)-1;  // max_pos

        // find beginning
        for (i = 0; i < cpl_vector_get_size(*vec); i++) {
            if (pvec[i] == -1) {
                min = i+1;
            } else {
                break;
            }
        }

        if (min != cpl_vector_get_size(*vec)) {
            // find ending
            for (i = cpl_vector_get_size(*vec)-1; i >= 0; i--) {
                if (pvec[i] == -1) {
                    max = i-1;
                } else {
                    break;
                }
            }

            if (cut == TRUE) {
                // extract appropriate part of vector
                KMO_TRY_EXIT_IF_NULL(
                    tmp_vec = cpl_vector_extract(*vec, min, max, 1));

                cpl_vector_delete(*vec); *vec= NULL;

                *vec = tmp_vec;
            }
        } else {
            if (cut == TRUE) {
                // all values are -1 return NULL
                cpl_vector_delete(*vec); *vec = NULL;
            }

            min = 0;
            max = 0;
        }

        // set return values if wanted
        if (begin != NULL) {
            *begin = min;
        }

        if (end != NULL) {
            *end = max;
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();

        if (begin != NULL) {
            *begin = 0;
        }

        if (end != NULL) {
            *end = 0;
        }

        cpl_vector_delete(*vec); *vec= NULL;
    }

    return ret_error;
}

/**
    @brief
        An easy gaussfit which iterates once if it doesn't converge.

    @param x      Positions to fit.
    @param y      Values to fit.
    @param x0     (Output) Center of best fit gaussian.
    @param sigma  (Output) Width of best fit gaussian. A positive number on success.
    @param area   (Output) Area of gaussian. A positive number on succes.
    @param offset (Output) Fitted background level.

    @return
        The function returns CPL_ERROR_NONE on success or a CPL error code
        otherwise.

    In the first run this function tries to fit all four parameters. If it
    doesn't converge, a second try is done with fixed area and sigma parameters
    from the first run. If it doesn't converge again, the found parameters are
    just returned.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    any of the inputs is NULL.
*/
cpl_error_code kmo_easy_gaussfit(const cpl_vector *x,
                                 const cpl_vector *y,
                                 double *x0,
                                 double *sigma,
                                 double *area,
                                 double *offset)
{
    cpl_error_code  ret_error   = CPL_ERROR_NONE,
                    fit_error   = CPL_ERROR_NONE;


    KMO_TRY
    {
        KMO_TRY_ASSURE((x != NULL) &&
                       (y != NULL) &&
                       (x0 != NULL) &&
                       (area != NULL) &&
                       (sigma != NULL) &&
                       (offset != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data is provided!");

        *x0 = 0.0;
        *sigma = 0.0;
        *area = 0.0;
        *offset = 0.0;
        fit_error = CPL_ERROR_NONE;

        fit_error = cpl_vector_fit_gaussian(x, NULL, y, NULL,
                                CPL_FIT_ALL,
                                x0,
                                sigma,
                                area,
                                offset,
                                NULL, NULL, NULL);

        // this happens only once in obscure test data...
        if ((fit_error == CPL_ERROR_NONE) &&
            (cpl_error_get_code() == CPL_ERROR_SINGULAR_MATRIX))
        {
            cpl_error_reset();
            fit_error = CPL_ERROR_CONTINUE;
        }

        if (fit_error == CPL_ERROR_CONTINUE) {
            // if first fit doesn't convert, try it again with fixed
            // area- and sigma-parameter
            KMO_TRY_RECOVER();

            fit_error = cpl_vector_fit_gaussian(x, NULL, y, NULL,
                                    CPL_FIT_CENTROID | CPL_FIT_OFFSET,
                                    x0,
                                    sigma,
                                    area,
                                    offset,
                                    NULL, NULL, NULL);

            if (fit_error == CPL_ERROR_CONTINUE) {
                // if it didn't convert again, give up and take the
                // estimated value
                KMO_TRY_RECOVER();

            }
        }
        KMO_TRY_CHECK_ERROR_STATE();
    }
    KMO_CATCH
    {
//        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();

        *x0  = -1;
        *sigma  = -1;
        *area  = -1;
        *offset  = -1;
    }

    return ret_error;
}

/**
    @brief
        An easy-to-handle wrapper to cpl_polynomial_fit() to fit a vector.

    @param x      Positions to fit.
    @param y      Values to fit.
    @param degree Degree of the polynomial to fit.

    @return
        The function returns a vector with the fit parameters. Its length is
        @c degree + 1
        The first value is the constant term:
        y(x) = fit_par[0] +
               fit_par[1]*x +
               fit_par[2]*x^2 +
               fit_par[3]*x^3 +
               ...

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    any of the inputs is NULL.
*/
cpl_vector* kmo_polyfit_1d(const cpl_vector *x,
                           const cpl_vector *y,
                           const int degree)
{
    cpl_vector          *fit_par    = NULL;

    cpl_polynomial      *poly_coeff = NULL;
    cpl_matrix          *x_matrix   = NULL;
    double              *pfit_par   = NULL,
                        *px          = NULL;
    cpl_size            k           = 0,
                        mindeg1d    = 0,    //1,
                        maxdeg1d    = degree;

    const cpl_boolean   sampsym     = CPL_FALSE;

    KMO_TRY
    {
        KMO_TRY_ASSURE((x != NULL) &&
                       (y != NULL) &&
                       (degree != 0),
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data is provided!");

        //
        //  setup data for fitting
        //
        KMO_TRY_EXIT_IF_NULL(
            poly_coeff = cpl_polynomial_new(1));

        // put x-vector into a matrix (cast away constness of x: is okay since
        // data is wrapped into a matrix which is passed as const again)
        KMO_TRY_EXIT_IF_NULL(
            px = cpl_vector_get_data((cpl_vector*)x));

        KMO_TRY_EXIT_IF_NULL(
            x_matrix = cpl_matrix_wrap(1, cpl_vector_get_size(x), px));

        //
        // fit 1d data
        //
        KMO_TRY_EXIT_IF_ERROR(
            cpl_polynomial_fit(poly_coeff,
                               x_matrix,
                               &sampsym,
                               y,
                               NULL,
                               CPL_FALSE,
                               &mindeg1d,
                               &maxdeg1d));

        cpl_matrix_unwrap(x_matrix); x_matrix = NULL;
        KMO_TRY_CHECK_ERROR_STATE();

        //
        // put fit coefficients into a vector to return
        //
        KMO_TRY_EXIT_IF_NULL(
            fit_par = cpl_vector_new(degree + 1));

        KMO_TRY_EXIT_IF_NULL(
            pfit_par = cpl_vector_get_data(fit_par));

        for(k = 0; k <= degree; k++) {
            pfit_par[k] = cpl_polynomial_get_coeff(poly_coeff, &k);
        }
    }
    KMO_CATCH
    {
//        KMO_CATCH_MSG();
        cpl_vector_delete(fit_par); fit_par = NULL;
    }

    cpl_matrix_unwrap(x_matrix); x_matrix = NULL;
    cpl_polynomial_delete(poly_coeff); poly_coeff = NULL;

    return fit_par;
}

/**
    @brief
        Converts the value @c val to degrees (hours), minutes and seconds.

    @param val The value to convert (formatted like hhmmss.sss).

    @return
        The value in degrees.

    It is expected that Degrees/hours are between -90° and +90° or between 0
    and 24hours

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_ILLEGAL_INPUT    if fabs(val)/1000000 >= 1.0.
*/
double kmo_to_deg(double val)
{
    int             deg     = 0,
                    min     = 0;

    double          sec     = 0.0,
                    ret_val = 0.0;

    KMO_TRY
    {
        KMO_TRY_ASSURE(fabs(val)/1000000 < 1.0,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Input value out of range!");

        deg = (int)(val/10000);
        min = (int)(fabs((val - deg*10000)/100));
        sec = fabs(val - deg*10000) - min*100;

        ret_val = abs(deg) + (double)(min)/60.0 + sec/(60.0*60.0);
        if (deg < 0) {
            ret_val *= -1.0;
        }
        // if deg is 0 and value negative, the minus sign has to be added in again
        if ((val < 0) && (deg == 0)) {
            ret_val*=-1;
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_val = 0.0;
    }

    return ret_val;
}

/**
    @brief
        Converts the string @c frame_type_str to an enum of
        type @c kmo_frame_type .

    @param frame_type_str The string to convert.

    @return
        The function returns an enum of type @c kmo_frame_type.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT if @c frame_type_str is NULL.
*/
enum kmo_frame_type kmo_string_to_frame_type(const char* frame_type_str)
{
    enum kmo_frame_type    type    = illegal_frame;

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

            if ((strcmp(frame_type_str, F2I) == 0) ||
                (strcmp(frame_type_str, F1I) == 0) ||
                (strcmp(frame_type_str, F3I) == 0)) {

                type = ifu_frame;
            } else if ((strcmp(frame_type_str, RAW) == 0) ||
                       (strcmp(frame_type_str, F1D) == 0) ||
                       (strcmp(frame_type_str, F2D) == 0) ||
                       (strcmp(frame_type_str, B2D) == 0)) {
                type = detector_frame;
            } else if ((strcmp(frame_type_str, F1L) == 0) ||
                       (strcmp(frame_type_str, F2L) == 0)) {
                type = list_frame;
            } else if (strcmp(frame_type_str, F1S) == 0) {
                type = spectrum_frame;
            } else {
               type = illegal_frame;
            }

            KMO_TRY_CHECK_ERROR_STATE();
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();

        type = illegal_frame;
    }

    return type;
}

/**
    @brief
        Extracts a @li main_fits_desc from a fits-file.

    @param filename The filename and path of the fits-file to describe.

    @return
        The function returns a structure of type @c main_fits_desc.

    Creates a structure of type @c main_fits_desc which contains all values
    relevant to the KMOS-pipeline of a given fits-file.
    These are: NAXIS, NAXIS1, NAXIS2, NAXIS3, KMOS_TYPE, EX_NOISE, NR_EXT
    The values are extracted from the main fits-header and from the headers
    of eventually existing extensions.
    The data from extensions is stored in the structure-member @c sub_desc .

    It is assumed, that RAW-files have empty main-header (NAXIS = 0) and
    that 3 extensions exist all with NAXIS=2 and that NAXIS1 and NAXIS2 of
    all 3 extansions have the same values each and it is assumed by default,
    that raw is data, so EX_NOISE will be FALSE


    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if @c filename is NULL.
    @li CPL_ERROR_ILLEGAL_INPUT The position is less than 0 or the properties
        cannot be read from the file name.
    @li CPL_ERROR_FILE_IO   The file name does not exist.
    @li CPL_ERROR_BAD_FILE_FORMAT   The file name is not a valid FITS file.
    @li CPL_ERROR_DATA_NOT_FOUND    The requested data set at index position
        does not exist.
*/
main_fits_desc kmo_identify_fits_header(const char *filename)
{
    cpl_propertylist    *main_header    = NULL,
                        *sub_header     = NULL;

    int                 i               = 0,
                        nr_data         = 0,
                        nr_noise        = 0,
                        nr_badpix       = 0,
                        nr_list         = 0,
                        n               = 0,
                        n1              = 0,
                        n2              = 0,
                        is_raw          = FALSE,
                        *arr_naxis      = NULL,
                        *arr_naxis1     = NULL,
                        *arr_naxis2     = NULL,
                        *arr_naxis3     = NULL,
                        *arr_id         = NULL,
                        *arr_tfields    = NULL,
                        emit_once1      = FALSE,
                        emit_once2      = FALSE,
                        emit_once3      = FALSE,
                        my_id           = 0,
                        recovered       = FALSE,
                        valid           = 0;

    main_fits_desc      desc;

    cpl_frame           *frame          = NULL;

    const char          *extname        = NULL;

    enum kmo_frame_type *arr_type       = NULL;

    char                *ifu_notused    = NULL,
                        *result         = NULL,
                        **arr_content   = NULL,
                        **arr_xtension  = NULL,
                        *tmp_xtension   = NULL;

    KMO_TRY
    {
        // init to default
        kmo_init_fits_desc(&desc);
        KMO_TRY_CHECK_ERROR_STATE();

        // check input
        KMO_TRY_ASSURE(filename != NULL,
                       CPL_ERROR_NULL_INPUT,
                       "No input data is provided!");

        KMO_TRY_EXIT_IF_NULL(
            main_header = kmclipm_propertylist_load(filename, 0));

        KMO_TRY_ASSURE(cpl_propertylist_get_int(main_header, NAXIS) == 0,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Primary extension must be empty!");

        if (cpl_error_get_code() != CPL_ERROR_NONE) {
            cpl_msg_error(cpl_func, "File '%s' not found", filename);
            KMO_TRY_CHECK_ERROR_STATE();
        }

        KMO_TRY_EXIT_IF_NULL(
            frame = cpl_frame_new());

        KMO_TRY_EXIT_IF_ERROR(
            cpl_frame_set_filename(frame, filename));

        desc.nr_ext = cpl_frame_get_nextensions(frame);
        KMO_TRY_CHECK_ERROR_STATE();

        KMO_TRY_ASSURE(desc.nr_ext > 0,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "There is only one (primary?) extension!");

        // check if EXTNAME keywords in subheaders correspond to "CHIPx.INTy"
        // notation
        // if yes: it is a RAW file, otherwise it must have been processed by
        // KMOS pipeline or it is invalid
        if (desc.nr_ext == KMOS_NR_DETECTORS) {
            is_raw = TRUE;
            i = 1;
            while (is_raw && (i <= KMOS_NR_DETECTORS)) {
                KMO_TRY_EXIT_IF_NULL(
                    sub_header = kmclipm_propertylist_load(filename, i));

                KMO_TRY_EXIT_IF_NULL(
                    extname = cpl_propertylist_get_string(sub_header, EXTNAME));

                result = strstr(extname, EXTNAME_RAW);

                if (result ==  NULL) {
                    is_raw = FALSE;
                }
                cpl_propertylist_delete(sub_header); sub_header = NULL;
                i++;
            }
        }

        KMO_TRY_EXIT_IF_NULL(
            desc.sub_desc = (sub_fits_desc*)cpl_malloc(desc.nr_ext *
                                                   sizeof(sub_fits_desc)));

        if (is_raw) {
            /* RAW fits-file */
            KMO_TRY_ASSURE(desc.nr_ext == KMOS_NR_DETECTORS,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "RAW frame must have primary extension plus 3 "
                           "data extensions!");

            desc.fits_type = raw_fits;
            desc.frame_type = detector_frame;
            desc.ex_noise = FALSE;
            desc.ex_badpix = FALSE;

            for (i = 1; i <= KMOS_NR_DETECTORS; i++) {
                KMO_TRY_EXIT_IF_NULL(
                    sub_header = kmclipm_propertylist_load(filename, i));

                KMO_TRY_ASSURE(cpl_propertylist_has(sub_header, XTENSION),
                               CPL_ERROR_ILLEGAL_INPUT,
                               "All extensions need the XTENSION keyword!");

                KMO_TRY_ASSURE(
                    strcmp(IMAGE,cpl_propertylist_get_string(sub_header,
                                                             XTENSION)) == 0,
                               CPL_ERROR_ILLEGAL_INPUT,
                               "XTENSION keyword must be IMAGE!");

                if (i == 1) {
                    desc.naxis  = cpl_propertylist_get_int(sub_header, NAXIS);
                    desc.naxis1 = cpl_propertylist_get_int(sub_header, NAXIS1);
                    desc.naxis2 = cpl_propertylist_get_int(sub_header, NAXIS2);
                    desc.naxis3 = 0;
                    KMO_TRY_CHECK_ERROR_STATE();
                } else {
                    n  = cpl_propertylist_get_int(sub_header, NAXIS);
                    n1 = cpl_propertylist_get_int(sub_header, NAXIS1);
                    n2 = cpl_propertylist_get_int(sub_header, NAXIS2);
                    KMO_TRY_CHECK_ERROR_STATE();

                    KMO_TRY_ASSURE((n == desc.naxis) &&
                                   (n1 == desc.naxis1) &&
                                   (n2 == desc.naxis2),
                                   CPL_ERROR_ILLEGAL_INPUT,
                                   "All data frames must have the same dimensions!");
                }

                desc.sub_desc[i-1] = kmo_identify_fits_sub_header(
                                        i,
                                        desc.naxis > 0,
                                        0,
                                        0,
                                        cpl_propertylist_get_int(sub_header,
                                                                 CHIPINDEX));
                KMO_TRY_CHECK_ERROR_STATE();

                cpl_propertylist_delete(sub_header); sub_header = NULL;
            }
        } else {
            /* other than RAW fits-file */
            KMO_TRY_EXIT_IF_NULL(
                arr_naxis   = (int*)cpl_calloc(desc.nr_ext, sizeof(int)));
            KMO_TRY_EXIT_IF_NULL(
                arr_naxis1  = (int*)cpl_calloc(desc.nr_ext, sizeof(int)));
            KMO_TRY_EXIT_IF_NULL(
                arr_naxis2  = (int*)cpl_calloc(desc.nr_ext, sizeof(int)));
            KMO_TRY_EXIT_IF_NULL(
                arr_naxis3  = (int*)cpl_calloc(desc.nr_ext, sizeof(int)));
            KMO_TRY_EXIT_IF_NULL(
                arr_type    = (enum kmo_frame_type*)cpl_calloc(desc.nr_ext,
                                                  sizeof(enum kmo_frame_type)));
            KMO_TRY_EXIT_IF_NULL(
                arr_id      = (int*)cpl_calloc(desc.nr_ext, sizeof(int)));
            KMO_TRY_EXIT_IF_NULL(
                arr_tfields = (int*)cpl_calloc(desc.nr_ext, sizeof(int)));
            KMO_TRY_EXIT_IF_NULL(
                arr_content = (char**)cpl_calloc(desc.nr_ext, sizeof(char*)));
            KMO_TRY_EXIT_IF_NULL(
                arr_xtension = (char**)cpl_calloc(desc.nr_ext, sizeof(char*)));
            for (i = 0; i < desc.nr_ext; i++) {
                KMO_TRY_EXIT_IF_NULL(
                    arr_content[i] = (char*)cpl_calloc(256, sizeof(char)));
            }
            KMO_TRY_CHECK_ERROR_STATE();

            // gather data from all sub_headers
            for (i = 0; i < desc.nr_ext; i++) {
                KMO_TRY_EXIT_IF_NULL(
                    sub_header = kmclipm_propertylist_load(filename, i+1));

                KMO_TRY_EXIT_IF_NULL(
                    extname = cpl_propertylist_get_string(sub_header, EXTNAME));

                arr_naxis[i]  = cpl_propertylist_get_int(sub_header, NAXIS);
                if (strcmp(IMAGE,
                        cpl_propertylist_get_string(sub_header, XTENSION)) == 0)
                {
                    arr_xtension[i] = IMAGE;

                    // gather NAXIS keywords
                    if (arr_naxis[i] > 0) {
                        arr_naxis1[i] = cpl_propertylist_get_int(sub_header,
                                                                 NAXIS1);
                        if (arr_naxis[i] > 1) {
                            arr_naxis2[i] = cpl_propertylist_get_int(sub_header,
                                                                     NAXIS2);
                            if (arr_naxis[i] > 2) {
                                arr_naxis3[i] = cpl_propertylist_get_int(sub_header,
                                                                         NAXIS3);
                            } else {
                                arr_naxis3[i] = 0;
                            }
                        } else {
                            arr_naxis2[i] = 0;
                            arr_naxis3[i] = 0;
                        }
                    } else {
                        arr_naxis1[i] = 0;
                        arr_naxis2[i] = 0;
                        arr_naxis3[i] = 0;
                    }
                    KMO_TRY_CHECK_ERROR_STATE();

                    // gather EXTNAME info
                    if (strcmp(extname, EXT_SPEC) == 0) {
                        arr_type[i] = spectrum_frame;
                    } else {
                        KMO_TRY_EXIT_IF_ERROR(
                            kmo_extname_extractor(extname,
                                                  &(arr_type[i]),
                                                  &(arr_id[i]),
                                                  arr_content[i]));
                        if (arr_type[i] == illegal_frame) {
                            // try to recover
                            if (arr_naxis[i] == 2) {
                                arr_type[i] = detector_frame;
                            } else {
                                arr_type[i] = ifu_frame;
                                recovered=TRUE;
                            }
                            if ((i > 0) && (arr_type[i-1] != arr_type[i])) {
                                 KMO_TRY_ASSURE(1==0,
                                                CPL_ERROR_ILLEGAL_INPUT,
                                                "NAXIS isn't the same "
                                                "for ext %d and %d!",
                                                i, i+1);
                            }
                        }

                        if (strcmp(arr_content[i], EXT_DATA) == 0) {
                            nr_data++;
                        } else if (strcmp(arr_content[i], EXT_NOISE) == 0) {
                            nr_noise++;
                        } else if (strcmp(arr_content[i], EXT_BADPIX) == 0) {
                            nr_badpix++;
                        } else if (strcmp(arr_content[i], EXT_LIST) == 0) {
                            nr_list++;
                            arr_id[i] = i+1;
                            arr_xtension[i] = BINTABLE;
                        } else {
                            KMO_TRY_ASSURE(1==0,
                                           CPL_ERROR_ILLEGAL_INPUT,
                                           "First subheader has a content flag "
                                           "of %s  (instead of DATA, NOISE or "
                                           "BADPIX)", arr_content[i]);
                        }
                    }
                } else if (strcmp(BINTABLE,
                        cpl_propertylist_get_string(sub_header, XTENSION)) == 0)
                {
                    nr_list++;
                    arr_xtension[i] = BINTABLE;

                    arr_type[i] = list_frame;

                    arr_id[i] = i+1;
                    strcpy(arr_content[i], EXT_DATA);

                    arr_tfields[i] = cpl_propertylist_get_int(sub_header,
                                                              TFIELDS);
                    arr_naxis2[i] = cpl_propertylist_get_int(sub_header,
                                                             NAXIS2);

                    KMO_TRY_ASSURE(strstr(extname, EXT_LIST) != NULL,
                                   CPL_ERROR_ILLEGAL_INPUT,
                                   "EXTNAME keyword should contain 'LIST'");
                } else {
                    KMO_TRY_ASSURE(1==0,
                                   CPL_ERROR_ILLEGAL_INPUT,
                                   "XTENSION keyword must be IMAGE or BINTABLE!");
                }

                cpl_propertylist_delete(sub_header); sub_header = NULL;
            }

            // set (and check) desc.type here
            desc.frame_type = arr_type[0];
            tmp_xtension = arr_xtension[0];
            for (i = 1; i < desc.nr_ext; i++) {
                KMO_TRY_ASSURE(desc.frame_type == arr_type[i],
                               CPL_ERROR_ILLEGAL_INPUT,
                               "frame type of all frames must be the same!");

                KMO_TRY_ASSURE(strcmp(tmp_xtension, arr_xtension[i]) == 0,
                               CPL_ERROR_ILLEGAL_INPUT,
                               "XTENSION keyword must be the same for all "
                               "extensions!");
            }

            if (desc.frame_type == detector_frame) {
                desc.ex_badpix = nr_badpix > 0;
                desc.ex_noise = nr_noise > 0;

                // check here if B2D, F1D or F2D with or without noise
                if (nr_badpix > 0) {
                    desc.fits_type = b2d_fits;

                    KMO_TRY_ASSURE((nr_data == 0) && (nr_noise == 0),
                                   CPL_ERROR_ILLEGAL_INPUT,
                                   "If badpix frames exists, there mustn't be "
                                   "data or noise frames!");

                    KMO_TRY_ASSURE(desc.nr_ext % KMOS_NR_DETECTORS ==0,
                                   CPL_ERROR_ILLEGAL_INPUT,
                                   "B2D must have 3 * x extensions (plus primary one)!");

                    for (i = 0; i < desc.nr_ext; i++) {
                        KMO_TRY_ASSURE(arr_naxis[i] == 2,
                                       CPL_ERROR_ILLEGAL_INPUT,
                                       "For B2D frames NAXIS must be 2!");
                    }

                    desc.naxis = arr_naxis[0];
                    desc.naxis1 = arr_naxis1[0];
                    desc.naxis2 = arr_naxis2[0];
                    desc.naxis3 = 0;
                    for (i = 1; i < desc.nr_ext; i++) {
                        if (desc.naxis == 0) {
                            desc.naxis = arr_naxis[i];
                        }
                        KMO_TRY_ASSURE((desc.naxis == arr_naxis[i]) ||
                                       (desc.naxis == 0),
                                       CPL_ERROR_ILLEGAL_INPUT,
                                       "NAXIS must equal to 2 (or 0 if the whole "
                                       "extension is invalid)!");

                        KMO_TRY_ASSURE((desc.naxis1 == arr_naxis1[i]) &&
                                       (desc.naxis2 == arr_naxis2[i]),
                                       CPL_ERROR_ILLEGAL_INPUT,
                                       "NAXIS1 and NAXIS2 have to be the same for "
                                       "all frames!");
                    }

                    // fill sub_desc
                    for (i = 0; i < desc.nr_ext; i++) {

                        desc.sub_desc[i] = kmo_identify_fits_sub_header(i+1,
                                                           TRUE,
                                        strcmp(arr_content[i], EXT_NOISE) == 0,
                                        strcmp(arr_content[i], EXT_BADPIX) == 0,
                                                            arr_id[i]);
                        KMO_TRY_CHECK_ERROR_STATE();
                    }

                    // check indices uniqueness of the ids
                    // (or twiceness when noise exists)
                    KMO_TRY_ASSURE(kmo_check_indices(arr_id, desc.nr_ext, desc.ex_noise),
                                   CPL_ERROR_ILLEGAL_INPUT,
                                   "The IDs of the frame seem to be incorrect"
                                   "(1, 2 and 3 expected)");

                } else {
                    KMO_TRY_ASSURE(nr_data > 0,
                                   CPL_ERROR_ILLEGAL_INPUT,
                                   "There must be data frames if there are no "
                                   "badpix frames!");

                    if (desc.ex_noise) {
                        KMO_TRY_ASSURE((nr_data == nr_noise),
                                       CPL_ERROR_ILLEGAL_INPUT,
                                       "The number of data and noise frames "
                                       "must be the same!");
                    }

                    desc.naxis = arr_naxis[my_id];
                    desc.naxis1 = arr_naxis1[my_id];
                    desc.naxis2 = arr_naxis2[my_id];
                    desc.naxis3 = 0;
                    for (i = 1; i < desc.nr_ext; i++) {
                        if (desc.naxis == 0) {
                            my_id = i;
                            desc.naxis = arr_naxis[my_id];
                            desc.naxis1 = arr_naxis1[my_id];
                            desc.naxis2 = arr_naxis2[my_id];
                        } else {
                            if (arr_naxis[i] != 0) {
                                KMO_TRY_ASSURE(desc.naxis == arr_naxis[i],
                                               CPL_ERROR_ILLEGAL_INPUT,
                                               "NAXIS has to be the same value for "
                                               "all extensions (or 0 if invalid)!");

                                if ((!emit_once1) &&
                                    (arr_naxis1[i] != 0) &&
                                    (desc.naxis1 != arr_naxis1[i]))
                                {
                                    cpl_msg_warning(cpl_func,
                                                    "NAXIS1 differs at least between "
                                                    "extensions %d and %d (%d and "
                                                    "%d), have any IFUs been rotated?",
                                                    my_id, i, desc.naxis1,
                                                    arr_naxis1[i]);
                                    emit_once1 = TRUE;
                                }
                                if (desc.naxis > 1) {
                                    if ((!emit_once2) &&
                                        (arr_naxis2[i] != 0) &&
                                        (desc.naxis2 != arr_naxis2[i]))
                                    {
                                        cpl_msg_warning(cpl_func,
                                                        "NAXIS2 differs at least "
                                                        "between extensions %d and "
                                                        "%d (%d and %d), have any "
                                                        "IFUs been rotated?", my_id,
                                                        i, desc.naxis2, arr_naxis2[i]);
                                        emit_once2 = TRUE;
                                    }

                                    KMO_TRY_ASSURE((desc.naxis1 == arr_naxis1[i]) &&
                                                   (desc.naxis2 == arr_naxis2[i]),
                                                   CPL_ERROR_ILLEGAL_INPUT,
                                                   "NAXIS1 and NAXIS2 have to be the same for "
                                                   "all frames!");
                                }
                            }
                        }
                    }

                    switch (desc.naxis) {
                    case 1:
                        desc.fits_type = f1d_fits;
                        break;
                    case 2:
                        desc.fits_type = f2d_fits;
                        break;
                    default:
                        cpl_msg_warning(cpl_func, "All extensions are empty, "
                                                  "assume F3I as type!");
                        desc.fits_type = f3i_fits;
                    }

                    if (desc.fits_type == f2d_fits) {
                        KMO_TRY_ASSURE((desc.nr_ext % KMOS_NR_DETECTORS == 0),
                                       CPL_ERROR_ILLEGAL_INPUT,
                                       "%s: F2D must have 3 * x extensions (plus "
                                       "primary one)!", filename);
                    }

                    // fill sub_desc
                    for (i = 0; i < desc.nr_ext; i++) {

                        desc.sub_desc[i] = kmo_identify_fits_sub_header(i+1,
                                                            arr_naxis[i] > 0,
                                        strcmp(arr_content[i], EXT_NOISE) == 0,
                                        strcmp(arr_content[i], EXT_BADPIX) == 0,
                                                            arr_id[i]);
                        KMO_TRY_CHECK_ERROR_STATE();
                    }

                    // check indices uniqueness of the ids
                    // (or twiceness when noise exists)
                    KMO_TRY_ASSURE(kmo_check_indices(arr_id, desc.nr_ext,
                    desc.ex_noise),
                                   CPL_ERROR_ILLEGAL_INPUT,
                                   "The IDs of the frame seem to be incorrect"
                                   "(1, 2 and 3 expected)");
                }
            } else if (desc.frame_type == ifu_frame) {
                // check here if F1I, F2I or F3I with or without noise
                KMO_TRY_ASSURE(nr_badpix == 0,
                               CPL_ERROR_ILLEGAL_INPUT,
                               "F1I, F2I or F3I frames can't contain badpix"
                               "information!");

                KMO_TRY_ASSURE(nr_data > 0,
                               CPL_ERROR_ILLEGAL_INPUT,
                               "For F1I, F2I or F3I there must be data frames!");

                if (nr_noise > 0) {
                    KMO_TRY_ASSURE(nr_data == nr_noise,
                                   CPL_ERROR_ILLEGAL_INPUT,
                                   "For F1I, F2I or F3I the number of data and "
                                   "noise frames must be the same!");
                }

                desc.ex_badpix = 0;
                desc.ex_noise = nr_noise > 0;

                KMO_TRY_ASSURE((desc.nr_ext >= 1) &&
                               (desc.nr_ext <= 2*KMOS_NR_IFUS),
                               CPL_ERROR_ILLEGAL_INPUT,
                               "F1I, F2I and F3I can have 1-24 extensions, "
                               "2-48 with noise frames (plus primary one)!");

                // emit warning if F1I, F2I or F3I have an other number of
                // extensions than 1 or 24 (2 or 48 with noise)
//                if (((desc.nr_ext > 2) && (desc.nr_ext < KMOS_NR_IFUS)) ||
//                    ((desc.nr_ext > KMOS_NR_IFUS) && (desc.nr_ext < 2*KMOS_NR_IFUS))) {
//                    cpl_msg_warning(cpl_func, "F1I, F2I and F3I should have 1 "
//                                    "or 24 extensions (2 or 48 with noise!)");
//                }

// too restrictive
//                KMO_TRY_ASSURE((desc.nr_ext == 1) ||
//                               (desc.nr_ext == 2) ||
//                               (desc.nr_ext == KMOS_NR_IFUS) ||
//                               (desc.nr_ext == 2*KMOS_NR_IFUS),
//                               CPL_ERROR_ILLEGAL_INPUT,
//                               "F1I, F2I and F3I can have 1 or 24 extensions, "
//                               "2 or 48 with noise frames (plus primary one)!");

                desc.naxis = arr_naxis[my_id];
                desc.naxis1 = arr_naxis1[my_id];
                desc.naxis2 = arr_naxis2[my_id];
                desc.naxis3 = arr_naxis3[my_id];
                for (i = 1; i < desc.nr_ext; i++) {
                    if (desc.naxis == 0) {
                        my_id = i;
                        desc.naxis = arr_naxis[my_id];
                        desc.naxis1 = arr_naxis1[my_id];
                        desc.naxis2 = arr_naxis2[my_id];
                        desc.naxis3 = arr_naxis3[my_id];
                    } else {
                        if (arr_naxis[i] != 0) {
                            KMO_TRY_ASSURE(desc.naxis == arr_naxis[i],
                                           CPL_ERROR_ILLEGAL_INPUT,
                                           "NAXIS has to be the same value for "
                                           "all extensions (or 0 if invalid)!");

                            if ((!emit_once1) &&
                                (arr_naxis1[i] != 0) &&
                                (desc.naxis1 != arr_naxis1[i]))
                            {
                                if (!kmclipm_omit_warning_one_slice) {
                                    cpl_msg_warning(cpl_func,
                                                    "NAXIS1 differs at least between "
                                                    "extensions %d and %d (%d and "
                                                    "%d), have any IFUs been rotated?",
                                                    my_id, i, desc.naxis1,
                                                    arr_naxis1[i]);
                                }
                                emit_once1 = TRUE;
                            }
                            if (desc.naxis > 1) {
                                if ((!emit_once2) &&
                                    (arr_naxis2[i] != 0) &&
                                    (desc.naxis2 != arr_naxis2[i]))
                                {
                                    if (!kmclipm_omit_warning_one_slice) {
                                        cpl_msg_warning(cpl_func,
                                                        "NAXIS2 differs at least "
                                                        "between extensions %d and "
                                                        "%d (%d and %d), have any "
                                                        "IFUs been rotated?", my_id,
                                                        i, desc.naxis2, arr_naxis2[i]);
                                    }
                                    emit_once2 = TRUE;
                                }
                                if (desc.naxis > 2) {
                                    if ((!emit_once3) &&
                                        (arr_naxis3[i] != 0) &&
                                        (desc.naxis3 != arr_naxis3[i]))
                                    {
                                        cpl_msg_warning(cpl_func,
                                                        "NAXIS2 differs at least "
                                                        "between extensions %d and "
                                                        "%d (%d and %d), have "
                                                        "any IFUs been rotated?",
                                                        my_id, i, desc.naxis3,
                                                        arr_naxis3[i]);
                                        emit_once3 = TRUE;
                                    }
                                }
                            }
                        }
                    }
                }

                switch (desc.naxis) {
                case 1:
                    desc.fits_type = f1i_fits;
                    break;
                case 2:
                    desc.fits_type = f2i_fits;
                    break;
                case 3:
                    desc.fits_type = f3i_fits;
                    break;
                default:
                    cpl_msg_warning(cpl_func, "All extensions are empty, "
                                              "assume F3I as type!");
                    desc.fits_type = f3i_fits;
                }

                // fill sub_desc
                for (i = 0; i < desc.nr_ext; i++) {
                    if (!recovered) {
                        KMO_TRY_EXIT_IF_NULL(
                            ifu_notused = cpl_sprintf("%s%d%s", IFU_VALID_PREFIX, arr_id[i], IFU_VALID_POSTFIX));
                        valid = !cpl_propertylist_has(main_header,ifu_notused) && arr_naxis[i] > 0;
                    } else {
                        valid = TRUE;
                    }
                    desc.sub_desc[i] = kmo_identify_fits_sub_header(i+1,
                                                                    valid,
                                                                    strcmp(arr_content[i], EXT_NOISE) == 0,
                                                                    strcmp(arr_content[i], EXT_BADPIX) == 0,
                                                                    arr_id[i]);
                    cpl_free(ifu_notused); ifu_notused= NULL;
                    KMO_TRY_CHECK_ERROR_STATE();
                }

                // check indices uniqueness of the ids
                // (or twiceness when noise exists)
                KMO_TRY_ASSURE(kmo_check_indices(arr_id, desc.nr_ext, desc.ex_noise),
                               CPL_ERROR_ILLEGAL_INPUT,
                               "The IDs of the frame seem to be incorrect"
                               "(1, 2 and 3 expected)");
            } else if (desc.frame_type == spectrum_frame) {
                KMO_TRY_ASSURE(desc.nr_ext == 1,
                               CPL_ERROR_ILLEGAL_INPUT,
                               "A F1S frame can contain only one "
                               "spectrum!");

                desc.naxis = arr_naxis[0];
                desc.naxis1 = arr_naxis1[0];
                desc.naxis2 = arr_naxis2[0];
                desc.naxis3 = arr_naxis3[0];

                KMO_TRY_ASSURE((desc.naxis == 1) &&
                               (desc.naxis2 == 0) &&
                               (desc.naxis3 == 0),
                               CPL_ERROR_ILLEGAL_INPUT,
                               "A F1S must have NAXIS = 1, NAXIS2 = 0, "
                               "NAXIS3 = 0!");

                desc.fits_type = f1s_fits;
                desc.ex_noise = 0;
                desc.ex_badpix = 0;
                desc.sub_desc[0] = kmo_identify_fits_sub_header(1,
                                            TRUE,
                                            0,
                                            0,
                                            1);
                KMO_TRY_CHECK_ERROR_STATE();
            } else if (desc.frame_type == list_frame) {
                KMO_TRY_ASSURE(((desc.nr_ext >= 1) &&
                               (desc.nr_ext <= KMOS_NR_IFUS)) || desc.nr_ext % 24 == 0,
                               CPL_ERROR_ILLEGAL_INPUT,
                               "F1L and F2L can have 1-24 extensions, or multiple of 24!");

                // do we have to store the value of TFIELDS here?
                // I think this is not needed (although almost everything is
                // prepared to do so...


                my_id = 0;
                for (i = 0; i < desc.nr_ext; i++) {
                    if (arr_naxis[i] > 0) {
                        my_id = i;
                        break;
                    }
                }
                desc.naxis = arr_naxis[my_id];

                KMO_TRY_ASSURE(desc.naxis == 2,
                               CPL_ERROR_ILLEGAL_INPUT,
                               "A F1L or F2L must have NAXIS = 2!");

                desc.naxis1 = arr_tfields[my_id];
                desc.naxis2 = arr_naxis2[my_id];
                desc.naxis3 = 0;
                if (arr_tfields[my_id] == 2) {
                    desc.fits_type = f1l_fits;
                } else if (arr_tfields[my_id] > 2) {
                    desc.fits_type = f2l_fits;
                } else {
                    KMO_TRY_ASSURE(1==0,
                                   CPL_ERROR_ILLEGAL_INPUT,
                                   "TFIELDS keyword must be >= 2!");
                }
                desc.ex_noise = 0;
                desc.ex_badpix = 0;

                // fill sub_desc
                for (i = 0; i < desc.nr_ext; i++) {
                    KMO_TRY_EXIT_IF_NULL(
                        ifu_notused = cpl_sprintf("%s%d%s", IFU_VALID_PREFIX, arr_id[i], IFU_VALID_POSTFIX));
                    desc.sub_desc[i] = kmo_identify_fits_sub_header(i+1,
                                                !cpl_propertylist_has(main_header,
                                                                      ifu_notused) &&
                                                arr_naxis[i] > 0,
                                                strcmp(arr_content[i], EXT_NOISE) == 0,
                                                strcmp(arr_content[i], EXT_BADPIX) == 0,
                                                arr_id[i]);
                    cpl_free(ifu_notused); ifu_notused = NULL;
                    KMO_TRY_CHECK_ERROR_STATE();
                }
                KMO_TRY_CHECK_ERROR_STATE();
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        if (desc.sub_desc != NULL) {
            cpl_free(desc.sub_desc); desc.sub_desc = NULL;
        }
        kmo_init_fits_desc(&desc);
    }

    cpl_propertylist_delete(main_header); main_header = NULL;
    cpl_propertylist_delete(sub_header); sub_header = NULL;
    cpl_frame_delete(frame); frame = NULL;
    cpl_free(arr_naxis); arr_naxis = NULL;
    cpl_free(arr_naxis1); arr_naxis1 = NULL;
    cpl_free(arr_naxis2); arr_naxis2 = NULL;
    cpl_free(arr_naxis3); arr_naxis3 = NULL;
    cpl_free(arr_type); arr_type = NULL;
    cpl_free(arr_id); arr_id = NULL;
    cpl_free(arr_tfields); arr_tfields = NULL;
    if (arr_content != NULL) {
        for (i = 0; i < desc.nr_ext; i++) {
            cpl_free(arr_content[i]); arr_content[i] = NULL;
        }
    }
    cpl_free(arr_content); arr_content = NULL;
    cpl_free(arr_xtension); arr_xtension = NULL;

    return desc;
}

/**
    @brief
        Creates a @li sub_fits_desc from a specific extension of a fits-file.

    @param filename The filename and path of the fits-file to describe.
    @param ext      The extension number (Starting at 0).

    @return
        The function returns a structure of type @c sub_desc.

    Creates a structure of type @c sub_desc which contains all values
    relevant to the KMOS-pipeline of a given fits-file.

    These are: VALIDDAT, IS_NOISE, DETEC_INDEX, IFU_INDEX

    The values are extracted from the headers of extensions.


    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if @c filename is NULL.
    @li CPL_ERROR_ILLEGAL_INPUT The position is less than 0 or the properties
        cannot be read from the file name or @c ext is negative.
    @li CPL_ERROR_FILE_IO   The file name does not exist.
    @li CPL_ERROR_BAD_FILE_FORMAT   The file name is not a valid FITS file.
    @li CPL_ERROR_DATA_NOT_FOUND    The requested data set at index position
        does not exist.
*/
sub_fits_desc kmo_identify_fits_sub_header(int ext_nr,
                                           int valid_data,
                                           int is_noise,
                                           int is_badpix,
                                           int id)
{
    sub_fits_desc       desc;

    KMO_TRY
    {
        // init to default
        kmo_init_fits_sub_desc(&desc);

        desc.ext_nr     = ext_nr;
        desc.valid_data = valid_data;
        desc.is_noise   = is_noise;
        desc.is_badpix  = is_badpix;
        desc.device_nr  = id;

        KMO_TRY_CHECK_ERROR_STATE();
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();

        // initialise fits_desc to default
        kmo_init_fits_sub_desc(&desc);
    }

    return desc;
}

/**
    @brief
        Checks the uniqueness of ID's.

    When @c ex_noise is FALSE then each ID in the @c id array can be present
    exactly once. If @c ex_noise is TRUE then each ID can be present twice

    If @c nr_id is greater than 3 (or 6 when @c ex_noise is set) each ID can
    occur nr_id % 3 times.

    @param id       The array containing the IDs.
    @param nr_id    The size of array id.
    @param ex_noise TRUE if noise exists, FALSE otherwise.

    @li CPL_ERROR_NULL_INPUT    if @c id is NULL.
    @li CPL_ERROR_ILLEGAL_INPUT if @c ex_noise isn't TRUE or FALSE.
*/
int kmo_check_indices(int *id, int nr_id, int ex_noise) {
    int ret_val = FALSE;

    int counter     = 0,
        tmp_id      = 1,
        i           = 0,
        j           = 0;

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

        KMO_TRY_ASSURE(nr_id > 0,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "id must be > 0!");

        KMO_TRY_ASSURE((ex_noise == 0) || (ex_noise == 1),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "ex_noise must be FALSE or TRUE!");


        for (j = 0; j < nr_id; j++) {
            tmp_id = id[j];
            for (i = 0; i < nr_id; i++) {
                if (id[i] == tmp_id) {
                    counter++;
                }
            }

            if (counter > 2) {
                KMO_TRY_ASSURE(counter == nr_id/3,
                        CPL_ERROR_ILLEGAL_INPUT,
                        "Id #%d should be present %d modulo 3, but "
                        "appears %d times!", tmp_id, nr_id, counter);
            } else {
                if (ex_noise == 1) {
                    KMO_TRY_ASSURE(counter == 2,
                            CPL_ERROR_ILLEGAL_INPUT,
                            "Id #%d should be present twice, but "
                            "appears %d times!", tmp_id, counter);
                } else {
                    if (counter != nr_id/3) {
                        KMO_TRY_ASSURE(counter == 1,
                                       CPL_ERROR_ILLEGAL_INPUT,
                                       "Id #%d should be present once, but "
                                       "appears %d times!", tmp_id, counter);
                    }
                }
            }
            counter = 0;
        }
        
        ret_val = TRUE;
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();

        ret_val = FALSE;
    }

    return ret_val;
}

/**
    @brief
        Deletes a @li main_fits_desc structure.

    @param desc The main_fits_desc to delete.
*/
void kmo_free_fits_desc(main_fits_desc *desc)
{
    KMO_TRY
    {
        KMO_TRY_ASSURE(desc != NULL,
                       CPL_ERROR_NULL_INPUT,
                       "No input data is provided!");

        if (desc->sub_desc != NULL) {

            /* free main_fits_desc */
            cpl_free(desc->sub_desc); desc->sub_desc = NULL;

            /* reset main_fits_desc */
            kmo_init_fits_desc(desc);
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
    }
}

/**
    @brief
        Initialises a @li main_fits_desc structure.

    @param desc The main_fits_desc to initialise.
*/
void kmo_init_fits_desc(main_fits_desc *desc)
{
    KMO_TRY
    {
        KMO_TRY_ASSURE(desc != NULL,
                       CPL_ERROR_NULL_INPUT,
                       "No input data is provided!");

        desc->fits_type = illegal_fits;
        desc->frame_type = illegal_frame;
        desc->naxis = -1;
        desc->naxis1 = -1;
        desc->naxis2 = -1;
        desc->naxis3 = -1;
        desc->ex_noise = -1;
        desc->ex_badpix = -1;
        desc->sub_desc = NULL;
        desc->nr_ext = -1;

    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
    }
}

/**
    @brief
        Initialises a @li sub_fits_desc structure.

    @param desc The sub_fits_desc to initialise.
*/
void kmo_init_fits_sub_desc(sub_fits_desc *desc)
{
    KMO_TRY
    {
        KMO_TRY_ASSURE(desc != NULL,
                       CPL_ERROR_NULL_INPUT,
                       "No input data is provided!");

        desc->ext_nr     = -1;
        desc->valid_data = -1;
        desc->is_noise   = -1;
        desc->is_badpix  = -1;
        desc->device_nr  = -1;

    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
    }
}

/**
    @brief
        Implements the where-function knownm from IDL.

    @param data    The vector.
    @param val     The value.
    @param op      The operator.

    @return
        A vector with the indices to the input vector which fulfill the given
        condition. When the condition is never met NULL is returned.

    The vector is checked elementwise like 'data[i] op val'.
    The operator can be any of following enums:
        eq: data[i] == val
        ne: data[i] != val
        ge: data[i] >= val
        gt: data[i] > val
        le: data[i] <= val
        lt: data[i] @c < val

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if @c data is NULL or if an illegal @c op is
                                provided.
*/
cpl_vector* kmo_idl_where(const cpl_vector *data, double val, int op)
{
    cpl_vector      *ret_vec    = NULL;

    double          *pret_vec   = NULL;

    const double    *pdata      = NULL;

    int             i           = 0,
                    j           = 0,
                    size        = 0;

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

        size = cpl_vector_get_size(data);

        // create vector of same size as input data and set default values
        // to -1.0 (will be shortened at the end)
        KMO_TRY_EXIT_IF_NULL(
            ret_vec = cpl_vector_new(size));

        KMO_TRY_EXIT_IF_NULL(
            pret_vec = cpl_vector_get_data(ret_vec));

        KMO_TRY_EXIT_IF_ERROR(
            cpl_vector_fill(ret_vec, -1.0));

        // identify valid values in data and copy inidices to temp vector
        KMO_TRY_EXIT_IF_NULL(
            pdata = cpl_vector_get_data_const(data));

        j = 0;
        for (i = 0; i < size; i++) {
            switch (op) {
                case eq:
                    if (pdata[i] == val)
                        pret_vec[j++] = i;
                    break;
                case ne:
                    if (fabs(pdata[i]-val) > 0.0001)
                        pret_vec[j++] = i;
                    break;
                case ge:
                    if (pdata[i] >= val)
                        pret_vec[j++] = i;
                    break;
                case gt:
                    if (pdata[i] > val)
                        pret_vec[j++] = i;
                    break;
                case le:
                    if (pdata[i] <= val)
                        pret_vec[j++] = i;
                    break;
                case lt:
                    if (pdata[i] < val)
                        pret_vec[j++] = i;
                    break;
                default:
                    KMO_TRY_ERROR_SET_MSG(CPL_ERROR_ILLEGAL_INPUT,
                                          "illegal operator");
                    break;
            }
        }

        //cut trailing -1
        kmo_cut_endings(&ret_vec, NULL, NULL, TRUE);
        KMO_TRY_CHECK_ERROR_STATE();
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        cpl_vector_delete(ret_vec); ret_vec = NULL;
    }

    return ret_vec;
}

/**
    @brief
        Returns a vector of given indices.

    @param data     The data vector.
    @param indices  The indices to the data vector.

    @return
        A (possibly shorter) vector with the desired values.

    The returned vector contains the values of @c data indicated by @c indices .
    @c indices needn't be orderd, so will also have the returned vector the same
    order.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if @c data is NULL or if an illegal @c op is
                                provided.
*/
cpl_vector* kmo_idl_values_at_indices(const cpl_vector *data,
                                      const cpl_vector *indices)
{
    cpl_vector*     ret_vec     = NULL;

    double          *pret_vec   = NULL;

    const double    *pdata      = NULL,
                    *pindices   = NULL;

    int             i           = 0,
                    size        = 0;

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

        KMO_TRY_EXIT_IF_NULL(
            pdata = cpl_vector_get_data_const(data));

        KMO_TRY_EXIT_IF_NULL(
            pindices = cpl_vector_get_data_const(indices));

        size = cpl_vector_get_size(indices);

        KMO_TRY_EXIT_IF_NULL(
            ret_vec = cpl_vector_new(size));

        KMO_TRY_EXIT_IF_NULL(
            pret_vec = cpl_vector_get_data(ret_vec));

        for (i = 0; i < size; i++) {
            if ((int)pindices[i] < 0) {
                KMO_TRY_ERROR_SET_MSG(CPL_ERROR_ILLEGAL_INPUT,
                                      "One of the indices is < 0!");
            } else {
                pret_vec[i] = pdata[(int)pindices[i]];
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        cpl_vector_delete(ret_vec); ret_vec = NULL;
    }

    return ret_vec;
}

/**
    @brief
        Checks which IFUs are unused.

    @param frameset      The input frameset.
    @param use_telluric TRUE if telluric should be checked, FALSE otherwise.
    @param use_illum    TRUE if illumination correction should be checked,
                         FALSE otherwise.

    @return
        A pointer to KMOS_NR_DETECTORS cpl_arrays of length
        KMOS_IFUS_PER_DETECTOR, one for each detector. It contains:
        0: if the IFU is valid
        1: if the keyword "ESO OCS ARMx NOTUSED" has been set by the instrument
           control software
        2. if the keyword "ESO PRO ARMx NOTUSED" has been set by the pipeline

    The returned pointer must be free'd manually with @c kmo_free_unused_ifus()!

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if @c frameset is NULL.
*/
cpl_array** kmo_get_unused_ifus(const cpl_frameset *frameset,
                                int use_telluric,
                                int use_illum)
{
    const char      *cat_check[] = {/*DARK,
                                    MASTER_DARK,
                                    BADPIXEL_DARK,*/
                                    FLAT_ON,
                                    BADPIXEL_FLAT,
                                    FLAT_EDGE,
                                    MASTER_FLAT,
                                    XCAL,
                                    YCAL,
                                    ARC_ON,
                                    LCAL,
                                    FLAT_SKY,
                                    ILLUM_CORR,
                                    ILLUM_CORR_FLAT,
                                    STD,
                                    TELLURIC,
                                    /*STD_IMAGE,
                                    STD_MASK,
                                    STAR_SPEC,*/
                                    OBJECT,
                                    ARITHMETIC,
                                    COMBINE,
                                    COPY,
                                    CUBE_DARK,
                                    CUBE_FLAT,
                                    CUBE_ARC,
                                    CUBE_OBJECT,
                                    CUBE_STD,
                                    EXTRACT_SPEC,
                                    EXTRACT_SPEC_MASK,
                                    FIT_PROFILE,
                                    /*FITS_STACK,
                                    FS_DATA,
                                    FS_NOISE,
                                    FS_BADPIX,*/
                                    MAKE_IMAGE,
                                    NOISE_MAP,
                                    ROTATE,
                                    SHIFT,
                                    SKY_MASK/*,
                                    STATS*/
                                    };
    cpl_array**         ret         = NULL;
    cpl_propertylist    *header     = NULL;
    int                 nr_cat      = 30,
                        ifu_nr      = 0,
                        tmp_int     = 0,
                        i           = 0,
                        j           = 0,
                        i_cat       = 0;
    char                *ifu_str    = NULL;



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

        // allocate unused_ifus-structure and initialise to zero
        KMO_TRY_EXIT_IF_NULL(
            ret = (cpl_array**)cpl_calloc(KMOS_NR_DETECTORS,
                                          sizeof(cpl_array*)));

        for (i = 0; i < KMOS_NR_DETECTORS; i++) {
            KMO_TRY_EXIT_IF_NULL(
                ret[i] = cpl_array_new(KMOS_IFUS_PER_DETECTOR, CPL_TYPE_INT));
            for (j = 0; j < KMOS_IFUS_PER_DETECTOR; j++) {
                cpl_array_set_int(ret[i], j, 0);
            }
        }

        // loop all categories to check
        for (i_cat = 0; i_cat < nr_cat; i_cat++) {
            if (!((use_telluric && (strcmp(cat_check[i_cat], TELLURIC)==0)) ||
                  (use_illum && (strcmp(cat_check[i_cat], ILLUM_CORR)==0)) ||
                  (use_illum && (strcmp(cat_check[i_cat], ILLUM_CORR_FLAT)==0))))
            {
                header = kmo_dfs_load_primary_header((cpl_frameset*)frameset,
                                                     cat_check[i_cat]);
                KMO_TRY_CHECK_ERROR_STATE();

                // loop frameset
                while (header != NULL) {
                    ifu_nr = 0;
                    for (i = 0; i < KMOS_NR_DETECTORS; i++) {
                        for (j = 0; j < KMOS_IFUS_PER_DETECTOR; j++) {
                            // check header for keywords of type
                            // "ESO OCS ARMx NOTUSED"
                            if ((cpl_array_get_int(ret[i], j, NULL) == 0) ||
                                (cpl_array_get_int(ret[i], j, NULL) == 2)) {
                                KMO_TRY_EXIT_IF_NULL(
                                    ifu_str = cpl_sprintf("%s%d%s",
                                                          IFU_VALID_PREFIX,
                                                          ifu_nr+1,
                                                          IFU_VALID_POSTFIX));
                                tmp_int = cpl_propertylist_has(header, ifu_str);
                                cpl_free(ifu_str); ifu_str = NULL;
                                KMO_TRY_CHECK_ERROR_STATE();

                                if (tmp_int == 1) {
                                    cpl_array_set_int(ret[i], j, tmp_int);
                                }
                            }

                            if (cpl_array_get_int(ret[i], j, NULL) == 0) {
                                // check header for keywords of type
                                // "ESO PRO ARMx NOTUSED"
                                KMO_TRY_EXIT_IF_NULL(
                                    ifu_str = cpl_sprintf("%s%d%s",
                                                          IFU_IGNORE_PREFIX,
                                                          ifu_nr+1,
                                                          IFU_IGNORE_POSTFIX));
                                cpl_array_set_int(ret[i], j,
                                         2*cpl_propertylist_has(header, ifu_str));
                                cpl_free(ifu_str); ifu_str = NULL;
                                KMO_TRY_CHECK_ERROR_STATE();
                            }
                            ifu_nr++;
                        }
                    }
                    cpl_propertylist_delete(header); header = NULL;

                    // get header of next frame of type cat_check[i]
                    header = kmo_dfs_load_primary_header((cpl_frameset*)frameset,
                                                         NULL);
                    KMO_TRY_CHECK_ERROR_STATE();
                } // END: loop frameset
            } // if (use_xxx)
        } // END: loop all categories to check
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        kmo_free_unused_ifus(ret); ret = NULL;
    }

    return ret;
}

/**
    @brief
        Updates a propertylist with unused IFU information.

    @param unused      The unused IFU structure.
    @param header      The propertylist to update with appropriate keywords
                       regarding unused IFUs.
    @param recipe_name The name of the recipe calling this function

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if any input is NULL.
*/
cpl_error_code kmo_set_unused_ifus(cpl_array **unused,
                                   cpl_propertylist *header,
                                   const char *recipe_name)
{
    cpl_error_code  err     = CPL_ERROR_NONE;

    int             i           = 0,
                    j           = 0,
                    ifu_nr      = 0,
                    *punused    = NULL;

    char            *ifu_str    = NULL,
                    *msg        = NULL;

    KMO_TRY
    {
        KMO_TRY_ASSURE((unused != NULL) &&
                       (header != NULL) &&
                       (recipe_name != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data is provided!");

        ifu_nr = 0;
        for (i = 0; i < KMOS_NR_DETECTORS; i++) {
            KMO_TRY_EXIT_IF_NULL(
                punused = cpl_array_get_data_int(unused[i]));
            for (j = 0; j < KMOS_IFUS_PER_DETECTOR; j++) {
                // write keyword of type "ESO OCS ARMx NOTUSED"
                if (punused[j] == 1) {
                    KMO_TRY_EXIT_IF_NULL(
                        ifu_str = cpl_sprintf("%s%d%s", IFU_VALID_PREFIX, ifu_nr+1,
                                                    IFU_VALID_POSTFIX));
                    if (!cpl_propertylist_has(header, ifu_str)) {
                        cpl_free(ifu_str); ifu_str = NULL;
                        // IFU wasn't set inactive until now from OCS
                        // this can be because in one input file it was
                        // inactive and in another it was active -->
                        // set it inactive in all files now with PRO category
                        KMO_TRY_EXIT_IF_NULL(
                            ifu_str = cpl_sprintf("%s%d%s", IFU_IGNORE_PREFIX, ifu_nr+1,
                                                        IFU_IGNORE_POSTFIX));
                        KMO_TRY_EXIT_IF_NULL(
                            msg = cpl_sprintf("%s%s%s",
                                          "IFU set inactive by ", recipe_name,
                                          ". IFU was inactive in any input frame."));
                        KMO_TRY_EXIT_IF_ERROR(
                            kmclipm_update_property_string(header,
                                                    ifu_str,
                                                    msg,
                                                    ""));
                        cpl_free(msg); msg = NULL;
                    }
                    cpl_free(ifu_str); ifu_str = NULL;
                }

                if (punused[j] == 2) {
                    // write keyword of type "ESO PRO ARMx NOTUSED"
                    KMO_TRY_EXIT_IF_NULL(
                        ifu_str = cpl_sprintf("%s%d%s", IFU_IGNORE_PREFIX, ifu_nr+1,
                                                    IFU_IGNORE_POSTFIX));

                    if (!cpl_propertylist_has(header, ifu_str)) {
                        // IFU wasn't set inactive until now, do this now
                        KMO_TRY_EXIT_IF_NULL(
                            msg = cpl_sprintf("%s%s%s",
                                          "IFU set inactive by ", recipe_name,
                                          ". IFU couldn't be processed."));
                        KMO_TRY_EXIT_IF_ERROR(
                            kmclipm_update_property_string(header,
                                                    ifu_str,
                                                    msg,
                                                    ""));
                        cpl_free(msg); msg = NULL;
                    }
                    cpl_free(ifu_str); ifu_str = NULL;
                }
                ifu_nr++;
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        err = cpl_error_get_code();
    }

    return err;
}

/**
    @brief
        Duplicates the structure for unused IFU information.

    @param unused The unused IFU structure.

    @return A duplicate of @c unused

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if any input is NULL.
*/
cpl_array** kmo_duplicate_unused_ifus(cpl_array **unused)
{
    cpl_array   **ret   = NULL;

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

        KMO_TRY_EXIT_IF_NULL(
            ret = (cpl_array**)cpl_calloc(KMOS_NR_DETECTORS,
                                          sizeof(cpl_array*)));
        int i = 0;
        for (i = 0; i < KMOS_NR_DETECTORS; i++) {
            ret[i] = cpl_array_duplicate(unused[i]);
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        kmo_free_unused_ifus(ret); ret = NULL;
    }

    return ret;
}

/**
    @brief
        Prints the status of (in)active IFUs

    @param unused The unused IFU structure.
    @param after  FALSE: Prints a string specific before processing,
                  TRUE: Prints a string specific after processing.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if @c unused is NULL.
    @li CPL_ERROR_ILLEGAL_INPUT if @c after isn't is TRUE or FALSE.
*/
void kmo_print_unused_ifus(cpl_array **unused, int after) {

    int     has_inactive_ICS    = 0,
            has_inactive_DRL    = 0,
            *punused            = NULL,
            i                   = 0,
            j                   = 0;

    char    str[512];

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

        KMO_TRY_ASSURE((after == TRUE) || (after == FALSE),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "after must be TRUE or FALSE!");

        for (i = 0; i < KMOS_NR_DETECTORS; i++) {
            KMO_TRY_EXIT_IF_NULL(
                punused = cpl_array_get_data_int(unused[i]));
            for (j = 0; j < KMOS_IFUS_PER_DETECTOR; j++) {
                switch (punused[j]) {
                case 0:
                    break;
                case 1:
                    has_inactive_ICS++;
                    break;
                case 2:
                    has_inactive_DRL++;
                    break;
                default:
                    KMO_TRY_ASSURE(1 == 0,
                                   CPL_ERROR_ILLEGAL_INPUT,
                                   "The unused structure can only contain "
                                   "0, 1 or 2!");
                }
            }
        }

        cpl_msg_info("", "-------------------------------------------");
        if (after == FALSE) {
            cpl_msg_info("", "IFU status before processing:");
        } else {
            cpl_msg_info("", "IFU status after processing:");
        }
        if ((has_inactive_ICS == 0) && (has_inactive_DRL == 0)) {
            cpl_msg_info("", "   All IFUs are active");
        } else {
            cpl_msg_info("", "   .: IFUs active");
            if (has_inactive_ICS != 0) {
                cpl_msg_info("", "   x: IFUs set inactive by ICS");
            }
            if (has_inactive_DRL != 0) {
                cpl_msg_info("", "   *: IFUs set inactive by KMOS pipeline");
            }
            cpl_msg_info("", "-------------------------------------------");

            for (i = 0; i < KMOS_NR_DETECTORS; i++) {
                sprintf(str, "      ");

                switch (i) {
                case 0:
                    cpl_msg_info("", "   IFU  1  2  3  4  5  6  7  8");
                    break;
                case 1:
                    cpl_msg_info("", "   IFU  9 10 11 12 13 14 15 16");
                    break;
                case 2:
                    cpl_msg_info("", "   IFU 17 18 19 20 21 22 23 24");
                    break;
                default:
                    ;
                }

                KMO_TRY_EXIT_IF_NULL(
                    punused = cpl_array_get_data_int(unused[i]));
                for (j = 0; j < KMOS_IFUS_PER_DETECTOR; j++) {
                    switch (punused[j]) {
                    case 0:
                        strcat(str, "  .");
                        break;
                    case 1:
                        strcat(str, "  x");
                        break;
                    case 2:
                        strcat(str, "  *");
                        break;
                    default:
                        ;
                    }
                }
                strcat(str, "\0");
                cpl_msg_info("", "%s", str);
            }
        }
        cpl_msg_info("", "-------------------------------------------");
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
    }
}

/**
    @brief
        Deallocates a unused IFU structure created with @c kmo_get_unused_ifus().

    @param unused The unused IFU structure.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if @c unused is NULL.
*/
void kmo_free_unused_ifus(cpl_array **unused) {
    KMO_TRY
    {
        KMO_TRY_ASSURE(unused != NULL,
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data is provided!");
        int i = 0;
        for (i = 0; i < KMOS_NR_DETECTORS; i++) {
            cpl_array_delete(unused[i]); unused[i] = NULL;
        }
        cpl_free(unused); unused = NULL;
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
    }
}

/**
  @brief Split a string into pieces at a given delimiter.

  @param string      The string to split.
  @param delimiter   String specifying the locations where to split.
  @param size        (Output) If non NULL, the number of found tokens is
                     returned.

  @return The function returns a newly allocated, @c NULL terminated
    array of strings, or @c NULL in case of an error.

  The function breaks up the string @em string into pieces at the places
  indicated by @em delimiter.
  The delimiter string @em delimiter never shows up in any of the resulting
  strings.

  As a special case, the result of splitting the empty string "" is an empty
  vector, not a vector containing a single string.

  The created result vector can be deallocated using @b kmo_strfreev().
*/
char** kmo_strsplit(const char *str, const char *delimiter, int *size)
{
    char        **sarray    = NULL,
                *s          = NULL;
    const char  *remainder  = NULL;
    int         nr          = 1,
                i           = 0;
    unsigned int length     = 0,
                 sz         = 0;
    char *token             = NULL;

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

        KMO_TRY_ASSURE(*delimiter != '\0',
                       CPL_ERROR_ILLEGAL_INPUT,
                       "delimiter is \"\\0\"!");

        sz = strlen(delimiter);

        // get the number of tokens to allocate
        remainder  = str;
        s = strstr(remainder, delimiter);
        if (s && (strlen(s) > sz)) {
            while (s && (strlen(s) > sz)) {
                nr++;
                remainder = s + sz;
                s = strstr(remainder, delimiter);
            }
        }

        // allocate array
        KMO_TRY_EXIT_IF_NULL(
            sarray = cpl_malloc((nr+1) * sizeof(char*)));
        if (size != NULL) {
            *size = nr;
        }

        // copy tokens into array
        remainder  = str;
        s = strstr(remainder, delimiter);
        if (s && (strlen(s) > sz)) {
            while (s && (strlen(s) >= sz)) {
                // get token
                length = s - remainder;
                KMO_TRY_EXIT_IF_NULL(
                    token = cpl_malloc((length + 1) * sizeof(char)));
                strncpy(token, remainder, length);
                token[length] = '\0';

                // put it into array
                sarray[i] = token;
                i++;

                // prepare next token
                remainder = s + sz;
                if (strlen(remainder) == 0) {
                    s = "";
                } else {
                    s = strstr(remainder, delimiter);
                }
            }
            if ((remainder != NULL) &&
                (strlen(remainder) > 0) &&
                (strcmp(remainder, delimiter) != 0))
            {
                // copy last token if there is one and if it isn't the delimiter
                KMO_TRY_EXIT_IF_NULL(
                    sarray[i] = cpl_sprintf("%s", remainder));
            } else {
                sarray[i] = NULL;
            }
        } else {
            // only one token, copy it
            KMO_TRY_EXIT_IF_NULL(
                sarray[i] = cpl_sprintf("%s", str));
        }

        // initialise last member to NULL
        sarray[nr] = NULL;
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
    }

    return sarray;
}

/**
  @brief Deallocate a @c NULL terminated string array.

  @param strarr  String array to deallocate

  The function deallocates the array of strings @em strarr and any string
  it possibly contains.
*/
void kmo_strfreev(char **strarr)
{
    register int i = 0;

    KMO_TRY
    {
        if (strarr != NULL) {
            while (strarr[i] != NULL) {
                cpl_free(strarr[i]); strarr[i] = NULL;
                i++;
            }

            cpl_free(strarr); strarr = NULL;
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
    }

    return;
}

/**
  @brief
    Convert all uppercase characters in a string into lowercase characters.

  @param s  The string to convert.

  @return Returns a pointer to the converted string.

  Walks through the given string and turns uppercase characters into
  lowercase characters using @b tolower().

  @see kmo_strupper()
*/
char* kmo_strlower(char *s)
{
    char *t = NULL;

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

        while (*t) {
            *t = tolower(*t);
            t++;
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        s = NULL;
    }

    return s;
}

/**
  @brief
    Convert all lowercase characters in a string into uppercase characters.

  @param s  The string to convert.

  @return Returns a pointer to the converted string.

  Walks through the given string and turns lowercase characters into
  uppercase characters using @b toupper().

  @see kmo_strlower()
*/
char* kmo_strupper(char *s)
{
    char *t = NULL;

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

        while (*t) {
            *t = toupper(*t);
            t++;
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        s = NULL;
    }

    return s;
}

/** @} */
