/*
 * 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 <cxtypes.h>
#include <cxmessages.h>

#include <cpl_recipe.h>
#include <cpl_plugininfo.h>
#include <cpl_parameterlist.h>
#include <cpl_frameset.h>
#include <cpl_propertylist.h>

#include "gialias.h"
#include "gierror.h"
#include "giframe.h"
#include "giwindow.h"
#include "gifibers.h"
#include "gislitgeometry.h"
#include "gipsfdata.h"
#include "gifiberutils.h"
#include "gibias.h"
#include "giextract.h"
#include "giqclog.h"
#include "giutils.h"
#include "giwlcalibration.h"
#include "girebinning.h"
#include "gisgcalibration.h"


static cxint giwavecalibration(cpl_parameterlist* config, cpl_frameset* set);
static cxint giqcwavecalibration(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
giwavecalibration_create(cpl_plugin* plugin)
{

    cpl_recipe* recipe = (cpl_recipe*)plugin;

    cpl_parameter* p;


    giraffe_error_init();


    /*
     * We have to provide the options 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);

    /* Flat Fielding */
    /* Not Yet Implemented */

    /* Spectrum Extraction */

    giraffe_extract_config_add(recipe->parameters);

    /* Wavelength Calibration */

    giraffe_wlcalibration_config_add(recipe->parameters);

    /* Arc-lamp spectrum rebinning */

    p = cpl_parameter_new_value("giraffe.wcal.rebin",
                                CPL_TYPE_BOOL,
                                "Rebin extracted arc-lamp spectra.",
                                "giraffe.wcal",
                                TRUE);

    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "wcal-rebin");
    cpl_parameterlist_append(recipe->parameters, p);

    giraffe_rebin_config_add(recipe->parameters);

    /* Slit geometry calibration */

    p = cpl_parameter_new_value("giraffe.wcal.slitgeometry",
                                CPL_TYPE_BOOL,
                                "Controls the slit geometry calibration.",
                                "giraffe.wcal",
                                FALSE);

    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "wcal-slit");
    cpl_parameterlist_append(recipe->parameters, p);

    giraffe_sgcalibration_config_add(recipe->parameters);

    return 0;

}

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

static cxint
giwavecalibration_exec(cpl_plugin* plugin)
{

    cpl_recipe* recipe = (cpl_recipe*)plugin;

    cxint status = 0;


    if (recipe->parameters == NULL || recipe->frames == NULL) {
        return 1;
    }

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

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

    status = giqcwavecalibration(recipe->frames);

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

    return 0;

}


static cxint
giwavecalibration_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); recipe->parameters = NULL;

    giraffe_error_clear();

    return 0;

}

/*
 * The actual recipe starts here.
 */

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

    const cxchar* const fctid = "giwavecalibration";


    const cxchar* filename = NULL;

    cxbool rebin = FALSE;
    cxbool slitgeometry = FALSE;

    cxint status = 0;
    cxint narcspectrum = 0;

    const cpl_propertylist* properties = NULL;

    cpl_frame* arcspec_frame = NULL;
    cpl_frame* bpixel_frame  = NULL;
    cpl_frame* mbias_frame   = NULL;
    cpl_frame* mlocy_frame   = NULL;
    cpl_frame* mlocw_frame   = NULL;
    cpl_frame* psfdata_frame = NULL;
    cpl_frame* slight_frame  = NULL;
    cpl_frame* grating_frame = NULL;
    cpl_frame* slitgeo_frame = NULL;
    cpl_frame* lines_frame   = NULL;
    cpl_frame* wcal_frame    = NULL;
    cpl_frame* ldata_frame   = NULL;
    cpl_frame* scal_frame    = NULL;
    cpl_frame* sext_frame    = NULL;

    cpl_parameter* p = NULL;

    cpl_matrix* biasareas = NULL;

    GiImage* arcspectrum   = NULL;
    GiImage* bsarcspectrum = NULL;
    GiImage* slight        = NULL;
    GiImage* mbias         = NULL;
    GiImage* bpixel        = NULL;

    GiLocalization* localization = NULL;
    GiExtraction* extraction = NULL;
    GiRebinning* rebinning = NULL;
    GiWCalData* wlsolution = NULL;

    GiTable* fibers       = NULL;
    GiTable* grating      = NULL;
    GiTable* slitgeo      = NULL;
    GiTable* wavelengths  = NULL;
    GiTable* wcal_initial = NULL;

//    GiWcalSolution* wcal_solution = NULL;

    GiBiasConfig* bias_config = NULL;
    GiExtractConfig* extract_config = NULL;
    GiWCalConfig* wcal_config = NULL;

    GiFrameCreator creator = NULL;

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

    GiGroupInfo groups[] = {
        {GIFRAME_ARC_SPECTRUM, CPL_FRAME_GROUP_RAW},
        {GIFRAME_BADPIXEL_MAP, CPL_FRAME_GROUP_CALIB},
        {GIFRAME_BIAS_MASTER, 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_LINE_CATALOG, CPL_FRAME_GROUP_CALIB},
        {GIFRAME_LINE_MASK, CPL_FRAME_GROUP_CALIB},
        {NULL, CPL_FRAME_GROUP_NONE}
    };



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

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

    p = cpl_parameterlist_find(config, "giraffe.wcal.rebin");

    if (p != NULL) {
        rebin = cpl_parameter_get_bool(p);
    }

    p = cpl_parameterlist_find(config, "giraffe.wcal.slitgeometry");

    if (p != NULL) {
        slitgeometry = cpl_parameter_get_bool(p);
    }

    status = giraffe_frameset_set_groups(set, groups);

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


    /*************************************************************************
                                    Preprocessing
    *************************************************************************/

    cpl_msg_info(fctid, "Recipe Step : Initialization");

    /*
     *  Verify the frame set contents
     */

    narcspectrum = cpl_frameset_count_tags(set, GIFRAME_ARC_SPECTRUM);

    if (narcspectrum > 1) {
        cpl_msg_error(fctid, "Only one arc spectrum frame allowed! "
                      "Aborting...");
        return 1;
    }
    else if (narcspectrum < 1) {
        cpl_msg_error(fctid, "Arc spectrum frame is missing! "
                      "Aborting...");
        return 1;
    }

    arcspec_frame = cpl_frameset_find(set, GIFRAME_ARC_SPECTRUM);
    bpixel_frame = cpl_frameset_find(set, GIFRAME_BADPIXEL_MAP);

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

    mbias_frame = cpl_frameset_find(set, GIFRAME_BIAS_MASTER);

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

    mlocy_frame = cpl_frameset_find(set, GIFRAME_PSF_CENTROID);

    if (mlocy_frame == NULL) {

        mlocy_frame = cpl_frameset_find(set, GIFRAME_LOCALIZATION_CENTROID);

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

    }

    mlocw_frame = cpl_frameset_find(set, GIFRAME_PSF_WIDTH);

    if (mlocw_frame == NULL) {

        mlocw_frame = cpl_frameset_find(set, GIFRAME_LOCALIZATION_WIDTH);

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

    }

    psfdata_frame = cpl_frameset_find(set, GIFRAME_PSF_DATA);

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

    slight_frame = cpl_frameset_find(set, GIFRAME_SCATTERED_LIGHT_MODEL);

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

    grating_frame = cpl_frameset_find(set, GIFRAME_GRATING);

    if (!grating_frame) {
        cpl_msg_error(fctid, "No grating table present in frame set, "
                             "aborting...");
        return 1;
    }

    slitgeo_frame = giraffe_get_slitgeometry(set);

    if (!slitgeo_frame) {
        cpl_msg_error(fctid, "No slit geometry table present in frame "
                             "set, aborting...");
        return 1;
    }

    lines_frame = cpl_frameset_find(set, GIFRAME_LINE_CATALOG);

    if (!lines_frame) {
        cpl_msg_error(fctid, "No wavelength table present in frame set, "
                             "aborting...");
        return 1;
    }

    wcal_frame = cpl_frameset_find(set, GIFRAME_WAVELENGTH_SOLUTION);

    if (!wcal_frame) {
        cpl_msg_info(fctid, "No wavelength solution present in frame set.");
    }

    scal_frame = cpl_frameset_find(set, GIFRAME_LINE_MASK);

    if (!scal_frame) {

        if (slitgeometry == TRUE) {
            cpl_msg_error(fctid, "No line mask present in frame "
                          "set. Aborting ...");
            return 1;
        }
        else {
            cpl_msg_info(fctid, "No slit geometry mask present in frame "
                         "set.");
        }

    }


    /*************************************************************************
                                     Processing
    *************************************************************************/

    /*
     * 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(fctid, "Cannot load bad pixel map from '%s'. "
                          "Aborting ...", filename);

            giraffe_image_delete(bpixel);
            return 1;
        }
    }


    /*
     *  Load arc spectrum
     */

    status = 0;
    filename = cpl_frame_get_filename(arcspec_frame);

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

    if (status) {
        cpl_msg_error(fctid, "Cannot load arc spectrum from '%s'. "
                      "Aborting...", filename);

        giraffe_image_delete(bpixel);
        giraffe_image_delete(arcspectrum);
        return 1;
    }


    /*
     *  Prepare bias subtraction
     */

    cpl_msg_info(fctid, "Recipe Step : Bias Removal");

    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(fctid, "Missing master bias frame! Selected bias "
                                 "removal method requires a master bias "
                                 "frame!");

            giraffe_image_delete(bpixel);
            giraffe_image_delete(arcspectrum);
            giraffe_bias_config_destroy(bias_config);

            return 1;
        }
        else {

            status = 0;
            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(fctid, "Cannot load master bias from '%s'. "
                                     "Aborting ...", filename);

                giraffe_image_delete(bpixel);
                giraffe_image_delete(arcspectrum);
                giraffe_image_delete(mbias);
                giraffe_bias_config_destroy(bias_config);

                return 1;
            }
        }
    }


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

    bsarcspectrum = giraffe_image_new(CPL_TYPE_DOUBLE);

    status = giraffe_bias_remove(bsarcspectrum, arcspectrum, mbias, bpixel,
                                 biasareas, bias_config);

    giraffe_image_delete(arcspectrum);
    arcspectrum = NULL;

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

    giraffe_bias_config_destroy(bias_config);
    bias_config = NULL;

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

        giraffe_image_delete(bpixel);
        giraffe_image_delete(bsarcspectrum);
        return 1;
    }


    /*
     * Determine fiber setup
     */

    cpl_msg_info(fctid, "Recipe Step : Fiber Setup");

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

    fibers = giraffe_fibers_setup(arcspec_frame, mlocy_frame);

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

        giraffe_image_delete(bpixel);
        giraffe_image_delete(bsarcspectrum);

        return 1;
    }

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


    /*
     * Load spectrum localization.
     */

    localization = giraffe_localization_new();

    filename = cpl_frame_get_filename(mlocy_frame);
    status = 0;

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

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

        giraffe_localization_destroy(localization);
        giraffe_image_delete(bpixel);
        giraffe_image_delete(bsarcspectrum);
        giraffe_table_delete(fibers);

        return 1;

    }


    filename = cpl_frame_get_filename(mlocw_frame);
    status = 0;

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

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

        giraffe_localization_destroy(localization);
        giraffe_image_delete(bpixel);
        giraffe_image_delete(bsarcspectrum);
        giraffe_table_delete(fibers);

        return 1;

    }


    /*
     * Perform spectrum extraction on the master flat field.
     */

    cpl_msg_info(fctid, "Recipe Step : Spectrum Extraction");

    if (slight_frame) {

        filename = cpl_frame_get_filename(slight_frame);
        status = 0;

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

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

            giraffe_image_delete(bpixel);
            giraffe_image_delete(bsarcspectrum);
            giraffe_table_delete(fibers);
            giraffe_localization_destroy(localization);
            giraffe_image_delete(slight);

            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(fctid, "%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(bsarcspectrum);
            bsarcspectrum = 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(fctid, "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(bsarcspectrum);
                bsarcspectrum = NULL;

                return 1;

            }

        }

    }


    extraction = giraffe_extraction_new();

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

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

         giraffe_image_delete(bpixel);
         giraffe_image_delete(bsarcspectrum);
         giraffe_table_delete(fibers);
         giraffe_localization_destroy(localization);
         giraffe_image_delete(slight);
         giraffe_extract_config_destroy(extract_config);
         giraffe_extraction_destroy(extraction);

        return 1;
    }

    giraffe_image_delete(slight);
    slight = NULL;

    giraffe_image_delete(bpixel);
    bpixel = NULL;

    giraffe_image_delete(bsarcspectrum);
    bsarcspectrum = NULL;

    giraffe_extract_config_destroy(extract_config);
    extract_config = NULL;

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

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

    /* Extracted spectra */

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

    sext_frame = giraffe_frame_create_image(extraction->spectra,
                                            GIFRAME_ARC_LAMP_EXTSPECTRA,
                                            CPL_FRAME_LEVEL_INTERMEDIATE,
                                            TRUE, TRUE);

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

        giraffe_table_delete(fibers);

        giraffe_localization_destroy(localization);
        giraffe_extraction_destroy(extraction);

        return 1;
    }

    status = giraffe_fiberlist_attach(sext_frame, fibers);

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

        cpl_frame_delete(sext_frame);

        giraffe_table_delete(fibers);

        giraffe_localization_destroy(localization);
        giraffe_extraction_destroy(extraction);

        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_ARC_LAMP_EXTERRORS,
                                            CPL_FRAME_LEVEL_INTERMEDIATE,
                                            TRUE, TRUE);

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

        giraffe_table_delete(fibers);

        giraffe_localization_destroy(localization);
        giraffe_extraction_destroy(extraction);

        return 1;
    }

    status = giraffe_fiberlist_attach(sext_frame, fibers);

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

        cpl_frame_delete(sext_frame);

        giraffe_table_delete(fibers);

        giraffe_localization_destroy(localization);
        giraffe_extraction_destroy(extraction);

        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_ARC_LAMP_EXTPIXELS,
                                                CPL_FRAME_LEVEL_INTERMEDIATE,
                                                TRUE, TRUE);

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

            giraffe_table_delete(fibers);

            giraffe_localization_destroy(localization);
            giraffe_extraction_destroy(extraction);

            return 1;
        }

        status = giraffe_fiberlist_attach(sext_frame, fibers);

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

            cpl_frame_delete(sext_frame);

            giraffe_table_delete(fibers);

            giraffe_localization_destroy(localization);
            giraffe_extraction_destroy(extraction);

            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_ARC_LAMP_EXTTRACE,
                                            CPL_FRAME_LEVEL_INTERMEDIATE,
                                            TRUE, TRUE);

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

        giraffe_table_delete(fibers);

        giraffe_localization_destroy(localization);
        giraffe_extraction_destroy(extraction);

        return 1;
    }

    status = giraffe_fiberlist_attach(sext_frame, fibers);

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

        cpl_frame_delete(sext_frame);

        giraffe_table_delete(fibers);

        giraffe_localization_destroy(localization);
        giraffe_extraction_destroy(extraction);

        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_ARC_LAMP_EXTMODEL,
                                                CPL_FRAME_LEVEL_FINAL,
                                                TRUE, TRUE);

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

            giraffe_table_delete(fibers);

            giraffe_localization_destroy(localization);
            giraffe_extraction_destroy(extraction);

            return 1;
        }

        status = giraffe_fiberlist_attach(sext_frame, fibers);

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

            cpl_frame_delete(sext_frame);

            giraffe_table_delete(fibers);

            giraffe_localization_destroy(localization);
            giraffe_extraction_destroy(extraction);

            return 1;
        }

        cpl_frameset_insert(set, sext_frame);

    }

    /*
     * Perform Wavelength Calibration
     */

    cpl_msg_info(fctid, "Recipe Step : Wavelength Calibration");

    filename = 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(fctid, "Cannot load grating table from '%s'. "
                      "Aborting ...", filename);

        giraffe_table_delete(fibers);
        giraffe_localization_destroy(localization);
        giraffe_extraction_destroy(extraction);

        return 1;
    }


    filename = cpl_frame_get_filename(slitgeo_frame);

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

    if (slitgeo == NULL) {
        cpl_msg_error(fctid, "Cannot load slit geometry table from '%s'. "
                      "Aborting ...", filename);

        giraffe_table_delete(fibers);
        giraffe_localization_destroy(localization);
        giraffe_extraction_destroy(extraction);
        giraffe_table_delete(grating);

        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(slitgeo, fibers) != 1) {
            cpl_msg_error(fctid, "Slit geometry data from '%s' is not "
                    "applicable for current fiber setup! "
                            "Aborting ...", filename);

            giraffe_table_delete(slitgeo);
            giraffe_table_delete(fibers);
            giraffe_localization_destroy(localization);
            giraffe_extraction_destroy(extraction);
            giraffe_table_delete(grating);

            return 1;
        }

    }


    filename = cpl_frame_get_filename(lines_frame);
    status = 0;

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

    if (status) {
        cpl_msg_error(fctid, "Cannot load arc-line data from '%s'. "
                      "Aborting ...", filename);

        giraffe_table_delete(fibers);
        giraffe_localization_destroy(localization);
        giraffe_extraction_destroy(extraction);
        giraffe_table_delete(grating);
        giraffe_table_delete(slitgeo);

        return 1;

    }

    if (wcal_frame != NULL) {

        wcal_initial = giraffe_table_new();

        filename = cpl_frame_get_filename(wcal_frame);
        status = giraffe_table_load(wcal_initial, filename, 1, NULL);

        if (status) {
            cpl_msg_error(fctid, "Cannot load initial wavelength solution "
                          "from '%s'. Aborting ...", filename);

            giraffe_table_delete(wcal_initial);

            giraffe_table_delete(fibers);
            giraffe_localization_destroy(localization);
            giraffe_extraction_destroy(extraction);
            giraffe_table_delete(grating);
            giraffe_table_delete(slitgeo);

            return 1;

        }

    }

    wcal_config = giraffe_wlcalibration_config_create(config);

    if (wcal_config == NULL) {

        cpl_msg_error(fctid, "Could not create wavelength calibration "
                      "setup: error parsing configuration parameters! "
                      "Aborting ...");
        giraffe_table_delete(fibers);
        giraffe_localization_destroy(localization);
        giraffe_extraction_destroy(extraction);
        giraffe_table_delete(grating);
        giraffe_table_delete(slitgeo);
        giraffe_table_delete(wavelengths);
        giraffe_table_delete(wcal_initial);

        return 1;

    }

    wlsolution = giraffe_wcaldata_new();

    status = giraffe_calibrate_wavelength(wlsolution, extraction,
                                          localization, fibers, slitgeo,
                                          grating, wavelengths, wcal_initial,
                                          wcal_config);

    if (status) {
        cpl_msg_error(fctid, "Error during wavelength calibration, "
                             "aborting...");

        giraffe_table_delete(fibers);
        giraffe_localization_destroy(localization);
        giraffe_extraction_destroy(extraction);
        giraffe_table_delete(grating);
        giraffe_table_delete(slitgeo);
        giraffe_table_delete(wavelengths);
        giraffe_table_delete(wcal_initial);

        giraffe_wcaldata_delete(wlsolution);
        giraffe_wlcalibration_config_destroy(wcal_config);

        return 1;

    }

    giraffe_wlcalibration_config_destroy(wcal_config);
    wcal_config = NULL;

    giraffe_table_delete(wcal_initial);
    wcal_initial = NULL;

    giraffe_table_delete(wavelengths);
    wavelengths = NULL;


    /*
     * Save and register the wavelength calibration results.
     */

    /* Coefficients of the x-residuals fit */

    giraffe_table_add_info(wlsolution->coeffs, &info, set);

    wcal_frame = giraffe_frame_create_table(wlsolution->coeffs,
                                            GIFRAME_WAVELENGTH_SOLUTION,
                                            CPL_FRAME_LEVEL_FINAL,
                                            TRUE, TRUE);

    if (wcal_frame == NULL) {

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

        giraffe_table_delete(fibers);
        giraffe_localization_destroy(localization);
        giraffe_extraction_destroy(extraction);
        giraffe_table_delete(grating);
        giraffe_table_delete(slitgeo);

        giraffe_wcaldata_delete(wlsolution);

        return 1;

    }

    cpl_frameset_insert(set, wcal_frame);

    /* Lines data */

    properties = giraffe_table_get_properties(wlsolution->coeffs);
    creator = (GiFrameCreator)giraffe_linedata_writer;

    ldata_frame = giraffe_frame_create(GIFRAME_LINE_DATA,
                                       CPL_FRAME_LEVEL_FINAL,
                                       properties, wlsolution->linedata,
                                       NULL,
                                       creator);

    if (ldata_frame == NULL) {

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

        giraffe_table_delete(fibers);
        giraffe_localization_destroy(localization);
        giraffe_extraction_destroy(extraction);
        giraffe_table_delete(grating);
        giraffe_table_delete(slitgeo);

        giraffe_wcaldata_delete(wlsolution);

        properties = NULL;

        return 1;

    }

    properties = NULL;

    giraffe_linedata_delete(wlsolution->linedata);
    wlsolution->linedata = NULL;

    cpl_frameset_insert(set, ldata_frame);


    /*
     * Optional slit geometry calibration. Note that this step requires a
     * rebinned arc-lamp frame as input!
     */

    if (slitgeometry == TRUE) {

        cpl_frame* slit_frame = NULL;

        GiSGCalConfig* scal_config = NULL;

        GiTable* mask = NULL;
        GiTable* slit = NULL;


        cpl_msg_info(fctid, "Calibrating slit geometry ...");

        scal_config = giraffe_sgcalibration_config_create(config);


        filename = cpl_frame_get_filename(scal_frame);

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

        if (status != 0) {
            cpl_msg_error(fctid, "Cannot load slit geometry mask from '%s'. "
                          "Aborting ...", filename);

            giraffe_sgcalibration_config_destroy(scal_config);

            giraffe_table_delete(mask);

            giraffe_wcaldata_delete(wlsolution);

            giraffe_localization_destroy(localization);
            giraffe_extraction_destroy(extraction);

            giraffe_table_delete(fibers);
            giraffe_table_delete(grating);
            giraffe_table_delete(slitgeo);

            return 1;

        }

        slit = giraffe_table_new();

        status = giraffe_calibrate_slit(slit, extraction, localization, fibers,
                                        wlsolution->coeffs, slitgeo, grating,
                                        mask, scal_config);

        if (status != 0) {
            cpl_msg_error(fctid, "Slit geometry calibration failed! "
                          "Aborting ...");

            giraffe_sgcalibration_config_destroy(scal_config);

            giraffe_table_delete(slit);
            giraffe_table_delete(mask);

            giraffe_wcaldata_delete(wlsolution);

            giraffe_localization_destroy(localization);
            giraffe_extraction_destroy(extraction);

            giraffe_table_delete(fibers);
            giraffe_table_delete(grating);
            giraffe_table_delete(slitgeo);

            return 1;

        }


        /*
         * Save and register the slit geometry calibration results.
         */


        giraffe_table_add_info(slit, &info, set);

        slit_frame = giraffe_slitgeometry_save(slit);

        if (!slit_frame) {

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

            giraffe_sgcalibration_config_destroy(scal_config);

            giraffe_table_delete(slit);
            giraffe_table_delete(mask);

            giraffe_wcaldata_delete(wlsolution);

            giraffe_localization_destroy(localization);
            giraffe_extraction_destroy(extraction);

            giraffe_table_delete(fibers);
            giraffe_table_delete(grating);
            giraffe_table_delete(slitgeo);

            return 1;
        }

        cpl_frameset_insert(set, slit_frame);

        giraffe_table_delete(mask);
        giraffe_sgcalibration_config_destroy(scal_config);


        /*
         * Replace the slit geometry table with the new one.
         */

        giraffe_table_delete(slitgeo);
        slitgeo = slit;

    }


    /*
     * Optional rebinning of the previously extracted arc-lamp spectra.
     */

    if (rebin == TRUE) {

        cpl_frame* rbin_frame = NULL;

        GiRebinConfig* rebin_config;


        cpl_msg_info(fctid, "Recipe Step : Spectrum Rebinning");

        rebin_config = giraffe_rebin_config_create(config);

        rebinning = giraffe_rebinning_new();

        status = giraffe_rebin_spectra(rebinning, extraction, fibers,
                                       localization, grating, slitgeo,
                                       wlsolution->coeffs, rebin_config);

        if (status) {
            cpl_msg_error(fctid, "Rebinning of arc-lamp spectra failed! "
                          "Aborting...");

            giraffe_wcaldata_delete(wlsolution);

            giraffe_rebinning_destroy(rebinning);
            giraffe_localization_destroy(localization);
            giraffe_extraction_destroy(extraction);

            giraffe_table_delete(grating);
            giraffe_table_delete(slitgeo);
            giraffe_table_delete(fibers);

            giraffe_rebin_config_destroy(rebin_config);

            return 1;

        }

        giraffe_rebin_config_destroy(rebin_config);

        /*
         * 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_ARC_LAMP_RBNSPECTRA,
                                                CPL_FRAME_LEVEL_FINAL,
                                                TRUE, TRUE);

        if (rbin_frame == NULL) {

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

            giraffe_wcaldata_delete(wlsolution);

            giraffe_rebinning_destroy(rebinning);
            giraffe_localization_destroy(localization);
            giraffe_extraction_destroy(extraction);

            giraffe_table_delete(grating);
            giraffe_table_delete(slitgeo);
            giraffe_table_delete(fibers);

            return 1;

        }

        status = giraffe_fiberlist_attach(rbin_frame, fibers);

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

            giraffe_wcaldata_delete(wlsolution);

            giraffe_rebinning_destroy(rebinning);
            giraffe_localization_destroy(localization);
            giraffe_extraction_destroy(extraction);

            giraffe_table_delete(grating);
            giraffe_table_delete(slitgeo);
            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_ARC_LAMP_RBNERRORS,
                                                CPL_FRAME_LEVEL_FINAL,
                                                TRUE, TRUE);

        if (rbin_frame == NULL) {

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

            giraffe_wcaldata_delete(wlsolution);

            giraffe_rebinning_destroy(rebinning);
            giraffe_localization_destroy(localization);
            giraffe_extraction_destroy(extraction);

            giraffe_table_delete(grating);
            giraffe_table_delete(slitgeo);
            giraffe_table_delete(fibers);

            return 1;

        }

        status = giraffe_fiberlist_attach(rbin_frame, fibers);

        if (status) {

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

            giraffe_wcaldata_delete(wlsolution);

            giraffe_rebinning_destroy(rebinning);
            giraffe_localization_destroy(localization);
            giraffe_extraction_destroy(extraction);

            giraffe_table_delete(grating);
            giraffe_table_delete(slitgeo);
            giraffe_table_delete(fibers);

            cpl_frame_delete(rbin_frame);

            return 1;

        }

        cpl_frameset_insert(set, rbin_frame);

    }


    /*
     * Postprocessing
     */

    giraffe_table_delete(fibers);
    fibers = NULL;

    giraffe_localization_destroy(localization);
    localization = NULL;

    giraffe_extraction_destroy(extraction);
    extraction = NULL;

    if (rebinning != NULL) {
        giraffe_rebinning_destroy(rebinning);
        rebinning = NULL;
    }

    giraffe_table_delete(grating);
    grating = NULL;

    giraffe_table_delete(slitgeo);
    slitgeo = NULL;

    giraffe_wcaldata_delete(wlsolution);
    wlsolution = NULL;

    return 0;

}


static cxint
giqcwavecalibration(cpl_frameset* set)
{

    const cxchar* const fctid = "giqcwavecalibration";


    cxint i = 0;
    cxint j = 0;
    cxint nx = 0;
    cxint ny = 0;
    cxint npixel = 0;
    cxint nsaturated = 0;
    cxint status = 0;

    const cxdouble rmsscale = 3.;
    const cxdouble saturation = 60000.;
    const cxdouble* pixels = NULL;

    cxdouble efficiency[2] = {0., 0.};
    cxdouble wlcenter = 0.;
    cxdouble mean = 0.;
    cxdouble rms = 0.;
    cxdouble pixel2nm = 1.;
    cxdouble fwhm_domain[2] = {0., 100.};
    cxdouble* _tdata = NULL;

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

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

    const cpl_image* _rimage = NULL;
    const cpl_image* _pimage = NULL;

    cpl_image* _test = NULL;
    cpl_image* _test0 = NULL;
    cpl_image* _test1 = NULL;

    cpl_table* _ptable = NULL;

    GiImage* rimage = NULL;
    GiImage* pimage = NULL;

    GiTable* ptable = NULL;

    GiLineData* plines = NULL;

    GiPaf* qc = NULL;

    GiWindow w;



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


    /*
     * Compute lamp efficiencies from the rebinned frame if
     * it is available. If not the efficiencies are set to 0.
     */

    pframe = giraffe_get_frame(set, GIFRAME_ARC_LAMP_EXTSPECTRA,
                               CPL_FRAME_GROUP_PRODUCT);

    if (pframe == NULL) {

        cpl_msg_warning(fctid, "Product '%s' not found.",
                        GIFRAME_ARC_LAMP_EXTSPECTRA);

        cpl_msg_warning(fctid, "Setting lamp efficiencies (%s, %s) to 0.",
                        GIALIAS_QCLAMP, GIALIAS_QCLAMP_SIMCAL);

        efficiency[0] = 0.;
        efficiency[1] = 0.;

    }


    pimage = giraffe_image_new(CPL_TYPE_DOUBLE);
    status = giraffe_image_load(pimage, cpl_frame_get_filename(pframe), 0);

    if (status != 0) {
        cpl_msg_error(fctid, "Could not load extracted spectra '%s'!",
                      cpl_frame_get_filename(pframe));

        giraffe_image_delete(pimage);
        pimage = NULL;

        giraffe_paf_delete(qc);
        qc = NULL;

        return 1;
    }

    _pimage = giraffe_image_get(pimage);
    cx_assert(_pimage != NULL);


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

    if (status != 0) {
        cpl_msg_error(fctid, "Could not load extracted spectra fiber setup!");

        giraffe_table_delete(ptable);
        ptable = NULL;

        giraffe_image_delete(pimage);
        pimage = NULL;

        giraffe_paf_delete(qc);
        qc = NULL;

        return 1;
    }

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

    if (cpl_table_has_column(_ptable, "RP") == FALSE) {

        cpl_msg_warning(fctid, "Column 'RP' not found in fiber setup table!");
        cpl_msg_warning(fctid, "Setting lamp efficiencies (%s, %s) to 0.",
                        GIALIAS_QCLAMP, GIALIAS_QCLAMP_SIMCAL);

        efficiency[0] = 0.;
        efficiency[1] = 0.;

    }
    else {

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

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

            cpl_msg_warning(fctid, "Property '%s' not found in '%s'.",
                            GIALIAS_EXPTIME, cpl_frame_get_filename(rframe));
            cpl_msg_warning(fctid, "Setting lamp efficiencies (%s, %s) to 0.",
                            GIALIAS_QCLAMP, GIALIAS_QCLAMP_SIMCAL);

            efficiency[0] = 0.;
            efficiency[1] = 0.;

        }
        else {

            cxint fiber = 0;
            cxint nb = cpl_image_get_size_y(_pimage);
            cxint nf[2] = {0, 0};

            cxdouble exptime = cpl_propertylist_get_double(properties,
                GIALIAS_EXPTIME);
            cxdouble* _sum = NULL;

            cpl_image* sum = NULL;


            sum = cpl_image_collapse_create(_pimage, 0);
            _sum = cpl_image_get_data_double(sum);

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

                cxint rp = cpl_table_get_int(_ptable, "RP", fiber, NULL);

                if (rp == -1) {
                    efficiency[1] += _sum[fiber];
                    ++nf[1];
                }
                else {
                    efficiency[0] += _sum[fiber];
                    ++nf[0];
                }

            }

            _sum = NULL;

            cpl_image_delete(sum);
            sum = NULL;

            if (nf[0] == 0) {
                cpl_msg_warning(fctid, "No OzPoz fibers found in the "
                        "current fiber setup.");
                cpl_msg_warning(fctid, "Setting lamp efficiency (%s) to 0.",
                        GIALIAS_QCLAMP);
                efficiency[0] = 0.;
            }
            else {
                efficiency[0] /= nf[0] * nb * exptime;
            }

            if (nf[1] == 0) {
                cpl_msg_warning(fctid, "No simultaneous calibration fibers "
                        "found in the current fiber setup.");
                cpl_msg_warning(fctid, "Setting lamp efficiency (%s) to 0.",
                        GIALIAS_QCLAMP_SIMCAL);
                efficiency[1] = 0.;
            }
            else {
                efficiency[1] /= nf[1] * nb * exptime;
            }

        }

        properties = NULL;

    }

    _ptable = NULL;
    _pimage = NULL;

    giraffe_table_delete(ptable);
    ptable = NULL;

    giraffe_image_delete(pimage);
    pimage = NULL;


    /*
     * Load first raw image as reference
     */

    rframe = cpl_frameset_find(set, GIFRAME_ARC_SPECTRUM);

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

        giraffe_paf_delete(qc);
        qc = NULL;

        return 1;
    }

    cpl_msg_info(fctid, "Processing reference frame '%s' (%s)",
                 cpl_frame_get_filename(rframe), cpl_frame_get_tag(rframe));

    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 arc-lamp spectra '%s'!",
                      cpl_frame_get_filename(rframe));

        giraffe_image_delete(rimage);
        rimage = NULL;

        giraffe_paf_delete(qc);
        qc = NULL;

        return 1;

    }

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


    /*
     * Compute mean level of the first raw frame and count the number
     * of saturated pixels.
     */

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

    if (cpl_propertylist_has(properties, GIALIAS_OVSCX)) {

        cxint _ox = cpl_propertylist_get_int(properties, GIALIAS_OVSCX);
        cxint _nx = cpl_image_get_size_x(_rimage) - 2 * CX_MAX(0, _ox) - 1;

        w.x0 = CX_MAX(0, _ox) + 1;
        w.x1 = w.x0 + _nx;

    }

    if (cpl_propertylist_has(properties, GIALIAS_OVSCY)) {

        cxint _oy = cpl_propertylist_get_int(properties, GIALIAS_OVSCY);
        cxint _ny = cpl_image_get_size_y(_rimage) - 2 * CX_MAX(0, _oy) - 1;

        w.y0 = CX_MAX(0, _oy) + 1;
        w.y1 = w.y0 + _ny;

    }

    mean = cpl_image_get_mean_window(_rimage, w.x0, w.y0, w.x1, w.y1);

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

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


    /*
     * Process dispersion solution
     */

    qc = giraffe_qclog_open(0);

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

        giraffe_image_delete(rimage);
        rimage = NULL;

        return 1;
    }

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

    pframe = giraffe_get_frame(set, GIFRAME_WAVELENGTH_SOLUTION,
                               CPL_FRAME_GROUP_PRODUCT);

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

        giraffe_paf_delete(qc);
        qc = NULL;

        giraffe_image_delete(rimage);
        rimage = 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,
                                NULL);

    if (status != 0) {
        cpl_msg_error(fctid, "Could not load dispersion solution '%s'!",
                      cpl_frame_get_filename(pframe));

        giraffe_table_delete(ptable);
        ptable = NULL;

        giraffe_image_delete(rimage);
        rimage = NULL;

        giraffe_paf_delete(qc);
        qc = NULL;

        return 1;
    }

    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);
    giraffe_propertylist_copy(qclog, "INS.EXP.MODE", properties,
                              GIALIAS_SETUPNAME);
    giraffe_propertylist_copy(qclog, "INS.SLIT.NAME", properties,
                              GIALIAS_SLITNAME);
    giraffe_propertylist_copy(qclog, "INS.GRAT.NAME", properties,
                              GIALIAS_GRATNAME);
    giraffe_propertylist_copy(qclog, "INS.GRAT.WLEN", properties,
                              GIALIAS_GRATWLEN);
    giraffe_propertylist_copy(qclog, "INS.GRAT.ENC", properties,
                              GIALIAS_GRATPOS);

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

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

    giraffe_propertylist_copy(qclog, "PRO.WSOL.RMS", properties,
                              GIALIAS_WSOL_RMS);
    giraffe_propertylist_copy(qclog, "PRO.WSOL.NLINES", properties,
                       GIALIAS_WSOL_NLINES);
    giraffe_propertylist_copy(qclog, "PRO.WSOL.NACCEPT", properties,
                       GIALIAS_WSOL_NACCEPT);
    giraffe_propertylist_copy(qclog, "PRO.WSOL.NREJECT", properties,
                       GIALIAS_WSOL_NREJECT);
    giraffe_propertylist_copy(qclog, "PRO.SLIT.NFIBRES", properties,
                              GIALIAS_NFIBERS);


    giraffe_table_delete(ptable);
    ptable = NULL;

    giraffe_qclog_close(qc);
    qc = NULL;


    /*
     * Process rebinned arc-lamp spectrum
     */

    qc = giraffe_qclog_open(1);

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

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

    pframe = giraffe_get_frame(set, GIFRAME_ARC_LAMP_RBNSPECTRA,
                               CPL_FRAME_GROUP_PRODUCT);

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

        giraffe_image_delete(rimage);
        rimage = NULL;

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

    pimage = giraffe_image_new(CPL_TYPE_DOUBLE);
    status = giraffe_image_load(pimage, cpl_frame_get_filename(pframe), 0);

    if (status != 0) {
        cpl_msg_error(fctid, "Could not load rebinned arc-lamp spectra '%s'!",
                      cpl_frame_get_filename(pframe));

        giraffe_image_delete(pimage);
        pimage = NULL;

        giraffe_image_delete(rimage);
        rimage = NULL;

        giraffe_paf_delete(qc);
        qc = NULL;

        return 1;
    }

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

    if (status != 0) {
        cpl_msg_error(fctid, "Could not load rebinned arc-lamp spectra '%s'!",
                      cpl_frame_get_filename(pframe));

        giraffe_table_delete(ptable);
        ptable = NULL;

        giraffe_image_delete(pimage);
        pimage = NULL;

        giraffe_image_delete(rimage);
        rimage = NULL;

        giraffe_paf_delete(qc);
        qc = NULL;

        return 1;
    }

    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);
    giraffe_propertylist_copy(qclog, "INS.EXP.MODE", properties,
                              GIALIAS_SETUPNAME);
    giraffe_propertylist_copy(qclog, "INS.SLIT.NAME", properties,
                              GIALIAS_SLITNAME);
    giraffe_propertylist_copy(qclog, "INS.GRAT.NAME", properties,
                              GIALIAS_GRATNAME);
    giraffe_propertylist_copy(qclog, "INS.GRAT.WLEN", properties,
                              GIALIAS_GRATWLEN);

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

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

    giraffe_propertylist_copy(qclog, "PRO.DATAAVG", properties,
                              GIALIAS_DATAMEAN);
    giraffe_propertylist_copy(qclog, "PRO.DATARMS", properties,
                              GIALIAS_DATASIG);
    giraffe_propertylist_copy(qclog, "PRO.DATAMED", properties,
                              GIALIAS_DATAMEDI);
    giraffe_propertylist_copy(qclog, "PRO.SLIT.NFIBRES", properties,
                              GIALIAS_NFIBERS);


    cpl_propertylist_update_double(properties, GIALIAS_QCMEAN, mean);
    cpl_propertylist_set_comment(properties, GIALIAS_QCMEAN, "Mean level of "
                          "first raw frame");

    giraffe_propertylist_copy(qclog, "QC.OUT1.MEAN.RAW", properties,
                       GIALIAS_QCMEAN);


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


    /*
     * Calibration lamp monitoring
     */

    cpl_propertylist_update_double(properties, GIALIAS_QCLAMP, efficiency[0]);
    cpl_propertylist_set_comment(properties, GIALIAS_QCLAMP,
                                 "Calibration lamp efficiency");

    giraffe_propertylist_copy(qclog, "QC.LAMP.EFFIC", properties,
                              GIALIAS_QCLAMP);

    cpl_propertylist_update_double(properties, GIALIAS_QCLAMP_SIMCAL,
                                   efficiency[1]);
    cpl_propertylist_set_comment(properties, GIALIAS_QCLAMP_SIMCAL,
                                 "SIMCAL lamp efficiency");

    giraffe_propertylist_copy(qclog, "QC.LAMP.EFFIC1", properties,
                              GIALIAS_QCLAMP_SIMCAL);


    /* Compute rebinned emission line straightness. */

    _pimage = giraffe_image_get(pimage);

    _test = cpl_image_duplicate(_pimage);
    _tdata = cpl_image_get_data(_test);

    nx = cpl_image_get_size_x(_test);
    ny = cpl_image_get_size_y(_test);

    for (i = 0; i < nx * ny; i++) {
        _tdata[i] = _tdata[i] > 0. ? log(_tdata[i]) : 0.;
    }

    _tdata = NULL;

    _test0 = cpl_image_extract(_test, 4, 1, 4, ny);

    _test1 = cpl_image_collapse_create(_test, 1);
    cpl_image_divide_scalar(_test1, nx);

    cpl_image_delete(_test);
    _test = NULL;


    _test = cpl_image_subtract_create(_test0, _test1);

    cpl_image_delete(_test0);
    _test0 = NULL;

    cpl_image_delete(_test1);
    _test1 = NULL;


    _tdata = cpl_image_get_data(_test);

    rms = 0;

    for (i = 0; i < ny; i++) {
        _tdata[i] = exp(_tdata[i]);
        rms += pow(_tdata[i], 2.);
    }

    rms = sqrt(rms / (ny - 1));

    cpl_image_delete(_test);
    _test = NULL;

    cpl_propertylist_update_double(properties, GIALIAS_QCRBRMS, rms);
    cpl_propertylist_set_comment(properties, GIALIAS_QCRBRMS,
                                 "RMS of rebinned arc-lamp spectra");

    giraffe_propertylist_copy(qclog, "QC.WSOL.REBIN.RMS", properties,
                       GIALIAS_QCRBRMS);


    status = giraffe_image_save(pimage, cpl_frame_get_filename(pframe));

    if (status != 0) {
        cpl_msg_error(fctid, "Could not save rebinned arc-lamp spectra "
                      "'%s'!", cpl_frame_get_filename(pframe));

        giraffe_table_delete(ptable);
        ptable = NULL;

        giraffe_image_delete(pimage);
        pimage = NULL;

        giraffe_image_delete(rimage);
        rimage = NULL;

        giraffe_paf_delete(qc);
        qc = NULL;

        return 1;
    }

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

    if (status != 0) {
        cpl_msg_error(fctid, "Could not save rebinned arc-lamp spectra "
                      "'%s'!", cpl_frame_get_filename(pframe));

        giraffe_table_delete(ptable);
        ptable = NULL;

        giraffe_image_delete(pimage);
        pimage = NULL;

        giraffe_image_delete(rimage);
        rimage = NULL;

        giraffe_paf_delete(qc);
        qc = NULL;

        return 1;
    }

    giraffe_image_delete(pimage);
    pimage = NULL;

    giraffe_table_delete(ptable);
    ptable = NULL;

    giraffe_qclog_close(qc);
    qc = NULL;


    /*
     * Process line data
     */

    qc = giraffe_qclog_open(2);

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

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

    pframe = giraffe_get_frame(set, GIFRAME_LINE_DATA,
                               CPL_FRAME_GROUP_PRODUCT);

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

        giraffe_image_delete(rimage);
        rimage = NULL;

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


    /*
     * Load line data
     */

    plines = giraffe_linedata_new();

    status = giraffe_linedata_load(plines, cpl_frame_get_filename(pframe));

    if (status != 0) {
        cpl_msg_error(fctid, "Could not load line data '%s'!",
                      cpl_frame_get_filename(pframe));

        giraffe_linedata_delete(plines);
        plines = NULL;

        giraffe_image_delete(rimage);
        rimage = NULL;

        giraffe_paf_delete(qc);
        qc = NULL;

        return 1;
    }

    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);
    giraffe_propertylist_copy(qclog, "INS.EXP.MODE", properties,
                              GIALIAS_SETUPNAME);
    giraffe_propertylist_copy(qclog, "INS.SLIT.NAME", properties,
                              GIALIAS_SLITNAME);
    giraffe_propertylist_copy(qclog, "INS.GRAT.NAME", properties,
                              GIALIAS_GRATNAME);
    giraffe_propertylist_copy(qclog, "INS.GRAT.WLEN", properties,
                              GIALIAS_GRATWLEN);

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


    _pimage = giraffe_linedata_get_data(plines, "FWHM");

    if (_pimage == NULL) {
        cpl_msg_error(fctid, "FWHM line data not found!");

        giraffe_linedata_delete(plines);
        plines = NULL;

        giraffe_image_delete(rimage);
        rimage = NULL;

        giraffe_paf_delete(qc);
        qc = NULL;

        return 1;
    }

    nx = cpl_image_get_size_x(_pimage);
    ny = cpl_image_get_size_y(_pimage);

    pixels = cpl_image_get_data_const(_pimage);

    for (j = 0; j < 2; ++j) {

        register cxint ndata = nx * ny;

        npixel = 0;
        mean = 0.;
        rms  = 0.;

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

            if ((pixels[i] >= fwhm_domain[0]) &&
                (pixels[i] < fwhm_domain[1])) {
                mean += pixels[i];
                ++npixel;
            }

        }

        if (npixel == 0) {
            cpl_msg_error(fctid, "All line FWHM data are invalid!");

            giraffe_linedata_delete(plines);
            plines = NULL;

            giraffe_image_delete(rimage);
            rimage = NULL;

            giraffe_paf_delete(qc);
            qc = NULL;

            return 1;
        }

        mean /= npixel;

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

            if ((pixels[i] >= fwhm_domain[0]) &&
                (pixels[i] < fwhm_domain[1])) {
                rms += pow(pixels[i] - mean, 2.);
            }

        }

        if (npixel > 1) {
            rms = sqrt(rms / (npixel - 1));
        }

        fwhm_domain[0] = CX_MAX(mean - rmsscale * rms, 0.);
        fwhm_domain[1] = CX_MIN(mean + rmsscale * rms, 100.);

    }


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

    cx_assert(properties != NULL);

    if (cpl_propertylist_has(properties, GIALIAS_GRATWLEN) == FALSE) {
        cpl_msg_error(fctid, "Grating central wavelength property '%s' not "
        		      "found!", GIALIAS_GRATWLEN);

        cpl_propertylist_delete(properties);
        properties = NULL;

        giraffe_linedata_delete(plines);
        plines = NULL;

        giraffe_image_delete(rimage);
        rimage = NULL;

        giraffe_paf_delete(qc);
        qc = NULL;

        return 1;
    }

    if (cpl_propertylist_has(properties, GIALIAS_WSOL_SCALE) == FALSE) {
        cpl_msg_error(fctid, "Line data property '%s' not found!",
                      GIALIAS_WSOL_SCALE);

        cpl_propertylist_delete(properties);
        properties = NULL;

        giraffe_linedata_delete(plines);
        plines = NULL;

        giraffe_image_delete(rimage);
        rimage = NULL;

        giraffe_paf_delete(qc);
        qc = NULL;

        return 1;
    }

    wlcenter = cpl_propertylist_get_double(properties, GIALIAS_GRATWLEN);
    pixel2nm = cpl_propertylist_get_double(properties, GIALIAS_WSOL_SCALE);

    mean *= pixel2nm;
    rms *= pixel2nm;

    cpl_propertylist_update_double(properties, GIALIAS_QCRESOLAVG, mean);
    cpl_propertylist_set_comment(properties, GIALIAS_QCRESOLAVG,
                                 "Average line FWHM [nm]");

    cpl_propertylist_update_double(properties, GIALIAS_QCRESOLRMS, rms);
    cpl_propertylist_set_comment(properties, GIALIAS_QCRESOLRMS,
                                 "RMS of line FWHM [nm]");

    cpl_propertylist_update_int(properties, GIALIAS_QCRESOLTOT, nx * ny);
    cpl_propertylist_set_comment(properties, GIALIAS_QCRESOLTOT,
                                 "Total number of lines available for FWHM RMS "
                                 "computation");

    cpl_propertylist_update_int(properties, GIALIAS_QCRESOLLIN, npixel);
    cpl_propertylist_set_comment(properties, GIALIAS_QCRESOLLIN,
                                 "Number of lines used for FWHM RMS "
                                 "computation");

    cpl_propertylist_update_double(properties, GIALIAS_QCRESOLPWR,
                                   wlcenter / mean);
    cpl_propertylist_set_comment(properties, GIALIAS_QCRESOLPWR,
                                 "Resolving power");

    giraffe_propertylist_copy(qclog, "QC.RESOL.MEAN", properties,
                              GIALIAS_QCRESOLAVG);
    giraffe_propertylist_copy(qclog, "QC.RESOL.RMS", properties,
                              GIALIAS_QCRESOLRMS);
    giraffe_propertylist_copy(qclog, "QC.RESOL.NTOTAL", properties,
                              GIALIAS_QCRESOLTOT);
    giraffe_propertylist_copy(qclog, "QC.RESOL.NLINES", properties,
                              GIALIAS_QCRESOLLIN);
    giraffe_propertylist_copy(qclog, "QC.RESOL.POWER", properties,
                              GIALIAS_QCRESOLPWR);


    status = giraffe_linedata_save(plines, properties,
                                   cpl_frame_get_filename(pframe));

    if (status != 0) {
        cpl_msg_error(fctid, "Could not save line data "
                      "'%s'!", cpl_frame_get_filename(pframe));

        cpl_propertylist_delete(properties);
        properties = NULL;

        giraffe_linedata_delete(plines);
        plines = NULL;

        giraffe_image_delete(rimage);
        rimage = NULL;

        giraffe_paf_delete(qc);
        qc = NULL;

        return 1;
    }

    cpl_propertylist_delete(properties);
    properties = NULL;

    giraffe_qclog_close(qc);
    qc = NULL;


    /*
     * Cleanup
     */

    giraffe_image_delete(rimage);

    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,
                    "giwavecalibration",
                    "Compute dispersion solution from an arc-lamp spectrum.",
                    "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(),
                    giwavecalibration_create,
                    giwavecalibration_exec,
                    giwavecalibration_destroy);

    cpl_pluginlist_append(list, plugin);

    return 0;

}
