/*
 * This file is part of the GIRAFFE Pipeline
 * Copyright (C) 2002-2019 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
 */

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

#include <math.h>

#include <cxslist.h>
#include <cxstrutils.h>
#include <cxmessages.h>

#include <cpl_array.h>
#include <cpl_propertylist.h>

#include "gialias.h"
#include "gimacros.h"
#include "gierror.h"
#include "gimessages.h"
#include "gigrating.h"
#include "gifov.h"
#include "gifiberutils.h"
#include "gisutils.h"
#include "giutils.h"


/**
 * @defgroup gifov Field of View Reconstruction
 *
 * The module provides the functions to create data cubes and field of view
 * images, which are constructed from the resampled spectra images.
 */

/**@{*/

inline static cxint
_giraffe_compare_int(cxcptr first, cxcptr second)
{

    cxint *_first = (cxint *)first;
    cxint *_second = (cxint *)second;

    return *_first - *_second;

}


inline static GiCube*
_giraffe_fov_create_cube(const GiImage* spectra,
                         const cpl_table* fibers,
                         const GiRange* limits)
{

    cxint first = 0;
    cxint last = 0;
    cxint nx = 0;
    cxint ny = 0;
    cxint nz = 0;

    cxdouble wmin = 0.;
    cxdouble wmax = 0.;
    cxdouble wstep = 0.;
    /*cxdouble fstart = 1.;*/
    /*cxdouble fend = 1.;*/

    cpl_propertylist* properties = giraffe_image_get_properties(spectra);

    cpl_image* _spectra = giraffe_image_get(spectra);

    GiCube* cube = NULL;


    if ((properties == NULL) || (_spectra == NULL)) {
        return NULL;
    }


    /*
     * Get the spectral range of the input spectra.
     */

    if (cpl_propertylist_has(properties, GIALIAS_BINWLMIN) == FALSE) {
        return NULL;
    }
    else {
        wmin = cpl_propertylist_get_double(properties, GIALIAS_BINWLMIN);
    }

    if (cpl_propertylist_has(properties, GIALIAS_BINWLMAX) == FALSE) {
        return NULL;
    }
    else {
        wmax = cpl_propertylist_get_double(properties, GIALIAS_BINWLMAX);
    }

    if (cpl_propertylist_has(properties, GIALIAS_BINSTEP) == FALSE) {
        return NULL;
    }
    else {
        wstep = cpl_propertylist_get_double(properties, GIALIAS_BINSTEP);
    }


    /*
     * Determine the pixel limits corresponding to the spectral range.
     */

    first = 0;
    last = cpl_image_get_size_y(_spectra) - 1;

    if (limits != NULL) {

        if (giraffe_range_get_min(limits) > wmin) {

            cxdouble pixel = (giraffe_range_get_min(limits) - wmin) / wstep;


            first  = ceil(pixel);
            /*fstart = pixel - first;*/

        }

        if (giraffe_range_get_max(limits) < wmax) {

            cxdouble pixel = last - (wmax - giraffe_range_get_max(limits)) / wstep;


            last = floor(pixel);
            /*fend = pixel - last;*/

        }

    }


    /*
     * Determine the layout of the cube from the list of fibers.
     */

    giraffe_error_push();

    nx = (cxint) cpl_table_get_column_max(fibers, "X");
    ny = (cxint) cpl_table_get_column_max(fibers, "Y");

    if (cpl_error_get_code() == CPL_ERROR_DATA_NOT_FOUND) {
        return NULL;
    }

    giraffe_error_pop();


    nz = last - first + 1;

    if (nz <= 0) {
        return NULL;
    }


    /*
     * Create the data cube and fill it with the flux values.
     */

    cube = giraffe_cube_create(nx, ny, nz, NULL);

    giraffe_cube_set_xaxis(cube, 1., 1.);
    giraffe_cube_set_yaxis(cube, 1., 1.);
    giraffe_cube_set_zaxis(cube, wmin, wstep);

    if (cube != NULL) {

        register cxint i = 0;
        register cxint nf = cpl_table_get_nrow(fibers);

        cxint ns = cpl_image_get_size_x(_spectra);

        cxdouble* spixels = cpl_image_get_data_double(_spectra);
        cxdouble* cpixels = giraffe_cube_get_data(cube);


        cx_assert(spixels != NULL);
        cx_assert(cpixels != NULL);
        cx_assert(nf <= ns);

        for (i = 0; i < nf; ++i) {

            register cxint j = 0;

            cxint idx = cpl_table_get_int(fibers, "INDEX", i, NULL) - 1;
            cxint x = cpl_table_get_int(fibers, "X", i, NULL) - 1;
            cxint y = cpl_table_get_int(fibers, "Y", i, NULL) - 1;


            /*
             * Fill image pixels skipping special fibers (CalSim or Sky)
             * which have x = 0 and y = 0.
             */

            if ((x >= 0) && (y >= 0)) {

                for (j = 0; j < nz; ++j) {
                    cpixels[(ny * j + y) * nx + x] =
                        spixels[(first + j) * ns + idx];
                }

            }

        }

    }

    return cube;

}


/*
 * Arrange the input images into an output image using a tabular layout.
 */

inline static cpl_image*
_giraffe_fov_arrange_images(const cx_slist* subimages,
                            cxsize nrows, cxsize ncolumns, cxint offset)
{

    cxint x = 0;
    cxint y = 0;
    cxint nx = 0;
    cxint ny = 0;
    cxint sx = 0;
    cxint sy = 0;
    cxint xshift = offset;
    cxint yshift = offset;

    cxsize nslit = 0;
    cxsize column = 0;

    cx_slist_iterator pos;

    cpl_image* image = NULL;


    cx_assert(subimages != NULL);
    cx_assert(nrows > 0);
    cx_assert(ncolumns > 0);


    /*
     * Compute the size of the combined output image from the largest
     * image in the list. The properties of the mosaic image are taken
     * from the first (non-empty) image in the list.
     */

    pos = cx_slist_begin(subimages);

    while (pos != cx_slist_end(subimages)) {

        const cpl_image* simage = cx_slist_get(subimages, pos);

        if (simage != NULL) {

            cxint _nx = cpl_image_get_size_x(simage);
            cxint _ny = cpl_image_get_size_y(simage);

            sx = CX_MAX(nx, _nx);
            sy = CX_MAX(ny, _ny);

        }

        pos = cx_slist_next(subimages, pos);

    }


    /*
     * Adjust the number of rows to what is actually needed.
     */

    nslit = cx_slist_size(subimages);
    nrows  = CX_MAX(nslit / ncolumns, nrows);

    if (nslit % ncolumns != 0) {
        ++nrows;
    }


    /*
     * Compute the size of the final "mosaic" image
     */

    nx = sx * ncolumns;
    ny = sy * nrows;


    if (offset < 0) {
        xshift = nx / -offset + 1;
        yshift = ny / -offset + 1;
    }

    nx += ncolumns * xshift - (xshift % 2);
    ny += nrows * yshift - (yshift % 2);


    /*
     * Arrange subimages into a single image.
     */

    image = cpl_image_new(nx, ny, CPL_TYPE_DOUBLE);

    y = yshift / 2;
    x = xshift / 2;

    pos = cx_slist_begin(subimages);

    while (pos != cx_slist_end(subimages)) {

        const cpl_image* simage = cx_slist_get(subimages, pos);

        if (simage != NULL) {

            cpl_error_code status = cpl_image_copy(image, simage,
                                                   x + 1, y + 1);

            if (status != CPL_ERROR_NONE) {
                cpl_image_delete(image);
                return NULL;
            }

        }

        ++column;

        if (column < ncolumns) {
            x += sx + xshift;
        }
        else {
            column = 0;

            x = xshift / 2;
            y += sy + yshift;
        }

        pos = cx_slist_next(subimages, pos);

    }

    return image;

}


inline static cpl_image*
_giraffe_fov_integrate_cube(const GiCube* cube, const GiRange* limits)
{

    cxsize depth = 0;

    cxdouble wmin = 0.;
    cxdouble wmax = 0.;
    cxdouble wstep = 0.;
    cxdouble start = 0.;
    cxdouble end = 0.;

    cpl_image* image = NULL;


    cx_assert(cube != NULL);

    depth = giraffe_cube_get_depth(cube);
    giraffe_cube_get_zaxis(cube, &wmin, &wstep);

    wmax = wmin + depth * wstep;
    end = depth;

    if (giraffe_range_get_min(limits) > wmin) {
        start = (giraffe_range_get_min(limits) - wmin) / wstep;
    }

    if (giraffe_range_get_max(limits) < wmax) {
        end = (giraffe_range_get_max(limits) - wmin) / wstep;
    }

    image = giraffe_cube_integrate(cube, start, end);

    return image;

}


/**
 * @brief
 *   Create and image and a data cube from extracted and rebinned spectra.
 *
 * @param result        The results of the image and data cube creation.
 * @param rebinning     The extracted and rebinned spectra.
 * @param fibers        The fiber setup
 * @param wsolution     The dispersion solution
 * @param grating       The grating table
 * @param slitgeometry  The slit geometry
 * @param config        Setup parameters for reconstruction task.
 *
 * @return The function returns 0 on success and a non-zero value otherwise.
 *
 * The function creates an image from the extracted and rebinned spectra of
 * an IFU or Argus observation. The rebinned spectra and errors are taken from
 * the rebinning structure @em rebinning. The corresponding fiber setup is
 * expected in the input table @em fibers. Configuration options are passed
 * through the setup structure @em config.
 */

cxint
giraffe_fov_build(GiFieldOfView* result, GiRebinning* rebinning,
                  GiTable* fibers, GiTable* wsolution,
                  GiTable* grating, GiTable* slitgeometry,
                  GiFieldOfViewConfig* config)
{

    const cxchar* const fctid = "giraffe_fov_build";

    cxbool log_scale = FALSE;

    cx_slist* simages = NULL;
    cx_slist* eimages = NULL;
    cx_slist* scubes = NULL;
    cx_slist* ecubes = NULL;

    cpl_propertylist* properties = NULL;

    cpl_array* ssn = NULL;

    cpl_image* fov = NULL;

    cpl_table* _fibers = NULL;

    GiInstrumentMode mode;

    GiRange* limits = NULL;


    if (result == NULL) {
        cpl_error_set(fctid, CPL_ERROR_NULL_INPUT);
        return -1;
    }
    else {

        /*
         * Make sure that the field of view object is empty
         */

        giraffe_fov_clear(result);

    }

    if (rebinning == NULL) {
        cpl_error_set(fctid, CPL_ERROR_NULL_INPUT);
        return -1;
    }

    if (rebinning->spectra == NULL || rebinning->errors == NULL) {
        cpl_error_set(fctid, CPL_ERROR_ILLEGAL_INPUT);
        return -1;
    }

    if (fibers == NULL) {
        cpl_error_set(fctid, CPL_ERROR_NULL_INPUT);
        return -1;
    }

    _fibers = giraffe_table_get(fibers);

    if (_fibers == NULL) {
        cpl_error_set(fctid, CPL_ERROR_DATA_NOT_FOUND);
        return -1;
    }

    if (!cpl_table_has_column(_fibers, "X") ||
        !cpl_table_has_column(_fibers, "Y")) {
        cpl_error_set(fctid, CPL_ERROR_DATA_NOT_FOUND);
        return -2;
    }

    if (config == NULL) {
        cpl_error_set(fctid, CPL_ERROR_NULL_INPUT);
        return -1;
    }


    /*
     * Determine the instrument mode. Spectra taken in IFU mode must
     * be processed on a per subslit basis, while ARGUS data can
     * be processed ignoring the subslit information (simple
     * reconstruction based on X and Y positions of fibers only)
     */

    properties = giraffe_image_get_properties(rebinning->spectra);

    if (properties == NULL) {
        cpl_error_set(fctid, CPL_ERROR_DATA_NOT_FOUND);
        return -1;
    }

    mode = giraffe_get_mode(properties);


    /*
     * By default the cube and the fov-image will be reconstructed from
     * the input spectra common wavelength range. If a wavelength range
     * was specified by parameter settings these will be used, clamped
     * to the input spectra common wavelength range.
     */

    limits = giraffe_rebin_get_wavelength_range(rebinning->spectra, wsolution,
                                                grating, slitgeometry, TRUE);

    if (limits == NULL) {
        cpl_msg_error(fctid, "Computation of spectra common wavelength "
                      "range failed!");
        return 1;
    }

    if (config->minimum > 0.) {
        if (config->minimum < giraffe_range_get_min(limits)) {
            cpl_msg_warning(fctid, "Ignoring invalid wavelength range "
                            "minimum %.3f nm", config->minimum);
        }
        else {
            giraffe_range_set_min(limits, config->minimum);
        }
    }

    if (config->maximum > 0.) {
        if (config->maximum > giraffe_range_get_max(limits)) {
            cpl_msg_warning(fctid, "Ignoring invalid wavelength range "
                            "maximum %.3f nm", config->maximum);
        }
        else {
            giraffe_range_set_max(limits, config->maximum);
        }
    }

    cpl_msg_info(fctid, "Building image for wavelength range [%.3f nm, "
                 "%.3f nm].", giraffe_range_get_min(limits),
                 giraffe_range_get_max(limits));


    /*
     * Convert limits if the spectrum wavelength scale is logarithmic
     */

    if (cpl_propertylist_has(properties, GIALIAS_BINSCALE)) {

        const cxchar* s = cpl_propertylist_get_string(properties,
                                                      GIALIAS_BINSCALE);

        if (cx_strncasecmp(s, "log", 3) == 0) {
            giraffe_range_set_min(limits, log(giraffe_range_get_min(limits)));
            giraffe_range_set_max(limits, log(giraffe_range_get_max(limits)));

            log_scale = TRUE;
        }
    }
    else {
        cpl_msg_warning(fctid, "Could not determine spectrum wavelength "
                        "scaling method. Missing property `%s'. Assuming "
                        "scaling method `linear'!", GIALIAS_BINSCALE);
    }


    /*
     * Create the containers to store the data cubes and the
     * reconstructed images.
     */

    simages = cx_slist_new();
    eimages = cx_slist_new();
    scubes  = cx_slist_new();
    ecubes  = cx_slist_new();


    switch (mode) {
        case GIMODE_IFU:
        {

            cxint i = 0;

            cpl_array* _ssn = NULL;

            cpl_image* smosaic = NULL;
            cpl_image* emosaic = NULL;

            GiImage* variance = NULL;


            /*
             * Determine the number and the list of used subslits.
             */

            ssn = giraffe_fiberlist_get_subslits(_fibers);

            if (ssn == NULL) {
                cx_slist_destroy(simages, (cx_free_func)cpl_image_delete);
                simages = NULL;

                cx_slist_destroy(eimages, (cx_free_func)cpl_image_delete);
                eimages = NULL;

                cx_slist_destroy(scubes, (cx_free_func)giraffe_cube_delete);
                scubes = NULL;

                cx_slist_destroy(ecubes, (cx_free_func)giraffe_cube_delete);
                ecubes = NULL;

                giraffe_range_delete(limits);
                limits = NULL;

                cpl_msg_error(fctid, "Sub-slit data missing in fiber table!");

                return 1;
            }


            /*
             * Compute the variances from the error map.
             */

            variance = giraffe_image_duplicate(rebinning->errors);

            if (variance == NULL) {
                cpl_array_delete(ssn);
                ssn = NULL;

                cx_slist_destroy(simages, (cx_free_func)cpl_image_delete);
                simages = NULL;

                cx_slist_destroy(eimages, (cx_free_func)cpl_image_delete);
                eimages = NULL;

                cx_slist_destroy(scubes, (cx_free_func)giraffe_cube_delete);
                scubes = NULL;

                cx_slist_destroy(ecubes, (cx_free_func)giraffe_cube_delete);
                ecubes = NULL;

                giraffe_range_delete(limits);
                limits = NULL;

                cpl_msg_error(fctid, "Failed to create variance map!");

                return 1;
            }

            cpl_image_power(giraffe_image_get(variance), 2.);


            /*
             * Build the data cubes and images for each sub-slit
             */

            _ssn = cpl_array_duplicate(ssn);

            for (i = 0; i < cpl_array_get_size(_ssn); ++i) {

                cxbool failed = FALSE;

                cxint nss = cpl_array_get_int(_ssn, i, NULL);

                cpl_table* ssf = NULL;

                cpl_table_unselect_all(_fibers);
                cpl_table_or_selected_int(_fibers, "SSN", CPL_EQUAL_TO, nss);

                /*
                 * Remove fibers without position information, i.e.
                 * simultaneous calibration fibers and sky fibers.
                 */

                cpl_table_and_selected_int(_fibers, "X", CPL_GREATER_THAN, 0);
                cpl_table_and_selected_int(_fibers, "Y", CPL_GREATER_THAN, 0);

                ssf = cpl_table_extract_selected(_fibers);

                if ((ssf != NULL) && (cpl_table_get_nrow(ssf) > 0)) {

                    cpl_matrix* transform = NULL;

                    cpl_propertylist* wcs = NULL;

                    cpl_image* _simage = NULL;
                    cpl_image* _eimage = NULL;

                    GiCube* _scube = NULL;
                    GiCube* _ecube = NULL;


                    _scube = _giraffe_fov_create_cube(rebinning->spectra,
                                                      ssf, NULL);

                    /*
                     * Build a world coordinate system for the cube
                     */

                    if (_scube != NULL) {

                        cxdouble xorigin = giraffe_cube_get_width(_scube) / 2.;
                        cxdouble yorigin = giraffe_cube_get_height(_scube) / 2.;

                        cxdouble xvalue =
                                cpl_table_get_double(ssf, "RA", 0, NULL);
                        cxdouble yvalue =
                                cpl_table_get_double(ssf, "DEC", 0, NULL);
                        cxdouble orientation =
                                cpl_table_get_double(ssf, "ORIENT", 0, NULL);

                        cxdouble zvalue   = 0.;
                        cxdouble zstep    = 0.;
                        cxdouble angle    = GI_IFU_POSANG_OFFSET - orientation;
                        cxdouble pixscale = GI_IFU_PIXSCALE / 3600.;


                        transform = cpl_matrix_new(3, 3);

                        wcs = cpl_propertylist_new();

                        cpl_propertylist_update_double(wcs, "XORIGIN", xorigin);
                        cpl_propertylist_update_double(wcs, "YORIGIN", yorigin);
                        cpl_propertylist_update_double(wcs, "ZORIGIN", 1.);

                        giraffe_cube_get_zaxis(_scube, &zvalue, &zstep);

                        cpl_propertylist_update_double(wcs, "XPOINT", xvalue);
                        cpl_propertylist_update_double(wcs, "YPOINT", yvalue);
                        cpl_propertylist_update_double(wcs, "ZPOINT", zvalue);

                        cpl_propertylist_update_string(wcs, "XTYPE",
                                                       "RA---TAN");
                        cpl_propertylist_update_string(wcs, "YTYPE",
                                                       "DEC--TAN");

                        if (log_scale == TRUE) {
                            cpl_propertylist_update_string(wcs,
                                                           "ZTYPE", "AWAV-LOG");
                        }
                        else {
                            cpl_propertylist_update_string(wcs,
                                                           "ZTYPE", "AWAV");
                        }

                        cpl_propertylist_update_string(wcs, "XUNIT", "deg");
                        cpl_propertylist_update_string(wcs, "YUNIT", "deg");
                        cpl_propertylist_update_string(wcs, "ZUNIT", "nm");


                        /*
                         * Right ascension is counted eastward from the
                         * equinox, hence the negative sign on the upper
                         * left element of the scale matrix.
                         */

                        angle *= CX_PI / 180.;

                        cpl_matrix_set(transform, 0, 0, -pixscale *  cos(angle));
                        cpl_matrix_set(transform, 0, 1,  pixscale * -sin(angle));
                        cpl_matrix_set(transform, 1, 0, -pixscale *  sin(angle));
                        cpl_matrix_set(transform, 1, 1,  pixscale *  cos(angle));
                        cpl_matrix_set(transform, 2, 2, zstep);

                    }

                    if (_scube != NULL) {
                        _simage = _giraffe_fov_integrate_cube(_scube, limits);
                    }

                    if ((_scube == NULL) || (_simage == NULL)) {

                        cpl_image_delete(_simage);
                        _simage = NULL;

                        giraffe_cube_delete(_scube);
                        _scube = NULL;

                        failed = TRUE;

                        cpl_msg_error(fctid, "Cannot create data cube for "
                                      "sub-slit %d", nss);
                    }
                    else {
                        giraffe_cube_set_wcs(_scube, wcs, transform);

                        cx_slist_push_back(scubes, _scube);
                        cx_slist_push_back(simages, _simage);
                    }

                    if (!failed) {

                        _ecube = _giraffe_fov_create_cube(variance,
                                                          ssf, NULL);

                        if (_ecube != NULL) {
                            _eimage = _giraffe_fov_integrate_cube(_ecube,
                                                                  limits);
                        }

                        if ((_ecube == NULL) || (_eimage == NULL)) {

                            cpl_image_delete(_eimage);
                            _eimage = NULL;

                            giraffe_cube_delete(_ecube);
                            _ecube = NULL;

                            failed = TRUE;

                            cpl_msg_error(fctid, "Cannot create error "
                                          "cube for sub-slit %d", nss);
                        }
                        else {
                            giraffe_cube_sqrt(_ecube);
                            cpl_image_power(_eimage, 0.5);

                            giraffe_cube_set_wcs(_ecube, wcs, transform);

                            cx_slist_push_back(ecubes, _ecube);
                            cx_slist_push_back(eimages, _eimage);
                        }

                    }

                    cpl_propertylist_delete(wcs);
                    wcs = NULL;

                    cpl_matrix_delete(transform);
                    transform = NULL;

                    if (failed) {

                        cpl_table_delete(ssf);
                        ssf = NULL;

                        giraffe_image_delete(variance);
                        variance = NULL;

                        cpl_array_delete(_ssn);
                        _ssn = NULL;

                        cpl_array_delete(ssn);
                        ssn = NULL;

                        cx_slist_destroy(simages,
                                         (cx_free_func)cpl_image_delete);
                        simages = NULL;

                        cx_slist_destroy(eimages,
                                         (cx_free_func)cpl_image_delete);
                        eimages = NULL;

                        cx_slist_destroy(scubes,
                                         (cx_free_func)giraffe_cube_delete);
                        scubes = NULL;

                        cx_slist_destroy(ecubes,
                                         (cx_free_func)giraffe_cube_delete);
                        ecubes = NULL;

                        giraffe_range_delete(limits);
                        limits = NULL;

                        return 1;

                    }

                }
                else {

                    if (ssf != NULL) {
                        cpl_msg_debug(fctid, "Unused IFU button detected. "
                                        "Skipping sub-slit %d", nss);

                        cpl_array_set_invalid(_ssn, i);

                        cx_slist_push_back(simages, NULL);
                        cx_slist_push_back(eimages, NULL);
                    }
                }

                cpl_table_delete(ssf);
                ssf = NULL;

            }


            /*
             * Extract valid subslit indices from the index array
             */

            if (cpl_array_count_invalid(_ssn) > 0) {

                cxint j = 0;
                cxint k = 0;

                cpl_array_set_size(ssn, cpl_array_get_size(ssn) -
                                   cpl_array_count_invalid(_ssn));

                for (k = 0; k < cpl_array_get_size(_ssn); ++k) {

                    cxint invalid = 1;

                    register cxint idx = cpl_array_get_int(_ssn, k, &invalid);

                    if (!invalid) {
                        cpl_array_set_int(ssn, j, idx);
                        ++j;
                    }

                }

            }

            cpl_array_delete(_ssn);
            _ssn = NULL;

            giraffe_image_delete(variance);
            variance = NULL;


            /*
             * Put the images of the reconstructed field of view in an
             * image using a tabular layout. This mosaic is stored as
             * the first image in the image containers, to be consistent
             * with the Argus mode, where only this image is provided.
             */

            smosaic = _giraffe_fov_arrange_images(simages, 5, 3, -4);
            emosaic = _giraffe_fov_arrange_images(eimages, 5, 3, -4);

            if ((smosaic == NULL) || (emosaic == NULL)) {

                cpl_image_delete(smosaic);
                smosaic = NULL;

                cpl_image_delete(emosaic);
                emosaic = NULL;

                cx_slist_destroy(simages, (cx_free_func)cpl_image_delete);
                simages = NULL;

                cx_slist_destroy(eimages, (cx_free_func)cpl_image_delete);
                eimages = NULL;

                cx_slist_destroy(scubes, (cx_free_func)giraffe_cube_delete);
                scubes = NULL;

                cx_slist_destroy(ecubes, (cx_free_func)giraffe_cube_delete);
                ecubes = NULL;

                cpl_array_delete(ssn);
                ssn = NULL;

                giraffe_range_delete(limits);
                limits = NULL;

                return 1;

            }

            cx_slist_push_front(simages, smosaic);
            cx_slist_push_front(eimages, emosaic);
            break;

        }

        case GIMODE_ARGUS:
        {
            cxbool failed = FALSE;

            cpl_image* simage = NULL;
            cpl_image* eimage = NULL;

            cpl_matrix* transform = NULL;

            cpl_propertylist* wcs = NULL;

            GiImage* variance = NULL;

            GiCube* scube = NULL;
            GiCube* ecube = NULL;


            /*
             * Compute the variances from the error map.
             */

            variance = giraffe_image_duplicate(rebinning->errors);

            if (variance == NULL) {

                cx_slist_destroy(simages, (cx_free_func)cpl_image_delete);
                simages = NULL;

                cx_slist_destroy(eimages, (cx_free_func)cpl_image_delete);
                eimages = NULL;

                cx_slist_destroy(scubes, (cx_free_func)giraffe_cube_delete);
                scubes = NULL;

                cx_slist_destroy(ecubes, (cx_free_func)giraffe_cube_delete);
                ecubes = NULL;

                giraffe_range_delete(limits);
                limits = NULL;

                cpl_msg_error(fctid, "Failed to create variance map!");
                return 1;

            }

            cpl_image_power(giraffe_image_get(variance), 2.);


            /*
             * Build the data cubes and field of view images
             */

            scube = _giraffe_fov_create_cube(rebinning->spectra,
                                             _fibers, NULL);

            /*
             * Build a world coordinate system for the cube.
             */
             
            /*
             * Argus has a +90 degrees offset with respect to the adaptor,
             * thus: PA_arg = PA_ada + 90.
             * 
             * The Argus long axis is aligned with the North-South
             * direction, and North up for PA_arg = 0. Since in the
             * image the Argus long axis is the x-axis an extra 90.
             * degrees offset has to be applied to the WCS axes. And
             * finally, since the coordinate system is transformed
             * relative to the image, and PA_arg is counted positive
             * counter clockwise (from North through East), the agular
             * offsets must be counted negative.
             */

            if (scube != NULL) {

                cxdouble xorigin = giraffe_cube_get_width(scube) / 2.;
                cxdouble yorigin = giraffe_cube_get_height(scube) / 2.;

                cxdouble xvalue = cpl_propertylist_get_double(properties,
                                                              GIALIAS_RADEG);
                cxdouble yvalue = cpl_propertylist_get_double(properties,
                                                              GIALIAS_DECDEG);
                cxdouble zvalue   = 0.;
                cxdouble zstep    = 0.;
                cxdouble angle    = -90. -
                        (cpl_propertylist_get_double(properties,
                                                     GIALIAS_POSANG) +
                                GI_ARGUS_POSANG_OFFSET);

                cxdouble pixscale = GI_ARGUS_PIXSCALE_LOW;

                const cxchar* scale =
                        cpl_propertylist_get_string(properties,
                                                    GIALIAS_ARGUS_SCALE);


                if ((scale != NULL) && (strcmp(scale, "POS_1_67") == 0)) {
                    pixscale = GI_ARGUS_PIXSCALE_HIGH;
                }

                /* Get pixel scale in degrees */

                pixscale /= 3600.;


                transform = cpl_matrix_new(3, 3);

                wcs = cpl_propertylist_new();

                cpl_propertylist_update_double(wcs, "XORIGIN", xorigin);
                cpl_propertylist_update_double(wcs, "YORIGIN", yorigin);
                cpl_propertylist_update_double(wcs, "ZORIGIN", 1.);

                giraffe_cube_get_zaxis(scube, &zvalue, &zstep);

                cpl_propertylist_update_double(wcs, "XPOINT", xvalue);
                cpl_propertylist_update_double(wcs, "YPOINT", yvalue);
                cpl_propertylist_update_double(wcs, "ZPOINT", zvalue);

                cpl_propertylist_update_string(wcs, "XTYPE", "RA---TAN");
                cpl_propertylist_update_string(wcs, "YTYPE", "DEC--TAN");

                if (log_scale == TRUE) {
                    cpl_propertylist_update_string(wcs,
                                                   "ZTYPE", "AWAV-LOG");
                }
                else {
                    cpl_propertylist_update_string(wcs,
                                                   "ZTYPE", "AWAV");
                }

                cpl_propertylist_update_string(wcs, "XUNIT", "deg");
                cpl_propertylist_update_string(wcs, "YUNIT", "deg");
                cpl_propertylist_update_string(wcs, "ZUNIT", "nm");


                /*
                 * Right ascension is counted eastward from the equinox,
                 * hence the negative sign on the upper left element of the
                 * scale matrix.
                 */

                angle *= CX_PI / 180.;

                cpl_matrix_set(transform, 0, 0, -pixscale *  cos(angle));
                cpl_matrix_set(transform, 0, 1,  pixscale * -sin(angle));
                cpl_matrix_set(transform, 1, 0, -pixscale *  sin(angle));
                cpl_matrix_set(transform, 1, 1,  pixscale *  cos(angle));
                cpl_matrix_set(transform, 2, 2, zstep);

            }


            if (scube != NULL) {
                simage = _giraffe_fov_integrate_cube(scube, limits);
            }

            if ((scube == NULL) || (simage == NULL)) {

                cpl_image_delete(simage);
                simage = NULL;

                giraffe_cube_delete(scube);
                scube = NULL;

                failed = TRUE;

                cpl_msg_error(fctid, "Cannot create data cube!");

            }
            else {

                giraffe_cube_set_wcs(scube, wcs, transform);

                cx_slist_push_back(scubes, scube);
                cx_slist_push_back(simages, simage);

            }


            if (!failed) {

                ecube = _giraffe_fov_create_cube(variance, _fibers, NULL);
                eimage = _giraffe_fov_integrate_cube(ecube, limits);

                if ((ecube == NULL) || (eimage == NULL)) {

                    cpl_image_delete(eimage);
                    eimage = NULL;

                    giraffe_cube_delete(ecube);
                    ecube = NULL;

                    failed = TRUE;

                    cpl_msg_error(fctid, "Cannot create error cube!");

                }
                else {

                    giraffe_cube_sqrt(ecube);
                    cpl_image_power(eimage, 0.5);

                    giraffe_cube_set_wcs(ecube, wcs, transform);

                    cx_slist_push_back(ecubes, ecube);
                    cx_slist_push_back(eimages, eimage);

                }

            }

            cpl_propertylist_delete(wcs);
            wcs = NULL;

            cpl_matrix_delete(transform);
            transform = NULL;

            giraffe_image_delete(variance);
            variance = NULL;

            if (failed) {

                cx_slist_destroy(simages, (cx_free_func)cpl_image_delete);
                simages = NULL;

                cx_slist_destroy(eimages, (cx_free_func)cpl_image_delete);
                eimages = NULL;

                cx_slist_destroy(scubes, (cx_free_func)giraffe_cube_delete);
                scubes = NULL;

                cx_slist_destroy(ecubes, (cx_free_func)giraffe_cube_delete);
                ecubes = NULL;

                giraffe_range_delete(limits);
                limits = NULL;

                return 1;

            }

            break;
        }

        default:
            return 1;
            break;
    }


    /*
     * Fill the results container.
     */

    result->mode = mode;
    result->ssn  = ssn;
    ssn = NULL;

    properties = giraffe_image_get_properties(rebinning->spectra);
    fov = cx_slist_pop_front(simages);

    result->fov.spectra = giraffe_image_new(CPL_TYPE_DOUBLE);
    giraffe_image_set(result->fov.spectra, fov);
    giraffe_image_set_properties(result->fov.spectra, properties);

    properties = giraffe_image_get_properties(result->fov.spectra);

    /* Clear left over WCS keywords  */

    giraffe_propertylist_update_wcs(properties, 0, NULL, NULL, NULL,
                                    NULL, NULL);

    cpl_propertylist_update_double(properties, GIALIAS_FOV_BANDMIN,
                                   giraffe_range_get_min(limits));
    cpl_propertylist_set_comment(properties, GIALIAS_FOV_BANDMIN,
                                 "Minimum wavelength of FOV band");

    cpl_propertylist_update_double(properties, GIALIAS_FOV_BANDMAX,
                                   giraffe_range_get_max(limits));
    cpl_propertylist_set_comment(properties, GIALIAS_FOV_BANDMAX,
                                 "Maximum wavelength of FOV band");

    cpl_image_delete(fov);
    fov = NULL;


    properties = giraffe_image_get_properties(rebinning->errors);
    fov = cx_slist_pop_front(eimages);

    result->fov.errors = giraffe_image_new(CPL_TYPE_DOUBLE);
    giraffe_image_set(result->fov.errors, fov);
    giraffe_image_set_properties(result->fov.errors, properties);

    properties = giraffe_image_get_properties(result->fov.errors);

    /* Clear left over WCS keywords  */

    giraffe_propertylist_update_wcs(properties, 0, NULL, NULL, NULL,
                                    NULL, NULL);
    cpl_propertylist_update_double(properties, GIALIAS_FOV_BANDMIN,
                                   giraffe_range_get_min(limits));
    cpl_propertylist_set_comment(properties, GIALIAS_FOV_BANDMIN,
                                 "Minimum wavelength of FOV band");

    cpl_propertylist_update_double(properties, GIALIAS_FOV_BANDMAX,
                                   giraffe_range_get_max(limits));
    cpl_propertylist_set_comment(properties, GIALIAS_FOV_BANDMAX,
                                 "Maximum wavelength of FOV band");

    cpl_image_delete(fov);
    fov = NULL;

    if (!cx_slist_empty(simages)) {

        cx_slist_iterator pos = cx_slist_begin(simages);

        result->images.spectra = cx_slist_new();

        while (pos != cx_slist_end(simages)) {

            GiImage* image = giraffe_image_new(CPL_TYPE_DOUBLE);

            giraffe_image_set(image, cx_slist_get(simages, pos));
            cx_slist_push_back(result->images.spectra, image);

            pos = cx_slist_next(simages, pos);
        }

    }

    if (!cx_slist_empty(eimages)) {

        cx_slist_iterator pos = cx_slist_begin(eimages);

        result->images.errors = cx_slist_new();

        while (pos != cx_slist_end(eimages)) {

            GiImage* image = giraffe_image_new(CPL_TYPE_DOUBLE);

            giraffe_image_set(image, cx_slist_get(eimages, pos));
            cx_slist_push_back(result->images.errors, image);

            pos = cx_slist_next(eimages, pos);
        }

    }

    if (config->cube == TRUE) {

        if (!cx_slist_empty(scubes)) {
            result->cubes.spectra = scubes;
            scubes = NULL;
        }

        if (!cx_slist_empty(ecubes)) {
            result->cubes.errors = ecubes;
            ecubes = NULL;
        }

    }


    /*
     * Cleanup
     */

    giraffe_range_delete(limits);
    limits = NULL;

    cx_slist_destroy(simages, (cx_free_func)cpl_image_delete);
    simages = NULL;

    cx_slist_destroy(eimages, (cx_free_func)cpl_image_delete);
    eimages = NULL;

    if (scubes != NULL) {
        cx_slist_destroy(scubes, (cx_free_func)giraffe_cube_delete);
        scubes = NULL;
    }

    if (ecubes != NULL) {
        cx_slist_destroy(ecubes, (cx_free_func)giraffe_cube_delete);
        ecubes = NULL;
    }

    return 0;

}


/**
 * @brief
 *   Create an empty container for the results of the field of view
 *   reconstruction.
 *
 * @return
 *   A newly allocated and empty field of view container.
 *
 * The function allocates the memory for a field of view container and
 * initializes the created object so that it is a valid empty container
 * object.
 */

GiFieldOfView*
giraffe_fov_new(void)
{
    GiFieldOfView* self = cx_malloc(sizeof *self);

    self->mode = GIMODE_NONE;
    self->ssn  = NULL;

    self->fov.spectra = NULL;
    self->fov.errors  = NULL;

    self->images.spectra = NULL;
    self->images.errors  = NULL;

    self->cubes.spectra = NULL;
    self->cubes.errors  = NULL;

    return self;

}


/**
 * @brief
 *   Delete the contents of a field of view object.
 *
 * @param self  The field of view object to be emptied.
 *
 * @return
 *   Nothing.
 *
 *
 */

void
giraffe_fov_clear(GiFieldOfView* self)
{

    if (self != NULL) {

        if (self->cubes.errors != NULL) {
            cx_slist_destroy(self->cubes.errors,
                             (cx_free_func)giraffe_cube_delete);
            self->cubes.errors = NULL;
        }

        if (self->cubes.spectra != NULL) {
            cx_slist_destroy(self->cubes.spectra,
                             (cx_free_func)giraffe_cube_delete);
            self->cubes.spectra = NULL;
        }

        if (self->images.errors != NULL) {
            cx_slist_destroy(self->images.errors,
                             (cx_free_func)giraffe_image_delete);
            self->images.errors = NULL;
        }

        if (self->images.spectra != NULL) {
            cx_slist_destroy(self->images.spectra,
                             (cx_free_func)giraffe_image_delete);
            self->images.spectra = NULL;
        }

        if (self->fov.errors != NULL) {
            giraffe_image_delete(self->fov.errors);
            self->fov.errors = NULL;
        }

        if (self->fov.spectra != NULL) {
            giraffe_image_delete(self->fov.spectra);
            self->fov.spectra = NULL;
        }

        if (self->ssn != NULL) {
            cpl_array_delete(self->ssn);
            self->ssn = NULL;
        }

        self->mode = GIMODE_NONE;

    }

    return;

}


/**
 * @brief
 *   Deallocate a field of view object and its contents.
 *
 * @param self  The field of view object to destroy.
 *
 * @return
 *   Nothing.
 *
 * The function the field-of-view container object @em self, and all data
 * components it possibly contains.
 */

void
giraffe_fov_delete(GiFieldOfView* self)
{

    if (self != NULL) {
        giraffe_fov_clear(self);
        cx_free(self);
    }

    return;

}


/**
 * @brief
 *   Write the cube components of a field-of-view object to a file.
 *
 * @param self        A field-of-view object
 * @param properties  The property list to save with the object
 * @param filename    The name of the output file
 * @param data        Pointer to an integer which specifies which cube
 *                    component will be saved.
 *
 * @return
 *   The function returns @c 0 on success, or a non-zero number in
 *   case an error occurred.
 *
 * The function creates a file @em filename for a cube component of an
 * field-of-view object. The cube components are either the spectrum cube,
 * or its associated error cube. Which component is written is determined
 * by the value of an integer variable referenced by @em data. If the value
 * is @c 0 the spectrum cube will be saved, and the error cube is saved in
 * case of a non-zero value.
 */

cxint
giraffe_fov_save_cubes(const GiFieldOfView* self,
                       cpl_propertylist* properties,
                       const cxchar* filename, cxptr data)
{


    cxint component = 0;

    cx_slist* cubes = NULL;


    if ((self == NULL) || (properties == NULL) || (filename == NULL)) {
        return -1;
    }


    /*
     * Get the cube component that should be saved. Spectra or errors.
     */

    if (data != NULL) {
        component = *((cxuint*)data);
    }

    if (component == 0) {
        cubes = self->cubes.spectra;
    }
    else {
        cubes = self->cubes.errors;
    }

    if (cubes == NULL) {
        return -2;
    }


    if (!cx_slist_empty(cubes)) {

        if (self->mode == GIMODE_ARGUS) {

            cxint status = 0;
            cxint iomode = CPL_IO_CREATE;

            GiCube* cube = cx_slist_front(cubes);

            status = giraffe_cube_save(cube, properties, filename, &iomode);

            if (status != 0) {
                return 1;
            }

        }
        else {

            cxint nss = 0;
            cxint status = 0;
            cxint iomode = CPL_IO_CREATE;

            cx_slist_const_iterator pos = cx_slist_begin(cubes);

            cx_string* name = NULL;

            cpl_propertylist* xproperties = NULL;


            status = giraffe_cube_save(NULL, properties, filename, &iomode);

            if (status != 0) {
                return 1;
            }


            name = cx_string_new();
            xproperties = cpl_propertylist_new();

            iomode = CPL_IO_EXTEND;

            while (pos != cx_slist_end(cubes)) {

                cxint ssn = cpl_array_get_int(self->ssn, nss, NULL);

                GiCube* cube = cx_slist_get(cubes, pos);


                cx_string_sprintf(name, "SSN%-d", ssn);
                cpl_propertylist_update_string(xproperties, "EXTNAME",
                                               cx_string_get(name));

                status = giraffe_cube_save(cube, xproperties, filename,
                                           &iomode);

                if (status != 0) {

                    cpl_propertylist_delete(xproperties);
                    xproperties = NULL;

                    cx_string_delete(name);
                    name = NULL;

                    return 1;

                }

                pos = cx_slist_next(cubes, pos);
                ++nss;

            }

            cpl_propertylist_delete(xproperties);
            xproperties = NULL;

            cx_string_delete(name);
            name = NULL;

        }

    }

    return 0;

}


/**
 * @brief
 *   Write the cube components of a field-of-view object to a file.
 *
 * @param self        A field-of-view object
 * @param properties  The property list to save with the object
 * @param filename    The name of the output file
 * @param data        Unused.
 *
 * @return
 *   The function returns @c 0 on success, or a non-zero number in
 *   case an error occurred.
 *
 * The function creates a file @em filename for a cube component of an
 * field-of-view object. The cube component may be one or more spectrum cubes
 * and optionally their associated error cubes. If error cubes are present,
 * they must be present for each spectrum cube.
 */

cxint
giraffe_fov_save_cubes_eso3d(const GiFieldOfView* self,
                             cpl_propertylist* properties,
                             const cxchar* filename, cxptr data)
{

    const cxchar* data_name     = "SPECTRA";
    const cxchar* error_name    = "ERRORS";
    const cxchar* link_names[2] = {"SCIDATA", "ERRDATA"};

    cx_slist* scubes = NULL;
    cx_slist* ecubes = NULL;


    /* Currently not used. Set to avoid compiler warnings */

    (void) data;


    if ((self == NULL) || (properties == NULL) || (filename == NULL)) {
        return -1;
    }

    if (self->cubes.spectra == NULL) {
        return -2;
    }

    if ((cpl_propertylist_has(properties, GIALIAS_EQUINOX) == FALSE) ||
        (cpl_propertylist_get_type(properties, GIALIAS_EQUINOX)
                != CPL_TYPE_DOUBLE)) {
        return -2;
    }


    /*
     * Get the cube components. If errors are present their number must
     * match the number of spectrum cubes!
     */

    scubes = self->cubes.spectra;

    if (cx_slist_empty(scubes)) {
        return -3;
    }

    if (self->cubes.errors != NULL) {

        ecubes = self->cubes.errors;

        if (cx_slist_size(scubes) != cx_slist_size(ecubes)) {
            return -4;
        }

    }


    if (self->mode == GIMODE_ARGUS) {

        cxint status = 0;
        cxint iomode = CPL_IO_CREATE;

        cxdouble equinox = cpl_propertylist_get_double(properties,
                                                       GIALIAS_EQUINOX);

        cpl_propertylist* xproperties = NULL;

        GiCube* scube = cx_slist_front(scubes);


        status = giraffe_cube_save(NULL, properties, filename, &iomode);

        if (status != 0) {
            return 1;
        }


        iomode = CPL_IO_EXTEND;

        xproperties = cpl_propertylist_new();

        cpl_propertylist_update_string(xproperties, GIALIAS_EXTNAME, data_name);
        cpl_propertylist_set_comment(xproperties, GIALIAS_EXTNAME,
                                     "FITS Extension name");

        cpl_propertylist_update_string(xproperties, "HDUCLASS", "ESO");
        cpl_propertylist_set_comment(xproperties, "HDUCLASS",
                                     "Conforms to ESO data cube conventions");

        cpl_propertylist_update_string(xproperties, "HDUDOC", "DICD");
        cpl_propertylist_set_comment(xproperties, "HDUDOC",
                                     "Data format specification document");

        cpl_propertylist_update_string(xproperties, "HDUVERS",
                                       "DICD version 6");
        cpl_propertylist_set_comment(xproperties, "HDUVERS",
                                     "Specific version of the data format "
                                     "document");

        cpl_propertylist_update_string(xproperties, "HDUCLAS1", "IMAGE");
        cpl_propertylist_set_comment(xproperties, "HDUCLAS1",
                                     "Image data format");

        cpl_propertylist_update_string(xproperties, "HDUCLAS2", "DATA");
        cpl_propertylist_set_comment(xproperties, "HDUCLAS2",
                                     "Science data extension");
        cpl_propertylist_update_string(xproperties, link_names[1], error_name);
        cpl_propertylist_set_comment(xproperties, link_names[1],
                                     "Linked error data extension");

        cpl_propertylist_update_double(xproperties, GIALIAS_EQUINOX,
                                       equinox);

        status = giraffe_cube_save(scube, xproperties, filename,
                                   &iomode);

        if (status != 0) {

            cpl_propertylist_delete(xproperties);
            xproperties = NULL;

            return 1;

        }

        cpl_propertylist_erase(xproperties, link_names[1]);
        cpl_propertylist_erase(xproperties, "BUNIT");
        cpl_propertylist_erase(xproperties, "DATAMIN");
        cpl_propertylist_erase(xproperties, "DATAMAX");


        if (ecubes != NULL) {

            GiCube* ecube = cx_slist_front(ecubes);


            cpl_propertylist_update_string(xproperties, "EXTNAME", error_name);

            cpl_propertylist_update_string(xproperties, "HDUCLAS2", "ERROR");
            cpl_propertylist_set_comment(xproperties, "HDUCLAS2",
                                         "Error data extension");

            cpl_propertylist_update_string(xproperties, "HDUCLAS3", "RMSE");
            cpl_propertylist_set_comment(xproperties, "HDUCLAS3",
                                         "Type of error: root mean squared");

            cpl_propertylist_update_string(xproperties, link_names[0],
                                           data_name);
            cpl_propertylist_set_comment(xproperties, link_names[0],
                                         "Linked science data extension");

            status = giraffe_cube_save(ecube, xproperties, filename,
                                       &iomode);

            if (status != 0) {

                cpl_propertylist_delete(xproperties);
                xproperties = NULL;

                return 1;

            }

        }

        cpl_propertylist_delete(xproperties);
        xproperties = NULL;

    }
    else {

        cxint nss = 0;
        cxint status = 0;
        cxint iomode = CPL_IO_CREATE;

        cxdouble equinox = cpl_propertylist_get_double(properties,
                                                       GIALIAS_EQUINOX);

        cx_slist_const_iterator spos = cx_slist_begin(scubes);
        cx_slist_const_iterator epos = cx_slist_begin(ecubes);

        cx_string* name = NULL;

        cpl_propertylist* xproperties = NULL;


        status = giraffe_cube_save(NULL, properties, filename, &iomode);

        if (status != 0) {
            return 1;
        }


        name = cx_string_new();
        xproperties = cpl_propertylist_new();

        iomode = CPL_IO_EXTEND;

        while (spos != cx_slist_end(scubes)) {

            cxint ssn = cpl_array_get_int(self->ssn, nss, NULL);

            GiCube* scube = cx_slist_get(scubes, spos);


            cx_string_sprintf(name, "SSN%-d.%s", ssn, data_name);

            cpl_propertylist_update_string(xproperties, GIALIAS_EXTNAME,
                                           cx_string_get(name));
            cpl_propertylist_set_comment(xproperties, GIALIAS_EXTNAME,
                                         "FITS Extension name");

            cpl_propertylist_update_string(xproperties, "HDUCLASS", "ESO");
            cpl_propertylist_set_comment(xproperties, "HDUCLASS",
                                         "Conforms to ESO data cube "
                                         "conventions");

            cpl_propertylist_update_string(xproperties, "HDUDOC", "DICD");
            cpl_propertylist_set_comment(xproperties, "HDUDOC",
                                         "Data format specification document");

            cpl_propertylist_update_string(xproperties, "HDUVERS",
                                           "DICD version 6");
            cpl_propertylist_set_comment(xproperties, "HDUVERS",
                                         "Specific version of the data format "
                                         "document");

            cpl_propertylist_update_string(xproperties, "HDUCLAS1", "IMAGE");
            cpl_propertylist_set_comment(xproperties, "HDUCLAS1",
                                         "Image data format");

            cpl_propertylist_update_string(xproperties, "HDUCLAS2", "DATA");
            cpl_propertylist_set_comment(xproperties, "HDUCLAS2",
                                         "Science data extension");

            cx_string_sprintf(name, "SSN%-d.%s", ssn, error_name);

            cpl_propertylist_update_string(xproperties, link_names[1],
                                           cx_string_get(name));
            cpl_propertylist_set_comment(xproperties, link_names[1],
                                         "Linked error data extension");

            cpl_propertylist_update_double(xproperties, GIALIAS_EQUINOX,
                                           equinox);

            status = giraffe_cube_save(scube, xproperties, filename,
                                       &iomode);

            if (status != 0) {

                cpl_propertylist_delete(xproperties);
                xproperties = NULL;

                cx_string_delete(name);
                name = NULL;

                return 1;

            }

            cpl_propertylist_erase(xproperties, link_names[1]);
            cpl_propertylist_erase(xproperties, "BUNIT");
            cpl_propertylist_erase(xproperties, "DATAMIN");
            cpl_propertylist_erase(xproperties, "DATAMAX");


            if (ecubes != NULL) {

                GiCube* ecube = cx_slist_get(ecubes, epos);


                cx_string_sprintf(name, "SSN%-d.%s", ssn, error_name);

                cpl_propertylist_update_string(xproperties, "EXTNAME",
                                               cx_string_get(name));

                cpl_propertylist_update_string(xproperties, "HDUCLAS2", "ERROR");
                cpl_propertylist_set_comment(xproperties, "HDUCLAS2",
                                             "Error data extension");

                cpl_propertylist_update_string(xproperties, "HDUCLAS3", "RMSE");
                cpl_propertylist_set_comment(xproperties, "HDUCLAS3",
                                             "Type of error: root mean squared");

                cx_string_sprintf(name, "SSN%-d.%s", ssn, data_name);

                cpl_propertylist_update_string(xproperties, link_names[0],
                                               cx_string_get(name));
                cpl_propertylist_set_comment(xproperties, link_names[0],
                                             "Linked science data extension");

                status = giraffe_cube_save(ecube, xproperties, filename,
                                           &iomode);


                if (status != 0) {

                    cpl_propertylist_delete(xproperties);
                    xproperties = NULL;

                    cx_string_delete(name);
                    name = NULL;

                    return 1;

                }

                epos = cx_slist_next(ecubes, epos);

            }

            spos = cx_slist_next(scubes, spos);
            ++nss;

        }

        cpl_propertylist_delete(xproperties);
        xproperties = NULL;

        cx_string_delete(name);
        name = NULL;

    }

    return 0;

}


/**
 * @brief
 *   Creates a setup structure for the field of view reconstruction.
 *
 * @param list  Parameter list from which the setup informations is read.
 *
 * @return
 *  A newly allocated and initialized setup structure if no errors
 *  occurred, or @c NULL otherwise.
 */

GiFieldOfViewConfig*
giraffe_fov_config_create(cpl_parameterlist* list)
{

    const cxchar* s = NULL;

    cpl_parameter* p;

    GiFieldOfViewConfig* config = NULL;


    if (list == NULL) {
        return NULL;
    }

    config = cx_calloc(1, sizeof *config);


    p = cpl_parameterlist_find(list, "giraffe.fov.range.minimum");
    config->minimum = cpl_parameter_get_double(p);

    p = cpl_parameterlist_find(list, "giraffe.fov.range.maximum");
    config->maximum = cpl_parameter_get_double(p);

    p = cpl_parameterlist_find(list, "giraffe.fov.cube");
    config->cube = cpl_parameter_get_bool(p);

    p = cpl_parameterlist_find(list, "giraffe.fov.cube.format");
    s = cpl_parameter_get_string(p);

    if (strcmp(s, "single") == 0) {
        config->format = GIFOV_FORMAT_SINGLE;
    }
    else if (strcmp(s, "eso3d") == 0) {
        config->format = GIFOV_FORMAT_ESO3D;
    }

    return config;

}


/**
 * @brief
 *   Destroys a field of view setup structure.
 *
 * @param config  The setup structure to destroy.
 *
 * @return
 *   Nothing.
 *
 * The function deallocates the memory used by the setup structure
 * @em config.
 */

void
giraffe_fov_config_destroy(GiFieldOfViewConfig* config)
{

    if (config != NULL) {
        cx_free(config);
    }

    return;

}


/**
 * @brief
 *   Adds parameters for the image and data cube construction.
 *
 * @param list  Parameter list to which parameters are added.
 *
 * @return Nothing.
 */

void
giraffe_fov_config_add(cpl_parameterlist* list)
{

    cpl_parameter* p;


    if (list == NULL) {
        return;
    }

    p = cpl_parameter_new_value("giraffe.fov.range.minimum",
                                CPL_TYPE_DOUBLE,
                                "Minimum wavelength for image reconstruction",
                                "giraffe.fov.range",
                                0.);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "recon-min");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.fov.range.maximum",
                                CPL_TYPE_DOUBLE,
                                "Maximum wavelength for image reconstruction",
                                "giraffe.fov.range",
                                0.);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "recon-max");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.fov.cube",
                                CPL_TYPE_BOOL,
                                "Turns data cube creation on and off",
                                "giraffe.fov.cube",
                                TRUE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "recon-cube");
    cpl_parameterlist_append(list, p);

    p = cpl_parameter_new_enum("giraffe.fov.cube.format",
                                CPL_TYPE_STRING,
                                "Selects the file format for cubes",
                                "giraffe.fov.cube",
                                "single", 2, "single", "eso3d");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "recon-format");
    cpl_parameterlist_append(list, p);

    return;

}
/**@}*/
