/*
 * 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 <cpl_recipe.h>
#include <cpl_plugininfo.h>
#include <cpl_parameterlist.h>
#include <cpl_frameset.h>
#include <cpl_msg.h>

#include "gialias.h"
#include "giframe.h"
#include "gifibers.h"
#include "gifiberutils.h"
#include "gislitgeometry.h"
#include "gipsfdata.h"
#include "gibias.h"
#include "gidark.h"
#include "giextract.h"
#include "giflat.h"
#include "gitransmission.h"
#include "girebinning.h"
#include "gifov.h"
#include "gifxcalibration.h"
#include "gimessages.h"
#include "gierror.h"
#include "giqclog.h"
#include "giutils.h"


static cxint gistandard(cpl_parameterlist* config, cpl_frameset* set);
static cxint giqcstandard(cpl_frameset* set);



/*
 * Create the recipe instance, i.e. setup the parameter list for this
 * recipe and make it availble to the application using the interface.
 */

static cxint
gistandard_create(cpl_plugin* plugin)
{

    cpl_recipe* recipe = (cpl_recipe*)plugin;

    cpl_parameter* p = NULL;


    giraffe_error_init();


    /*
     * We have to provide the option we accept to the application. We
     * need to setup our parameter list and hook it into the recipe
     * interface.
     */

    recipe->parameters = cpl_parameterlist_new();
    cx_assert(recipe->parameters != NULL);


    /*
     * Fill the parameter list.
     */

    /* Bias removal */

    giraffe_bias_config_add(recipe->parameters);

    /* Dark subtraction */

    /* TBD */

    /* Spectrum extraction */

    giraffe_extract_config_add(recipe->parameters);

    /* Flat fielding and relative fiber transmission correction */

    giraffe_flat_config_add(recipe->parameters);

    p = cpl_parameterlist_find(recipe->parameters, "giraffe.flat.apply");
    cx_assert(p != NULL);

    cpl_parameter_set_default_bool(p, FALSE);

    /* Spectrum rebinning */

    giraffe_rebin_config_add(recipe->parameters);

    p = cpl_parameterlist_find(recipe->parameters,
                               "giraffe.rebinning.range");
    cx_assert(p != NULL);

    cpl_parameter_set_default_string(p, "common");

    /* Image reconstruction (IFU and Argus only) */

    giraffe_fov_config_add(recipe->parameters);

    /* Flux calibration */

    giraffe_fxcalibration_config_add(recipe->parameters);

    return 0;

}


/*
 * Execute the plugin instance given by the interface.
 */

static cxint
gistandard_exec(cpl_plugin* plugin)
{

    cxint status = 0;

    cpl_recipe* recipe = (cpl_recipe*)plugin;


    cx_assert(recipe->parameters != NULL);
    cx_assert(recipe->frames != NULL);

    status = gistandard(recipe->parameters, recipe->frames);

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

    status = giqcstandard(recipe->frames);

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

    return 0;

}


static cxint
gistandard_destroy(cpl_plugin* plugin)
{

    cpl_recipe* recipe = (cpl_recipe*)plugin;


    /*
     * We just destroy what was created during the plugin initialization
     * phase, i.e. the parameter list. The frame set is managed by the
     * application which called us, so we must not touch it,
     */

    cpl_parameterlist_delete(recipe->parameters);

    giraffe_error_clear();

    return 0;

}


/*
 * The actual recipe starts here.
 */

static cxint
gistandard(cpl_parameterlist* config, cpl_frameset* set)
{

    const cxchar* const _id = "gistandard";


    const cxchar* filename = NULL;

    cxint status = 0;

    cxlong i;
    cxlong nstandard = 0;

    cxdouble exptime = 0.;

    cx_slist* slist = NULL;

    cpl_propertylist* properties = NULL;

    cpl_matrix* biasareas = NULL;

    cpl_frame* standard_frame = NULL;
    cpl_frame* mbias_frame = NULL;
    cpl_frame* mdark_frame = NULL;
    cpl_frame* bpixel_frame = NULL;
    cpl_frame* slight_frame = NULL;
    cpl_frame* locy_frame = NULL;
    cpl_frame* locw_frame = NULL;
    cpl_frame* flat_frame = NULL;
    cpl_frame* psfdata_frame = NULL;
    cpl_frame* grating_frame = NULL;
    cpl_frame* slit_frame = NULL;
    cpl_frame* wcal_frame = NULL;
    cpl_frame* rstandard_frame = NULL;
    cpl_frame* sext_frame = NULL;
    cpl_frame* rbin_frame = NULL;
    cpl_frame* atmext_frame = NULL;
    cpl_frame* flxstd_frame = NULL;
    cpl_frame* rsp_frame = NULL;

    GiImage* mbias = NULL;
    GiImage* mdark = NULL;
    GiImage* bpixel = NULL;
    GiImage* slight = NULL;
    GiImage* sstandard = NULL;
    GiImage* rstandard = NULL;

    GiTable* fibers = NULL;
    GiTable* slitgeometry = NULL;
    GiTable* grating = NULL;
    GiTable* wcalcoeff = NULL;
    GiTable* atmext = NULL;
    GiTable* flxstd = NULL;
    GiTable* refflx = NULL;

    GiLocalization* localization = NULL;
    GiExtraction* extraction = NULL;
    GiRebinning* rebinning = NULL;
    GiResponse* response = NULL;

    GiBiasConfig* bias_config = NULL;
    GiExtractConfig* extract_config = NULL;
    GiFlatConfig* flat_config = NULL;
    GiRebinConfig* rebin_config = NULL;
    GiFxCalibrationConfig* fxcal_config = NULL;


    GiInstrumentMode mode;

    GiRecipeInfo info = {(cxchar*)_id, 1, NULL, config};

    GiGroupInfo groups[] = {
        {GIFRAME_STANDARD, CPL_FRAME_GROUP_RAW},
        {GIFRAME_BADPIXEL_MAP, CPL_FRAME_GROUP_CALIB},
        {GIFRAME_BIAS_MASTER, CPL_FRAME_GROUP_CALIB},
        {GIFRAME_DARK_MASTER, CPL_FRAME_GROUP_CALIB},
        {GIFRAME_FIBER_FLAT_EXTSPECTRA, CPL_FRAME_GROUP_CALIB},
        {GIFRAME_FIBER_FLAT_EXTERRORS, CPL_FRAME_GROUP_CALIB},
        {GIFRAME_SCATTERED_LIGHT_MODEL, CPL_FRAME_GROUP_CALIB},
        {GIFRAME_LOCALIZATION_CENTROID, CPL_FRAME_GROUP_CALIB},
        {GIFRAME_LOCALIZATION_WIDTH, CPL_FRAME_GROUP_CALIB},
        {GIFRAME_PSF_CENTROID, CPL_FRAME_GROUP_CALIB},
        {GIFRAME_PSF_WIDTH, CPL_FRAME_GROUP_CALIB},
        {GIFRAME_PSF_DATA, CPL_FRAME_GROUP_CALIB},
        {GIFRAME_WAVELENGTH_SOLUTION, CPL_FRAME_GROUP_CALIB},
        {GIFRAME_SLITSETUP, CPL_FRAME_GROUP_CALIB},
        {GIFRAME_SLITMASTER, CPL_FRAME_GROUP_CALIB},
        {GIFRAME_GRATING, CPL_FRAME_GROUP_CALIB},
        {GIFRAME_EXTINCTION, CPL_FRAME_GROUP_CALIB},
        {GIFRAME_FLUX_STANDARDS, CPL_FRAME_GROUP_CALIB},
        {NULL, CPL_FRAME_GROUP_NONE}
    };



    if (!config) {
        cpl_msg_error(_id, "Invalid parameter list! Aborting ...");
        return 1;
    }

    if (!set) {
        cpl_msg_error(_id, "Invalid frame set! Aborting ...");
        return 1;
    }

    status = giraffe_frameset_set_groups(set, groups);

    if (status != 0) {
        cpl_msg_error(_id, "Setting frame group information failed!");
        return 1;
    }


    /*
     * Verify the frame set contents
     */

    nstandard = cpl_frameset_count_tags(set, GIFRAME_STANDARD);

    if (nstandard < 1) {
        cpl_msg_error(_id, "Too few (%ld) raw frames (%s) present in "
                      "frame set! Aborting ...", nstandard, GIFRAME_STANDARD);
        return 1;
    }

    locy_frame = cpl_frameset_find(set, GIFRAME_PSF_CENTROID);

    if (locy_frame == NULL) {

        locy_frame = cpl_frameset_find(set, GIFRAME_LOCALIZATION_CENTROID);

        if (locy_frame == NULL) {
            cpl_msg_info(_id, "No master localization (centroid position) "
                         "present in frame set. Aborting ...");
            return 1;
        }

    }

    locw_frame = cpl_frameset_find(set, GIFRAME_PSF_WIDTH);

    if (locw_frame == NULL) {

        locw_frame = cpl_frameset_find(set, GIFRAME_LOCALIZATION_WIDTH);

        if (locw_frame == NULL) {
            cpl_msg_info(_id, "No master localization (spectrum width) "
                         "present in frame set. Aborting ...");
            return 1;
        }

    }

    flat_frame = cpl_frameset_find(set, GIFRAME_FIBER_FLAT_EXTSPECTRA);

    if (flat_frame == NULL) {

        cpl_msg_error(_id, "No extracted flat field spectra frame present in frame set. "
                      "Aborting ...");
        return 1;

    }

    grating_frame = cpl_frameset_find(set, GIFRAME_GRATING);

    if (!grating_frame) {
        cpl_msg_error(_id, "No grating data present in frame set. "
                      "Aborting ...");
        return 1;
    }

    slit_frame = giraffe_get_slitgeometry(set);

    if (!slit_frame) {
        cpl_msg_error(_id, "No slit geometry present in frame set. "
                      "Aborting ...");
        return 1;
    }

    wcal_frame = cpl_frameset_find(set, GIFRAME_WAVELENGTH_SOLUTION);

    if (!wcal_frame) {
        cpl_msg_error(_id, "No dispersion solution present in frame set. "
                      "Aborting ...");
        return 1;
    }

    atmext_frame = cpl_frameset_find(set, GIFRAME_EXTINCTION);

    if (!atmext_frame) {
        cpl_msg_error(_id, "No atmospheric extinction table present in "
                      "frame set. Aborting ...");
        return 1;
    }

    flxstd_frame = cpl_frameset_find(set, GIFRAME_FLUX_STANDARDS);

    if (!flxstd_frame) {
        cpl_msg_error(_id, "No flux standards present in frame set. "
                      "Aborting ...");
        return 1;
    }

    bpixel_frame = cpl_frameset_find(set, GIFRAME_BADPIXEL_MAP);

    if (!bpixel_frame) {
        cpl_msg_info(_id, "No bad pixel map present in frame set.");
    }

    mbias_frame = cpl_frameset_find(set, GIFRAME_BIAS_MASTER);

    if (!mbias_frame) {
        cpl_msg_info(_id, "No master bias present in frame set.");
    }

    mdark_frame = cpl_frameset_find(set, GIFRAME_DARK_MASTER);

    if (!mdark_frame) {
        cpl_msg_info(_id, "No master dark present in frame set.");
    }

    slight_frame = cpl_frameset_find(set, GIFRAME_SCATTERED_LIGHT_MODEL);

    if (!slight_frame) {
        cpl_msg_info(_id, "No scattered light model present in frame set.");
    }

    psfdata_frame = cpl_frameset_find(set, GIFRAME_PSF_DATA);

    if (!psfdata_frame) {
        cpl_msg_info(_id, "No PSF profile parameters present in frame set.");
    }


    /*
     * Load raw images
     */

    slist = cx_slist_new();

    standard_frame = cpl_frameset_find(set, GIFRAME_STANDARD);

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

        filename = cpl_frame_get_filename(standard_frame);

        GiImage* raw = giraffe_image_new(CPL_TYPE_DOUBLE);


        status = giraffe_image_load(raw, filename, 0);

        if (status) {
            cpl_msg_error(_id, "Cannot load raw standard frame from '%s'. "
                          "Aborting ...", filename);

            cx_slist_destroy(slist, (cx_free_func) giraffe_image_delete);

            return 1;
        }

        cx_slist_push_back(slist, raw);

        standard_frame = cpl_frameset_find(set, NULL);

    }

    nstandard = (cxint)cx_slist_size(slist);
    sstandard = cx_slist_pop_front(slist);

    properties = giraffe_image_get_properties(sstandard);
    cx_assert(properties != NULL);

    if (nstandard > 1) {

        /*
         * Create a stacked image from the list of raw images.
         * Each raw image is disposed when it is no longer needed.
         */

        cpl_msg_info(_id, "Averaging standard star observations ...");

        exptime = cpl_propertylist_get_double(properties, GIALIAS_EXPTIME);

        for (i = 1; i < nstandard; i++) {

            cpl_propertylist* _properties;

            GiImage* standard = cx_slist_pop_front(slist);


            cpl_image_add(giraffe_image_get(sstandard),
                          giraffe_image_get(standard));

            _properties = giraffe_image_get_properties(standard);
            cx_assert(_properties != NULL);

            exptime += cpl_propertylist_get_double(_properties,
                                                   GIALIAS_EXPTIME);

            giraffe_image_delete(standard);

        }

        cpl_image_divide_scalar(giraffe_image_get(sstandard), nstandard);
    }

    cx_assert(cx_slist_empty(slist));
    cx_slist_delete(slist);
    slist = NULL;


    if (nstandard > 1) {

        /*
         * Update stacked standard star image properties
         */

        cpl_msg_info(_id, "Updating stacked standard star image "
                     "properties ...");

        cpl_propertylist_set_double(properties, GIALIAS_EXPTIME,
                                    exptime / nstandard);

        cpl_propertylist_append_double(properties, GIALIAS_EXPTTOT, exptime);
        cpl_propertylist_set_comment(properties, GIALIAS_EXPTTOT,
                                     "Total exposure time of all frames "
                                     "combined");


        cpl_propertylist_erase(properties, GIALIAS_TPLEXPNO);

    }

    cpl_propertylist_append_int(properties, GIALIAS_DATANCOM, nstandard);
    cpl_propertylist_set_comment(properties, GIALIAS_DATANCOM, "Number of "
                          "combined frames");


    /*
     * Prepare for bias subtraction
     */

    bias_config = giraffe_bias_config_create(config);

    /*
     * Setup user defined areas to use for the bias computation
     */

    if (bias_config->method == GIBIAS_METHOD_MASTER ||
        bias_config->method == GIBIAS_METHOD_ZMASTER) {

        if (!mbias_frame) {
            cpl_msg_error(_id, "Missing master bias frame! Selected bias "
                          "removal method requires a master bias frame!");

            giraffe_bias_config_destroy(bias_config);
            giraffe_image_delete(sstandard);

            return 1;
        }
        else {
            filename = cpl_frame_get_filename(mbias_frame);


            mbias = giraffe_image_new(CPL_TYPE_DOUBLE);
            status = giraffe_image_load(mbias, filename, 0);

            if (status) {
                cpl_msg_error(_id, "Cannot load master bias from '%s'. "
                              "Aborting ...", filename);

                giraffe_bias_config_destroy(bias_config);
                giraffe_image_delete(sstandard);

                return 1;
            }
        }
    }


    /*
     * Load bad pixel map if it is present in the frame set.
     */

    if (bpixel_frame) {

        filename = cpl_frame_get_filename(bpixel_frame);


        bpixel = giraffe_image_new(CPL_TYPE_INT);
        status = giraffe_image_load(bpixel, filename, 0);

        if (status) {
            cpl_msg_error(_id, "Cannot load bad pixel map from '%s'. "
                          "Aborting ...", filename);

            giraffe_image_delete(bpixel);
            bpixel = NULL;

            if (mbias != NULL) {
                giraffe_image_delete(mbias);
                mbias = NULL;
            }

            giraffe_bias_config_destroy(bias_config);
            bias_config = NULL;

            giraffe_image_delete(sstandard);
            sstandard = NULL;

            return 1;
        }

    }


    /*
     * Compute and remove the bias from the stacked flat field frame.
     */

    rstandard = giraffe_image_new(CPL_TYPE_DOUBLE);

    status = giraffe_bias_remove(rstandard, sstandard, mbias, bpixel,
                                 biasareas, bias_config);

    giraffe_image_delete(sstandard);

    if (mbias) {
        giraffe_image_delete(mbias);
        mbias = NULL;
    }

    giraffe_bias_config_destroy(bias_config);

    if (status) {
        cpl_msg_error(_id, "Bias removal failed. Aborting ...");

        giraffe_image_delete(rstandard);
        rstandard = NULL;

        if (bpixel != NULL) {
            giraffe_image_delete(bpixel);
            bpixel = NULL;
        }

        return 1;
    }


    /*
     * Load master dark if it is present in the frame set and correct
     * the master flat field for the dark current.
     */

    if (mdark_frame) {

        GiDarkConfig dark_config = {GIDARK_METHOD_ZMASTER, 0.};


        cpl_msg_info(_id, "Correcting for dark current ...");

        filename = cpl_frame_get_filename(mdark_frame);

        mdark = giraffe_image_new(CPL_TYPE_DOUBLE);
        status = giraffe_image_load(mdark, filename, 0);

        if (status != 0) {
            cpl_msg_error(_id, "Cannot load master dark from '%s'. "
                          "Aborting ...", filename);

            giraffe_image_delete(rstandard);
            rstandard = NULL;

            if (bpixel != NULL) {
                giraffe_image_delete(bpixel);
                bpixel = NULL;
            }

            return 1;
        }

        status = giraffe_subtract_dark(rstandard, mdark, bpixel, NULL,
                                       &dark_config);

        if (status != 0) {
            cpl_msg_error(_id, "Dark subtraction failed! Aborting ...");

            giraffe_image_delete(mdark);
            mdark = NULL;

            giraffe_image_delete(rstandard);
            rstandard = NULL;

            if (bpixel != NULL) {
                giraffe_image_delete(bpixel);
                bpixel = NULL;
            }

            return 1;
        }

        giraffe_image_delete(mdark);
        mdark = NULL;

    }


    /*
     * Update the reduced standard star observation properties, save the
     * reduced standard star frame and register it as product.
     */

    cpl_msg_info(_id, "Writing pre-processed standard star image ...");

    giraffe_image_add_info(rstandard, &info, set);

    rstandard_frame = giraffe_frame_create_image(rstandard,
                                             GIFRAME_STANDARD_REDUCED,
                                             CPL_FRAME_LEVEL_INTERMEDIATE,
                                             TRUE, TRUE);

    if (rstandard_frame == NULL) {
        cpl_msg_error(_id, "Cannot create local file! Aborting ...");

        giraffe_image_delete(rstandard);

        return 1;
    }

    cpl_frameset_insert(set, rstandard_frame);


    /*
     * Determine fiber setup
     */

    standard_frame = cpl_frameset_find(set, GIFRAME_STANDARD);

    cpl_msg_info(_id, "Building fiber setup for frame '%s'.",
                 cpl_frame_get_filename(standard_frame));

    fibers = giraffe_fibers_setup(standard_frame, locy_frame);

    if (!fibers) {
        cpl_msg_error(_id, "Cannot create fiber setup for frame '%s'! "
                      "Aborting ...", cpl_frame_get_filename(standard_frame));

        if (bpixel) {
            giraffe_image_delete(bpixel);
            bpixel = NULL;
        }

        giraffe_image_delete(rstandard);

        return 1;
    }

    cpl_msg_info(_id, "Fiber reference setup taken from localization "
                 "frame '%s'.", cpl_frame_get_filename(locy_frame));


    /*
     * Load fiber localization
     */

    localization = giraffe_localization_new();

    filename = cpl_frame_get_filename(locy_frame);
    status = 0;

    localization->locy  = giraffe_image_new(CPL_TYPE_DOUBLE);
    status = giraffe_image_load(localization->locy, filename, 0);

    if (status) {
        cpl_msg_error(_id, "Cannot load localization (centroid "
                      "position) frame from '%s'. Aborting ...",
                      filename);

        giraffe_localization_destroy(localization);

        if (bpixel) {
            giraffe_image_delete(bpixel);
            bpixel = NULL;
        }

        giraffe_table_delete(fibers);
        giraffe_image_delete(rstandard);

        return 1;
    }


    filename = cpl_frame_get_filename(locw_frame);
    status = 0;

    localization->locw  = giraffe_image_new(CPL_TYPE_DOUBLE);
    status = giraffe_image_load(localization->locw, filename, 0);

    if (status) {
        cpl_msg_error(_id, "Cannot load localization (spectrum width) "
                      "frame from '%s'. Aborting ...", filename);

        giraffe_localization_destroy(localization);

        if (bpixel) {
            giraffe_image_delete(bpixel);
            bpixel = NULL;
        }

        giraffe_table_delete(fibers);
        giraffe_image_delete(rstandard);

        return 1;
    }


    /*
     * Spectrum extraction
     */

    if (slight_frame) {

        filename = cpl_frame_get_filename(slight_frame);


        slight = giraffe_image_new(CPL_TYPE_DOUBLE);
        status = giraffe_image_load(slight, filename, 0);

        if (status) {
            cpl_msg_error(_id, "Cannot load scattered light model from '%s'. "
                          "Aborting ...", filename);

            giraffe_image_delete(slight);

            giraffe_localization_destroy(localization);

            if (bpixel) {
                giraffe_image_delete(bpixel);
                bpixel = NULL;
            }

            giraffe_table_delete(fibers);
            giraffe_image_delete(rstandard);

            return 1;

        }

    }


    extract_config = giraffe_extract_config_create(config);

    if ((extract_config->emethod == GIEXTRACT_OPTIMAL) ||
        (extract_config->emethod == GIEXTRACT_HORNE)) {

        if (psfdata_frame == NULL) {

            const cxchar* emethod = "Optimal";

            if (extract_config->emethod == GIEXTRACT_HORNE) {
                emethod = "Horne";
            }

            cpl_msg_error(_id, "%s spectrum extraction requires PSF "
                          "profile data. Aborting ...", emethod);

            giraffe_extract_config_destroy(extract_config);
            extract_config = NULL;

            if (slight != NULL) {
                giraffe_image_delete(slight);
                slight = NULL;
            }

            giraffe_localization_destroy(localization);
            localization = NULL;

            if (bpixel) {
                giraffe_image_delete(bpixel);
                bpixel = NULL;
            }

            giraffe_table_delete(fibers);
            fibers = NULL;

            giraffe_image_delete(rstandard);
            rstandard = NULL;

            return 1;

        }
        else {

            filename = cpl_frame_get_filename(psfdata_frame);
            status = 0;

            localization->psf  = giraffe_psfdata_new();
            status = giraffe_psfdata_load(localization->psf, filename);

            if (status) {
                cpl_msg_error(_id, "Cannot load PSF profile data frame from "
                              "'%s'. Aborting ...", filename);

                giraffe_extract_config_destroy(extract_config);
                extract_config = NULL;

                if (slight != NULL) {
                    giraffe_image_delete(slight);
                    slight = NULL;
                }

                giraffe_localization_destroy(localization);
                localization = NULL;

                if (bpixel) {
                    giraffe_image_delete(bpixel);
                    bpixel = NULL;
                }

                giraffe_table_delete(fibers);
                fibers = NULL;

                giraffe_image_delete(rstandard);
                rstandard = NULL;

                return 1;

            }

        }

    }


    extraction = giraffe_extraction_new();

    status = giraffe_extract_spectra(extraction, rstandard, fibers,
                                     localization, bpixel, slight,
                                     extract_config);

    if (status) {
        cpl_msg_error(_id, "Spectrum extraction failed! Aborting ...");

        giraffe_extraction_destroy(extraction);
        giraffe_extract_config_destroy(extract_config);

        giraffe_image_delete(slight);

        giraffe_localization_destroy(localization);

        if (bpixel) {
            giraffe_image_delete(bpixel);
            bpixel = NULL;
        }

        giraffe_table_delete(fibers);
        giraffe_image_delete(rstandard);

        return 1;
    }

    giraffe_image_delete(slight);
    slight = NULL;

    if (bpixel) {
        giraffe_image_delete(bpixel);
        bpixel = NULL;
    }

    giraffe_image_delete(rstandard);
    rstandard = NULL;

    giraffe_extract_config_destroy(extract_config);


    /*
     * Get the relative fiber transmission data from the extracted flat
     * field and add it to the fiber table.
     */

    filename = cpl_frame_get_filename(flat_frame);

    cpl_msg_info(_id, "Loading relative fiber transmission data from '%s'.",
                 filename);

    status = giraffe_transmission_attach(fibers, filename);

    if (status != 0) {

        giraffe_extraction_destroy(extraction);
        giraffe_localization_destroy(localization);

        giraffe_table_delete(grating);
        giraffe_table_delete(fibers);

        cpl_msg_error(_id, "Cannot load relative fiber transmission data "
                      "from '%s'. Aborting ...", filename);

        return 1;

    }


    /*
     * Apply flat field and/or the relative fiber transmission correction.
     */

    flat_config = giraffe_flat_config_create(config);

    if (flat_config->apply == TRUE) {

        const cpl_frame* flat_errors_frame = NULL;

        GiImage* flat = NULL;
        GiImage* errors = NULL;


        filename = cpl_frame_get_filename(flat_frame);

        flat = giraffe_image_new(CPL_TYPE_DOUBLE);
        status = giraffe_image_load(flat, filename, 0);

        if (status) {
            cpl_msg_error(_id, "Cannot load flat field spectra from '%s'. "
                          "Aborting ...", filename);

            giraffe_image_delete(flat);

            giraffe_flat_config_destroy(flat_config);

            giraffe_extraction_destroy(extraction);
            giraffe_localization_destroy(localization);

            giraffe_table_delete(grating);
            giraffe_table_delete(fibers);

            return 1;
        }


        flat_errors_frame =
            cpl_frameset_find(set, GIFRAME_FIBER_FLAT_EXTERRORS);

        if (flat_errors_frame == NULL) {
            cpl_msg_warning(_id, "Missing flat field spectra errors "
                            "frame!");
        }
        else {

            filename = cpl_frame_get_filename(flat_errors_frame);

            errors = giraffe_image_new(CPL_TYPE_DOUBLE);
            status = giraffe_image_load(errors, filename, 0);

            if (status) {
                cpl_msg_error(_id, "Cannot load flat field spectra "
                              "errors from '%s'. Aborting ...",
                              filename);

                giraffe_image_delete(errors);
                giraffe_image_delete(flat);

                giraffe_flat_config_destroy(flat_config);

                giraffe_extraction_destroy(extraction);
                giraffe_localization_destroy(localization);

                giraffe_table_delete(grating);
                giraffe_table_delete(fibers);

                return 1;
            }

        }

        cpl_msg_info(_id, "Applying flat field correction ...");

        status = giraffe_flat_apply(extraction, fibers, flat, errors,
                                    flat_config);

        if (status) {
            cpl_msg_error(_id, "Flat field correction failed! "
                          "Aborting ...");

            giraffe_image_delete(errors);
            giraffe_image_delete(flat);

            giraffe_flat_config_destroy(flat_config);

            giraffe_extraction_destroy(extraction);
            giraffe_localization_destroy(localization);

            giraffe_table_delete(grating);
            giraffe_table_delete(fibers);

            return 1;
        }

        giraffe_image_delete(errors);
        errors = NULL;

        giraffe_image_delete(flat);
        flat = NULL;

    }

    if (flat_config->transmission == TRUE) {

        cpl_msg_info(_id, "Applying relative fiber transmission "
                     "correction");

        status = giraffe_transmission_apply(extraction, fibers);

        if (status) {

            cpl_msg_error(_id, "Relative transmission correction failed! "
                          "Aborting ...");

            giraffe_flat_config_destroy(flat_config);
            flat_config = NULL;

            giraffe_extraction_destroy(extraction);
            giraffe_localization_destroy(localization);

            giraffe_table_delete(grating);
            giraffe_table_delete(fibers);

            return 1;

        }

    }

    giraffe_flat_config_destroy(flat_config);
    flat_config = NULL;


    /*
     * Save the spectrum extraction results and register them as
     * products.
     */

    cpl_msg_info(_id, "Writing extracted spectra ...");

    /* Extracted spectra */

    giraffe_image_add_info(extraction->spectra, &info, set);

    sext_frame = giraffe_frame_create_image(extraction->spectra,
                                            GIFRAME_STANDARD_EXTSPECTRA,
                                            CPL_FRAME_LEVEL_FINAL,
                                            TRUE, TRUE);

    if (sext_frame == NULL) {
        cpl_msg_error(_id, "Cannot create local file! Aborting ...");

        giraffe_extraction_destroy(extraction);
        giraffe_localization_destroy(localization);

        giraffe_table_delete(grating);
        giraffe_table_delete(fibers);

        return 1;
    }

    status = giraffe_fiberlist_attach(sext_frame, fibers);

    if (status) {
        cpl_msg_error(_id, "Cannot attach fiber setup to local file '%s'! "
                      "Aborting ...", cpl_frame_get_filename(sext_frame));

        cpl_frame_delete(sext_frame);

        giraffe_extraction_destroy(extraction);
        giraffe_localization_destroy(localization);

        giraffe_table_delete(grating);
        giraffe_table_delete(fibers);

        return 1;
    }

    cpl_frameset_insert(set, sext_frame);

    /* Extracted spectra errors */

    giraffe_image_add_info(extraction->error, &info, set);

    sext_frame = giraffe_frame_create_image(extraction->error,
                                            GIFRAME_STANDARD_EXTERRORS,
                                            CPL_FRAME_LEVEL_FINAL,
                                            TRUE, TRUE);

    if (sext_frame == NULL) {
        cpl_msg_error(_id, "Cannot create local file! Aborting ...");

        giraffe_extraction_destroy(extraction);
        giraffe_localization_destroy(localization);

        giraffe_table_delete(grating);
        giraffe_table_delete(fibers);

        return 1;
    }

    status = giraffe_fiberlist_attach(sext_frame, fibers);

    if (status) {
        cpl_msg_error(_id, "Cannot attach fiber setup to local file '%s'! "
                      "Aborting ...", cpl_frame_get_filename(sext_frame));

        cpl_frame_delete(sext_frame);

        giraffe_extraction_destroy(extraction);
        giraffe_localization_destroy(localization);

        giraffe_table_delete(grating);
        giraffe_table_delete(fibers);

        return 1;
    }

    cpl_frameset_insert(set, sext_frame);

    /* Extracted spectra pixels */

    if (extraction->npixels != NULL) {

        giraffe_image_add_info(extraction->npixels, &info, set);

        sext_frame = giraffe_frame_create_image(extraction->npixels,
                                                GIFRAME_STANDARD_EXTPIXELS,
                                                CPL_FRAME_LEVEL_FINAL,
                                                TRUE, TRUE);

        if (sext_frame == NULL) {
            cpl_msg_error(_id, "Cannot create local file! Aborting ...");

            giraffe_extraction_destroy(extraction);
            giraffe_localization_destroy(localization);

            giraffe_table_delete(grating);
            giraffe_table_delete(fibers);

            return 1;
        }

        status = giraffe_fiberlist_attach(sext_frame, fibers);

        if (status) {
            cpl_msg_error(_id, "Cannot attach fiber setup to local file "
                          "'%s'! Aborting ...",
                          cpl_frame_get_filename(sext_frame));

            cpl_frame_delete(sext_frame);

            giraffe_extraction_destroy(extraction);
            giraffe_localization_destroy(localization);

            giraffe_table_delete(grating);
            giraffe_table_delete(fibers);

            return 1;
        }

        cpl_frameset_insert(set, sext_frame);

    }

    /* Extracted spectra centroids */

    giraffe_image_add_info(extraction->centroid, &info, set);

    sext_frame = giraffe_frame_create_image(extraction->centroid,
                                            GIFRAME_STANDARD_EXTTRACE,
                                            CPL_FRAME_LEVEL_FINAL,
                                            TRUE, TRUE);

    if (sext_frame == NULL) {
        cpl_msg_error(_id, "Cannot create local file! Aborting ...");

        giraffe_extraction_destroy(extraction);
        giraffe_localization_destroy(localization);

        giraffe_table_delete(grating);
        giraffe_table_delete(fibers);

        return 1;
    }

    status = giraffe_fiberlist_attach(sext_frame, fibers);

    if (status) {
        cpl_msg_error(_id, "Cannot attach fiber setup to local file '%s'! "
                      "Aborting ...", cpl_frame_get_filename(sext_frame));

        cpl_frame_delete(sext_frame);

        giraffe_extraction_destroy(extraction);
        giraffe_localization_destroy(localization);

        giraffe_table_delete(grating);
        giraffe_table_delete(fibers);

        return 1;
    }

    cpl_frameset_insert(set, sext_frame);

    /* Extraction model spectra */

    if (extraction->model != NULL) {

        giraffe_image_add_info(extraction->model, &info, set);

        sext_frame = giraffe_frame_create_image(extraction->model,
                                                GIFRAME_STANDARD_EXTMODEL,
                                                CPL_FRAME_LEVEL_FINAL,
                                                TRUE, TRUE);

        if (sext_frame == NULL) {
            cpl_msg_error(_id, "Cannot create local file! Aborting ...");

            giraffe_extraction_destroy(extraction);
            giraffe_localization_destroy(localization);

            giraffe_table_delete(grating);
            giraffe_table_delete(fibers);

            return 1;
        }

        status = giraffe_fiberlist_attach(sext_frame, fibers);

        if (status != 0) {
            cpl_msg_error(_id, "Cannot attach fiber setup to local file '%s'! "
                          "Aborting ...", cpl_frame_get_filename(sext_frame));

            cpl_frame_delete(sext_frame);

            giraffe_extraction_destroy(extraction);
            giraffe_localization_destroy(localization);

            giraffe_table_delete(grating);
            giraffe_table_delete(fibers);

            return 1;
        }

        cpl_frameset_insert(set, sext_frame);

    }


    /*
     * Load dispersion solution
     */


    filename = (cxchar *)cpl_frame_get_filename(wcal_frame);

    wcalcoeff = giraffe_table_new();
    status = giraffe_table_load(wcalcoeff, filename, 1, NULL);

    if (status) {
        cpl_msg_error(_id, "Cannot load dispersion solution from "
                      "'%s'. Aborting ...", filename);

        giraffe_extraction_destroy(extraction);
        giraffe_localization_destroy(localization);

        giraffe_table_delete(wcalcoeff);

        giraffe_table_delete(grating);
        giraffe_table_delete(fibers);

        return 1;
    }


    /*
     * Load grating data
     */

    filename = (cxchar *)cpl_frame_get_filename(grating_frame);

    status = 0;

    grating = giraffe_table_new();
    status = giraffe_table_load(grating, filename, 1, NULL);

    if (status) {
        cpl_msg_error(_id, "Cannot load grating data from '%s'. "
                      "Aborting ...", filename);

        giraffe_extraction_destroy(extraction);
        giraffe_localization_destroy(localization);

        giraffe_table_delete(wcalcoeff);

        giraffe_table_delete(grating);
        giraffe_table_delete(fibers);

        return 1;
    }


    /*
     * Load slit geometry data
     */


    filename = (cxchar *)cpl_frame_get_filename(slit_frame);

    slitgeometry = giraffe_slitgeometry_load(fibers, filename, 1, NULL);

    if (slitgeometry == NULL) {
        cpl_msg_error(_id, "Cannot load slit geometry data from '%s'. "
                      "Aborting ...", filename);

        giraffe_table_delete(wcalcoeff);

        giraffe_extraction_destroy(extraction);
        giraffe_localization_destroy(localization);

        giraffe_table_delete(wcalcoeff);

        giraffe_table_delete(grating);
        giraffe_table_delete(fibers);

        return 1;
    }
    else {

        /*
         * Check whether the contains the positions for all fibers
         * provided by the fiber setup. If this is not the case
         * this is an error.
         */

        if (giraffe_fiberlist_compare(slitgeometry, fibers) != 1) {
            cpl_msg_error(_id, "Slit geometry data from '%s' is not "
                    "applicable for current fiber setup! "
                            "Aborting ...", filename);

            giraffe_table_delete(slitgeometry);
            giraffe_table_delete(wcalcoeff);

            giraffe_extraction_destroy(extraction);
            giraffe_localization_destroy(localization);

            giraffe_table_delete(wcalcoeff);

            giraffe_table_delete(grating);
            giraffe_table_delete(fibers);

            return 1;
        }

    }


    /*
     * Spectrum rebinning
     */

    cpl_msg_info(_id, "Spectrum rebinning");

    rebin_config = giraffe_rebin_config_create(config);

    rebinning = giraffe_rebinning_new();

    status = 0;

    status = giraffe_rebin_spectra(rebinning, extraction, fibers,
                                   localization, grating, slitgeometry,
                                   wcalcoeff, rebin_config);

    if (status) {
        cpl_msg_error(_id, "Rebinning of standard spectra failed! "
                      "Aborting...");

        giraffe_rebinning_destroy(rebinning);

        giraffe_extraction_destroy(extraction);
        giraffe_localization_destroy(localization);

        giraffe_table_delete(wcalcoeff);

        giraffe_table_delete(slitgeometry);
        giraffe_table_delete(grating);
        giraffe_table_delete(fibers);

        giraffe_rebin_config_destroy(rebin_config);

        return 1;

    }

    giraffe_extraction_destroy(extraction);
    extraction = NULL;

    giraffe_localization_destroy(localization);
    localization = NULL;

    giraffe_rebin_config_destroy(rebin_config);
    rebin_config = NULL;


    /*
     * Save and register the results of the spectrum rebinning.
     */

    /* Rebinned spectra */

    giraffe_image_add_info(rebinning->spectra, &info, set);

    rbin_frame = giraffe_frame_create_image(rebinning->spectra,
                                            GIFRAME_STANDARD_RBNSPECTRA,
                                            CPL_FRAME_LEVEL_FINAL,
                                            TRUE, TRUE);

    if (rbin_frame == NULL) {
        cpl_msg_error(_id, "Cannot create local file! Aborting ...");

        giraffe_rebinning_destroy(rebinning);

        giraffe_table_delete(wcalcoeff);

        giraffe_table_delete(slitgeometry);
        giraffe_table_delete(grating);
        giraffe_table_delete(fibers);

        return 1;
    }

    status = giraffe_fiberlist_attach(rbin_frame, fibers);

    if (status) {
        cpl_msg_error(_id, "Cannot attach fiber setup to local "
                      "file '%s'! Aborting ...",
                      cpl_frame_get_filename(rbin_frame));

        giraffe_rebinning_destroy(rebinning);
        giraffe_table_delete(wcalcoeff);

        giraffe_table_delete(slitgeometry);
        giraffe_table_delete(grating);
        giraffe_table_delete(fibers);

        cpl_frame_delete(rbin_frame);

        return 1;
    }

    cpl_frameset_insert(set, rbin_frame);

    /* Rebinned spectra errors */

    giraffe_image_add_info(rebinning->errors, &info, set);

    rbin_frame = giraffe_frame_create_image(rebinning->errors,
                                            GIFRAME_STANDARD_RBNERRORS,
                                            CPL_FRAME_LEVEL_FINAL,
                                            TRUE, TRUE);

    if (rbin_frame == NULL) {
        cpl_msg_error(_id, "Cannot create local file! Aborting ...");

        giraffe_rebinning_destroy(rebinning);

        giraffe_table_delete(wcalcoeff);

        giraffe_table_delete(slitgeometry);
        giraffe_table_delete(grating);
        giraffe_table_delete(fibers);

        return 1;
    }

    status = giraffe_fiberlist_attach(rbin_frame, fibers);

    if (status) {
        cpl_msg_error(_id, "Cannot attach fiber setup to local "
                      "file '%s'! Aborting ...",
                      cpl_frame_get_filename(rbin_frame));

        giraffe_rebinning_destroy(rebinning);

        giraffe_table_delete(wcalcoeff);

        giraffe_table_delete(slitgeometry);
        giraffe_table_delete(grating);
        giraffe_table_delete(fibers);

        cpl_frame_delete(rbin_frame);

        return 1;
    }

    cpl_frameset_insert(set, rbin_frame);


    /*
     * Optional image and data cube construction (only for IFU and Argus)
     */

    properties = giraffe_image_get_properties(rebinning->spectra);
    mode = giraffe_get_mode(properties);


    if (mode == GIMODE_IFU || mode == GIMODE_ARGUS) {

        cpl_frame* rimg_frame = NULL;

        GiFieldOfView* fov = NULL;

        GiFieldOfViewConfig* fov_config = NULL;

        GiFieldOfViewCubeFormat cube_format = GIFOV_FORMAT_ESO3D;


        fov_config = giraffe_fov_config_create(config);

        cube_format = fov_config->format;


        cpl_msg_info(_id, "Reconstructing image and data cube from rebinned "
                     "spectra ...");

        fov = giraffe_fov_new();

        status = giraffe_fov_build(fov, rebinning, fibers, wcalcoeff, grating,
                                   slitgeometry, fov_config);

        if (status) {

            if (status == -2) {
                cpl_msg_warning(_id, "No reconstructed image was built. "
                                "Fiber list has no fiber position "
                                "information.");
            }
            else {
                cpl_msg_error(_id, "Image reconstruction failed! Aborting...");

                giraffe_fov_delete(fov);
                giraffe_rebinning_destroy(rebinning);

                giraffe_table_delete(wcalcoeff);

                giraffe_table_delete(slitgeometry);
                giraffe_table_delete(grating);
                giraffe_table_delete(fibers);

                giraffe_fov_config_destroy(fov_config);

                return 1;
            }

        }

        giraffe_fov_config_destroy(fov_config);


        /*
         * Save and register the results of the image reconstruction.
         */

        /* Reconstructed image */

        giraffe_image_add_info(fov->fov.spectra, &info, set);

        rimg_frame = giraffe_frame_create_image(fov->fov.spectra,
                                                GIFRAME_STANDARD_RCSPECTRA,
                                                CPL_FRAME_LEVEL_FINAL,
                                                TRUE, TRUE);

        if (rimg_frame == NULL) {
            cpl_msg_error(_id, "Cannot create local file! Aborting ...");

            giraffe_fov_delete(fov);
            giraffe_rebinning_destroy(rebinning);

            giraffe_table_delete(wcalcoeff);

            giraffe_table_delete(slitgeometry);
            giraffe_table_delete(grating);
            giraffe_table_delete(fibers);

            return 1;
        }

        cpl_frameset_insert(set, rimg_frame);


        /* Reconstructed image errors */

        giraffe_image_add_info(fov->fov.errors, &info, set);

        rimg_frame = giraffe_frame_create_image(fov->fov.errors,
                                                GIFRAME_STANDARD_RCERRORS,
                                                CPL_FRAME_LEVEL_FINAL,
                                                TRUE, TRUE);

        if (rimg_frame == NULL) {
            cpl_msg_error(_id, "Cannot create local file! Aborting ...");

            giraffe_fov_delete(fov);
            giraffe_rebinning_destroy(rebinning);

            giraffe_table_delete(wcalcoeff);

            giraffe_table_delete(slitgeometry);
            giraffe_table_delete(grating);
            giraffe_table_delete(fibers);

            return 1;
        }

        cpl_frameset_insert(set, rimg_frame);


        /* Save data cubes according to format selection */

        if (cube_format == GIFOV_FORMAT_SINGLE) {

            /* Spectrum cube */

            if (fov->cubes.spectra != NULL) {

                cxint component = 0;

                GiFrameCreator creator = (GiFrameCreator) giraffe_fov_save_cubes;


                properties = giraffe_image_get_properties(rebinning->spectra);
                properties = cpl_propertylist_duplicate(properties);

                giraffe_add_frameset_info(properties, set, info.sequence);

                rimg_frame = giraffe_frame_create(GIFRAME_STANDARD_CUBE_SPECTRA,
                                                  CPL_FRAME_LEVEL_FINAL,
                                                  properties,
                                                  fov,
                                                  &component,
                                                  creator);

                cpl_propertylist_delete(properties);
                properties = NULL;

                if (rimg_frame == NULL) {
                    cpl_msg_error(_id, "Cannot create local file! Aborting ...");

                    giraffe_fov_delete(fov);
                    fov = NULL;

                    giraffe_rebinning_destroy(rebinning);
                    rebinning = NULL;

                    giraffe_table_delete(wcalcoeff);
                    wcalcoeff = NULL;

                    giraffe_table_delete(slitgeometry);
                    slitgeometry = NULL;

                    giraffe_table_delete(grating);
                    grating = NULL;

                    giraffe_table_delete(fibers);
                    fibers = NULL;

                    return 1;
                }

                status = giraffe_fiberlist_attach(rimg_frame, fibers);

                if (status != 0) {
                    cpl_msg_error(_id, "Cannot attach fiber setup to local "
                                  "file '%s'! Aborting ...",
                                  cpl_frame_get_filename(rimg_frame));

                    cpl_frame_delete(rimg_frame);

                    giraffe_fov_delete(fov);
                    fov = NULL;

                    giraffe_rebinning_destroy(rebinning);
                    rebinning = NULL;

                    giraffe_table_delete(wcalcoeff);
                    wcalcoeff = NULL;

                    giraffe_table_delete(slitgeometry);
                    slitgeometry = NULL;

                    giraffe_table_delete(grating);
                    grating = NULL;

                    giraffe_table_delete(fibers);
                    fibers = NULL;

                    return 1;
                }

                cpl_frameset_insert(set, rimg_frame);

            }

            /* Error cube */

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

                cxint component = 1;

                GiFrameCreator creator = (GiFrameCreator) giraffe_fov_save_cubes;


                properties = giraffe_image_get_properties(rebinning->errors);
                properties = cpl_propertylist_duplicate(properties);

                giraffe_add_frameset_info(properties, set, info.sequence);

                rimg_frame = giraffe_frame_create(GIFRAME_STANDARD_CUBE_ERRORS,
                                                  CPL_FRAME_LEVEL_FINAL,
                                                  properties,
                                                  fov,
                                                  &component,
                                                  creator);

                cpl_propertylist_delete(properties);
                properties = NULL;

                if (rimg_frame == NULL) {
                    cpl_msg_error(_id, "Cannot create local file! Aborting ...");

                    giraffe_fov_delete(fov);
                    fov = NULL;

                    giraffe_rebinning_destroy(rebinning);
                    rebinning = NULL;

                    giraffe_table_delete(wcalcoeff);
                    wcalcoeff = NULL;

                    giraffe_table_delete(slitgeometry);
                    slitgeometry = NULL;

                    giraffe_table_delete(grating);
                    grating = NULL;

                    giraffe_table_delete(fibers);
                    fibers = NULL;

                    return 1;
                }

                status = giraffe_fiberlist_attach(rimg_frame, fibers);

                if (status != 0) {
                    cpl_msg_error(_id, "Cannot attach fiber setup to local "
                                  "file '%s'! Aborting ...",
                                  cpl_frame_get_filename(rimg_frame));

                    cpl_frame_delete(rimg_frame);

                    giraffe_fov_delete(fov);
                    fov = NULL;

                    giraffe_rebinning_destroy(rebinning);
                    rebinning = NULL;

                    giraffe_table_delete(wcalcoeff);
                    wcalcoeff = NULL;

                    giraffe_table_delete(slitgeometry);
                    slitgeometry = NULL;

                    giraffe_table_delete(grating);
                    grating = NULL;

                    giraffe_table_delete(fibers);
                    fibers = NULL;

                    return 1;
                }

                cpl_frameset_insert(set, rimg_frame);
            }

        }
        else {

            /* Data Cube (ESO 3D format) */

            GiFrameCreator creator = (GiFrameCreator) giraffe_fov_save_cubes_eso3d;

            properties = giraffe_image_get_properties(rebinning->spectra);
            properties = cpl_propertylist_duplicate(properties);

            giraffe_add_frameset_info(properties, set, info.sequence);

            rimg_frame = giraffe_frame_create(GIFRAME_STANDARD_CUBE,
                                              CPL_FRAME_LEVEL_FINAL,
                                              properties,
                                              fov,
                                              NULL,
                                              creator);

            cpl_propertylist_delete(properties);
            properties = NULL;

            if (rimg_frame == NULL) {
                cpl_msg_error(_id, "Cannot create local file! Aborting ...");

                giraffe_fov_delete(fov);
                fov = NULL;

                giraffe_rebinning_destroy(rebinning);
                rebinning = NULL;

                giraffe_table_delete(wcalcoeff);
                wcalcoeff = NULL;

                giraffe_table_delete(slitgeometry);
                slitgeometry = NULL;

                giraffe_table_delete(grating);
                grating = NULL;

                giraffe_table_delete(fibers);
                fibers = NULL;

                return 1;
            }

            status = giraffe_fiberlist_attach(rimg_frame, fibers);

            if (status != 0) {
                cpl_msg_error(_id, "Cannot attach fiber setup to local "
                              "file '%s'! Aborting ...",
                              cpl_frame_get_filename(rimg_frame));

                cpl_frame_delete(rimg_frame);

                giraffe_fov_delete(fov);
                fov = NULL;

                giraffe_rebinning_destroy(rebinning);
                rebinning = NULL;

                giraffe_table_delete(wcalcoeff);
                wcalcoeff = NULL;

                giraffe_table_delete(slitgeometry);
                slitgeometry = NULL;

                giraffe_table_delete(grating);
                grating = NULL;

                giraffe_table_delete(fibers);
                fibers = NULL;

                return 1;
            }

            cpl_frameset_insert(set, rimg_frame);

        }

        giraffe_fov_delete(fov);
        fov = NULL;

    }


    /*
     * Response computation
     */

    cpl_msg_info(_id, "Computing instrument response function...");

    filename = cpl_frame_get_filename(flxstd_frame);

    flxstd = giraffe_table_new();
    status = giraffe_table_load(flxstd, filename, 1, NULL);

    cpl_msg_info(_id, "Flux table loaded ...");

    if (status != 0) {
        cpl_msg_error(_id, "Cannot load flux standards catalog from "
                      "'%s'. Aborting ...", filename);

        giraffe_rebinning_destroy(rebinning);
        rebinning = NULL;

        giraffe_table_delete(wcalcoeff);
        wcalcoeff = NULL;

        giraffe_table_delete(slitgeometry);
        slitgeometry = NULL;

        giraffe_table_delete(grating);
        grating = NULL;

        giraffe_table_delete(fibers);
        fibers = NULL;

        return 1;
    }


    fxcal_config = giraffe_fxcalibration_config_create(config);

   /*
    * Search the flux standard catalog for the observed flux standard
    * object.
    */

    refflx = giraffe_select_flux_standard(flxstd, rebinning->spectra, fxcal_config->max_dist);


    if (refflx == NULL) {
        cpl_msg_error(_id, "No matching flux standard found in the "
                "catalog '%s'! Aborting ...", filename);

        giraffe_table_delete(flxstd);
        flxstd = NULL;

        giraffe_rebinning_destroy(rebinning);
        rebinning = NULL;

        giraffe_table_delete(wcalcoeff);
        wcalcoeff = NULL;

        giraffe_table_delete(slitgeometry);
        slitgeometry = NULL;

        giraffe_table_delete(grating);
        grating = NULL;

        giraffe_table_delete(fibers);
        fibers = NULL;

        return 1;
    }

    giraffe_table_delete(flxstd);
    flxstd = NULL;


    filename = cpl_frame_get_filename(atmext_frame);

    atmext = giraffe_table_new();
    status = giraffe_table_load(atmext, filename, 1, NULL);

    if (status != 0) {
        cpl_msg_error(_id, "Cannot load atmospheric extinction data from "
                      "'%s'. Aborting ...", filename);

        giraffe_table_delete(refflx);
        refflx = NULL;

        giraffe_rebinning_destroy(rebinning);
        rebinning = NULL;

        giraffe_table_delete(wcalcoeff);
        wcalcoeff = NULL;

        giraffe_table_delete(slitgeometry);
        slitgeometry = NULL;

        giraffe_table_delete(grating);
        grating = NULL;

        giraffe_table_delete(fibers);
        fibers = NULL;

        return 1;
    }

    response = giraffe_response_new();

    status = giraffe_calibrate_flux(response, rebinning, fibers, NULL,
                                    refflx, atmext, fxcal_config);

    if (status != 0) {

        cpl_msg_error(_id, "Instrument response computation failed!");

        giraffe_response_delete(response);
        response = NULL;

        giraffe_fxcalibration_config_destroy(fxcal_config);
        fxcal_config = NULL;

        giraffe_table_delete(atmext);
        atmext = NULL;

        giraffe_table_delete(refflx);
        refflx = NULL;

        giraffe_rebinning_destroy(rebinning);
        rebinning = NULL;

        giraffe_table_delete(wcalcoeff);
        wcalcoeff = NULL;

        giraffe_table_delete(slitgeometry);
        slitgeometry = NULL;

        giraffe_table_delete(grating);
        grating = NULL;

        giraffe_table_delete(fibers);
        fibers = NULL;

        return 1;

    }

    giraffe_fxcalibration_config_destroy(fxcal_config);
    fxcal_config = NULL;

    giraffe_table_delete(refflx);
    refflx = NULL;

    giraffe_table_delete(atmext);
    atmext = NULL;


    /*
     * Save and register the instrument response product
     */

    giraffe_image_add_info(response->response, &info, set);

    rsp_frame = giraffe_frame_create_image(response->response,
                                           GIFRAME_INSTRUMENT_RESPONSE,
                                           CPL_FRAME_LEVEL_FINAL,
                                           TRUE, TRUE);

    if (rsp_frame == NULL) {

        cpl_msg_error(_id, "Cannot create local file! Aborting ...");

        giraffe_response_delete(response);
        response = NULL;

        giraffe_rebinning_destroy(rebinning);
        rebinning = NULL;

        giraffe_table_delete(wcalcoeff);
        wcalcoeff = NULL;

        giraffe_table_delete(slitgeometry);
        slitgeometry = NULL;

        giraffe_table_delete(grating);
        grating = NULL;

        giraffe_table_delete(fibers);
        fibers = NULL;

        return 1;

    }

    cpl_frameset_insert(set, rsp_frame);


    /*
     * Save and register the efficiency curve product
     */

    giraffe_table_add_info(response->efficiency, &info, set);

    rsp_frame = giraffe_frame_create_table(response->efficiency,
                                           GIFRAME_EFFICIENCY_CURVE,
                                           CPL_FRAME_LEVEL_FINAL,
                                           TRUE, TRUE);

    if (rsp_frame == NULL) {

        cpl_msg_error(_id, "Cannot create local file! Aborting ...");

        giraffe_response_delete(response);
        response = NULL;

        giraffe_rebinning_destroy(rebinning);
        rebinning = NULL;

        giraffe_table_delete(wcalcoeff);
        wcalcoeff = NULL;

        giraffe_table_delete(slitgeometry);
        slitgeometry = NULL;

        giraffe_table_delete(grating);
        grating = NULL;

        giraffe_table_delete(fibers);
        fibers = NULL;

        return 1;

    }

    cpl_frameset_insert(set, rsp_frame);

    giraffe_response_delete(response);
    response = NULL;


    /*
     * Cleanup
     */

    giraffe_table_delete(wcalcoeff);

    giraffe_table_delete(slitgeometry);
    giraffe_table_delete(grating);
    giraffe_table_delete(fibers);

    giraffe_rebinning_destroy(rebinning);

    return 0;

}


static cxint
giqcstandard(cpl_frameset* set)
{

    const cxchar* const fctid = "giqcstandard";


    const cxdouble saturation = 60000.;
    const cxdouble wlscale = 0.1;

    cxint i = 0;
    cxint status = 0;
    cxint nbin = 0;
    cxint npixel = 0;
    cxint nsaturated = 0;

    const cxdouble* pixels = NULL;

    cxdouble wlmin = 0.;
    cxdouble wlmax = 0.;
    cxdouble efficiency = 0.;

    cpl_propertylist* properties = NULL;
    cpl_propertylist* _properties = NULL;
    cpl_propertylist* qclog = NULL;

    cpl_frame* rframe = NULL;
    cpl_frame* pframe = NULL;

    cpl_image* _rimage = NULL;

    cpl_table* _ptable = NULL;

    GiImage* rimage = NULL;

    GiTable* ptable = NULL;

    GiPaf* qc = NULL;


    cpl_msg_info(fctid, "Computing QC1 parameters ...");

    qc = giraffe_qclog_open(0);

    if (qc == NULL) {
        cpl_msg_error(fctid, "Cannot create QC1 log!");
        return 1;
    }

    qclog = giraffe_paf_get_properties(qc);
    cx_assert(qclog != NULL);


    /*
     * Process efficiency table
     */

    pframe = giraffe_get_frame(set, GIFRAME_EFFICIENCY_CURVE,
                               CPL_FRAME_GROUP_PRODUCT);

    if (pframe == NULL) {
        cpl_msg_error(fctid, "Missing product frame (%s)",
                      GIFRAME_EFFICIENCY_CURVE);

        giraffe_paf_delete(qc);
        qc = NULL;

        return 1;
    }

    cpl_msg_info(fctid, "Processing product frame '%s' (%s)",
                 cpl_frame_get_filename(pframe), cpl_frame_get_tag(pframe));

    ptable = giraffe_table_new();
    status = giraffe_table_load(ptable, cpl_frame_get_filename(pframe), 1,
                                "EFFICIENCY_CURVE");

    if (status != 0) {
        cpl_msg_error(fctid, "Could not load efficiency table '%s'! "
                      "Aborting ...", cpl_frame_get_filename(pframe));

        giraffe_table_delete(ptable);
        ptable = NULL;

        giraffe_paf_delete(qc);
        qc = NULL;

        return 1;
    }


    /*
     * Load first raw image as reference
     */

    rframe = cpl_frameset_find(set, GIFRAME_STANDARD);

    if (rframe == NULL) {
        cpl_msg_error(fctid, "Missing raw frame (%s)", GIFRAME_STANDARD);

        giraffe_table_delete(ptable);
        ptable = NULL;

        giraffe_paf_delete(qc);
        qc = NULL;

        return 1;
    }

    rimage = giraffe_image_new(CPL_TYPE_DOUBLE);
    status = giraffe_image_load(rimage, cpl_frame_get_filename(rframe), 0);

    if (status != 0) {
        cpl_msg_error(fctid, "Could not load standard star observation '%s'!",
                      cpl_frame_get_filename(rframe));

        giraffe_image_delete(rimage);
        rimage = NULL;

        giraffe_table_delete(ptable);
        ptable = NULL;

        giraffe_paf_delete(qc);
        qc = NULL;

        return 1;

    }

    _rimage = giraffe_image_get(rimage);
    cx_assert(_rimage != NULL);

    properties = giraffe_image_get_properties(rimage);
    cx_assert(properties != NULL);

    giraffe_propertylist_copy(qclog, "ARCFILE", properties, GIALIAS_ARCFILE);
    giraffe_propertylist_copy(qclog, "TPL.ID", properties, GIALIAS_TPLID);

    cpl_propertylist_update_string(qclog, "PRO.CATG",
                                   cpl_frame_get_tag(pframe));
    cpl_propertylist_set_comment(qclog, "PRO.CATG",
                                 "Pipeline product category");


    /*
     * Count the number of saturated pixels in the raw frame
     */

    pixels = cpl_image_get_data(_rimage);
    npixel = cpl_image_get_size_x(_rimage) * cpl_image_get_size_y(_rimage);

    _rimage = NULL;

    for (i = 0; i < npixel; i++) {
        if (pixels[i] > saturation) {
            ++nsaturated;
        }
    }

    pixels = NULL;

    giraffe_image_delete(rimage);
    rimage = NULL;


    /*
     * Compute mean efficiency from a wavelength range around the
     * central wavelength.
     */

    _ptable = giraffe_table_get(ptable);
    cx_assert(_ptable != NULL);

    properties = giraffe_table_get_properties(ptable);
    cx_assert(properties != NULL);

    if (cpl_propertylist_has(properties, GIALIAS_GRATWLEN) == FALSE) {

        giraffe_table_delete(ptable);
        ptable = NULL;

        giraffe_paf_delete(qc);
        qc = NULL;

        cpl_msg_error(fctid, "Missing property '%s'", GIALIAS_GRATWLEN);

        return 1;

    }
    else {

        cxdouble wlband = 0.;
        cxdouble wl0 = cpl_propertylist_get_double(properties,
                                                   GIALIAS_GRATWLEN);


        wlmin = cpl_propertylist_get_double(properties, GIALIAS_BINWLMIN);
        wlmax = cpl_propertylist_get_double(properties, GIALIAS_BINWLMAX);

        cx_assert((wlmin < wl0) && (wl0 < wlmax));

        wlband = wlscale * fabs(wlmax - wlmin);

        wlmin = CX_MAX(wlmin, (wl0 - wlband));
        wlmax = CX_MIN(wlmax, (wl0 + wlband));

        cpl_msg_info(fctid, "Computing spectrograph efficiency from "
                     "wavelength range ]%.1f, %.1f[", wlmin, wlmax);

    }

    nbin = 0;

    for (i = 0; i < cpl_table_get_nrow(_ptable); ++i) {

        cxdouble wavelength = cpl_table_get_double(_ptable, "WLEN", i, NULL);

        if ((wavelength > wlmin) && (wavelength < wlmax)) {

            efficiency += cpl_table_get_double(_ptable, "EFFICIENCY",
                                               i, NULL);
            ++nbin;

        }

    }

    efficiency /= (cxdouble)nbin;


    cpl_propertylist_update_int(properties, GIALIAS_QCNSAT, nsaturated);
    cpl_propertylist_set_comment(properties, GIALIAS_QCNSAT, "Number of "
                                 "saturated pixels in the first raw frame");

    giraffe_propertylist_copy(qclog, "QC.OUT1.NSAT.RAW", properties,
                              GIALIAS_QCNSAT);


    cpl_propertylist_update_double(properties, GIALIAS_QCEFFICIENCY,
                                   efficiency);
    cpl_propertylist_set_comment(properties, GIALIAS_QCEFFICIENCY,
                                 "Efficiency of the spectrograph.");

    giraffe_propertylist_copy(qclog, "QC.EFFICIENCY.MEAN", properties,
                              GIALIAS_QCEFFICIENCY);


    if (cpl_propertylist_has(properties, GIALIAS_SKY_LEVEL) == TRUE) {

        cxdouble mean_sky = cpl_propertylist_get_double(properties,
                                                        GIALIAS_SKY_LEVEL);

        cpl_propertylist_update_double(properties, GIALIAS_QCSKYLEVEL,
                                       mean_sky);
        cpl_propertylist_set_comment(properties, GIALIAS_QCSKYLEVEL,
                                     "Mean sky level [ADU]");

        giraffe_propertylist_copy(qclog, "QC.SKY.MEAN", properties,
                                  GIALIAS_QCSKYLEVEL);

    }


    /*
     * Write QC1 log and save updated product.
     */

    _properties = cpl_propertylist_load_regexp(cpl_frame_get_filename(pframe),
                                               0, "^COMMENT$", TRUE);

    cpl_propertylist_erase_regexp(_properties, "ESO QC.*", 0);

    cpl_image_save(NULL, cpl_frame_get_filename(pframe), CPL_BPP_8_UNSIGNED,
                   _properties, CPL_IO_CREATE);

    cpl_propertylist_delete(_properties);
    _properties = NULL;

    giraffe_table_attach(ptable, cpl_frame_get_filename(pframe), 1, NULL);

    giraffe_table_delete(ptable);
    ptable = NULL;

    giraffe_qclog_close(qc);
    qc = NULL;

    return 0;

}


/*
 * Build table of contents, i.e. the list of available plugins, for
 * this module. This function is exported.
 */

int
cpl_plugin_get_info(cpl_pluginlist* list)
{

    cpl_recipe* recipe = cx_calloc(1, sizeof *recipe);
    cpl_plugin* plugin = &recipe->interface;


    cpl_plugin_init(plugin,
                    CPL_PLUGIN_API,
                    GIRAFFE_BINARY_VERSION,
                    CPL_PLUGIN_TYPE_RECIPE,
                    "gistandard",
                    "Process a spectro-photometric standard star "
                    "observation and compute the instrument response curve.",
                    "For detailed information please refer to the "
                    "GIRAFFE pipeline user manual.\nIt is available at "
                    "http://www.eso.org/pipelines.",
                    "Giraffe Pipeline",
                    PACKAGE_BUGREPORT,
                    giraffe_get_license(),
                    gistandard_create,
                    gistandard_exec,
                    gistandard_destroy);

    cpl_pluginlist_append(list, plugin);

    return 0;

}
