/*
 * 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 <ctype.h>
#include <math.h>

#include <cpl.h>

#include "kmclipm_math.h"

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

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

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

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

static char ** kmos_str_split(char* a_str, const char a_delim) ;
static double kmos_illum_find_ext(
        const char  *   filename,
        int             ifu_nr,
        double          angle,
        int         *   xtnum) ;
static int kmos_sci_red_clean_plist(cpl_propertylist *) ;

/*----------------------------------------------------------------------------*/
/**
  @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 CRIRES 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, Inc., 59 Temple Place, Suite 330, Boston, \n"
        "MA  02111-1307  USA" ;
    return kmos_license ;
}


/*----------------------------------------------------------------------------*/
/**
  @brief   Load the illumination image from the illum file.
  @param
  @return 
  The function finds amongst the IFU.X.DATA extensions the one with the
  closest angle (ESO PRO ROT NAANGLE). The image is loaded and returned.
 */
/*----------------------------------------------------------------------------*/
cpl_image * kmos_illum_load(
        const char  *   filename,
        cpl_type        im_type,
        int             ifu_nr,
        double          angle,
        double  *       angle_found)
{
    cpl_image   *   img ;
    float       *   pimg ;
    int             nx, ny, ix, iy, xtnum ;

    /* Check Inputs */

    /* Initialise */
    *angle_found = kmos_illum_find_ext(filename, ifu_nr, angle, &xtnum) ;
    if (*angle_found < 0.0) return NULL ;

    if (*angle_found > 99.0) {
        cpl_msg_info(__func__, 
                "Use Illum ext. %d for IFU %d (no angle, only matching ext)",
                xtnum, ifu_nr) ;
    } else {
        cpl_msg_info(__func__, 
                "Angle %g (closest to %g) found in Illum ext. %d for IFU %d",
                *angle_found, angle, xtnum, ifu_nr) ;
    }

    /* Load image */
    img = cpl_image_load(filename, im_type, 0, xtnum);

    /* reject invalid values for internal bad pixel mask */
    pimg = cpl_image_get_data(img);
    nx = cpl_image_get_size_x(img);
    ny = cpl_image_get_size_y(img);
    for (iy = 0; iy < ny; iy++) {
        for (ix = 0; ix < nx; ix++) {
            if (kmclipm_is_nan_or_inf(pimg[ix+iy*nx]) == 1) {
                cpl_image_reject(img, ix+1, iy+1);
            }
        }
    }
    return img;
}

/*----------------------------------------------------------------------------*/
/**
  @brief        Split "A.B.C" in A B  and C
  @param
  @return 
		tokens = kmos_str_split("IFU.3.DATA", '.');
        *(tokens + 0) is "IFU"
        *(tokens + 1) is "3"
        *(tokens + 2) is "DATA"
 */
/*----------------------------------------------------------------------------*/
static char ** kmos_str_split(char* a_str, const char a_delim)
{
    char** result    = 0;
    size_t count     = 0;
    char* tmp        = a_str;
    char* last_comma = 0;
    char delim[2];
    delim[0] = a_delim;
    delim[1] = 0;

    /* Count how many elements will be extracted. */
    while (*tmp) {
        if (a_delim == *tmp) {
            count++;
            last_comma = tmp;
        }
        tmp++;
    }

    /* Add space for trailing token. */
    count += last_comma < (a_str + strlen(a_str) - 1);

    /* Add space for terminating null string so caller
       knows where the list of returned strings ends. */
    count++;

    result = cpl_malloc(sizeof(char*) * count);

    if (result) {
        size_t idx  = 0;
        char* token = strtok(a_str, delim);

        while (token) {
			if (idx >= count) return NULL ;
            *(result + idx++) = cpl_strdup(token);
            token = strtok(0, delim);
        }
		if (idx != count-1) return NULL ;
        *(result + idx) = 0;
    }

    return result;
}

/*----------------------------------------------------------------------------*/
/**
  @brief   Find which extenѕion for a given IFU has the best angle
  @param
  @return 
 */
/*----------------------------------------------------------------------------*/
static double kmos_illum_find_ext(
        const char  *   filename,
        int             ifu_nr,
        double          angle,
        int         *   xtnum) 
{
    cpl_propertylist    *   plist ;
    double                  cur_angle ;
    const char          *   extname ;
	char				** 	tokens;
    double                  angle_found, angle_diff, angle_in_loc,
                            angle_cur_loc ;
    int                     nr_ext, i, j, unique_extension ;

    /* Check Inputs */
    if (xtnum==NULL) return -1.0 ;

    /* Initialise */
    *xtnum = -1 ;
    angle_found = -1.0 ;
    angle_diff = 720 ;
    unique_extension = -1 ;

    nr_ext = cpl_fits_count_extensions(filename);
    if (nr_ext <= 0) return -1.0 ;

    /* Extract all available angles from header */
    for (i=1 ; i<=nr_ext ; i++) {
        plist = cpl_propertylist_load(filename, i);
		extname = cpl_propertylist_get_string(plist, EXTNAME);
        if (cpl_error_get_code() != CPL_ERROR_NONE) {
            cpl_error_reset() ;
            cpl_propertylist_delete(plist) ;
            continue ;
        }
		tokens = kmos_str_split((char*)extname, '.');
        if (tokens) {
            if (atoi(*(tokens + 1)) == ifu_nr && 
                    !strcmp(*(tokens + 2), "DATA")) {
                /* Unique extension  */
                if (unique_extension > 0) unique_extension = 0 ;
                if (unique_extension < 0) unique_extension = i ;

                cur_angle = cpl_propertylist_get_double(plist, CAL_ROTANGLE);
                if (cpl_error_get_code() != CPL_ERROR_NONE) {
                    /* This extension does not contain the ANGLE in header */
                    cpl_error_reset() ;
                    cpl_propertylist_delete(plist) ;
                    for (j = 0; *(tokens + j); j++) cpl_free(*(tokens + j));
                    cpl_free(tokens);
                    continue ;
                }
                /* In case angles are <0.0 */
                angle_cur_loc = cur_angle ;
                if (angle_cur_loc < 0.0) angle_cur_loc += 360.0 ;
                angle_in_loc = angle ;
                if (angle_in_loc < 0.0) angle_in_loc += 360.0 ;
                /* Keep the smallest difference */
                if (fabs(angle_in_loc-angle_cur_loc) < angle_diff) {
                    angle_diff = fabs(cur_angle-angle) ;
                    angle_found = cur_angle ;
                    *xtnum = i ;
                }
            }
			for (j = 0; *(tokens + j); j++) cpl_free(*(tokens + j));
			cpl_free(tokens);
        }
        cpl_propertylist_delete(plist) ;
    }

    /* If could not find any extension - No angle in header ?  */
    /* Return unique extension -> backward compatibility */
    if (*xtnum < 0) {
        if (unique_extension > 0) {
            *xtnum = unique_extension ;
            angle_found = 99.9 ;
        }
    }

    return angle_found;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Compute REFLEX SUFFIX keyword
  @param    
  @return  
 */
/*----------------------------------------------------------------------------*/
char * kmos_get_reflex_suffix(
        int             mapping_id,
        const char  *   ifus_txt,
        const char  *   name,
        const char  *   obj_name)
{
    char    *   out ;
    int         i ;

    /* Check Inputs */
    if (ifus_txt == NULL || name == NULL) return NULL;

    /* Mapping */
    if (mapping_id > 0)     return cpl_strdup("mapping") ;

    /* Name is used */
    if (strcmp(name, ""))   return cpl_strdup(name) ;

    /* IFUs option is used */
    if (strcmp(ifus_txt, "")) {
        out = cpl_strdup(ifus_txt) ;

        /* Replace ; by _ */
        int i=0;
        while(out[i]!='\0') {
            if(out[i]==';') out[i]='_';
            i++; 
        }
        return out ;
    }

    /* Default case */
    return cpl_strdup(obj_name) ;
}
 
/*----------------------------------------------------------------------------*/
/**
  @brief    Collapse some cubes
  @param    frameset            Set of frames 
  @return   0 if ok, -1 in error case
 */
/*----------------------------------------------------------------------------*/
int kmos_collapse_cubes(
        char                    *   pro_catg,
        cpl_frameset            *   frameset,
        cpl_parameterlist       *   parlist,
        double                      threshold,
        const char              *   range,
        const char              *   cmethod,
        double                      cpos_rej,
        double                      cneg_rej,
        int                         citer,
        int                         cmin,
        int                         cmax)
{
    cpl_frame           *   in_cube_frame ;
    cpl_frame           *   ref_spectrum_frame ;
    const char          *   fn_cube ;
    const char          *   pro_catg_out ;
    cpl_vector          *   ranges ;
    cpl_vector          *   spec_data_in ;
    cpl_vector          *   spec_lambda_in ;
    cpl_vector          *   identified_slices ;
    cpl_propertylist    *   sub_header_data ;
    cpl_propertylist    *   sub_header_noise ;
    cpl_propertylist    *   plist ;
    cpl_propertylist    *   pro_plist ;
    cpl_imagelist       *   data_in ;
    cpl_imagelist       *   noise_in ;
    cpl_image           *   data_out ;
    cpl_image           *   noise_out ;
    kmclipm_vector      *   kmclipm_tmp_vec ;
    double                  spec_crpix, spec_crval, spec_cdelt,
                            ifu_crpix, ifu_crval, ifu_cdelt ;
    char                *   filename ;
    int                     nr_devices, devnr, index_data, index_noise ;
    main_fits_desc          desc1 ;
    int                     i;

    /* Check Inputs */
    if (frameset == NULL || parlist == NULL || pro_catg == NULL) return -1 ;

    if (!strcmp(pro_catg, COMBINED_CUBE)) {
        pro_catg_out = COMBINED_COLLAPSED ;
    } else if (!strcmp(pro_catg, RECONSTRUCTED_CUBE)) {
        pro_catg_out = RECONSTRUCTED_COLLAPSED ;
    } else if (!strcmp(pro_catg, COMBINED_RECONS)) {
        pro_catg_out = COMBINED_RECONS_COLLAPSED ;
    } else {
        return -1 ;
    }

    /* Get ranges */
    ranges = kmo_identify_ranges(range);

    /* Get OH_SPEC */
    if (ref_spectrum_frame = kmo_dfs_get_frame(frameset, OH_SPEC)) {
        plist = kmos_dfs_load_sub_header(ref_spectrum_frame, 1, FALSE);
        spec_crpix = cpl_propertylist_get_double(plist, CRPIX1);
        spec_crval = cpl_propertylist_get_double(plist, CRVAL1);
        spec_cdelt = cpl_propertylist_get_double(plist, CDELT1);
        cpl_propertylist_delete(plist) ;

        /* Load OH lines data */
        kmclipm_tmp_vec = kmos_dfs_load_vector(ref_spectrum_frame, 1, FALSE) ;
        spec_data_in = kmclipm_vector_create_non_rejected(kmclipm_tmp_vec);
        kmclipm_vector_delete(kmclipm_tmp_vec);

        /* Convert threshold from percentage to absolute value */
        threshold = threshold * cpl_vector_get_max(spec_data_in);

        /* Create lambda-vector for OH-lines */
        spec_lambda_in = kmo_create_lambda_vec(
                cpl_vector_get_size(spec_data_in), (int)spec_crpix, spec_crval,
                spec_cdelt) ;
    } else {
        spec_data_in = NULL ;
        spec_lambda_in = NULL ;
    }

    /* Loop on the cubes with the requested PRO.CATG */
    in_cube_frame = kmo_dfs_get_frame(frameset, pro_catg);
    while (in_cube_frame != NULL ) {
        fn_cube = cpl_frame_get_filename(in_cube_frame);
        kmo_init_fits_desc(&desc1);
        desc1 = kmo_identify_fits_header(fn_cube);

        /* Create the Header and save the initial file */
        pro_plist = cpl_propertylist_new() ;
        plist = cpl_propertylist_load(fn_cube, 0) ;
        cpl_propertylist_update_string(pro_plist,CPL_DFS_PRO_CATG,pro_catg_out);
        cpl_propertylist_copy_property(pro_plist, plist, 
                "ESO PRO REFLEX SUFFIX"); 
        filename = cpl_sprintf("make_image_%s", fn_cube) ;
        cpl_dfs_save_propertylist(frameset, NULL, parlist, frameset,
                in_cube_frame, "kmos_sci_red", pro_plist, NULL, VERSION,
                filename) ;
        cpl_propertylist_delete(pro_plist) ;
        cpl_propertylist_delete(plist) ;

        if (desc1.ex_noise == TRUE)     nr_devices = desc1.nr_ext / 2;
        else                            nr_devices = desc1.nr_ext;
        for (i = 1; i <= nr_devices; i++) {
            if (desc1.ex_noise == FALSE) devnr=desc1.sub_desc[i-1].device_nr;
            else                         devnr=desc1.sub_desc[2*i-1].device_nr;
            if (desc1.ex_badpix == FALSE)
                index_data = kmo_identify_index_desc(desc1, devnr, FALSE);
            else
                index_data = kmo_identify_index_desc(desc1, devnr, 2);
            index_noise = 0 ;
            if (desc1.ex_noise)
                index_noise = kmo_identify_index_desc(desc1, devnr, TRUE);

            /* Load the Extension Header */
            sub_header_data = kmos_dfs_load_sub_header(in_cube_frame, devnr,
                    FALSE);

            /* Load noise */
            sub_header_noise = NULL ;
            if (desc1.ex_noise)
               sub_header_noise=kmos_dfs_load_sub_header(in_cube_frame, devnr,
                       TRUE);

            /* If Data Valid */
            if (desc1.sub_desc[index_data-1].valid_data == TRUE) {
                /* Interpolate oh-lines to fit input data */
                ifu_crpix = cpl_propertylist_get_double(sub_header_data,CRPIX3);
                ifu_crval = cpl_propertylist_get_double(sub_header_data,CRVAL3);
                ifu_cdelt = cpl_propertylist_get_double(sub_header_data,CDELT3);

                if (spec_data_in == NULL) {
                    identified_slices = kmo_identify_slices(ranges, ifu_crpix,
                            ifu_crval, ifu_cdelt, desc1.naxis3);
                } else {
                    identified_slices = kmo_identify_slices_with_oh(
                            spec_data_in, spec_lambda_in, ranges, threshold,
                            ifu_crpix, ifu_crval, ifu_cdelt, desc1.naxis3);
                }
                kmos_sci_red_clean_plist(sub_header_data) ;

                /* Load data */
                data_in = kmos_dfs_load_cube(in_cube_frame, devnr, FALSE) ;

                /* Load noise, if existing */
                noise_in = NULL ;
                if (desc1.ex_noise && desc1.sub_desc[index_noise-1].valid_data)
                    noise_in = kmos_dfs_load_cube(in_cube_frame, devnr, TRUE) ;

                /* Process */
                noise_out = NULL ;
                kmclipm_make_image(data_in, noise_in, &data_out, &noise_out,
                        identified_slices, cmethod, cpos_rej, cneg_rej, citer,
                        cmax, cmin);
                cpl_imagelist_delete(data_in);
                if (noise_in != NULL)
                    cpl_imagelist_delete(noise_in);
                cpl_vector_delete(identified_slices);

                /* Save Data */
                kmclipm_image_save(data_out, filename, CPL_BPP_IEEE_FLOAT,
                        sub_header_data, CPL_IO_EXTEND, 0./0.) ;

                /* Process & save noise, if existing */
                if (desc1.ex_noise) {
                    kmclipm_image_save(noise_out, filename, CPL_BPP_IEEE_FLOAT,
                            sub_header_noise, CPL_IO_EXTEND, 0./0.);
                }

                /* Free memory */
                cpl_image_delete(data_out);
                if (noise_out != NULL) cpl_image_delete(noise_out);
            } else {
                kmos_sci_red_clean_plist(sub_header_data) ;

                /* invalid IFU, just save sub_headers */
                cpl_propertylist_save(sub_header_data, filename, CPL_IO_EXTEND);                 if (desc1.ex_noise)
                    cpl_propertylist_save(sub_header_noise, filename,
                            CPL_IO_EXTEND);
            }
            cpl_propertylist_delete(sub_header_data);
            if (sub_header_noise != NULL)
                cpl_propertylist_delete(sub_header_noise);
        }
        cpl_free(filename) ;
        kmo_free_fits_desc(&desc1);

        /* Next candidate */
        in_cube_frame = kmo_dfs_get_frame(frameset, NULL);
    }

    if (spec_data_in != NULL) cpl_vector_delete(spec_data_in);
    if (spec_lambda_in != NULL) cpl_vector_delete(spec_lambda_in);
    return 0 ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Clean a propertylist
  @param    plist       The propertylist to clean
  @return   0 if ok, -1 in error case
 */
/*----------------------------------------------------------------------------*/
static int kmos_sci_red_clean_plist(cpl_propertylist * plist)
{
    if (cpl_propertylist_has(plist, CRPIX3))
        cpl_propertylist_erase(plist, CRPIX3);
    if (cpl_propertylist_has(plist, CRVAL3))
        cpl_propertylist_erase(plist, CRVAL3);
    if (cpl_propertylist_has(plist, CDELT3))
        cpl_propertylist_erase(plist, CDELT3);
    if (cpl_propertylist_has(plist, CTYPE3))
        cpl_propertylist_erase(plist, CTYPE3);
    if (cpl_propertylist_has(plist, CUNIT3))
        cpl_propertylist_erase(plist, CUNIT3);
    if (cpl_propertylist_has(plist, CD1_3))
        cpl_propertylist_erase(plist, CD1_3);
    if (cpl_propertylist_has(plist, CD2_3))
        cpl_propertylist_erase(plist, CD2_3);
    if (cpl_propertylist_has(plist, CD3_3))
        cpl_propertylist_erase(plist, CD3_3);
    if (cpl_propertylist_has(plist, CD3_2))
        cpl_propertylist_erase(plist, CD3_2);
    if (cpl_propertylist_has(plist, CD3_1))
        cpl_propertylist_erase(plist, CD3_1);

    return 0 ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Get the frameset for a given angle and DO_CATG
  @param    frameset    Set of frames with several ARC_ON frames
  @param    angle       The wished angle
  @param    do_catg     The tag of the concerned frames
  @return   Extract the frames with the wished angle or NULL if not found
            Needs to be freed with cpl_frameset_delete()
 */
/*----------------------------------------------------------------------------*/
cpl_frameset * kmos_get_angle_frameset(
        cpl_frameset        *   frameset,
        int                     angle,
        const char          *   do_catg)
{
    cpl_frameset        *   out ;
    cpl_frame           *   frame ;
    cpl_propertylist    *   plist ;
    int                     rot_angle ;

    /* Check entries */
    if (frameset == NULL || do_catg == NULL) return NULL;

    /* Create the output frameset */
    out = cpl_frameset_new() ;

    /* Loop on the frames to find the wished one */
    frame = kmo_dfs_get_frame(frameset, do_catg);
    while (frame != NULL) {
        plist=cpl_propertylist_load(cpl_frame_get_filename(frame), 0);
        if (cpl_propertylist_has(plist, ROTANGLE)) {
            rot_angle = (int)rint(cpl_propertylist_get_double(plist, ROTANGLE));
            if (rot_angle < 0)  rot_angle += 360;
            if (angle == rot_angle) {
                cpl_frameset_insert(out, cpl_frame_duplicate(frame)) ;
            }
        }
        cpl_propertylist_delete(plist);
        frame = kmo_dfs_get_frame(frameset, NULL);
    }

    /* If no frame found - return NULL */
    if (cpl_frameset_get_size(out) == 0) {
        cpl_frameset_delete(out) ;
        return NULL ;
    }
    return out ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Get the frameset for a given angle and DO_CATG - PLUS calibrations
  @param    frameset    Set of frames
  @param    angle       The wished angle
  @param    do_catg     The tag of the concerned frames
  @return   Extract the frames with the wished angle or NULL if not found
            Needs to be freed with cpl_frameset_delete()
 */
/*----------------------------------------------------------------------------*/
cpl_frameset * kmos_purge_wrong_angles_frameset(
        cpl_frameset        *   frameset,
        int                     angle,
        const char          *   do_catg)
{
    cpl_frameset        *   out ;
    cpl_frame           *   frame ;
    cpl_propertylist    *   plist ;
    int                     rot_angle, copy, i ;
    cpl_size                nbframes ;

    /* Check entries */
    if (frameset == NULL || do_catg == NULL) return NULL;

    /* Initalise */
    nbframes = cpl_frameset_get_size(frameset) ;

    /* Create the output frameset */
    out = cpl_frameset_new() ;

    /* Loop on the frames to find the wished one */
    for (i=0 ; i<nbframes ; i++) {
        frame = cpl_frameset_get_position(frameset, i) ;

        /* Copy a priori */
        copy = 1 ;

        if (cpl_frame_get_group(frame) == CPL_FRAME_GROUP_RAW) {
            /* Purge RAW files with wrong do_catg */
            if (strcmp(cpl_frame_get_tag(frame), do_catg)) copy = 0 ;

            /* Purge RAW files with wrong angle */
            plist=cpl_propertylist_load(cpl_frame_get_filename(frame), 0);
            if (cpl_propertylist_has(plist, ROTANGLE)) {
                rot_angle = (int)rint(cpl_propertylist_get_double(plist, 
                            ROTANGLE));
                if (rot_angle < 0)  rot_angle += 360;
                if (angle != rot_angle) copy = 0 ;
            } else {
                copy = 0 ;
            }
            cpl_propertylist_delete(plist);
        }
        
        /* Copy or not */
        if (copy) {
            cpl_frameset_insert(out, cpl_frame_duplicate(frame)) ;
        }
    }

    /* If no frame found - return NULL */
    if (cpl_frameset_get_size(out) == 0) {
        cpl_frameset_delete(out) ;
        return NULL ;
    }
    return out ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Get the frame for a given angle and DO_CATG
  @param    frameset    Set of frames with several ARC_ON frames
  @param    angle       The wished angle
  @param    do_catg     The tag of the concerned frames
  @return   The first found frame with the wished angle (no need to free) 
            or NULL if not found
 */
/*----------------------------------------------------------------------------*/
cpl_frame * kmos_get_angle_frame(
        cpl_frameset        *   frameset,
        int                     angle,
        const char          *   do_catg)
{
    cpl_frame           *   frame ;
    cpl_propertylist    *   plist ;
    int                     rot_angle ;

    /* Check entries */
    if (frameset == NULL || do_catg == NULL) return NULL;

    /* Loop on the frames to find the wished one */
    frame = kmo_dfs_get_frame(frameset, do_catg);
    while (frame != NULL) {
        plist=cpl_propertylist_load(cpl_frame_get_filename(frame), 0);
        if (cpl_propertylist_has(plist, ROTANGLE)) {
            rot_angle = (int)rint(cpl_propertylist_get_double(plist, ROTANGLE));
            if (rot_angle < 0)  rot_angle += 360;
            if (angle == rot_angle) {
                cpl_propertylist_delete(plist);
                return frame ;
            }
        }
        cpl_propertylist_delete(plist);
        frame = kmo_dfs_get_frame(frameset, NULL);
    }
    return NULL ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Get Rotator angles
  @param    frameset    Set of frames with several ARC_ON frames
  @param    nb [out]    Number of different angles
  @param    do_catg     The tag of the concerned frames
  @return   The angles array. Needs to be freed by cpl_free()
 */
/*----------------------------------------------------------------------------*/
int * kmos_get_angles(
        cpl_frameset        *   frameset,
        int                 *   nb,
        const char          *   do_catg)
{
    int                 *   angles_nb ;
    int                     nb_bins = 360 ;
    cpl_frame           *   frame ;
    cpl_propertylist    *   plist ;
    int                 *   angles ;
    int                     nb_angles ;
    int                     rot_angle ;
    int                     i ;

    /* Check entries */
    if (nb == NULL || frameset == NULL || do_catg == NULL) return NULL;

    /* Create output angles */
    angles_nb = cpl_calloc(nb_bins, sizeof(int)) ;

    /* Loop on frames to fill angles_nb */
    frame = kmo_dfs_get_frame(frameset, do_catg);
    while (frame != NULL) {
        plist = cpl_propertylist_load(cpl_frame_get_filename(frame), 0);
        if (cpl_propertylist_has(plist, ROTANGLE)) {
            rot_angle = (int)rint(cpl_propertylist_get_double(plist, ROTANGLE));
            if (rot_angle < 0)                      rot_angle += 360;
            if (rot_angle < 360 && rot_angle >= 0)  angles_nb[rot_angle]++;
        } else {
            cpl_msg_warning(__func__,"File %s has no keyword \"ROTANGLE\"",
                    cpl_frame_get_filename(frame));
        }
        cpl_propertylist_delete(plist);
        frame = kmo_dfs_get_frame(frameset, NULL);
    }

    /* Count the number of different angles */
    nb_angles = 0 ;
    for (i = 0; i < nb_bins; i++) {
        if (angles_nb[i] != 0) nb_angles++;
    }

    /* Create the output angles array */
    angles = cpl_calloc(nb_angles, sizeof(int)) ;

    /* Fill angles from angles_nb */
    nb_angles = 0 ;
    for (i = 0; i < nb_bins; i++) {
        if (angles_nb[i] != 0) {
            cpl_msg_info(__func__, "Found %d frames with angle %d",
                    angles_nb[i],i);
            angles[nb_angles] = i;
            nb_angles++;
        }
    }
    cpl_free(angles_nb) ;

    /* Return */
    *nb = nb_angles ;
    return angles ;
}

/**
    @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
    {
        /* Initialize */
        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");

        /* Load the Primary Header */
        KMO_TRY_EXIT_IF_NULL(
            main_header = kmclipm_propertylist_load(filename, 0));

        /* Check input */
        KMO_TRY_ASSURE(cpl_propertylist_get_int(main_header, NAXIS) == 0,
                CPL_ERROR_ILLEGAL_INPUT, "Primary extension must be empty");

        /* Get the Number of extensions */
        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,
                "No Extension Found");

        /* Check if the frame is RAW */
        /* All EXTNAME values of all extensions must contain "CHIP" */
        /* Otherwise, is_raw is set to FALSE */
        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));

                /* Check if extname contains "CHIP" */
                result = strstr(extname, EXTNAME_RAW);
                if (result ==  NULL)    is_raw = FALSE;
                cpl_propertylist_delete(sub_header); sub_header = NULL;
                i++;
            }
        }

        /* Allocate the sub_desc for each extension */
        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 unit and 3 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 */

            /* Create Local Infos Holders */
            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 Informations 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);

                /* XTENSION = IMAGE */
                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 is different 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) {
                /* XTENSION = BINTABLE */
                    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 {
                /* XTENSION seems wrong */
                    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 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 infos");

                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)!");

                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 ext_nr
  @param valid_data
  @param is_noise
  @param is_badpix
  @param device_nr
  @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.

  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     device_nr)
{
    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  = device_nr;

        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, "Null Inputs");
        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->nr_ext = -1;
        desc->sub_desc = NULL;
    }
    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,
                                    SNR_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;
}

/** @} */
