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

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

#include <cpl.h>

#include "kmclipm_constants.h"
#include "kmclipm_functions.h"

#include "kmo_error.h"
#include "kmo_dfs.h"
#include "kmo_priv_fits_stack.h"
#include "kmo_priv_functions.h"
#include "kmo_debug.h"

/*----------------------------------------------------------------------------*/
/**
    @defgroup kmos_priv_fits_stack     Helper functions for recipe kmo_fits_stack.

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

double kmo_dummy_main_header_alpha[] = {
235949.663,
    10.677,
   118.650,
    32.047,
   149.485,
    24.458,
   220.278,
    46.486,
   224.378,
    42.524,
   108.768,
    12.372,
235947.347,
    10.409,
235926.671,
235945.066,
235828.354,
235922.570,
235724.482,
235837.166,
235732.130,
     0.304,
235824.083,
235926.594
};

double kmo_dummy_main_header_delta[] = {
-845648.643,
-845906.234,
-845803.444,
-845847.857,
-845758.709,
-845904.853,
-850001.737,
-845934.311,
-850011.816,
-850029.098,
-850209.139,
-850018.706,
-850259.832,
-850059.271,
-850208.302,
-850050.872,
-850039.122,
-850026.384,
-845947.845,
-845936.460,
-845849.366,
-845943.954,
-845709.717,
-845807.650
};

const char* kmo_dummy_main_header_name[] = {
"FDF_050",
"FDF_070",
"FDF_051",
"FDF_081",
"SKY",
"FDF_079",
"SKY",
"SKY",
"FDF_080",
"AGA",
"AGA2",
"SKY",
"FDF_086",
"FDF_066",
"FDF_069",
"SKY",
"SKY",
"DADA",
"DADA2",
"SKY",
"SKY",
"FDF_067",
"SKY",
"SKY"
};

/**
    @brief
        Adds KMOS-specific keywords to the main-header.

    The propertylist @c pl will be extended by properties indicating if it
    contains noise or bad pixel information and the number of extensions.
    If kmostype is RAW, then nothing will be added.


    Attention:  A file can either contain data with or without noise or bad
                pixel information, but not all three in the same time.
                It must be asserted by the calling function that this is
                asserted.

    @param parlist      The parameter list.
    @param frameset     The frame set.

    @return
        In case of success 0 is returned, -1 otherwise.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if @c parlist or @c frameset are NULL.
    @li CPL_ERROR_ILLEGAL_INPUT if @c parlist contains a wrong KMOS type or
                                if mainkeys or subkeys don't contain 3 entries
                                   for each keyword to add or
                                if an input frame has an empty main extension or
                                if the frames don't have 1, 2 or 3 dimensions or
                                if the frames are not correctly ordered (like
                                   STACK_DATA, STACK_NOISE, STACK_DATA,
                                   STACK_NOISE, ...) or
                                if the number of provided frames doesn't match
                                   the required number for a specific file type
*/
int kmo_priv_fits_stack(cpl_parameterlist *parlist, cpl_frameset *frameset)
{
    const char          *type               = NULL,
                        *format             = NULL,
                        *title              = NULL,
                        *filename           = NULL,
                        *mainkey            = NULL,
                        *subkey             = NULL,
                        *tag                = NULL,
                        *p_name             = NULL,
                        **ptbl_names        = NULL,
                        *valid_txt          = NULL,
                        *input              = NULL;

    char                **mainkey_values    = NULL,
                        **subkey_values     = NULL,
                        **format_values     = NULL,
                        **title_values      = NULL,
                        msg_string[256],
                        *name               = NULL,
                        *tmp_name           = NULL,
                        *tmp_comment        = NULL,
                        *err_msg            = NULL,
                        *category           = NULL;

    int                 ret_val             = 0,
                        is_ascii_file       = FALSE,
                        nr_data             = 0,
                        nr_noise            = 0,
                        nr_badpix           = 0,
                        dim                 = 0,
                        exists_noise        = 0,
                        is_noise            = 0,
                        is_badpix           = 0,
                        nr_inputs           = 0,
                        x                   = -1,
                        y                   = -1,
                        z                   = -1,
                        index               = 0,
                        actual_frame        = FALSE,
                        last_frame          = FALSE,
                        size                = 0,
                        sizetitle           = 0,
                        needs_procat        = 0,
                        i                   = 0,
                        j                   = 0,
                        k                   = 0;

    cpl_propertylist    *header            = NULL,
                        *ref_sub_header    = NULL,
                        *global_headers[4],
                        *pl                = NULL;

    cpl_property        *p                 = NULL;

    cpl_frame           *frame             = NULL,
                        **input_frames     = NULL;

    kmclipm_vector      *vec               = NULL;
    cpl_vector          *valid             = NULL;
    cpl_image           *img               = NULL;
    cpl_imagelist       *cube              = NULL;

    float               val                 = 0;

    cpl_table           *tbl                = NULL;

    cpl_array           *tbl_names          = NULL;

    KMO_TRY
    {
        global_headers[0] = NULL;
        global_headers[1] = NULL;
        global_headers[2] = NULL;
        global_headers[3] = NULL;

        strcpy(msg_string, "");

        /* --- check input --- */
        KMO_TRY_ASSURE((parlist != NULL) && (frameset != NULL),
                        CPL_ERROR_NULL_INPUT,
                        "Not all input data is provided!");

        cpl_msg_info("", "--- Parameter setup for kmo_fits_stack ---");
        input = kmo_dfs_get_parameter_string(parlist,
                                            "kmos.kmo_fits_stack.input");
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_fits_stack.input"));

        if ((input == NULL) || (strcmp(input, "") == 0)) {
            KMO_TRY_ASSURE(cpl_frameset_get_size(frameset) > 0,
                            CPL_ERROR_ILLEGAL_INPUT,
                            "Frameset must contain at least one frame!");

            KMO_TRY_ASSURE((cpl_frameset_count_tags(frameset, FS_DATA) >= 1) ||
                           (cpl_frameset_count_tags(frameset, FS_BADPIX) == 3),
                   CPL_ERROR_NULL_INPUT,
                   "At least one STACK_DATA frame or exactly 3 STACK_BADPIX frames "
                   "needed in this frameset!");
        } else {
            KMO_TRY_ASSURE(cpl_frameset_get_size(frameset) == 0,
                            CPL_ERROR_ILLEGAL_INPUT,
                            "Frameset must be empty when used with input-parameter!");

            KMO_TRY_EXIT_IF_NULL(
                frame = cpl_frame_new());
            KMO_TRY_EXIT_IF_ERROR(
                cpl_frame_set_filename(frame, input));
            KMO_TRY_EXIT_IF_ERROR(
                cpl_frame_set_tag(frame, FS_DATA));
            KMO_TRY_EXIT_IF_ERROR(
                cpl_frame_set_type(frame, CPL_FRAME_TYPE_NONE));
            KMO_TRY_EXIT_IF_ERROR(
                cpl_frame_set_group(frame, CPL_FRAME_GROUP_NONE));
            KMO_TRY_EXIT_IF_ERROR(
                cpl_frame_set_level(frame, CPL_FRAME_LEVEL_NONE));
            KMO_TRY_EXIT_IF_ERROR(
                cpl_frameset_insert(frameset, frame));
        }

        type = kmo_dfs_get_parameter_string(parlist,
                                            "kmos.kmo_fits_stack.type");
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_fits_stack.type"));

        needs_procat = kmo_dfs_get_parameter_bool(parlist,
                                                "kmos.kmo_fits_stack.category");
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_fits_stack.category"));
        KMO_TRY_CHECK_ERROR_STATE();

        KMO_TRY_ASSURE((type != NULL) &&
                       (strcmp(type, "") != 0) &&
                       ((strcmp(type, RAW) == 0) ||
                        (strcmp(type, F1D) == 0) ||
                        (strcmp(type, F2D) == 0) ||
                        (strcmp(type, B2D) == 0) ||
                        (strcmp(type, F1I) == 0) ||
                        (strcmp(type, F2I) == 0) ||
                        (strcmp(type, F3I) == 0) ||
                        (strcmp(type, F1S) == 0) ||
                        (strcmp(type, F1L) == 0) ||
                        (strcmp(type, F2L) == 0)),
                        CPL_ERROR_ILLEGAL_INPUT,
                       ">>> The 'type' parameter must be supplied "
                        "(Either RAW, F1D, F2D, B2D, "
                        "F1I, F2I, F3I, F1S, F1L, F2L)!");

        if ((strcmp(type, F2L) == 0) ||
            (strcmp(type, F1L) == 0))
        {
            // we have a ascii-file (text)
            format = kmo_dfs_get_parameter_string(parlist,
                                              "kmos.kmo_fits_stack.format");
            KMO_TRY_EXIT_IF_ERROR(
                kmo_dfs_print_parameter_help(parlist, "kmos.kmo_fits_stack.format"));


            title = kmo_dfs_get_parameter_string(parlist,
                                              "kmos.kmo_fits_stack.title");
            KMO_TRY_EXIT_IF_ERROR(
                kmo_dfs_print_parameter_help(parlist, "kmos.kmo_fits_stack.title"));

            // find out if the frame is a table
            KMO_TRY_EXIT_IF_NULL(
                    frame = cpl_frameset_find(frameset, NULL)) ;

            header = kmclipm_propertylist_load(cpl_frame_get_filename(frame), 0);

            if (cpl_error_get_code() == CPL_ERROR_FILE_IO) {
                // file not found
                KMO_TRY_CHECK_ERROR_STATE();
            } else if ((cpl_error_get_code() != CPL_ERROR_NONE) &&
                       ((strcmp(type, F1L) == 0) ||
                        (strcmp(type, F2L) == 0)))
            {
                // it's a text file: so check if format and title are correct
                KMO_TRY_ASSURE(strcmp(format, "") != 0,
                                CPL_ERROR_ILLEGAL_INPUT,
                               ">>> The 'format' parameter must be supplied!");

                KMO_TRY_ASSURE(strcmp(title, "") != 0,
                                CPL_ERROR_ILLEGAL_INPUT,
                               ">>> The 'title' parameter must be supplied!");
            } else {
                // we probably have a FITS table, don't read in format and title
                // parameters
            }
            cpl_error_reset();
            cpl_propertylist_delete(header); header = NULL;
            frame = NULL;
        }

        filename = kmo_dfs_get_parameter_string(parlist,
                                        "kmos.kmo_fits_stack.filename");
        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_fits_stack.filename"));

        if (needs_procat == 1) {
            category = cpl_sprintf("KMOS_%s", filename);
            kmo_strupper(category);
        }
        KMO_TRY_CHECK_ERROR_STATE();


        mainkey = kmo_dfs_get_parameter_string(parlist,
                                        "kmos.kmo_fits_stack.mainkey");

        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_fits_stack.mainkey"));

        subkey = kmo_dfs_get_parameter_string(parlist,
                                        "kmos.kmo_fits_stack.subkey");
        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_fits_stack.subkey"));

        valid_txt = kmo_dfs_get_parameter_string(parlist,
                                                 "kmos.kmo_fits_stack.valid");
        KMO_TRY_CHECK_ERROR_STATE();

        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_fits_stack.valid"));

        cpl_msg_info("", "-------------------------------------------");

        if ((mainkey == NULL) || (strcmp(mainkey, "") == 0)) {
            cpl_msg_info("", "No additional keywords added to primary header.");
        } else {
            i = 0;
            KMO_TRY_EXIT_IF_NULL(
                mainkey_values = kmo_strsplit(mainkey, ";", &i));

            KMO_TRY_ASSURE((i % 3) == 0,
                CPL_ERROR_ILLEGAL_INPUT,
                "mainkey must have 3 entries for each desired keyword to be "
                "added (KEYWORD;type;value)! Total number of entries: %d", i);
        }

        if ((subkey == NULL) || (strcmp(subkey, "") == 0)) {
            cpl_msg_info("", "No additional keywords added to sub headers.");
        } else {
            i = 0;
            KMO_TRY_EXIT_IF_NULL(
                subkey_values = kmo_strsplit(subkey, ";", &i));

            KMO_TRY_ASSURE((i % 3) == 0,
                CPL_ERROR_ILLEGAL_INPUT,
                "subkey must have 3 entries for each desired keyword to be "
                "added (KEYWORD;type;value)! Total number of entries: %d", i);
        }

        if (strcmp(valid_txt, "") == 0) {
            // default: set all IFUs valid
            valid = cpl_vector_new(KMOS_NR_IFUS);
            for (i = 0; i < KMOS_NR_IFUS; i++) {
                cpl_vector_set(valid, i, 1);
            }
        } else {
            if (strcmp(valid_txt, "none") == 0) {
                // do nothing
            } else {
                valid = kmo_identify_values(valid_txt);
                KMO_TRY_CHECK_ERROR_STATE();

                KMO_TRY_ASSURE(cpl_vector_get_size(valid) == KMOS_NR_IFUS,
                               CPL_ERROR_ILLEGAL_INPUT,
                               "valid parameter must have 24 elements!");
            }
        }

        /* analyse frameset for correct number of input frames of correct
            type and save pointer to input frames in tmp memory
        */

        nr_inputs = cpl_frameset_get_size(frameset);

        KMO_TRY_EXIT_IF_NULL(
            input_frames = (cpl_frame**)cpl_malloc(nr_inputs *
                                                    sizeof(cpl_frame*)));

        KMO_TRY_EXIT_IF_NULL(
                frame = cpl_frameset_find(frameset, NULL)) ;

        i = 0;
        while (frame != NULL) {
            input_frames[i] = frame;
            KMO_TRY_EXIT_IF_NULL(
                tag = cpl_frame_get_tag(frame));

            if (strcmp(tag, FS_DATA) == 0) {
                nr_data++;
                actual_frame = TRUE;
            } else if (strcmp(tag, FS_NOISE) == 0) {
                nr_noise++;
                actual_frame = FALSE;
            } else if (strcmp(tag, FS_BADPIX) == 0) {
                nr_badpix++;
            } else {
                KMO_TRY_ERROR_SET_MSG(CPL_ERROR_ILLEGAL_INPUT,
                    "Wrong tag for file in sof-file!");
            }
            KMO_TRY_CHECK_ERROR_STATE();

            header = cpl_propertylist_load(cpl_frame_get_filename(frame), 0);

            if (cpl_error_get_code() == CPL_ERROR_FILE_IO) {
                // file not found
                cpl_msg_error("", "File not found: %s\n", cpl_frame_get_filename(frame));
                KMO_TRY_CHECK_ERROR_STATE();
            } else if ((cpl_error_get_code() != CPL_ERROR_NONE) &&
                       ((strcmp(type, F1L) == 0) ||
                        (strcmp(type, F2L) == 0)))
            {
                is_ascii_file = TRUE;
                cpl_error_reset();
            } else {
                KMO_TRY_CHECK_ERROR_STATE();
            }

            if (!is_ascii_file)
            {
                if (cpl_error_get_code() != CPL_ERROR_NONE) {
                    cpl_msg_error(cpl_func, "File '%s' not found",
                                  cpl_frame_get_filename(frame));
                    KMO_TRY_CHECK_ERROR_STATE();
                }
                if ((strcmp(type, F1L) != 0) && (strcmp(type, F2L) != 0))
                {
                    if (cpl_propertylist_get_int(header, NAXIS) == 0){
                        KMO_TRY_ERROR_SET_MSG(CPL_ERROR_ILLEGAL_INPUT,
                            "A file seems to have an empty primary extension. "
                            "With this recipe only simple fits-files with one "
                            "extension can be handled!");
                    }
                    KMO_TRY_CHECK_ERROR_STATE();

                    if (i == 0) {
                        switch(cpl_propertylist_get_int(header, NAXIS)) {
                            case 3:
                                z = cpl_propertylist_get_int(header, NAXIS3);
                                y = cpl_propertylist_get_int(header, NAXIS2);
                                x = cpl_propertylist_get_int(header, NAXIS1);
                                break;
                            case 2:
                                y = cpl_propertylist_get_int(header, NAXIS2);
                                x = cpl_propertylist_get_int(header, NAXIS1);
                                break;
                            case 1:
                                x = cpl_propertylist_get_int(header, NAXIS1);
                                break;
                            default:
                                KMO_TRY_ERROR_SET_MSG(CPL_ERROR_ILLEGAL_INPUT,
                                    "NAXIS of the first frame isn't neither "
                                    "1, 2 or 3!");
                        }
                    }
                    KMO_TRY_CHECK_ERROR_STATE();

                    if (((strcmp(type, F1I) == 0) ||
                         (strcmp(type, F2I) == 0) ||
                         (strcmp(type, F3I) == 0)) &&
                        (nr_badpix == 0))
                    {
                        if (i == 0) {
                            KMO_TRY_ASSURE((strcmp(tag, FS_DATA) == 0),
                                            CPL_ERROR_ILLEGAL_INPUT,
                                            "F1I, F2I or F3I files must begin with a "
                                            "STACK_DATA frame!");
                        } else {
                            if (nr_noise > 0) {
                                KMO_TRY_ASSURE(actual_frame != last_frame,
                                                CPL_ERROR_ILLEGAL_INPUT,
                                                "STACK_DATA and STACK_NOISE "
                                                "frames must be alternating!");
                            }
                        }
                    }
                    KMO_TRY_CHECK_ERROR_STATE();

                    last_frame = actual_frame;
                }
                cpl_propertylist_delete(header); header = NULL;
                KMO_TRY_CHECK_ERROR_STATE();
            } else {
                // parse file (how many lines, columns & length of maximum line)
                x = 0;
                y = 1;
                int line_length     = 0,
                    tmp_length      = 0,
                    is_column       = 0,
                    ch              = 0,
                    comment_ch      = 0;

                FILE *fh = fopen(cpl_frame_get_filename(frame), "r" );
                ch = getc(fh);

                while (ch != EOF) {
                    // ignore lines beginning with #
                    if ((ch==35) || (comment_ch==35)) {   // 35==#
                        comment_ch = 35;
                        ch = getc(fh);
                        if ((ch == 10) || (ch == 13)) { // 10==LF, 13==CR
                            comment_ch = 0;
                            ch = getc(fh);
                        }
                        continue;
                    }

                    if (y == 1){
                        if ((ch!=32) && (!is_column)) {      // 32==Space
                            is_column = 1;
                            x++;
                        }
                        if ((ch==32) && (is_column)) {
                            is_column = 0;
                        }
                    }

                    if ((ch == 10) || (ch == 13)) { // 10==LF, 13==CR
                        if (tmp_length > 0) {
                            y++;
                        }
                        if (tmp_length > line_length)
                            line_length = tmp_length;
                        tmp_length = 0;
                    } else {
                        tmp_length++;
                    }
                    ch = getc(fh);
                }

                KMO_TRY_EXIT_IF_NULL(
                    tbl = cpl_table_new(y));

                // get format of table to allocate
                KMO_TRY_EXIT_IF_NULL(
                    format_values = kmo_strsplit(format, ";", &size));

                KMO_TRY_EXIT_IF_NULL(
                    title_values = kmo_strsplit(title, ";", &sizetitle));

                KMO_TRY_ASSURE(size == sizetitle,
                               CPL_ERROR_ILLEGAL_INPUT,
                               "The number of data fields in 'format' and "
                               "'title' don't match (%d vs. %d)!", size, sizetitle);

                KMO_TRY_ASSURE(size == x,
                               CPL_ERROR_ILLEGAL_INPUT,
                               "The number of columns in the STACK_DATA frame "
                               "and the number of data fields in 'format' "
                               "don't match (%d vs. %d)!", x, size);

                if (strcmp(type, F1L) == 0) {
                    KMO_TRY_ASSURE(x == 2,
                                   CPL_ERROR_ILLEGAL_INPUT,
                                   "The input file for a F1L frame must "
                                   "contain exactly two columns!");
                } else if (strcmp(type, F2L) == 0) {
                    KMO_TRY_ASSURE(x > 2,
                                   CPL_ERROR_ILLEGAL_INPUT,
                                   "The input file for a F2L frame must "
                                   "contain more than two columns!");
                } else {
                    KMO_TRY_ERROR_SET_MSG(CPL_ERROR_UNSUPPORTED_MODE,
                                          "Ooops, something went wrong!\n");
                }
                KMO_TRY_CHECK_ERROR_STATE();

                // create columns
                for (k = 0; k < x; k++) {
                    if (strcmp(format_values[k], "%f")== 0) {
                        KMO_TRY_EXIT_IF_ERROR(
                            cpl_table_new_column(tbl, title_values[k], CPL_TYPE_FLOAT));
                    } else if (strcmp(format_values[k], "%s")== 0) {
                        KMO_TRY_EXIT_IF_ERROR(
                            cpl_table_new_column(tbl, title_values[k], CPL_TYPE_STRING));
                    } else {
                        KMO_TRY_ASSURE(size == x,
                                       CPL_ERROR_ILLEGAL_INPUT,
                                       "The allowed data field types for "
                                       "'format' are: f and s");
                    }
                }
                KMO_TRY_CHECK_ERROR_STATE();

                // rewind file and read now values and fill into table
                int br = FALSE,
                    cnt = FALSE;
                char line[2000],
                     *pEnd = NULL,
                     *ptr = NULL;
                rewind(fh);
                KMO_TRY_EXIT_IF_NULL(
                    tbl_names = cpl_table_get_column_names(tbl));
                KMO_TRY_EXIT_IF_NULL(
                    ptbl_names = cpl_array_get_data_string_const(tbl_names));

                while(fgets(line, sizeof(line), fh)) {
                    if (*line == '#') {
                        // ignoring comment line
                        continue;
                    }

                    ptr = strtok(line, " ");
                    for (k = 0; k < x; k++) {
                        if (strcmp(format_values[k], "%f")== 0) {
                            val = (float)strtod(ptr, &pEnd);
                            if (ptr != pEnd) {
                                KMO_TRY_EXIT_IF_ERROR(
                                    cpl_table_set_float(tbl, ptbl_names[k], j, val));
                                ptr = strtok(NULL, " ");

                            } else {
                                cpl_msg_warning("",
                                               "Couldn't read value in STACK_DATA "
                                                "frame: \"%s\" "
                                                "(expected float value)! skipping"
                                                " this line." , line);
                                cnt = TRUE;
                                break;
                            }
                        } else if (strcmp(format_values[k], "%s")== 0) {
                            if ((ptr[strlen(ptr)-1] == 10) ||   // 10==LF, 13==CR
                                (ptr[strlen(ptr)-1] == 13))
                            {
                                // remove trailing carriage returns
                                ptr[strlen(ptr)-1] = '\0';
                            }
                            KMO_TRY_EXIT_IF_ERROR(
                                cpl_table_set_string(tbl, ptbl_names[k], j, ptr));
                            ptr = strtok(NULL, " ");
                        }
                    }

                    if (cnt) {
                        cnt = FALSE;
                        continue;
                    }
                    if (br) {
                        break;
                    }
                    j++;
                }
                cpl_array_delete(tbl_names); tbl_names = NULL;
                fclose(fh);
                KMO_TRY_EXIT_IF_ERROR(
                    cpl_table_erase_invalid_rows(tbl));

                kmo_strfreev(format_values); format_values = NULL;
                kmo_strfreev(title_values); title_values = NULL;
                KMO_TRY_CHECK_ERROR_STATE();
            }
            frame = cpl_frameset_find(frameset, NULL);

            i++;
        }

        if (nr_noise > 0) {
            exists_noise = 1;
        }

        if (strcmp(type, RAW) == 0) {

            KMO_TRY_ASSURE((nr_data == 3) &&
                           (nr_noise == 0) &&
                           (nr_badpix == 0),
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Exactly 3 STACK_DATA frames needed to create a RAW!");

            dim = 2;
        } else if (strcmp(type, F1D) == 0) {
            KMO_TRY_ASSURE((nr_data > 0) &&(nr_data <= 3),
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Between 1 and 3 STACK_DATA frames needed to create a F1D!");

            KMO_TRY_ASSURE((nr_noise == nr_data) ||
                           (nr_noise == 0),
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Either no STACK_NOISE frame or as many STACK_NOISE frames as "
                           "STACK_DATA frames present are needed to create a F1D!");
            dim = 1;
        } else if (strcmp(type, F2D) == 0) {
            KMO_TRY_ASSURE((((nr_data == 3) && (nr_noise == 0)) ||
                            ((nr_data == 3) && (nr_noise == 3))) &&
                           (nr_badpix == 0),
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Exactly 3 STACK_DATA frames and either 0 or 3 "
                           "STACK_NOISE frames needed to create a F2D!");

            dim = 2;
        } else if (strcmp(type, B2D) == 0) {
            KMO_TRY_ASSURE((nr_data == 0) &&
                           (nr_noise == 0) &&
                           (nr_badpix == 3),
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Exactly 3 STACK_BADPIX frames needed to create a B2D!");

            dim = 2;
        } else if ((strcmp(type, F1I) == 0) ||
                   (strcmp(type, F2I) == 0) ||
                   (strcmp(type, F3I) == 0))
        {
            KMO_TRY_ASSURE(nr_badpix == 0,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "F1I, F2I or F3I can't contain STACK_BADPIX frames!");

            KMO_TRY_ASSURE(nr_data > 0,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "At least 1 STACK_DATA frame needed to create a F1I, "
                           "F2I or F3I!");

            KMO_TRY_ASSURE((nr_noise == nr_data) ||
                           (nr_noise == 0),
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Either no STACK_NOISE frame or as many STACK_NOISE frames as "
                           "STACK_DATA frames present are needed to create a F1I, "
                           "F2I or F3II!");

            if (strcmp(type, F1I) == 0) {
                dim = 1;
            } else if (strcmp(type, F2I) == 0) {
                dim = 2;
            } else if (strcmp(type, F3I) == 0) {
                dim = 3;
            }
        } else if (strcmp(type, F1S) == 0) {
            KMO_TRY_ASSURE((nr_data == 1) &&
                           (nr_noise == 0) &&
                           (nr_badpix == 0),
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Exactly one STACK_DATA frame is "
                           "needed to create a F1S!");

            dim = 1;
        } else if (strcmp(type, F1L) == 0) {
            KMO_TRY_ASSURE((nr_data == 1) &&
                           (nr_noise == 0) &&
                           (nr_badpix == 0),
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Exactly one STACK_DATA frame is "
                           "needed to create a F1L!");

            dim = 1;
        } else if (strcmp(type, F2L) == 0) {
            KMO_TRY_ASSURE((nr_data == 1) &&
                           (nr_noise == 0) &&
                           (nr_badpix == 0),
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Exactly one STACK_DATA frame is "
                           "needed to create a F2L!");

            dim = 1;
        }

        // load reference headers
        KMO_TRY_EXIT_IF_NULL(
            global_headers[0] = cpl_propertylist_new());
        KMO_TRY_EXIT_IF_NULL(
            global_headers[1] = cpl_propertylist_new());
        KMO_TRY_EXIT_IF_NULL(
            global_headers[2] = cpl_propertylist_new());
        KMO_TRY_EXIT_IF_NULL(
            global_headers[3] = cpl_propertylist_new());

        // just do this step if valid_txt != "none"
        if (strcmp(valid_txt, "none") != 0) {
            for (i = 0; i < cpl_vector_get_size(valid); i++) {
                if (fabs(cpl_vector_get(valid, i)-1) > 0.01 ) {
                    /* if IFU is inactive mark is as such */

                    KMO_TRY_EXIT_IF_NULL(
                        tmp_name = cpl_sprintf("%s%d%s",
                                           IFU_VALID_PREFIX, i+1, IFU_VALID_POSTFIX));
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_propertylist_update_string(global_headers[0], tmp_name,
                                                       "kmo_fits_stack disabled "
                                                       "this IFU"));
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_propertylist_set_comment (global_headers[0],
                                                      tmp_name,
                                                     "set with kmo_fits_stack"));
                    cpl_free(tmp_name); tmp_name = NULL;
                } else {
                    // if IFU is active, add alpha & delta
                    KMO_TRY_EXIT_IF_NULL(
                        tmp_name = cpl_sprintf("%s%d%s",
                                           IFU_ALPHA_PREFIX, i+1, IFU_ALPHA_POSTFIX));
                    KMO_TRY_EXIT_IF_NULL(
                        tmp_comment = cpl_sprintf("%s", "DUMMY: alpha hosted by arm i [HHMMSS.SSS]"));
                    KMO_TRY_EXIT_IF_ERROR(
                        kmclipm_update_property_double(global_headers[0],
                                                tmp_name,
                                                kmo_dummy_main_header_alpha[i],
                                                tmp_comment));
                    cpl_free(tmp_name); tmp_name = NULL;
                    cpl_free(tmp_comment); tmp_comment = NULL;

                    KMO_TRY_EXIT_IF_NULL(
                        tmp_name = cpl_sprintf("%s%d%s",
                                           IFU_DELTA_PREFIX, i+1, IFU_DELTA_POSTFIX));
                    KMO_TRY_EXIT_IF_NULL(
                        tmp_comment = cpl_sprintf("%s", "DUMMY: delta hosted by arm i [DDMMSS.SSS]"));
                    KMO_TRY_EXIT_IF_ERROR(
                        kmclipm_update_property_double(global_headers[0],
                                                tmp_name,
                                                kmo_dummy_main_header_delta[i],
                                                tmp_comment));
                    cpl_free(tmp_name); tmp_name = NULL;
                    cpl_free(tmp_comment); tmp_comment = NULL;

                    KMO_TRY_EXIT_IF_NULL(
                        tmp_name = cpl_sprintf("%s%d%s",
                                           IFU_NAME_PREFIX, i+1, IFU_NAME_POSTFIX));
                    KMO_TRY_EXIT_IF_NULL(
                        tmp_comment = cpl_sprintf("%s", "DUMMY: name hosted by arm i"));
                    KMO_TRY_EXIT_IF_ERROR(
                        kmclipm_update_property_string(global_headers[0],
                                                tmp_name,
                                                kmo_dummy_main_header_name[i],
                                                tmp_comment));
                    cpl_free(tmp_name); tmp_name = NULL;
                    cpl_free(tmp_comment); tmp_comment = NULL;
                }
            }
        }

        // add additional KMOS keywords to primary header
        i = 0;
        if (mainkey_values != NULL) {
            while (mainkey_values[i] != NULL) {
                if (strcmp("string", mainkey_values[i+1]) == 0) {
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_propertylist_update_string(global_headers[0],
                                                        mainkey_values[i],
                                                        mainkey_values[i+2]));
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_propertylist_set_comment (global_headers[0],
                                                      mainkey_values[i],
                                                 "set with kmo_fits_stack"));
                } else if (strcmp("int", mainkey_values[i+1]) == 0) {
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_propertylist_update_int(global_headers[0],
                                                    mainkey_values[i],
                                                    atoi(mainkey_values[i+2])));
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_propertylist_set_comment (global_headers[0],
                                                      mainkey_values[i],
                                                 "set with kmo_fits_stack"));
                } else if (strcmp("float", mainkey_values[i+1]) == 0) {
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_propertylist_update_float(global_headers[0],
                                                        mainkey_values[i],
                                    (float)strtod(mainkey_values[i+2], (char**)NULL)));
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_propertylist_set_comment (global_headers[0],
                                                      mainkey_values[i],
                                                 "set with kmo_fits_stack"));
                } else if (strcmp("double", mainkey_values[i+1]) == 0) {
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_propertylist_update_double(global_headers[0],
                                                        mainkey_values[i],
                                    strtod(mainkey_values[i+2], (char**)NULL)));
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_propertylist_set_comment (global_headers[0],
                                                      mainkey_values[i],
                                                 "set with kmo_fits_stack"));
                } else if (strcmp("bool", mainkey_values[i+1]) == 0) {
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_propertylist_update_bool(global_headers[0],
                                                        mainkey_values[i],
                                                    atoi(mainkey_values[i+2])));
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_propertylist_set_comment (global_headers[0],
                                                      mainkey_values[i],
                                                 "set with kmo_fits_stack"));
                }
                i = i+3;
            }
        }

        KMO_TRY_ASSURE(filename != NULL,
                       CPL_ERROR_NULL_INPUT,
                       "output filename hasn't been specified!");

        char *nnn = NULL;
        KMO_TRY_EXIT_IF_NULL(
            nnn = cpl_sprintf("%s", filename));
        kmo_strlower(nnn);
        name = cpl_sprintf("%s.fits", nnn);


        // save primary header
        if (needs_procat == 1) {

            // group must be set, otherwise kmo_dfs_save_main_header() fails!
            frame = cpl_frameset_find(frameset, NULL);
            while (frame != NULL) {
                if (CPL_FRAME_GROUP_NONE == cpl_frame_get_group(frame)) {
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_frame_set_group(frame, CPL_FRAME_GROUP_RAW));
                }
                frame = cpl_frameset_find(frameset, NULL);
            }

            KMO_TRY_EXIT_IF_ERROR(
                kmo_dfs_save_main_header(frameset, category, "", NULL, NULL,
                                    parlist, cpl_func));
        } else  {
            KMO_TRY_EXIT_IF_ERROR(
                cpl_propertylist_save(global_headers[0], name, CPL_IO_CREATE));
        }
        cpl_free(nnn); nnn = NULL;

        /* loop through frameset and at frames in the order given in the
            sof-file */
        i = 0;
        while (i < nr_inputs) {
            // calculating index for accessing proper extension
            if (exists_noise) {
                if ((strcmp(type, F1D) == 0) ||
                    (strcmp(type, F2D) == 0)) {
                    index = i / 2 + 1;
                }

                if ((strcmp(type, F1I) == 0) ||
                    (strcmp(type, F2I) == 0) ||
                    (strcmp(type, F3I) == 0)) {
                    index = i / (2 * KMOS_IFUS_PER_DETECTOR) + 1;
                }
            } else {
                if ((strcmp(type, RAW) == 0) ||
                    (strcmp(type, F1D) == 0) ||
                    (strcmp(type, F2D) == 0) ||
                    (strcmp(type, B2D) == 0) ||
                    (strcmp(type, F1S) == 0) ||
                    (strcmp(type, F1L) == 0) ||
                    (strcmp(type, F2L) == 0)) {
                    index = i + 1;
                }

                if ((strcmp(type, F1I) == 0) ||
                    (strcmp(type, F2I) == 0) ||
                    (strcmp(type, F3I) == 0)) {
                    index = i / KMOS_IFUS_PER_DETECTOR + 1;
                }
            }

            // load subheader of ref-file
            KMO_TRY_EXIT_IF_NULL(
                ref_sub_header = global_headers[index]);

            frame = input_frames[i];

            if (!is_ascii_file) {
                KMO_TRY_EXIT_IF_NULL(
                    header = kmclipm_propertylist_load(cpl_frame_get_filename(frame),
                                                    0));

                if ((strcmp(type, F1L) != 0) &&
                    (strcmp(type, F2L) != 0))
                {
                    kmo_priv_check_dimensions(header, dim, x, y, z);
                }
            }

            if (x !=-1) {
                cpl_free(err_msg); err_msg = NULL;
                KMO_TRY_EXIT_IF_NULL(
                    err_msg = cpl_sprintf(">>> The provided type of '%s' and the "
                                      "dimensions of the data (x=%d) don't seem "
                                      "to match! <<<", type, x));
            }
            if (y !=-1) {
                cpl_free(err_msg); err_msg = NULL;
                KMO_TRY_EXIT_IF_NULL(
                    err_msg = cpl_sprintf(">>> The provided type of '%s' and the "
                                      "dimensions of the data (y=%d) don't seem "
                                      "to match! <<<", type, y));
            }
            if (z !=-1) {
                cpl_free(err_msg); err_msg = NULL;
                KMO_TRY_EXIT_IF_NULL(
                    err_msg = cpl_sprintf(">>> The provided type of '%s' and the "
                                      "dimensions of the data (z=%d) don't seem "
                                      "to match! <<<", type, z));
            }

            KMO_TRY_CHECK_ERROR_STATE_MSG(err_msg);
            cpl_free(err_msg); err_msg = NULL;

            // add common KMOS keywords to sub-header
            KMO_TRY_EXIT_IF_NULL(
                tag = cpl_frame_get_tag(frame));

            if (strcmp(tag, FS_DATA) == 0) {
                is_noise = 0;
                is_badpix = 0;
            } else if (strcmp(tag, FS_NOISE) == 0) {
                is_noise = 1;
                is_badpix = 0;
            } else if (strcmp(tag, FS_BADPIX) == 0) {
                is_noise = 0;
                is_badpix = 1;
            }

            // for F1I, F2I, F3I change index from detector-based to ifu-based
            if ((strcmp(type, F1I) == 0) ||
                (strcmp(type, F2I) == 0) ||
                (strcmp(type, F3I) == 0))
            {
                if (exists_noise) {
                   index = i / 2 + 1;
                } else {
                   index = i + 1;
                }
            }

            if (!is_ascii_file)
            {
                if (strcmp(type, RAW) == 0) {
                    cpl_propertylist_erase(ref_sub_header, "CRPIX1");
                    KMO_TRY_CHECK_ERROR_STATE();
                    cpl_propertylist_erase(ref_sub_header, "CRPIX2");
                    KMO_TRY_CHECK_ERROR_STATE();
                }

                // copy keywords from input file to sub-header
                for (j = 0; j < cpl_propertylist_get_size(header); j++) {
                    KMO_TRY_EXIT_IF_NULL(
                        p = cpl_propertylist_get(header, j));

                    KMO_TRY_EXIT_IF_NULL(
                        p_name = cpl_property_get_name(p));

                    if ((strcmp(p_name, "SIMPLE") != 0) &&
                        (strcmp(p_name, "BITPIX") != 0) &&
                        (strcmp(p_name, "NAXIS") != 0) &&
                        (strcmp(p_name, "NAXIS1") != 0) &&
                        (strcmp(p_name, "NAXIS2") != 0) &&
                        (strcmp(p_name, "NAXIS3") != 0) &&
                        (strcmp(p_name, "COMMENT") != 0))
                    {

                        if ((strcmp(type, RAW) != 0) ||
                            ((strcmp(type, RAW) == 0) &&
                            (strcmp(p_name, "CRPIX1") != 0) &&
                            (strcmp(p_name, "CRPIX2") != 0) &&
                            (strcmp(p_name, "CRPIX3") != 0) &&
                            (strcmp(p_name, "CRVAL1") != 0) &&
                            (strcmp(p_name, "CRVAL2") != 0) &&
                            (strcmp(p_name, "CRVAL3") != 0) &&
                            (strcmp(p_name, "CDELT1") != 0) &&
                            (strcmp(p_name, "CDELT2") != 0) &&
                            (strcmp(p_name, "CDELT3") != 0) &&
                            (strcmp(p_name, "CTYPE1") != 0) &&
                            (strcmp(p_name, "CTYPE2") != 0) &&
                            (strcmp(p_name, "CTYPE3") != 0) &&
                            (strcmp(p_name, "CD1_1") != 0) &&
                            (strcmp(p_name, "CD1_2") != 0) &&
                            (strcmp(p_name, "CD2_1") != 0) &&
                            (strcmp(p_name, "CD2_2") != 0) &&
                            (strcmp(p_name, "LTV1") != 0) &&
                            (strcmp(p_name, "LTV2") != 0) &&
                            (strcmp(p_name, "LTM1_1") != 0) &&
                            (strcmp(p_name, "LTM2_1") != 0) &&
                            (strcmp(p_name, "LTM1_2") != 0) &&
                            (strcmp(p_name, "LTM2_2") != 0)))
                            {
                            cpl_propertylist_erase(ref_sub_header, p_name);
                            KMO_TRY_CHECK_ERROR_STATE();

                            KMO_TRY_EXIT_IF_ERROR(
                                cpl_propertylist_copy_property(ref_sub_header,
                                                               header,
                                                               p_name));
                        }
                    }
                }
            }

            if (strcmp(type, RAW) == 0) {
                KMO_TRY_EXIT_IF_NULL(
                    tmp_name = cpl_sprintf("%s%d%s", EXTNAME_RAW, index, ".INT1"));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_string(ref_sub_header,
                                            EXTNAME,
                                            tmp_name,
                                            "set with kmo_fits_stack"));
                cpl_free(tmp_name); tmp_name = NULL;
                KMO_TRY_EXIT_IF_ERROR(
                    cpl_propertylist_update_int(ref_sub_header, CHIPINDEX,
                                                   index));
                KMO_TRY_EXIT_IF_ERROR(
                    cpl_propertylist_set_comment (ref_sub_header,
                                                  CHIPINDEX,
                                                 "set with kmo_fits_stack"));
            } else {
                KMO_TRY_EXIT_IF_ERROR(
                    kmo_update_sub_keywords(ref_sub_header,
                                            is_noise,
                                            is_badpix,
                                            kmo_string_to_frame_type(type),
                                            index));
                KMO_TRY_EXIT_IF_ERROR(
                    cpl_propertylist_set_comment(ref_sub_header,
                                                 EXTNAME,
                                                "set with kmo_fits_stack"));
            }

            // add additional KMOS keywords to sub-header
            j = 0;
            if (subkey_values != NULL) {
                while (subkey_values[j] != NULL) {
                    if (strcmp("string", subkey_values[j+1]) == 0) {
                        KMO_TRY_EXIT_IF_ERROR(
                            cpl_propertylist_update_string(ref_sub_header,
                                     subkey_values[j],
                                     subkey_values[j+2]));
                        KMO_TRY_EXIT_IF_ERROR(
                            cpl_propertylist_set_comment (ref_sub_header,
                                                          subkey_values[j],
                                                 "set with kmo_fits_stack"));
                    } else if (strcmp("int", subkey_values[j+1]) == 0) {
                        KMO_TRY_EXIT_IF_ERROR(
                            cpl_propertylist_update_int(ref_sub_header,
                                     subkey_values[j],
                                     atoi(subkey_values[j+2])));
                        KMO_TRY_EXIT_IF_ERROR(
                            cpl_propertylist_set_comment (ref_sub_header,
                                                          subkey_values[j],
                                                 "set with kmo_fits_stack"));
                    } else if (strcmp("float", subkey_values[j+1]) == 0) {
                        KMO_TRY_EXIT_IF_ERROR(
                            cpl_propertylist_update_float(ref_sub_header,
                                     subkey_values[j],
                                     (float)strtod(subkey_values[j+2], (char**)NULL)));
                        KMO_TRY_EXIT_IF_ERROR(
                            cpl_propertylist_set_comment (ref_sub_header,
                                                          subkey_values[j],
                                                 "set with kmo_fits_stack"));
                    } else if (strcmp("double", subkey_values[j+1]) == 0) {
                        KMO_TRY_EXIT_IF_ERROR(
                            cpl_propertylist_update_double(ref_sub_header,
                                     subkey_values[j],
                                     strtod(subkey_values[j+2], (char**)NULL)));
                        KMO_TRY_EXIT_IF_ERROR(
                            cpl_propertylist_set_comment (ref_sub_header,
                                                          subkey_values[j],
                                                 "set with kmo_fits_stack"));
                    } else if (strcmp("bool", subkey_values[j+1]) == 0) {
                        KMO_TRY_EXIT_IF_ERROR(
                            cpl_propertylist_update_bool(ref_sub_header,
                                     subkey_values[j],
                                     atoi(subkey_values[j+2])));
                        KMO_TRY_EXIT_IF_ERROR(
                            cpl_propertylist_set_comment (ref_sub_header,
                                                          subkey_values[j],
                                                 "set with kmo_fits_stack"));
                    }
                    j += 3;
                }
            }

            if ((strcmp(type, F1I) == 0) ||
                (strcmp(type, F1D) == 0) ||
                (strcmp(type, F1S) == 0)) {
                // load vector
                KMO_TRY_EXIT_IF_NULL(
                    vec = kmclipm_vector_load(cpl_frame_get_filename(frame), 0));

                // save vector
                if (needs_procat == 1) {
                    KMO_TRY_EXIT_IF_NULL(
                        pl = cpl_propertylist_new());
                    if (strcmp(type, F1S) == 0) {
                        KMO_TRY_EXIT_IF_NULL(
                            pl = kmclipm_propertylist_load(
                                                  cpl_frame_get_filename(frame),
                                                  0));
                        KMO_TRY_EXIT_IF_ERROR(
                            cpl_propertylist_update_string(pl, EXTNAME, EXT_SPEC));
                        KMO_TRY_EXIT_IF_ERROR(
                            cpl_propertylist_set_comment (pl,
                                                          EXTNAME,
                                                          "set with kmo_fits_stack"));
                    }
                    KMO_TRY_EXIT_IF_ERROR(
                        kmo_dfs_save_vector(vec, category, "", pl, 0./0.));
                    cpl_propertylist_delete(pl); pl = NULL;
                    strcpy(msg_string,"Saved KMOS vector frame (");
                    strcat(msg_string, category);
                    strcat(msg_string, ").");
                } else  {
                    KMO_TRY_EXIT_IF_ERROR(
                        kmclipm_vector_save(vec, name, CPL_BPP_IEEE_FLOAT,
                                        ref_sub_header, CPL_IO_EXTEND, 0./0.));
                    strcpy(msg_string,"Saved KMOS vector frame (");
                    strcat(msg_string, name);
                    strcat(msg_string, ").");
                }

                kmclipm_vector_delete(vec); vec = NULL;
            } else if ((strcmp(type, F3I) == 0)) {
                // load cube
                KMO_TRY_EXIT_IF_NULL(
                    cube = kmclipm_imagelist_load(cpl_frame_get_filename(frame),
                                              CPL_TYPE_FLOAT, 0));
                // save cube
                if (needs_procat == 1) {
                    KMO_TRY_EXIT_IF_NULL(
                        pl = cpl_propertylist_new());
                    KMO_TRY_EXIT_IF_ERROR(
                        kmo_dfs_save_cube(cube, category, "", pl, 0./0.));
                    cpl_propertylist_delete(pl); pl = NULL;
                    strcpy(msg_string,"Saved KMOS cube frame (");
                    strcat(msg_string, category);
                    strcat(msg_string, ").");
                } else  {
                    KMO_TRY_EXIT_IF_ERROR(
                        kmclipm_imagelist_save(cube, name, CPL_BPP_IEEE_FLOAT,
                                               ref_sub_header, CPL_IO_EXTEND, 0./0.));
                    strcpy(msg_string,"Saved KMOS cube frame (");
                    strcat(msg_string, name);
                    strcat(msg_string, ").");
                }

                cpl_imagelist_delete(cube); cube = NULL;
            } else if ((strcmp(type, F1L) == 0) ||
                       (strcmp(type, F2L) == 0))
            {
                if (!is_ascii_file) {
                    KMO_TRY_CHECK_ERROR_STATE();
                    KMO_TRY_EXIT_IF_NULL(
                        tbl = kmclipm_table_load(cpl_frame_get_filename(frame),
                                             1, 0));
                    if (strcmp(type, F1L) == 0) {
                        KMO_TRY_ASSURE(cpl_table_get_ncol(tbl) == 2,
                            CPL_ERROR_NULL_INPUT,
                            "Table must have two columns for F1L!");
                    } else {
                        KMO_TRY_ASSURE(cpl_table_get_ncol(tbl) > 2,
                            CPL_ERROR_NULL_INPUT,
                            "Table must have three or more columns for F2L!");
                    }
                }

                // save table
                if (needs_procat == 1) {
                    KMO_TRY_EXIT_IF_NULL(
                        pl = cpl_propertylist_new());
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_propertylist_update_string(pl, EXTNAME, EXT_LIST));
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_propertylist_set_comment (pl,
                                                      EXTNAME,
                                                      "set with kmo_fits_stack"));
                    KMO_TRY_EXIT_IF_ERROR(
                        kmo_dfs_save_table(tbl, category, "", pl));
                    cpl_propertylist_delete(pl); pl = NULL;
                    strcpy(msg_string,"Saved KMOS table frame (");
                    strcat(msg_string, category);
                    strcat(msg_string, ").");
                } else  {
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_table_save(tbl, NULL, ref_sub_header, name,
                                       CPL_IO_EXTEND));
                    strcpy(msg_string,"Saved KMOS table frame (");
                    strcat(msg_string, name);
                    strcat(msg_string, ").");
                }

                cpl_table_delete(tbl); tbl = NULL;
            } else {
                // load image
                KMO_TRY_EXIT_IF_NULL(
                    img = kmclipm_image_load(cpl_frame_get_filename(frame),
                                            CPL_TYPE_FLOAT, 0, 0));
                // save image
                if (needs_procat == 1) {
                    KMO_TRY_EXIT_IF_NULL(
                        pl = cpl_propertylist_new());
                    KMO_TRY_EXIT_IF_ERROR(
                        kmo_dfs_save_image(img, category, "", pl, 0./0.));
                    cpl_propertylist_delete(pl); pl = NULL;
                    strcpy(msg_string,"Saved KMOS image frame (");
                    strcat(msg_string, category);
                    strcat(msg_string, ").");
                } else  {
                    KMO_TRY_EXIT_IF_ERROR(
                        kmclipm_image_save(img, name, CPL_BPP_IEEE_FLOAT,
                                           ref_sub_header, CPL_IO_EXTEND, 0./0.));
                    strcpy(msg_string,"Saved KMOS image frame (");
                    strcat(msg_string, name);
                    strcat(msg_string, ").");
                }

                cpl_image_delete(img); img = NULL;
            }

            cpl_propertylist_delete(header); header = NULL;

            i++;
        }
        KMO_TRY_CHECK_ERROR_STATE();

        cpl_msg_info("kmo_fits_stack", "%s (%s)", msg_string, type);
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();

        ret_val = -1;
    }

    cpl_propertylist_delete(header); header = NULL;
    cpl_propertylist_delete(global_headers[0]); global_headers[0] = NULL;
    cpl_propertylist_delete(global_headers[1]); global_headers[1] = NULL;
    cpl_propertylist_delete(global_headers[2]); global_headers[2] = NULL;
    cpl_propertylist_delete(global_headers[3]); global_headers[3] = NULL;
    cpl_free(input_frames); input_frames = NULL;
    cpl_array_delete(tbl_names); tbl_names = NULL;
    cpl_table_delete(tbl); tbl = NULL;
    cpl_vector_delete(valid); valid = NULL;
    cpl_free(err_msg); err_msg = NULL;
    cpl_free(name); name = NULL;
    if (needs_procat == 1) {
        cpl_free(category); category = NULL;
    }
    if (mainkey_values != NULL) {
        kmo_strfreev(mainkey_values); mainkey_values = NULL;
    }

    if (subkey_values != NULL) {
        kmo_strfreev(subkey_values); subkey_values = NULL;
    }
    if (title_values != NULL) {
        kmo_strfreev(title_values); title_values = NULL;
    }
    if (format_values != NULL) {
        kmo_strfreev(format_values); format_values = NULL;
    }

    return ret_val;
}

/**
    @brief
        Checks if a propertylist has given dimensions.

    The propertylist @c pl will be checked if it contains a vector (@c dim == 1),
    an image (@c dim == 2) or a cube (@c dim == 3). Furthermore the dimensions
    of the data is checked.

    @param pl       The propertylist.
    @param dim      The number of dimensions.
    @param x        The size in first dimension.
    @param y        The size in second dimension.
    @param z        The size in third dimension.

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

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT     if @c pl is NULL.
    @li CPL_ERROR_ILLEGAL_INPUT  if @c dim is not 1, 2, or 3.
*/
cpl_error_code kmo_priv_check_dimensions(cpl_propertylist *pl,
                                         int dim,
                                         int x,
                                         int y,
                                         int z)
{
    cpl_error_code  ret_error   = CPL_ERROR_NONE;

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

        KMO_TRY_ASSURE((dim > 0) && (dim <= 3),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Values must be greater than 0!");

        if (cpl_propertylist_get_int(pl, NAXIS) != dim) {
            // check dimensions
            switch (dim)
            {
                case 3:
                    KMO_TRY_ERROR_SET_MSG(CPL_ERROR_ILLEGAL_INPUT,
                                          "Frame doesn't contain a cube!");
                    break;
                case 2:
                    KMO_TRY_ERROR_SET_MSG(CPL_ERROR_ILLEGAL_INPUT,
                                          "Frame doesn't contain an image!");
                    break;
                case 1:
                    KMO_TRY_ERROR_SET_MSG(CPL_ERROR_ILLEGAL_INPUT,
                                          "Frame doesn't contain a vector!");
                    break;
                default:
                    break;
            }
        } else {
            // check size
            KMO_TRY_ASSURE(cpl_propertylist_get_int(pl, NAXIS1) == x,
                           CPL_ERROR_ILLEGAL_INPUT,
                        "Size in 1st dimension not the same as in 1st frame!!");

            if (dim >= 2) {
                KMO_TRY_ASSURE(cpl_propertylist_get_int(pl, NAXIS2) == y,
                               CPL_ERROR_ILLEGAL_INPUT,
                        "Size in 2nd dimension not the same as in 1st frame!!");
            }

            if (dim == 3) {
                KMO_TRY_ASSURE(cpl_propertylist_get_int(pl, NAXIS3) == z,
                               CPL_ERROR_ILLEGAL_INPUT,
                        "Size in 3rd dimension not the same as in 1st frame!!");
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }

    return ret_error;
}

/** @{ */
