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

#include <cpl_parameter.h>
#include <cpl_parameterlist.h>
#include <cpl_image.h>
#include <cpl_msg.h>

#include "gimacros.h"
#include "gidebug.h"
#include "gierror.h"
#include "gialias.h"
#include "gimatrix.h"
#include "gimessages.h"
#include "gimath.h"
#include "gimath_lm.h"
#include "gifiberutils.h"
#include "giutils.h"
#include "girebinning.h"


/**
 * @defgroup girebinning Rebinning
 *
 * TBD
 */

/**@{*/

#define GIFITS_KEYWORD_MISSING_MSG "FITS KEYWORD [%s] not found!! Aborting..."
#define GIWAVECAL_GRATING_WAVELENGTH_EPSILON 0.0001


enum GiLocDataType {
    GILOCDATATYPE_UNDEFINED,
    GILOCDATATYPE_FITTED_DATA,
    GILOCDATATYPE_FIT_COEFFS
};

typedef enum GiLocDataType GiLocDataType;


struct GiGrat {
    cx_string *name;         /**< grating name e.g. HR            */
    cx_string *filter_name;  /**< grating filter name e.g. HR13   */
    cx_string *setup_name;   /**< grating setup name              */
    cx_string *slit_name;    /**< grating slit name e.g. Medusa1  */
    cxint      order;        /**< grating diffraction order       */
    cxdouble   wlen0;        /**< grating central wavelength {mm} */
    cxdouble   wlenmin;      /**< grating minimum wavelength {mm} */
    cxdouble   wlenmax;      /**< grating maximum wavelength {mm} */
    cxdouble   band;         /**< grating band                    */
    cxdouble   resol;        /**< grating resolution              */
    cxdouble   space;        /**< grating groove spacing {mm}     */
    cxdouble   theta;        /**< grating angle {rad}             */
    cxdouble   fcoll;        /**< collimator focal length {mm}    */
    cxdouble   gcam;         /**< camera magnification factor     */
    cxdouble   slitdx;       /**< slit position X offset {mm}     */
    cxdouble   slitdy;       /**< slit position Y offset {mm}     */
    cxdouble   slitphi;      /**< slit position angle {rad}       */
};

typedef struct GiGrat GiGrat;


struct GiFiberPosition {
    cpl_matrix *x_fiber;   /**< X Position of Fibers {mm} */
    cpl_matrix *y_fiber;   /**< Y Position of Fibers {mm} */
};

typedef struct GiFiberPosition GiFiberPosition;


struct GiLocPosition {
    cxint           ydeg;
    cxint           wdeg;
    GiLocDataType   type;
    cpl_image      *centroids;
    cpl_image      *widths;
};

typedef struct GiLocPosition GiLocPosition;


struct GiBinnParams {
    cxint  xdeg;     /**< Binning step in X direction {pixel} */
    cxint  ydeg;     /**< Binning step in Y direction {pixel} */
};

typedef struct GiBinnParams GiBinnParams;


struct GiSlitGeo {
    cxint        nsubslits;
    cpl_matrix **subslits;
};

typedef struct GiSlitGeo GiSlitGeo;

struct GiWcalSolution {
    cxbool subslitfit;
    lmrq_model_id opt_mod;
    cpl_matrix *opt_mod_params;
    GiSlitGeo *wav_coeffs;
    GiSlitGeo *wav_limits;
};

typedef struct GiWcalSolution GiWcalSolution;


struct GiRebinInfo {
    const cxchar* method;
    const cxchar* scale;
    const cxchar* range;
    const cxchar* units;

    cxdouble wmin;
    cxdouble wcenter;
    cxdouble wmax;
    cxdouble wstep;

    cxint offset;

};

typedef struct GiRebinInfo GiRebinInfo;


/*
 *  Static vars for spline interpolation...
 */

static cxdouble ddb, dde;


inline static cxint
_giraffe_resample_update_properties(GiImage* spectra, GiRebinInfo* info)
{

    cpl_image* image = giraffe_image_get(spectra);

    cpl_propertylist* properties = giraffe_image_get_properties(spectra);


    giraffe_error_push();

    cpl_propertylist_update_double(properties, GIALIAS_DATAMIN,
                                   cpl_image_get_min(image));
    cpl_propertylist_update_double(properties, GIALIAS_DATAMAX,
                                   cpl_image_get_max(image));

    cpl_propertylist_update_string(properties, GIALIAS_GIRFTYPE,
                                   "BINSP");

    cpl_propertylist_update_int(properties, GIALIAS_BINWNX,
                                cpl_image_get_size_y(image));
    cpl_propertylist_update_int(properties, GIALIAS_BINWNS,
                                cpl_image_get_size_x(image));

    cpl_propertylist_update_string(properties, GIALIAS_BUNIT,
                                   "adu");

    cpl_propertylist_update_string(properties, GIALIAS_CTYPE1,
                                   "INDEX");
    cpl_propertylist_update_string(properties, GIALIAS_CUNIT1,
                                   "");
    cpl_propertylist_update_double(properties, GIALIAS_CRPIX1,
                                   1.);
    cpl_propertylist_update_double(properties, GIALIAS_CRVAL1,
                                   1.);
    cpl_propertylist_update_double(properties, GIALIAS_CDELT1,
                                   1.);

    cpl_propertylist_update_string(properties, GIALIAS_CTYPE2,
                                   "AWAV");
    cpl_propertylist_update_string(properties, GIALIAS_CUNIT2,
                                   info->units);
    cpl_propertylist_update_double(properties, GIALIAS_CRPIX2,
                                   info->offset + 1);
    cpl_propertylist_update_double(properties, GIALIAS_CRVAL2,
                                   info->wmin);
    cpl_propertylist_update_double(properties, GIALIAS_CDELT2,
                                   info->wstep);

    cpl_propertylist_update_double(properties, GIALIAS_BINWLMIN,
                                   info->wmin);
    cpl_propertylist_update_double(properties, GIALIAS_BINWL0,
                                   info->wcenter);
    cpl_propertylist_update_double(properties, GIALIAS_BINWLMAX,
                                   info->wmax);
    cpl_propertylist_update_double(properties, GIALIAS_BINSTEP,
                                   info->wstep);
    cpl_propertylist_update_string(properties, GIALIAS_BINMETHOD,
                                   info->method);
    cpl_propertylist_update_string(properties, GIALIAS_BINSCALE,
                                   info->scale);
    cpl_propertylist_update_string(properties, GIALIAS_BINRANGE,
                                   info->range);

    if (cpl_error_get_code() != CPL_ERROR_NONE) {
        return 1;
    }

    giraffe_error_pop();

    return 0;

}


static GiGrat*
_giraffe_grating_new(void)
{

    GiGrat *grating = NULL;

    grating = (GiGrat*) cx_calloc(1, (cxsize)sizeof(GiGrat));

    grating->name        = cx_string_create("UNKNOWN");
    grating->filter_name = cx_string_create("UNKNOWN");
    grating->setup_name  = cx_string_create("UNKNOWN");
    grating->slit_name   = cx_string_create("UNKNOWN");

    return grating;

}


static void
_giraffe_grating_delete(GiGrat *grating)
{

    if (grating==NULL) { return; }

    if (grating->name!=NULL) {
        cx_string_delete(grating->name);
    }
    if (grating->filter_name!=NULL) {
        cx_string_delete(grating->filter_name);
    }
    if (grating->setup_name!=NULL) {
        cx_string_delete(grating->setup_name);
    }
    if (grating->slit_name!=NULL) {
        cx_string_delete(grating->slit_name);
    }
    cx_free(grating);

}


static cxint
_giraffe_grating_setup(const GiTable *grating_table,
                       const GiImage *grating_ass_img, GiGrat *grating_setup)
{

    /*************************************************************************
                                     Variables
    *************************************************************************/

    const cxchar *fctid = "_giraffe_grating_setup";

    cxdouble    wlen_match    = 0.0,
        wlen          = 0.0,
        tmp_gratgrv   = 0.0;

    cxint32     row_match     = 0,
        row_nulls,
        i             = 0;

    const cxchar *c_name_setup  = "SETUP";
    const cxchar *c_name_order  = "ORDER";
    const cxchar *c_name_wl0    = "WLEN0";
    const cxchar *c_name_wlmin  = "WLMIN";
    const cxchar *c_name_wlmax  = "WLMAX";
    const cxchar *c_name_band   = "BAND";
    const cxchar *c_name_theta  = "THETA";
    const cxchar *c_name_fcoll  = "FCOLL";
    const cxchar *c_name_gcam   = "GCAM";
    const cxchar *c_name_sdx    = "SDX";
    const cxchar *c_name_sdy    = "SDY";
    const cxchar *c_name_sdphi  = "SPHI";
    const cxchar *c_name_rmed   = "RMED";
    const cxchar *c_name_rifa   = "RIFA";

    cpl_propertylist  *ref_plimg     = NULL;
    cpl_table  *ref_gtable    = NULL;
    cx_string  *slit_name     = NULL;

    GiInstrumentMode  instrument_mode;


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

    if (grating_table  ==NULL) { return 1; }
    if (grating_ass_img==NULL) { return 1; }
    if (grating_setup  ==NULL) { return 1; }

    if ((ref_plimg=giraffe_image_get_properties(grating_ass_img))==NULL) {
        return 128;
    }

    if ((ref_gtable = giraffe_table_get(grating_table))==NULL) {
        return 128;
    }

    slit_name = cx_string_new();

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

    /*
     *  Retrieve Grating information from associated image...
     */

    if (!cpl_propertylist_has(ref_plimg, GIALIAS_GRATWLEN)) {
        cpl_msg_error(fctid, GIFITS_KEYWORD_MISSING_MSG, GIALIAS_GRATWLEN);
        cx_string_delete(slit_name);
        return 2;
    }
    else {
        grating_setup->wlen0 = cpl_propertylist_get_double(ref_plimg,
                                                           GIALIAS_GRATWLEN);
    }

    if (!cpl_propertylist_has(ref_plimg, GIALIAS_GRATORDER)) {
        cpl_msg_error(fctid, GIFITS_KEYWORD_MISSING_MSG, GIALIAS_GRATORDER);
        cx_string_delete(slit_name);
        return 2;
    }
    else {
        grating_setup->order = cpl_propertylist_get_int(ref_plimg, GIALIAS_GRATORDER);
    }

    if (!cpl_propertylist_has(ref_plimg, GIALIAS_SLITNAME)) {

        cpl_msg_error(fctid, GIFITS_KEYWORD_MISSING_MSG, GIALIAS_SLITNAME);
        cx_string_delete(slit_name);
        return 2;
    } else {
        cx_string_set(slit_name,
                      cpl_propertylist_get_string(ref_plimg, GIALIAS_SLITNAME));
    }

    if (!cpl_propertylist_has(ref_plimg, GIALIAS_GRATGRV)) {

        cpl_msg_error(fctid, GIFITS_KEYWORD_MISSING_MSG, GIALIAS_GRATGRV);
        cx_string_delete(slit_name);
        return 2;
    } else {
        tmp_gratgrv = cpl_propertylist_get_double(ref_plimg, GIALIAS_GRATGRV );
    }

    if (!cpl_propertylist_has(ref_plimg, GIALIAS_GRATNAME)) {
        cpl_msg_error(fctid, GIFITS_KEYWORD_MISSING_MSG, GIALIAS_GRATNAME);
        cx_string_delete(slit_name);
        return 2;
    } else {
        cx_string_set(grating_setup->name,
                      cpl_propertylist_get_string(ref_plimg, GIALIAS_GRATNAME));
    }

    if (!cpl_propertylist_has(ref_plimg, GIALIAS_FILTNAME)) {
        cpl_msg_error(fctid, GIFITS_KEYWORD_MISSING_MSG, GIALIAS_FILTNAME);
        cx_string_delete(slit_name);
        return 2;
    }
    else {
        cx_string_set(grating_setup->filter_name,
                      cpl_propertylist_get_string(ref_plimg, GIALIAS_FILTNAME));
    }


    /*
     *  Find wavelength nearest to central wavelength...
     */

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

        cxint _order = cpl_table_get_int(ref_gtable, c_name_order, i, NULL);

        if (_order == grating_setup->order) {

            wlen = cpl_table_get(ref_gtable, c_name_wl0, i, &row_nulls);

            if (fabs(wlen - grating_setup->wlen0) <
                fabs(wlen_match - grating_setup->wlen0)) {
                wlen_match = wlen;
                row_match  = i;
            }

        }
    }


    /*
     *  Have we found a match?...
     */

    if (fabs(wlen_match - grating_setup->wlen0) >
        GIWAVECAL_GRATING_WAVELENGTH_EPSILON) {

        cpl_msg_error(fctid, "Grating setup (wavelength %.2f nm, order %d) "
                      "not found in grating table!", grating_setup->wlen0,
                      grating_setup->order);
        cx_string_delete(slit_name);
        return 3;
    }
    else {
        cpl_msg_debug(fctid, "Found wlen0 in grating table at position %d",
                      row_match);
    }


    /*
     *  Retrieve values associated to matched wavelength from grating table...
     */

    cx_string_set(grating_setup->setup_name,
                  (cxchar*) cpl_table_get_string(ref_gtable, c_name_setup,
                                                 row_match));

    cx_string_set(grating_setup->slit_name, cx_string_get(slit_name));

    grating_setup->wlenmin = cpl_table_get(ref_gtable, c_name_wlmin,
                                           row_match, &row_nulls);

    grating_setup->wlenmax = cpl_table_get(ref_gtable, c_name_wlmax,
                                           row_match, &row_nulls);

    grating_setup->band = cpl_table_get(ref_gtable, c_name_band,
                                        row_match, &row_nulls);

    grating_setup->theta = cpl_table_get(ref_gtable, c_name_theta,
                                         row_match, &row_nulls);

    grating_setup->space = 1.0 / fabs(GI_MM_TO_NM * tmp_gratgrv);


    instrument_mode = giraffe_get_mode(ref_plimg);

    switch (instrument_mode) {
        case GIMODE_MEDUSA:
            grating_setup->resol = cpl_table_get(ref_gtable, c_name_rmed,
                                                 row_match, &row_nulls);
            break;

        case GIMODE_IFU:
            grating_setup->resol = cpl_table_get(ref_gtable, c_name_rifa,
                                                 row_match, &row_nulls);
            break;

        case GIMODE_ARGUS:
            grating_setup->resol = cpl_table_get(ref_gtable, c_name_rifa,
                                                 row_match, &row_nulls);
            break;

        default:
            grating_setup->resol = -1.0;
            break;
    }

    grating_setup->fcoll   =
        cpl_table_get(ref_gtable, c_name_fcoll, row_match, &row_nulls);

    grating_setup->gcam    =
        cpl_table_get(ref_gtable, c_name_gcam,  row_match, &row_nulls);

    grating_setup->slitdx  =
        cpl_table_get(ref_gtable, c_name_sdx,   row_match, &row_nulls);

    grating_setup->slitdy  =
        cpl_table_get(ref_gtable, c_name_sdy,   row_match, &row_nulls);

    grating_setup->slitphi =
        cpl_table_get(ref_gtable, c_name_sdphi, row_match, &row_nulls);

    cx_string_delete(slit_name);

    return 0;

}


static GiFiberPosition*
_giraffe_fiberposition_new(void)
{

    GiFiberPosition* tmp = NULL;

    tmp = (GiFiberPosition*) cx_calloc(1, sizeof(GiFiberPosition));

    tmp->x_fiber = NULL;
    tmp->y_fiber = NULL;

    return tmp;
}


static void
_giraffe_fiberposition_delete(GiFiberPosition *fp)
{

    if (fp != NULL) {

        if (fp->x_fiber) {
            cpl_matrix_delete(fp->x_fiber);
        }

        if (fp->y_fiber) {
            cpl_matrix_delete(fp->y_fiber);
        }

        cx_free(fp);

    }

    return;

}


static GiSlitGeo*
_giraffe_slitgeo_new(void)
{

    GiSlitGeo *sgeometry = NULL;

    sgeometry = cx_malloc(sizeof(GiSlitGeo));

    sgeometry->subslits = NULL;
    sgeometry->nsubslits = 0;

    return sgeometry;

}


static void
_giraffe_slitgeo_delete(GiSlitGeo *sgeometry)
{

    if (sgeometry != NULL) {

        if (sgeometry->subslits != NULL) {

            cxint i;

            for (i = 0; i < sgeometry->nsubslits; i++) {
                cpl_matrix_delete(sgeometry->subslits[i]);
            }

            cx_free(sgeometry->subslits);
        }

        cx_free(sgeometry);

    }

    return;

}


static cxint
_giraffe_slitgeo_size(GiSlitGeo *sgeometry)
{

    if (sgeometry == NULL) {
        return -1;
    }

    if (sgeometry->subslits != NULL) {
        return sgeometry->nsubslits;
    }

    return -1;

}


static void
_giraffe_slitgeo_resize(GiSlitGeo *sgeometry, cxint size)
{

    if (sgeometry == NULL) {
        return;
    }

    if (size == sgeometry->nsubslits) {
        return;
    }

    if (sgeometry->subslits != NULL) {

        cxint i;

        for (i = 0; i < sgeometry->nsubslits; i++) {
            cpl_matrix_delete(sgeometry->subslits[i]);
        }
    }

    cx_free(sgeometry->subslits);

    sgeometry->nsubslits = size;
    sgeometry->subslits = cx_calloc(sgeometry->nsubslits, sizeof(cpl_matrix*));

    return;

}


static void
_giraffe_slitgeo_create(GiSlitGeo *sgeometry, cxint idx, cxint nrow,
                        cxint ncol)
{

    if (sgeometry == NULL) {
        return;
    }

    if (sgeometry->subslits == NULL) {
        return;
    }

    if ((idx < 0) || (idx > sgeometry->nsubslits)) {
        return;
    }

    if (sgeometry->subslits[idx] != NULL) {
        cpl_matrix_delete(sgeometry->subslits[idx]);
    }

    sgeometry->subslits[idx] = cpl_matrix_new(nrow, ncol);

    return;

}


static void
_giraffe_slitgeo_set(GiSlitGeo *sgeometry, cxint idx, cpl_matrix *nm)
{

    if (sgeometry == NULL) {
        return;
    }

    if (sgeometry->subslits == NULL) {
        return;
    }

    if ((idx < 0) || (idx > sgeometry->nsubslits)) {
        return;
    }

    if (sgeometry->subslits[idx] != NULL) {
        cpl_matrix_delete(sgeometry->subslits[idx]);
    }

    if (nm) {
        sgeometry->subslits[idx] = cpl_matrix_duplicate(nm);
    }
    else {
        sgeometry->subslits[idx] = NULL;
    }

}


static cpl_matrix*
_giraffe_slitgeo_get(GiSlitGeo *sgeometry, cxint idx)
{

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

    if (sgeometry->subslits == NULL) {
        return NULL;
    }

    if ((idx < 0)||(idx > sgeometry->nsubslits)) {
        return NULL;
    }

    return (sgeometry->subslits[idx]);

}


static cxint
_giraffe_slitgeo_setup(const GiTable *slitgeo,
                       GiFiberPosition *fiber_slit_position,
                       GiSlitGeo *subslits, cxbool fitsubslit)
{

    const cxchar *const fctid = "_giraffe_slitgeo_setup";


    const cxchar *c_name_xf     = "XF";
    const cxchar *c_name_yf     = "YF";
    const cxchar *c_name_nspec  = "FPS";
    const cxchar *c_name_ssn    = "SSN";


    cpl_matrix *nspec = NULL;
    cpl_matrix *nsubslits = NULL;

    cxint       nr_slitgeo    = 0,
        max_nsubslits = 0,
        i             = 0,
        j             = 0,
        row_null      = 0,
        count         = 0,
        column_index  = 0,
        tmp_nspec     = 0,
        tmp_nsubslits = 0;

    cxdouble    tmp_xf,
        tmp_yf;

    cpl_table  *ref_slitgeo   = NULL;

    /*cpl_error_code ce_code;*/


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

    if (slitgeo            ==NULL) { return 1; }
    if (fiber_slit_position==NULL) { return 1; }
    if (subslits           ==NULL) { return 1; }

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

    ref_slitgeo = giraffe_table_get(slitgeo);
    nr_slitgeo  = cpl_table_get_nrow(ref_slitgeo);

    fiber_slit_position->x_fiber = cpl_matrix_new(nr_slitgeo, 1);
    fiber_slit_position->y_fiber = cpl_matrix_new(nr_slitgeo, 1);

    nspec     = cpl_matrix_new(nr_slitgeo, 1);
    nsubslits = cpl_matrix_new(nr_slitgeo, 1);

    /*
     *  Copy relevant data to matrices
     */

    max_nsubslits = 0;

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

        tmp_xf = cpl_table_get(ref_slitgeo, c_name_xf, i, &row_null);
        tmp_yf = cpl_table_get(ref_slitgeo, c_name_yf, i, &row_null);

        tmp_nspec = cpl_table_get_int(ref_slitgeo, c_name_nspec, i,
                                      &row_null) - 1;

        tmp_nsubslits = cpl_table_get_int(ref_slitgeo, c_name_ssn, i,
                                          &row_null);

        if (tmp_nsubslits>max_nsubslits) {
            max_nsubslits = tmp_nsubslits;
        }

        /*ce_code =*/ cpl_matrix_set(fiber_slit_position->x_fiber, i, 0, tmp_xf);
        /*ce_code =*/ cpl_matrix_set(fiber_slit_position->y_fiber, i, 0, tmp_yf);

        /*ce_code =*/ cpl_matrix_set(nspec,     i, 0, (cxdouble)tmp_nspec);
        /*ce_code =*/ cpl_matrix_set(nsubslits, i, 0, (cxdouble)tmp_nsubslits);

    }

    /*
     *  Create Slit Geometry
     */

    if (fitsubslit) {

        /* create multiple subslits */

        _giraffe_slitgeo_resize(subslits, max_nsubslits);

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

            cpl_matrix *ref_matrix = NULL;
            cxint       curr_ssn;

            count = 0;
            for (j=0; j<nr_slitgeo; j++) {
                curr_ssn = (cxint) cpl_matrix_get(nsubslits, j, 0);
                if (i==curr_ssn) {
                    ++count;
                }
            }

            _giraffe_slitgeo_create(subslits, i-1, count, 1);

            ref_matrix = _giraffe_slitgeo_get(subslits, i-1);

            column_index = 0;
            for (j = 0; j < nr_slitgeo; j++) {

                curr_ssn = (cxint) cpl_matrix_get(nsubslits, j, 0);

                if (i == curr_ssn) {
                    /*ce_code =*/ cpl_matrix_set(ref_matrix, column_index, 0,
                                             (cxdouble)j);
                    column_index++;
                }

            }
        }

        cpl_msg_debug(fctid, "Using multiple slits for Slit Geometry");

    }
    else {

        const cxchar *idx = giraffe_fiberlist_query_index(ref_slitgeo);


        /*
         * Create one subslit containing all fibers
         */

        cpl_matrix *ref_matrix = NULL;

        _giraffe_slitgeo_resize(subslits, 1);
        _giraffe_slitgeo_create(subslits, 0, nr_slitgeo, 1);

        ref_matrix = _giraffe_slitgeo_get(subslits, 0);

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

            cxint cs = cpl_table_get_int(ref_slitgeo, idx, j, NULL) - 1;
            /*ce_code =*/ cpl_matrix_set(ref_matrix, j, 0, cs);
//            ce_code = cpl_matrix_set(ref_matrix, j, 0, (cxdouble)j);

        }

        cpl_msg_debug(fctid, "Using single slit for Slit Geometry");

    }

    cpl_matrix_delete(nspec);
    nspec = NULL;

    cpl_matrix_delete(nsubslits);
    nsubslits = NULL;

    return 0;

}


static GiWcalSolution*
_giraffe_wcalsolution_new(void)
{

    GiWcalSolution* tmp = NULL;

    tmp = (GiWcalSolution*) cx_calloc(1, sizeof(GiWcalSolution));

    tmp->subslitfit     = FALSE;
    tmp->opt_mod        = LMRQ_UNDEFINED;
    tmp->opt_mod_params = NULL;
    tmp->wav_coeffs     = NULL;
    tmp->wav_limits     = NULL;

    return tmp;
}


static void
_giraffe_wcalsolution_delete(GiWcalSolution *ws)
{

    if (ws != NULL) {

        if (ws->opt_mod_params!=NULL) {
            cpl_matrix_delete(ws->opt_mod_params);
        }

        if (ws->wav_coeffs!=NULL) {
            _giraffe_slitgeo_delete(ws->wav_coeffs);
        }

        if (ws->wav_limits!=NULL) {
            _giraffe_slitgeo_delete(ws->wav_limits);
        }

        cx_free(ws);

    }

    return;

}


static GiWcalSolution*
_giraffe_wcalsolution_create(const GiTable *wavesolution)
{

    cxchar buffer[68];

    cxint i             = 0;
    cxint poly_x_deg    = 0;
    cxint poly_y_deg    = 0;
    cxint ncoefficients = 0;

    cxdouble* pd_coefficients = NULL;

    cpl_matrix* coefficients = NULL;
    cpl_matrix* limits       = NULL;

    cpl_propertylist* _properties = NULL;

    cpl_table* _table = NULL;

    GiWcalSolution* wavcoeff = NULL;



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

    wavcoeff = _giraffe_wcalsolution_new();

    _properties = giraffe_table_get_properties(wavesolution);


    /*
     *  Build up optical model from the wavelength solution properties
     */

    if (cpl_propertylist_has(_properties, GIALIAS_OPT_MOD) == TRUE) {

        const cxchar* optmod = cpl_propertylist_get_string(_properties,
            GIALIAS_OPT_MOD);

        if (strncmp(optmod, "xoptmod2", 8) == 0) {
            wavcoeff->opt_mod = LMRQ_XOPTMOD2;
        }
        else if (strncmp(optmod, "xoptmod", 7) == 0) {
            wavcoeff->opt_mod = LMRQ_XOPTMOD;
        }
        else {
            wavcoeff->opt_mod = LMRQ_UNDEFINED;
        }
    }

    if (wavcoeff->opt_mod == LMRQ_XOPTMOD2) {

        wavcoeff->opt_mod_params = cpl_matrix_new(7,1);

        if (cpl_propertylist_has(_properties, GIALIAS_OPTMDIR)) {
            cpl_matrix_set(
                wavcoeff->opt_mod_params,
                0,
                0,
                cpl_propertylist_get_int(_properties, GIALIAS_OPTMDIR)
                );
        } else {
            _giraffe_wcalsolution_delete(wavcoeff);
            return NULL;
        }

        if (cpl_propertylist_has(_properties, GIALIAS_WSOL_OPTMFCOLL)) {
            cpl_matrix_set(
                wavcoeff->opt_mod_params,
                1,
                0,
                cpl_propertylist_get_double(_properties, GIALIAS_WSOL_OPTMFCOLL)
                );
        } else {
            _giraffe_wcalsolution_delete(wavcoeff);
            return NULL;
        }

        if (cpl_propertylist_has(_properties, GIALIAS_WSOL_OPTMGCAM)) {
            cpl_matrix_set(
                wavcoeff->opt_mod_params,
                2,
                0,
                cpl_propertylist_get_double(_properties, GIALIAS_WSOL_OPTMGCAM)
                );
        } else {
            _giraffe_wcalsolution_delete(wavcoeff);
            return NULL;
        }

        if (cpl_propertylist_has(_properties, GIALIAS_WSOL_OPTMTHETA)) {
            cpl_matrix_set(
                wavcoeff->opt_mod_params,
                3,
                0,
                cpl_propertylist_get_double(_properties, GIALIAS_WSOL_OPTMTHETA)
                );
        } else {
            _giraffe_wcalsolution_delete(wavcoeff);
            return NULL;
        }

        if (cpl_propertylist_has(_properties, GIALIAS_WSOL_OPTMSDX)) {
            cpl_matrix_set(
                wavcoeff->opt_mod_params,
                4,
                0,
                cpl_propertylist_get_double(_properties, GIALIAS_WSOL_OPTMSDX)
                );
        } else {
            _giraffe_wcalsolution_delete(wavcoeff);
            return NULL;
        }

        if (cpl_propertylist_has(_properties, GIALIAS_WSOL_OPTMSDY)) {



            cpl_matrix_set(
                wavcoeff->opt_mod_params,
                5,
                0,
                cpl_propertylist_get_double(_properties, GIALIAS_WSOL_OPTMSDY)
                );

        } else {
            _giraffe_wcalsolution_delete(wavcoeff);
            return NULL;
        }

        if (cpl_propertylist_has(_properties, GIALIAS_WSOL_OPTMSPHI)) {
            cpl_matrix_set(
                wavcoeff->opt_mod_params,
                6,
                0,
                cpl_propertylist_get_double(_properties, GIALIAS_WSOL_OPTMSPHI)
                );

        } else {
            _giraffe_wcalsolution_delete(wavcoeff);
            return NULL;
        }

    } else if (wavcoeff->opt_mod==LMRQ_XOPTMOD) {

        wavcoeff->opt_mod_params = cpl_matrix_new(4,1);

        if (cpl_propertylist_has(_properties, GIALIAS_OPTMDIR)) {
            cpl_matrix_set(
                wavcoeff->opt_mod_params,
                0,
                0,
                cpl_propertylist_get_int(_properties, GIALIAS_OPTMDIR)
                );
        } else {
            _giraffe_wcalsolution_delete(wavcoeff);
            return NULL;
        }

        if (cpl_propertylist_has(_properties, GIALIAS_WSOL_OPTMFCOLL)) {
            cpl_matrix_set(
                wavcoeff->opt_mod_params,
                1,
                0,
                cpl_propertylist_get_double(_properties, GIALIAS_WSOL_OPTMFCOLL)
                );
        } else {
            _giraffe_wcalsolution_delete(wavcoeff);
            return NULL;
        }

        if (cpl_propertylist_has(_properties, GIALIAS_WSOL_OPTMGCAM)) {
            cpl_matrix_set(
                wavcoeff->opt_mod_params,
                2,
                0,
                cpl_propertylist_get_double(_properties, GIALIAS_WSOL_OPTMGCAM)
                );
        } else {
            _giraffe_wcalsolution_delete(wavcoeff);
            return NULL;
        }

        if (cpl_propertylist_has(_properties, GIALIAS_WSOL_OPTMTHETA)) {
            cpl_matrix_set(
                wavcoeff->opt_mod_params,
                3,
                0,
                cpl_propertylist_get_double(_properties, GIALIAS_WSOL_OPTMTHETA)
                );
        } else {
            _giraffe_wcalsolution_delete(wavcoeff);
            return NULL;
        }


    } else {

        _giraffe_wcalsolution_delete(wavcoeff);
        return NULL;

    }


    /*
     * Set up the optical model residuals if the given table contains them.
     */

    _table = giraffe_table_get(wavesolution);

    if (_table != NULL) {


        if (cpl_propertylist_has(_properties, GIALIAS_SSF)) {

            if (cpl_propertylist_get_bool(_properties, GIALIAS_SSF) == 0) {
                wavcoeff->subslitfit = FALSE;
            }
            else {
                wavcoeff->subslitfit = TRUE;
            }

        }
        else {

            _giraffe_wcalsolution_delete(wavcoeff);
            return NULL;

        }

        wavcoeff->wav_limits = _giraffe_slitgeo_new();
        _giraffe_slitgeo_resize(wavcoeff->wav_limits, 1);

        limits = cpl_matrix_new(1, 4);
        cpl_matrix_fill(limits, -1.);

        if (cpl_table_has_column(_table, "XMIN") &&
            cpl_table_has_column(_table, "XMAX")) {
            cpl_matrix_set(limits, 0, 0,
                           cpl_table_get_double(_table, "XMIN", 0, NULL));
            cpl_matrix_set(limits, 0, 1,
                           cpl_table_get_double(_table, "XMAX", 0, NULL));
        }

        if (cpl_table_has_column(_table, "YMIN") &&
            cpl_table_has_column(_table, "YMAX")) {
            cpl_matrix_set(limits, 0, 2,
                           cpl_table_get_double(_table, "YMIN", 0, NULL));
            cpl_matrix_set(limits, 0, 3,
                           cpl_table_get_double(_table, "YMAX", 0, NULL));
        }

        _giraffe_slitgeo_set(wavcoeff->wav_limits, 0, limits);

        cpl_matrix_delete(limits);
        limits = NULL;

        wavcoeff->wav_coeffs = _giraffe_slitgeo_new();
        _giraffe_slitgeo_resize(wavcoeff->wav_coeffs, 1);

        if (cpl_propertylist_has(_properties, GIALIAS_XRES_PDEG)) {

            cxchar *l, *r, *tmpstr;

            tmpstr = (cxchar*) cpl_propertylist_get_string(_properties,
                                                           GIALIAS_XRES_PDEG);

            l = &(tmpstr[0]);
            r = &(tmpstr[2]);

            poly_x_deg = atoi(l) + 1;
            poly_y_deg = atoi(r) + 1;

        }
        else {

            _giraffe_wcalsolution_delete(wavcoeff);
            return NULL;

        }

        ncoefficients = poly_x_deg * poly_y_deg;

        coefficients = cpl_matrix_new(poly_x_deg,poly_y_deg);
        pd_coefficients = cpl_matrix_get_data(coefficients);

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

            snprintf(buffer, sizeof buffer, "XC%-d", i);

            pd_coefficients[i] =
                cpl_table_get_double(_table, buffer, 0, NULL);

        }

        _giraffe_slitgeo_set(wavcoeff->wav_coeffs, 0, coefficients);

        cpl_matrix_delete(coefficients);
        coefficients = NULL;

    }

    return wavcoeff;

}


static cpl_image*
_giraffe_compute_pixel_abscissa(cpl_matrix* m_wavelengths,
                                cpl_matrix* m_wloffset,
                                GiFiberPosition* fiber_slit_position,
                                cpl_matrix* m_opt_mod_params,
                                lmrq_model lmrq_opt_mod_x)
{

    /*************************************************************************
                                     VARIABLES
    *************************************************************************/

    const cxchar *fctid = "_giraffe_compute_pixel_abscissa";

    register cxint n;
    register cxint line;
    register cxint nwlen;    /* number of reference lines    */
    register cxint ns;       /* number of reference spectra  */

    cxint nr_m_opt_mod_params = 0;

    cxdouble xccd = 0.;
    cxdouble* pd_xref             = NULL;
    cxdouble* pd_m_inputs         = NULL;
    cxdouble* pd_m_yfibre         = NULL;
    cxdouble* pd_m_xfibre         = NULL;
    cxdouble* pd_m_wavelengths    = NULL;
    cxdouble* pd_m_opt_mod_params = NULL;

    cpl_image* xref = NULL;

    cpl_matrix* m_inputs = NULL;


    /************************************************************************
                                   INITIALIZATION
    ************************************************************************/

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

    if ((fiber_slit_position == NULL) ||
        (fiber_slit_position->x_fiber == NULL) ||
        (fiber_slit_position->y_fiber == NULL)) {
        return NULL;
    }

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


    nwlen = cpl_matrix_get_nrow(m_wavelengths);
    ns    = cpl_matrix_get_nrow(fiber_slit_position->y_fiber);

    if ((m_wloffset != NULL) && (cpl_matrix_get_nrow(m_wloffset) != ns)) {
        return NULL;
    }


    /************************************************************************
                                    PREPROCESSING
    ************************************************************************/

    xref       = cpl_image_new(ns, nwlen, CPL_TYPE_DOUBLE);
    pd_xref    = cpl_image_get_data_double(xref);

    m_inputs    = cpl_matrix_new(lmrq_opt_mod_x.ninputs, 1);
    pd_m_inputs = cpl_matrix_get_data(m_inputs);

    pd_m_yfibre      = cpl_matrix_get_data(fiber_slit_position->y_fiber);
    pd_m_xfibre      = cpl_matrix_get_data(fiber_slit_position->x_fiber);
    pd_m_wavelengths = cpl_matrix_get_data(m_wavelengths);

    pd_m_opt_mod_params = cpl_matrix_get_data(m_opt_mod_params);
    nr_m_opt_mod_params = cpl_matrix_get_nrow(m_opt_mod_params);


    /************************************************************************
                                     PROCESSING
    ************************************************************************/

    if (m_wloffset != NULL) {

        cxdouble* pd_m_wloffset = cpl_matrix_get_data(m_wloffset);

        for (n = 0; n < ns; n++) {

            pd_m_inputs[2] = pd_m_yfibre[n];
            pd_m_inputs[1] = pd_m_xfibre[n];

            for (line = 0; line < nwlen; line++) {

                pd_m_inputs[0] = pd_m_wavelengths[line] + pd_m_wloffset[n];

                lmrq_opt_mod_x.cfunc(pd_m_inputs, pd_m_opt_mod_params,
                                     NULL, &xccd, NULL, nr_m_opt_mod_params);

                pd_xref[line * ns + n] = xccd;

            } /* each line */

        } /* each spectrum */

    }
    else {

        for (n = 0; n < ns; n++) {

            pd_m_inputs[2] = pd_m_yfibre[n];
            pd_m_inputs[1] = pd_m_xfibre[n];

            for (line = 0; line < nwlen; line++) {

                pd_m_inputs[0] = pd_m_wavelengths[line];

                lmrq_opt_mod_x.cfunc(pd_m_inputs, pd_m_opt_mod_params,
                                     NULL, &xccd, NULL, nr_m_opt_mod_params);

                pd_xref[line * ns + n] = xccd;

            } /* each line */

        } /* each spectrum */

    }

    cpl_matrix_delete(m_inputs);

    cpl_msg_debug(fctid, "Processing completed: Returning image [x,y] ="
                  " [%" CPL_SIZE_FORMAT ",%" CPL_SIZE_FORMAT "]",
                  cpl_image_get_size_x(xref), cpl_image_get_size_y(xref));

    return xref;

}


inline static cpl_matrix *
_giraffe_rebin_setup_model(GiImage *extspectra, GiWcalSolution *wcal)
{

    cxint npixel;

    cxdouble pixelsize;

    cpl_propertylist *properties = NULL;

    cpl_matrix *model = NULL;


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

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

    properties = giraffe_image_get_properties(extspectra);

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


    /*
     * Get the number of pixels extracted along the dispersion axis.
     */

    if (!cpl_propertylist_has(properties, GIALIAS_EXT_NX)) {
        return NULL;
    }

    npixel = cpl_propertylist_get_int(properties, GIALIAS_EXT_NX);


    /*
     * Compute pixel size along the dispersion axis in terms of mm.
     */

    if (!cpl_propertylist_has(properties, GIALIAS_PIXSIZX)) {
        return NULL;
    }

    pixelsize = cpl_propertylist_get_double(properties, GIALIAS_PIXSIZX);
    pixelsize /= 1000.;


    /*
     * Setup the optical model parameters
     */

    switch (wcal->opt_mod) {
        case LMRQ_XOPTMOD:
            if (cpl_matrix_get_nrow(wcal->opt_mod_params) != 4) {
                return NULL;
            }
            else {

                cxdouble direction = cpl_matrix_get(wcal->opt_mod_params, 0, 0);
                cxdouble fcoll = cpl_matrix_get(wcal->opt_mod_params, 1, 0);
                cxdouble cfact = cpl_matrix_get(wcal->opt_mod_params, 2, 0);

                model = cpl_matrix_new(4, 1);

                cpl_matrix_set(model, 0, 0, npixel * direction);
                cpl_matrix_set(model, 1, 0, pixelsize);
                cpl_matrix_set(model, 2, 0, fcoll);
                cpl_matrix_set(model, 3, 0, cfact);
            }
            break;

        case LMRQ_XOPTMOD2:
            if (cpl_matrix_get_nrow(wcal->opt_mod_params) != 7) {
                return NULL;
            }
            else {

                cxdouble direction = cpl_matrix_get(wcal->opt_mod_params, 0, 0);
                cxdouble fcoll = cpl_matrix_get(wcal->opt_mod_params, 1, 0);
                cxdouble cfact = cpl_matrix_get(wcal->opt_mod_params, 2, 0);
                cxdouble sdx = cpl_matrix_get(wcal->opt_mod_params, 4, 0);
                cxdouble sdy = cpl_matrix_get(wcal->opt_mod_params, 5, 0);
                cxdouble sphi = cpl_matrix_get(wcal->opt_mod_params, 6, 0);

                model = cpl_matrix_new(7, 1);

                cpl_matrix_set(model, 0, 0, npixel * direction);
                cpl_matrix_set(model, 1, 0, pixelsize);
                cpl_matrix_set(model, 2, 0, fcoll);
                cpl_matrix_set(model, 3, 0, cfact);
                cpl_matrix_set(model, 4, 0, sdx);
                cpl_matrix_set(model, 5, 0, sdy);
                cpl_matrix_set(model, 6, 0, sphi);
            }
            break;

        default:
            return NULL;
            break;
    }

    cx_assert(model != NULL);

    return model;

}


inline static cpl_matrix *
_giraffe_rebin_setup_grating(GiImage *extspectra, GiTable *grating,
                             GiTable *wlsolution)
{

    cxint status = 0;

    cpl_propertylist *properties = NULL;

    cpl_matrix *setup = NULL;

    GiGrat *grating_data = _giraffe_grating_new();


    status = _giraffe_grating_setup(grating, extspectra, grating_data);

    if (status != 0) {
        _giraffe_grating_delete(grating_data);
        return NULL;
    }


    properties = giraffe_table_get_properties(wlsolution);

    if (cpl_propertylist_has(properties, GIALIAS_WSOL_OMFCOLL)) {
        grating_data->fcoll = cpl_propertylist_get_double(properties,
                                                          GIALIAS_WSOL_OMFCOLL);
    }

    if (cpl_propertylist_has(properties, GIALIAS_WSOL_OMGCAM)) {
        grating_data->gcam = cpl_propertylist_get_double(properties,
                                                         GIALIAS_WSOL_OMGCAM);
    }

    if (cpl_propertylist_has(properties, GIALIAS_WSOL_OMGTHETA)) {
        grating_data->theta = cpl_propertylist_get_double(properties,
                                                          GIALIAS_WSOL_OMGTHETA);
    }

    if (cpl_propertylist_has(properties, GIALIAS_WSOL_OMSDX)) {
        grating_data->slitdx = cpl_propertylist_get_double(properties,
                                                           GIALIAS_WSOL_OMSDX);
    }

    if (cpl_propertylist_has(properties, GIALIAS_WSOL_OMSDY)) {
        grating_data->slitdy = cpl_propertylist_get_double(properties,
                                                           GIALIAS_WSOL_OMSDY);
    }

    if (cpl_propertylist_has(properties, GIALIAS_WSOL_OMSPHI)) {
        grating_data->slitphi = cpl_propertylist_get_double(properties,
                                                            GIALIAS_WSOL_OMSPHI);
    }


    setup = cpl_matrix_new(7, 1);

    cpl_matrix_set(setup, 0, 0, grating_data->theta);
    cpl_matrix_set(setup, 1, 0, grating_data->order);
    cpl_matrix_set(setup, 2, 0, grating_data->wlenmin / GI_MM_TO_NM);
    cpl_matrix_set(setup, 3, 0, grating_data->wlen0 / GI_MM_TO_NM);
    cpl_matrix_set(setup, 4, 0, grating_data->wlenmax / GI_MM_TO_NM);
    cpl_matrix_set(setup, 5, 0, grating_data->resol);
    cpl_matrix_set(setup, 6, 0, grating_data->space);

    _giraffe_grating_delete(grating_data);
    grating_data = NULL;

    return setup;

}


/**
 * @brief
 *   Find index i of first element of t >= x
 *
 * @param x - threshold value
 * @param t - array containing values
 * @param n - number of values in array @em t
 *
 * @return index i of first value @em t >= @em x, or -1 if an error occured
 *
 */

inline static cxint
_giraffe_spline_calc_circe(cxdouble x, register cxdouble* t, cxint n)
{

    register cxint lo = 0;
    register cxint hi = n - 1;


    if (x >= t[0] && x <= t[n - 1]) {

        while (hi - lo > 1) {

            register cxint mid = (lo + hi) / 2.;

            cxdouble tm = 0.;

            tm = t[mid];

            if (x < tm) {
                hi = mid;
            }
            else {
                lo = mid;
            }
        }

        return hi;

    }

    return -1;

}

/**
 * @brief
 *   Solve tridiagonal system
 *
 * @param a - TBD
 * @param b - TBD
 * @param c - TBD
 * @param f - TBD
 * @param x - TBD
 * @param n - number of values in array @em t
 *
 * @par Description
 * Function solves a tridiagonal linear system.
 * Diag elts mii=ai, subdiag mii-1=bi, superdiag mii+1=ci.
 * It is understood that b0 and cn-1 are zero, but are not referenced.
 * f is rhs, x soln, may be same array; all arrays are trashed
 *
 */

inline static void
_giraffe_spline_calc_tridi(register cxdouble* a, register cxdouble* b,
                           register cxdouble* c, register cxdouble* f,
                           register cxdouble* x, cxint n)
{

    register cxint i = 0;

    c[0] /= a[0];

    for (i = 1; i < n; i++) {
        c[i] /= (a[i] - b[i] * c[i - 1]);
    }

    f[0] /= a[0];

    for (i = 1; i < n; i++) {
        f[i] = (f[i] - b[i] * f[i - 1]) / (a[i] - b[i] * c[i - 1]);
    }

    x[n - 1] = f[n - 1];

    for (i = n - 2; i >= 0; i--) {
        x[i] = f[i] - c[i] * x[i + 1];
    }

    return;
}

/**
 * @brief
 *   Interpolate spline
 *
 * @param z   - Oordinate for which to calculate spline value
 * @param val - Value at z
 * @param x   - X coordinates
 * @param y   - Y coordinates
 * @param k   - Derivatives at x's
 * @retval Value of spline at @c z
 * @return ==0 if succesful, 1 if z is outside of range
 *
 * @par Description
 * General spline evaluator; xyk are the x,y, and derivative arrays, of
 * dimension n, dx the argument = (x-xi-1), m the index of the next GREATER
 * abscissa.
 *
 */

inline static cxint
_giraffe_spline_calc_interpolate(cxdouble z, cxdouble* val,
                                 register cxdouble* x, register cxdouble* y,
                                 register cxdouble* k, cxint n)
{

    cxint m = 0;

    cxdouble h = 0.;
    cxdouble t = 0.;
    cxdouble d = 0.;
    cxdouble a = 0.;
    cxdouble b = 0.;
    cxdouble dx = 0.;


    m = _giraffe_spline_calc_circe(z, x, n);

    if (m < 0) {

        /* out of bounds */
        if (z < x[0]) {
            dx   = z - x[0];
            *val = y[0] + dx * (k[0] + 0.5 * dx * ddb);
        } else {
            dx   = z - x[n - 1];
            *val = y[n - 1] + dx * (k[n - 1] + 0.5 * dx * dde);
        }

        return 1;

    }

    dx   = z - x[m - 1];
    h    = x[m] - x[m - 1];
    d    = (y[m] - y[m - 1]) / h;
    t    = dx / h;
    a    = (k[m - 1] - d) * (1 - t);
    b    = (k[m] - d) * t;
    *val = t * y[m] + (1 - t) * y[m - 1] + h * t * (1 - t) * (a - b);

    return 0;

}

/**
 * @brief
 *   Initialise Spline
 *
 * @param x     - TBD
 * @param y     - Value at z
 * @param k     - TBD
 * @param n     - TBD
 * @param q2b   - TBD
 * @param q2e   - TBD
 *
 * @retval ==0 if succesful, >0 if an error occured
 *
 * @par Description
 * Sets up spline derivative array k for a given x and y array of length
 * n points, n-1 intervals, for given estimates for the second derivatives
 * at the endpoints, q2b and q2e; "natural" boundary conditions for q2b=q2e=0
 *
 */

inline static cxint
_giraffe_spline_calc_initalize(cxdouble* x, cxdouble* y, cxdouble* k,
                               cxint n, cxdouble q2b, cxdouble q2e)
{

    register cxint i = 0;
    register cxint ip = 0;

    register cxdouble* a;
    register cxdouble* b;
    register cxdouble* c;
    register cxdouble* f;

    cxdouble hio = 0.;
    cxdouble hip = 0.;
    cxdouble dio = 0.;
    cxdouble dip = 0.;



    ddb = q2b;
    dde = q2e; /* save end second derivatives */

    a   = (cxdouble*) cx_malloc(4 * n * sizeof(cxdouble));

    b   = a + n;
    c   = b + n;
    f   = c + n;

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

        hip  = ((ip = i + 1) < n ? x[ip] - x[i] : 0.0);
        dip  = (ip < n ? (y[ip] - y[i]) / hip : 0.0);
        b[i] = (ip < n ? hip : hio);
        a[i] = 2.0 * (hip + hio);
        c[i] = (i > 0 ? hio : hip);
        f[i] = 3.0 * (hip * dio + hio * dip);

        if (i == 0) {
            f[0] = 3.0 * hip * dip - hip * hip * q2b * 0.5;
        }
        else if (i == n - 1) {
            f[n - 1] = 3.0 * hio * dio + hio * hio * q2e * 0.5;
        }

        dio = dip;
        hio = hip;
    }

    _giraffe_spline_calc_tridi(a, b, c, f, k, n);

    cx_free(a);

    return 0;

}

/**
 * @brief
 *   Spline interpolation
 *
 * @param  x_1 - X1 abcissa [n1]
 * @param  y_1 - Y1 values  [n1]
 * @param  x_2 - X2 abcissa [n2]
 * @retval y_2 - Y2 abcissa [n2]
 *
 * @par Description:
 * Given the vectors @em x1[1..n1] and @em y1[1..n1], fit a spline
 * to @em x1[1..n1] and @em y1[1..n1] and interpolate to the position of
 * vector @em x2[1..n2], leaving the answers on @em y2[1..n2]
 *
 * @return ==0 if succesful, >0 if an error occured
 *
 */

inline static cxint
_giraffe_rebin_interpolate_spline(cpl_matrix* x_1, cpl_matrix* y_1,
                                  cpl_matrix* x_2, cpl_matrix* y_2)
{

    cxint i = 0;
    cxint res = 0;
    cxint nr_x1 = 0;
    cxint nr_x2 = 0;

    cxdouble* k = NULL;
    cxdouble* pd_x1 = NULL;
    cxdouble* pd_y1 = NULL;
    cxdouble* pd_x2 = NULL;
    cxdouble* pd_y2 = NULL;



    if (x_1 == NULL || y_1 == NULL || x_2 == NULL || y_2 == NULL) {
        return 1;
    }

    nr_x1 = cpl_matrix_get_nrow(x_1);
    nr_x2 = cpl_matrix_get_nrow(x_2);

    pd_x1 = cpl_matrix_get_data(x_1);
    pd_y1 = cpl_matrix_get_data(y_1);
    pd_y2 = cpl_matrix_get_data(y_2);
    pd_x2 = cpl_matrix_get_data(x_2);


    /*
     * Get storage for spline coefficients and vector y2
     */

    k = (cxdouble*) cx_malloc(nr_x1 * sizeof(cxdouble));


    /*
     * Initialise spline calculation...
     */

    res = _giraffe_spline_calc_initalize(pd_x1, pd_y1, k, nr_x1, 0.0, 0.0);

    if (res < 0) {
        cx_free(k);
        return res;
    }

    /*
     *  Calculate spline...
     */

    for (i = 0; i < nr_x2; i++) {
        res = _giraffe_spline_calc_interpolate(pd_x2[i], &(pd_y2[i]), pd_x1,
                                               pd_y1, k, nr_x1);
    }

    cx_free(k);

    return 0;

}

/**
 * @brief
 *   Linear signal interpolation.
 *
 * @param x_1  -  Input matrix of x positions.
 * @param y_1  -  Input matrix of y positions.
 * @param x_2  -  Matrix of abscissas where the signal must be computed.
 * @retval y_2 -  Output matrix of computed signal values.
 *
 * @return ==0 if succesful, >0 if an error occured
 *
 * @par Description:
 * To apply this interpolation, you need to provide matrices @em x1 of X and
 * @em y1 of Y positions, and a matrix @em x2 of X positions where you
 * want Y to be computed (with linear interpolation).
 * @par
 * The returned signal @em y2 is an already allocated matrix of the same size
 * of @em x2
 *
 */

inline static cxint
_giraffe_rebin_interpolate_linear(
    cpl_matrix *x_1,
    cpl_matrix *y_1,
    cpl_matrix *x_2,
    cpl_matrix *y_2
    )
{

    /*************************************************************************
                                     Variables
    *************************************************************************/

    register cxdouble a, b ;
    register cxint    i, j, j_1, found, n1;

    cxint     nr_x1 = 0,
        nr_x2 = 0;
    cxdouble *pd_x1 = NULL,
        *pd_x2 = NULL,
        *pd_y2 = NULL,
        *pd_y1 = NULL;

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

    if (x_1 == NULL) { return 1; }
    if (y_1 == NULL) { return 1; }
    if (x_2 == NULL) { return 1; }
    if (y_2 == NULL) { return 1; }

    nr_x1 = cpl_matrix_get_nrow(x_1);
    nr_x2 = cpl_matrix_get_nrow(x_2);
    pd_x1 = cpl_matrix_get_data(x_1);
    pd_x2 = cpl_matrix_get_data(x_2);
    pd_y1 = cpl_matrix_get_data(y_1);
    pd_y2 = cpl_matrix_get_data(y_2);

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

    n1 = nr_x1 - 1;

    for (i = 0; i < nr_x2; i++) {
        /* Find (x1,y1) on the left of the current point */
        found = 0;
        for (j = 0; j < n1; j++) {
            if ((pd_x2[i] >= pd_x1[j]) && (pd_x2[i] <= pd_x1[j+1])) {
                found++ ;
                break ;
            }
        }

        if (!found) {
            pd_y2[i] = 0.0;
        } else {
            j_1      = j + 1;
            a        = (pd_y1[j_1] - pd_y1[j]) / (pd_x1[j_1] - pd_x1[j]);
            b        = pd_y1[j] - a * pd_x1[j];
            pd_y2[i] = (a * pd_x2[i] + b);

        }
    }

    return 0;

} /* end giraffe_rebin_interpolate_linear() */

/**
 * @brief
 *   Linear signal interpolation including errors.
 *
 * @param x_1    - Input matrix of x positions.
 * @param y_1    - Input matrix of y positions.
 * @param y_1err - Input matrix of y positions error.
 * @param x_2    - Matrix of abscissas where the signal must be computed.
 *
 * @retval y_2    - Output matrix of computed signal values.
 * @retval y_2err - Output matrix of computed signal error values.
 *
 * @return ==0 if succesful, >0 if an error occured
 *
 * @par Description:
 * To apply this interpolation, you need to provide matrices @em x_1 of X and
 * @em y_1 of Y positions and @em y_1err of Y position errors, and a
 * matrix @em x_2 of X positions where you want Y to be computed
 * (with linear interpolation).
 * @par
 * The returned signal @em y2 and @em y2err is an already allocated matrix of
 * the same size as @em x2
 *
 */

inline static cxint
_giraffe_rebin_interpolate_linear_error(
    cpl_matrix *x_1,
    cpl_matrix *y_1,
    cpl_matrix *y_1err,
    cpl_matrix *x_2,
    cpl_matrix *y_2,
    cpl_matrix *y_2err
    ) {

    /*************************************************************************
                                     Variables
    *************************************************************************/

    register double a, b ,dx;
    register int    i, j, j_1, found, n1 ;

    cxint     nr_x1    = 0,
        nr_x2    = 0;
    cxdouble *pd_x1    = NULL,
        *pd_y1    = NULL,
        *pd_y1err = NULL,
        *pd_x2    = NULL,
        *pd_y2    = NULL,
        *pd_y2err = NULL;

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

    if (x_1 == NULL) { return 1; }
    if (y_1 == NULL) { return 1; }
    if (y_1err == NULL) { return 1; }
    if (x_2 == NULL) { return 1; }
    if (y_2 == NULL) { return 1; }
    if (y_2err == NULL) { return 1; }

    nr_x1    = cpl_matrix_get_nrow(x_1);
    nr_x2    = cpl_matrix_get_nrow(x_2);
    pd_x1    = cpl_matrix_get_data(x_1);
    pd_y1    = cpl_matrix_get_data(y_1);
    pd_y1err = cpl_matrix_get_data(y_1err);
    pd_x2    = cpl_matrix_get_data(x_2);
    pd_y2    = cpl_matrix_get_data(y_2);
    pd_y2err = cpl_matrix_get_data(y_2err);

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

    n1 = nr_x1 - 1;

    for (i = 0; i < nr_x2; i++) {
        /* Find (x1,y1) on the left of the current point */
        found = 0;
        for (j = 0; j < n1; j++) {
            if ((pd_x2[i] >= pd_x1[j]) && (pd_x2[i] <= pd_x1[j+1])) {
                found++ ;
                break ;
            }
        }

        if (!found) {
            pd_y2[i]    = 0.0;
            pd_y2err[i] = 0.0;
        } else {

            j_1         = j + 1;
            dx          = (pd_x1[j_1] - pd_x1[j]);
            a           = (pd_y1[j_1] - pd_y1[j]) / dx;
            b           = pd_y1[j] - a * pd_x1[j] ;
            pd_y2[i]    = (a * pd_x2[i] + b) ;
            a           = (pd_y1err[j_1] - pd_y1err[j]) / dx;
            b           = pd_y1err[j] - a * pd_x1[j] ;
            pd_y2err[i] = (a * pd_x2[i] + b) ;

        }
    }

    return 0;

} /* end giraffe_rebin_interpolate_linear_error() */

/**
 * @brief
 *   Resample spectra using a linear interpolation scheme.
 *
 * @param rbspectra      Resampled spectrum fluxes.
 * @param rberrors       Resampled spectrum errors.
 * @param abcissa        Wavelength grid [nwavelength, nspectra]
 * @param exspectra      Extracted spectra [nspectra, nx]
 * @param exerrors       Extracted spectra errors [nspectra, nx]
 * @param opt_direction  Optical dispersion direction 1 or -1
 *
 * @return
 *   The function returns @c 0 on success, and a non-zero value otherwise.
 *
 * TBD
 *
 * @see giraffe_resample_spline()
 */

inline static cxint
_giraffe_resample_linear(cpl_image* rbspectra, cpl_image* rberrors,
                         cpl_image* abscissa, cpl_image* exspectra,
                         cpl_image* exerrors, cxint opt_direction)
{

    const cxchar* const fctid = "_giraffe_resample_linear";

    register cxlong n = 0;

    /*cxint status = 0;*/
    cxint nx = 0;         /* size of extracted spectra   */
    cxint ns = 0;         /* number of extracted spectra */
    cxint nwl = 0;        /* size of rebinned spectra    */

    cxdouble nx1 = 0.;
    cxdouble* _mabscissa = NULL;
    cxdouble* _mexspectra = NULL;
    cxdouble* _mexerrors = NULL;
    cxdouble* _mwavelength = NULL;
    cxdouble* _mrbspectra = NULL;
    cxdouble* _mrberrors = NULL;
    cxdouble* _abscissa = NULL;
    cxdouble* _exspectra = NULL;
    cxdouble* _exerrors = NULL;
    cxdouble* _rbspectra = NULL;
    cxdouble* _rberrors = NULL;

    cpl_matrix* mabscissa = NULL;
    cpl_matrix* mwavelength = NULL;
    cpl_matrix* mexspectra = NULL;
    cpl_matrix* mexerrors = NULL;
    cpl_matrix* mrbspectra = NULL;
    cpl_matrix* mrberrors = NULL;



    if ((abscissa == NULL) || (exspectra == NULL) || (rbspectra == NULL)) {
        return 1;
    }

    if ((exerrors != NULL) && (rberrors == NULL)) {
        return 1;
    }


    nx = cpl_image_get_size_y(exspectra);
    ns = cpl_image_get_size_x(exspectra);
    nwl = cpl_image_get_size_y(abscissa);

    if ((exerrors != NULL) &&
        ((nx != cpl_image_get_size_y(exerrors)) ||
         (ns != cpl_image_get_size_x(exerrors)))) {
         return 1;
    }

    nx1 = nx - 0.5;

    mabscissa = cpl_matrix_new(nx, 1);
    mexspectra = cpl_matrix_new(nx, 1);

    mwavelength = cpl_matrix_new(nwl, 1);
    mrbspectra = cpl_matrix_new(nwl, 1);

    _mabscissa = cpl_matrix_get_data(mabscissa);
    _mexspectra = cpl_matrix_get_data(mexspectra);
    _mwavelength = cpl_matrix_get_data(mwavelength);
    _mrbspectra = cpl_matrix_get_data(mrbspectra);

    _abscissa = cpl_image_get_data_double(abscissa);
    _exspectra = cpl_image_get_data_double(exspectra);
    _rbspectra = cpl_image_get_data_double(rbspectra);

    if (exerrors != NULL) {
        mexerrors = cpl_matrix_new(nx, 1);
        mrberrors = cpl_matrix_new(nwl, 1);

        _mexerrors = cpl_matrix_get_data(mexerrors);
        _mrberrors = cpl_matrix_get_data(mrberrors);

        _exerrors = cpl_image_get_data_double(exerrors);
        _rberrors = cpl_image_get_data_double(rberrors);
    }


    /*
     * Resample each spectrum to the new grid, taking the direction of the
     * optical model into account. If the errors of the spectra are
     * available they are also resampled to the new grid.
     */

    cpl_msg_debug(fctid, "Rebinning %d spectra, using dispersion direction "
                  "%d and linear interpolation", ns, opt_direction);

    for (n = 0; n < ns; n++) {

        register cxlong x = 0;


        for (x = 0; x < nwl; x++) {
            register cxlong j = x * ns + n;
            _mwavelength[x] = _abscissa[j];
        }

        if (exerrors == NULL) {

            if (opt_direction < 0) {

                for (x = 0; x < nx; x++) {

                    register cxlong j = x * ns + n;
                    register cxlong k = nx - x - 1;

                    _mabscissa[x] = (cxdouble) x;
                    _mexspectra[k] = _exspectra[j];
                }

            }
            else {

                for (x = 0; x < nx; x++) {

                    register cxlong j = x * ns + n;

                    _mabscissa[x] = (cxdouble) x;
                    _mexspectra[x] = _exspectra[j];

                }

            }


            /*
             * Linear interpolation of spectra and errors
             */

            /*status =*/ _giraffe_rebin_interpolate_linear(mabscissa,
                                                       mexspectra,
                                                       mwavelength,
                                                       mrbspectra);

            for (x = 0; x < nwl; x++) {

                register cxlong j = x * ns + n;

                if ((-0.5 > _mwavelength[x]) || (_mwavelength[x] > nx1)) {
                    _rbspectra[j] = 0.;
                }
                else {
                    _rbspectra[j] = _mrbspectra[x];
                }

            }

        }
        else {

            if (opt_direction < 0) {

                for (x = 0; x < nx; x++) {

                    register cxlong j = x * ns + n;
                    register cxlong k = nx - x - 1;

                    _mabscissa[x] = (cxdouble) x;
                    _mexspectra[k] = _exspectra[j];
                    _mexerrors[k] = _exerrors[j];
                }

            }
            else {

                for (x = 0; x < nx; x++) {

                    register cxlong j = x * ns + n;

                    _mabscissa[x] = (cxdouble) x;
                    _mexspectra[x] = _exspectra[j];
                    _mexerrors[x] = _exerrors[j];

                }

            }


            /*
             * Linear interpolation of spectra and errors
             */

            /*status =*/
                _giraffe_rebin_interpolate_linear_error(mabscissa,
                                                        mexspectra,
                                                        mexerrors,
                                                        mwavelength,
                                                        mrbspectra,
                                                        mrberrors);

            for (x = 0; x < nwl; x++) {

                register cxlong j = x * ns + n;

                if ((-0.5 > _mwavelength[x]) || (_mwavelength[x] > nx1)) {
                    _rbspectra[j] = 0.;
                    _rberrors[j] = 0.;
                }
                else {
                    _rbspectra[j] = _mrbspectra[x];
                    _rberrors[j] = _mrberrors[x];
                }

            }

        }

    } /* each spectrum */


    cpl_matrix_delete(mrbspectra);
    mrbspectra = NULL;

    cpl_matrix_delete(mwavelength);
    mwavelength = NULL;

    cpl_matrix_delete(mexspectra);
    mexspectra = NULL;

    cpl_matrix_delete(mabscissa);
    mabscissa = NULL;

    if (exerrors != NULL) {
        cpl_matrix_delete(mrberrors);
        mrberrors = NULL;

        cpl_matrix_delete(mexerrors);
        mexerrors = NULL;
    }

    cpl_msg_debug(fctid, "Rebinned %d spectra", ns);

    return 0;

}


/**
 * @brief
 *   Resample spectra using a spline interpolation scheme.
 *
 * @param rbspectra      Resampled spectrum fluxes.
 * @param rberrors       Resampled spectrum errors.
 * @param abcissa        Wavelength grid [nwavelength, nspectra]
 * @param exspectra      Extracted spectra [nspectra, nx]
 * @param exerrors       Extracted spectra errors [nspectra, nx]
 * @param opt_direction  Optical dispersion direction 1 or -1
 *
 * @return
 *   The function returns @c 0 on success, and a non-zero value otherwise.
 *
 * TBD
 *
 * @see giraffe_resample_linear()
 */

inline static cxint
_giraffe_resample_spline(cpl_image* rbspectra, cpl_image* rberrors,
                         cpl_image* abscissa, cpl_image* exspectra,
                         cpl_image* exerrors, cxint opt_direction)
{

    const cxchar* const fctid = "_giraffe_resample_spline";

    register cxlong n = 0;

    /*cxint status = 0;*/
    cxint nx = 0;         /* size of extracted spectra   */
    cxint ns = 0;         /* number of extracted spectra */
    cxint nwl = 0;        /* size of rebinned spectra    */

    cxdouble nx1 = 0.;
    cxdouble* _mabscissa = NULL;
    cxdouble* _mexspectra = NULL;
    cxdouble* _mexerrors = NULL;
    cxdouble* _mwavelength = NULL;
    cxdouble* _mrbspectra = NULL;
    cxdouble* _mrberrors = NULL;
    cxdouble* _abscissa = NULL;
    cxdouble* _exspectra = NULL;
    cxdouble* _exerrors = NULL;
    cxdouble* _rbspectra = NULL;
    cxdouble* _rberrors = NULL;

    cpl_matrix* mabscissa = NULL;
    cpl_matrix* mwavelength = NULL;
    cpl_matrix* mexspectra = NULL;
    cpl_matrix* mexerrors = NULL;
    cpl_matrix* mrbspectra = NULL;
    cpl_matrix* mrberrors = NULL;



    if ((abscissa == NULL) || (exspectra == NULL) || (rbspectra == NULL)) {
        return 1;
    }

    if ((exerrors != NULL) && (rberrors == NULL)) {
        return 1;
    }


    nx = cpl_image_get_size_y(exspectra);
    ns = cpl_image_get_size_x(exspectra);
    nwl = cpl_image_get_size_y(abscissa);

    if ((exerrors != NULL) &&
        ((nx != cpl_image_get_size_y(exerrors)) ||
         (ns != cpl_image_get_size_x(exerrors)))) {
             return 1;
         }

    nx1 = nx - 0.5;

    mabscissa = cpl_matrix_new(nx, 1);
    mexspectra = cpl_matrix_new(nx, 1);

    mwavelength = cpl_matrix_new(nwl, 1);
    mrbspectra = cpl_matrix_new(nwl, 1);

    _mabscissa = cpl_matrix_get_data(mabscissa);
    _mexspectra = cpl_matrix_get_data(mexspectra);
    _mwavelength = cpl_matrix_get_data(mwavelength);
    _mrbspectra = cpl_matrix_get_data(mrbspectra);

    _abscissa = cpl_image_get_data_double(abscissa);
    _exspectra = cpl_image_get_data_double(exspectra);
    _rbspectra = cpl_image_get_data_double(rbspectra);

    if (exerrors != NULL) {
        mexerrors = cpl_matrix_new(nx, 1);
        mrberrors = cpl_matrix_new(nwl, 1);

        _mexerrors = cpl_matrix_get_data(mexerrors);
        _mrberrors = cpl_matrix_get_data(mrberrors);

        _exerrors = cpl_image_get_data_double(exerrors);
        _rberrors = cpl_image_get_data_double(rberrors);
    }


    /*
     * Resample each spectrum to the new grid, taking the direction of the
     * optical model into account. If the errors of the spectra are
     * available they are also resampled to the new grid.
     */

    cpl_msg_debug(fctid, "Rebinning %d spectra, using dispersion direction "
                  "%d and linear interpolation", ns, opt_direction);

    for (n = 0; n < ns; n++) {

        register cxlong x = 0;


        for (x = 0; x < nwl; x++) {
            register cxlong j = x * ns + n;
            _mwavelength[x] = _abscissa[j];
        }

        if (exerrors == NULL) {

            if (opt_direction < 0) {

                for (x = 0; x < nx; x++) {

                    register cxlong j = x * ns + n;
                    register cxlong k = nx - x - 1;

                    _mabscissa[x] = (cxdouble) x;
                    _mexspectra[k] = _exspectra[j];
                }

            }
            else {

                for (x = 0; x < nx; x++) {

                    register cxlong j = x * ns + n;

                    _mabscissa[x] = (cxdouble) x;
                    _mexspectra[x] = _exspectra[j];

                }

            }


            /*
             * Spline interpolation of spectra and errors
             */

            /*status =*/ _giraffe_rebin_interpolate_spline(mabscissa,
                                                       mexspectra,
                                                       mwavelength,
                                                       mrbspectra);

            for (x = 0; x < nwl; x++) {

                register cxlong j = x * ns + n;

                if ((-0.5 > _mwavelength[x]) || (_mwavelength[x] > nx1)) {
                    _rbspectra[j] = 0.;
                }
                else {
                    _rbspectra[j] = _mrbspectra[x];
                }

            }

        }
        else {

            if (opt_direction < 0) {

                for (x = 0; x < nx; x++) {

                    register cxlong j = x * ns + n;
                    register cxlong k = nx - x - 1;

                    _mabscissa[x] = (cxdouble) x;
                    _mexspectra[k] = _exspectra[j];
                    _mexerrors[k] = _exerrors[j];
                }

            }
            else {

                for (x = 0; x < nx; x++) {

                    register cxlong j = x * ns + n;

                    _mabscissa[x] = (cxdouble) x;
                    _mexspectra[x] = _exspectra[j];
                    _mexerrors[x] = _exerrors[j];

                }

            }


            /*
             * Spline interpolation of spectra and linear interpolation of
             * errors
             */

            /*status =*/ _giraffe_rebin_interpolate_spline(mabscissa,
                                                       mexspectra,
                                                       mwavelength,
                                                       mrbspectra);

            /*status =*/ _giraffe_rebin_interpolate_linear(mabscissa,
                                                       mexerrors,
                                                       mwavelength,
                                                       mrberrors);

            for (x = 0; x < nwl; x++) {

                register cxlong j = x * ns + n;

                if ((-0.5 > _mwavelength[x]) || (_mwavelength[x] > nx1)) {
                    _rbspectra[j] = 0.;
                    _rberrors[j] = 0.;
                }
                else {
                    _rbspectra[j] = _mrbspectra[x];
                    _rberrors[j] = _mrberrors[x];
                }

            }

        }

    } /* each spectrum */


    cpl_matrix_delete(mrbspectra);
    mrbspectra = NULL;

    cpl_matrix_delete(mwavelength);
    mwavelength = NULL;

    cpl_matrix_delete(mexspectra);
    mexspectra = NULL;

    cpl_matrix_delete(mabscissa);
    mabscissa = NULL;

    if (exerrors != NULL) {
        cpl_matrix_delete(mrberrors);
        mrberrors = NULL;

        cpl_matrix_delete(mexerrors);
        mexerrors = NULL;
    }

    cpl_msg_debug(fctid, "Rebinned %d spectra", ns);

    return 0;

}


/**
 * @brief
 *   Compute Optical Model 3
 *
 * @param xccd
 * @param xfibre
 * @param yfibre
 * @param nx
 * @param pixsize
 * @param fcoll
 * @param cfact
 * @param gtheta
 * @param gorder
 * @param gspace
 * @param slitdx
 * @param slitdy
 * @param slitphi
 *
 * @return Computed value
 *
 * @par Description:
 * TBD
 *
 * \f[
 *  x_f = x_{fibre} \times ( 1.0 + slit_{phi} \times y_{fibre} ) + slit_{dx}
 * \f]
 * \f[
 *  y_f = y_{fibre} \times \sqrt {( 1.0 - slit_{phi}^2  )} + slit_{dy}
 * \f]
 * \f[
 *  d = \sqrt { x_f^2 + y_f^2 + f_{coll}^2  }
 * \f]
 * \f[
 *  SS_1 =
 *  ({d^2}-{y_f^2}) \times n_x^2 \times pixsize^2
 * \f]
 * \f[
 *  SS_2 =
 *  4.0 \times n_x^2 \times pixsize^2 \times cos(g_{theta})^2 \times
 *  (pixsize^2 \times x_{ccd} \times (x_{ccd} - n_x) + (c_{fact}^2 \times f_{coll}^2))
 * \f]
 * \f[
 *  SS_3 =
 *  4.0 \times (cos(g_{theta})^2 \times pixsize^2 \times x_{ccd} \times (x_{ccd} - n_x) +
 *  cos(g_{theta}) \times c_{fact} \times f_{coll} \times sin(g_{theta}) \times pixsize \times (x_{ccd} - n_x) +
 *  c_{fact}^2 \times f_{coll}^2 \times (1.0 - cos(g_{theta})^2))
 * \f]
 * \f[
 *  PS_1 =
 *  \sqrt { SS_1 + SS_2 + SS_3 }
 * \f]
 * \f[
 *  OptMod_3 = C_1 \times (
 *  n_x^2 \times pixsize^2 \times ( x_f \times cos(g_{theta}) + f_{coll} \times sin(g_{theta})) +
 *  4.0 \times x_f \times cos(g_{theta}) \times pixsize^2 \times x_{ccd} \times (x_{ccd} + n_x) +
 *  4.0 \times f_{coll} \times sin(g_{theta}) \times pixsize^2 \times x_{ccd} \times (x_{ccd} + n_x) -
 *  4.0 \times c_{fact}^2 \times f_{coll}^2 \times (f_{coll} \times sin(g_{theta}) - x_f \times cos(g_{theta})) +
 *  PS_1)
 * \f]
 * \f[
 *  C_1 = \frac {g_{space}} {
 *  n_x^2 \times pixsize^2 +
 *  4.0 \times x_{ccd}^2 \times pixsize^2 -
 *  4.0 \times pixsize^2 \times x_{ccd} \times n_x +
 *  4.0 \times c_{fact}^2 \times f_{coll}^2 \times g_{order} \times d
 *  }
 * \f]
 *
 * @see giraffe_rebin_compute_lambda_range()
 *
 */


static cxdouble
giraffe_rebin_compute_opt_mod3(cxdouble xccd, cxdouble xfibre,
                               cxdouble yfibre, cxdouble nx,
                               cxdouble pixsize, cxdouble fcoll,
                               cxdouble cfact, cxdouble gtheta,
                               cxdouble gorder, cxdouble gspace,
                               cxdouble slitdx, cxdouble slitdy,
                               cxdouble slitphi)
{

    /*************************************************************************
                                     Variables
    *************************************************************************/

    cxdouble xf, yf, d, t1, t12, t13, t18, t19, t2, t23, t28,
        t3, t31, t32, t36, t37, t39, t4, t40, t5, t62, t8, t9;

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

    /* should take care of negative NX value */

    xf = xfibre * (1.0 + slitphi * yfibre) + slitdx;
    yf = yfibre * sqrt(1.0 - slitphi * slitphi) + slitdy;
    d = sqrt(xf * xf + yf * yf + fcoll * fcoll);
    t1 = cos(gtheta);
    t2 = xf*t1;
    t3 = xccd*xccd;
    t4 = pixsize*pixsize;
    t5 = t3*t4;
    t8 = sin(gtheta);
    t9 = fcoll*t8;
    t12 = cfact*cfact;
    t13 = fcoll*fcoll;
    t18 = nx*nx;
    t19 = t18*t4;
    t23 = xccd*t4*nx;
    t28 = t12*t13;
    t31 = yf*yf;
    t32 = d*d;
    t36 = 4.0*t28;
    t37 = t19+4.0*t5-4.0*t23+t36;
    t39 = t1*t1;
    t40 = t39*t4;
    t62 = sqrt((-t31+t32)*t37*(4.0*t40*t3-4.0*xccd*nx*t40 +
                               8.0*xccd*t1*cfact*t9*pixsize+t19*t39 -
                               4.0*cfact*fcoll*t8*nx*t1*pixsize+t36 -
                               4.0*t28*t39));

    return((4.0*t2*t5 + 4.0*t9*t5 + 4.0*t12*t13*fcoll*t8+t2*t19+t9*t19 -
            4.0*t9*t23 - 4.0*t2*t23 + 4.0*t28*t2+t62)*gspace/t37/gorder/d);

} /* end giraffe_rebin_compute_opt_mod3() */

/**
 * @brief
 *   Compute Logarithm of Optical Model 3
 *
 * @param xccd
 * @param xfibre
 * @param yfibre
 * @param nx
 * @param pixsize
 * @param fcoll
 * @param cfact
 * @param gtheta
 * @param gorder
 * @param gspace
 * @param slitdx
 * @param slitdy
 * @param slitphi
 *
 * @return Computed value
 *
 * @see giraffe_rebin_compute_opt_mod3()
 *
 */

static cxdouble
giraffe_rebin_compute_log_opt_mod3(cxdouble xccd, cxdouble xfibre,
                                   cxdouble yfibre, cxdouble nx,
                                   cxdouble pixsize, cxdouble fcoll,
                                   cxdouble cfact, cxdouble gtheta,
                                   cxdouble gorder, cxdouble gspace,
                                   cxdouble slitdx, cxdouble slitdy,
                                   cxdouble slitphi)
{

    return log(giraffe_rebin_compute_opt_mod3(xccd, xfibre, yfibre, nx,
                                              pixsize, fcoll, cfact, gtheta,
                                              gorder, gspace, slitdx, slitdy,
                                              slitphi));

} /* end of giraffe_rebin_compute_log_opt_mod3() */

/**
 * @brief
 *   Compute lambda range
 *
 * @param fiber_slit_position - fibre X and Y positions {mm}
 * @param opt_mod_params - X optical model parameters
 * @param grat_params - Grating parameters
 * @param loglambda - linear or logarithmic
 * @param wlen_range_common - if TRUE, returns values common to all spectra,
 *                            if FALSE return true minimum and maximum lambda
 *
 * @retval lambda_min  - minimum value of lambda  {mm}
 * @retval lambda_max  - maximum value of lambda  {mm}
 *
 * @return ==0 if succesful, >0 if an error occured
 *
 * @par Description:
 * Computes linear or logarithmic lambda range for all spectra applying
 * the inverse model for the first (x = 0) and last (x = nx-1) CCD abcissa.
 * @em opt_mod_params are the optical model function parameters:
 * - nx      : size of abcissa
 * - pixsize : pixel size {mm}
 * - fcoll   : collimator focal length {mm}
 * - cfact   : camera magnification factor
 * - slitdx  : slit position X offset {mm}
 * - slitdy  : slit position Y offset {mm}
 * - slitphi : slit position angle {rad}
 * @par
 * @em grat_params are the optical model function parameters:
 * - gtheta   : grating angle {radian}
 * - gorder   : grating diffraction order
 * - gwlenmin : grating minimum wavelength {mm}
 * - gwlen0   : grating central wavelength {mm}
 * - gwlenmax : grating maximum wavelength {mm}
 * - gresol   : grating resolution
 * - gspace   : grating groove spacing {mm}
 * @par
 * The returned values @em lambda_min and @em lambda_max in millimeters
 * are rounded to the nearest integer value in nanometers.
 *
 * @see giraffe_rebin_wlen_linear()
 * @see giraffe_rebin_compute_log_opt_mod3()
 * @see giraffe_rebin_compute_opt_mod3()
 *
 */

static cxint
giraffe_rebin_compute_lambda_range(GiFiberPosition *fiber_slit_position,
                                   cpl_matrix *opt_mod_params,
                                   cpl_matrix *grat_params,
                                   cxbool lambda_logarithmic,
                                   cxbool wlen_range_common,
                                   cxdouble *lambda_min, cxdouble *lambda_max)
{

    /*************************************************************************
                                     Variables
    *************************************************************************/

    const cxchar *fctid = "giraffe_rebin_compute_lambda_range";

    register cxlong   n, ns;
    register cxdouble dx2, dnx, wl1, wl2;

    double (*computeWl) (double, double, double, double, double, double,
                         double, double, double, double, double, double,
                         double);

    cxdouble    *pd_opt_mod_params = NULL,
        *pd_xfiber        = NULL,
        *pd_yfiber        = NULL,
        *pd_grat_params    = NULL;

    cxint        nr_xfiber;

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

    if (fiber_slit_position         ==NULL) { return 1; }
    if (fiber_slit_position->x_fiber==NULL) { return 1; }
    if (fiber_slit_position->y_fiber==NULL) { return 1; }
    if (opt_mod_params              ==NULL) { return 1; }
    if (grat_params                 ==NULL) { return 1; }
    if (lambda_min                  ==NULL) { return 1; }
    if (lambda_max                  ==NULL) { return 1; }

    pd_opt_mod_params = cpl_matrix_get_data(opt_mod_params);

    pd_xfiber = cpl_matrix_get_data(fiber_slit_position->x_fiber);
    nr_xfiber = cpl_matrix_get_nrow(fiber_slit_position->x_fiber);

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

    if (lambda_logarithmic==TRUE) {
        computeWl = giraffe_rebin_compute_log_opt_mod3;
    } else {
        computeWl = giraffe_rebin_compute_opt_mod3;
    }

    dnx = abs(pd_opt_mod_params[O_NX]);
    dx2 = dnx - 1.0;
    ns  = nr_xfiber;

    if (wlen_range_common==TRUE) {
        *lambda_min = 0.0;
        *lambda_max = CX_MAXDOUBLE;
    } else {
        *lambda_min = CX_MAXDOUBLE;
        *lambda_max = 0.0;
    }

    pd_yfiber = cpl_matrix_get_data(fiber_slit_position->y_fiber);
    pd_grat_params = cpl_matrix_get_data(grat_params);

    for (n = 0; n < ns; n++) {

        /* first abcissa  of each spectrum */
        wl1 =
            computeWl(
                0.0,
                pd_xfiber[n],
                pd_yfiber[n],
                dnx,
                pd_opt_mod_params[O_PXSIZ],
                pd_opt_mod_params[O_FCOLL],
                pd_opt_mod_params[O_CFACT],
                pd_grat_params[G_THETA],
                pd_grat_params[G_ORDER],
                pd_grat_params[G_SPACE],
                pd_opt_mod_params[O_SOFFX],
                pd_opt_mod_params[O_SOFFY],
                pd_opt_mod_params[O_SPHI]
                );

        /* last abcissa  of each spectrum */
        wl2 =
            computeWl(
                dx2,
                pd_xfiber[n],
                pd_yfiber[n],
                dnx,
                pd_opt_mod_params[O_PXSIZ],
                pd_opt_mod_params[O_FCOLL],
                pd_opt_mod_params[O_CFACT],
                pd_grat_params[G_THETA],
                pd_grat_params[G_ORDER],
                pd_grat_params[G_SPACE],
                pd_opt_mod_params[O_SOFFX],
                pd_opt_mod_params[O_SOFFY],
                pd_opt_mod_params[O_SPHI]
                );

        if (wlen_range_common==TRUE) {

            /* common range = [max(wlen_min), min(wlen_max)] */

            if (pd_opt_mod_params[O_NX] < 0) {
                *lambda_max = CX_MIN(*lambda_max, wl1);
                *lambda_min = CX_MAX(*lambda_min, wl2);
            } else {
                *lambda_max = CX_MIN(*lambda_max, wl2);
                *lambda_min = CX_MAX(*lambda_min, wl1);
            }

        } else {

            /* full range = [min(wlen_min), max(wlen_max)] */

            if (pd_opt_mod_params[O_NX] < 0) {
                *lambda_max = CX_MAX(*lambda_max, wl1);
                *lambda_min = CX_MIN(*lambda_min, wl2);
            } else {
                *lambda_max = CX_MAX(*lambda_max, wl2);
                *lambda_min = CX_MIN(*lambda_min, wl1);
            }

        }

    } /* for each spectrum */

    if (wlen_range_common==TRUE) {
        /* round to minimum range in integer nanometers */
        *lambda_max = floor((*lambda_max) * GI_MM_TO_NM) / GI_MM_TO_NM;
        *lambda_min = ceil( (*lambda_min) * GI_MM_TO_NM) / GI_MM_TO_NM;
    } else {
        /* round to maximum range in integer nanometers */
        *lambda_max = ceil( (*lambda_max) * GI_MM_TO_NM) / GI_MM_TO_NM;
        *lambda_min = floor((*lambda_min) * GI_MM_TO_NM) / GI_MM_TO_NM;
    }

    cpl_msg_debug(fctid, "Rebinning lambda range now: [%12.6f,%12.6f]",
                  *lambda_min, *lambda_max);

    return 0;

} /* end giraffe_rebin_compute_lambda_range() */

/**
 * @brief
 *   Compute X residuals in pixels.
 *
 * @param locPos - localization mask position
 * @param abcissa - X CCD abcissa [nwl,ns] {pixels}
 * @param slitGeo - subslit fibre geometry
 * @param xresiduals_coeff - @em xresiduals_coeff[] list of
 *                    matrices [xwsXdeg,xwsYdeg] containing X residuals
 *                    fitted coefficients. one matrix for each subslit.
 * @return image of fitted X residuals [ns,nwl] or NULL if an error occured
 *
 * @par Description:
 * For each spectrum of each subslit given by @em slitGeo computes
 * the fit of the residuals using the Chebyshev coefficients given by
 * The returned image @em x_residuals_img[nwlen,ns] must be freed using
 * cpl_image_delete().
 *
 * @see computePixelAbcissa(), GiLocPosition, GiSlitGeo
 *
 */

static cpl_image*
giraffe_rebin_compute_pixel_x_residuals(
    GiLocPosition   *locPos,
    cpl_image       *abcissa,
    GiSlitGeo  *slitGeo,
    GiSlitGeo  *xresiduals_limits,
    GiSlitGeo  *xresiduals_coeff
    ) {

    /*************************************************************************
                                     Variables
    *************************************************************************/

    const cxchar *fctid = "giraffe_rebin_compute_pixel_x_residuals";

    cxdouble xmin, xmax, ymin, ymax, yup, ylo, yccd, ywid;
    cxint    n, m, x, xx, x0, i, j, k, l, xxp, x0p;

    cxint             subslit,
        nfibers,
        /*nmin,*/
        /*nmax,*/
        /*nlen,*/
        nx,       /* size of original spectra */
        ns,       /* number of spectra */
        nwl,      /* size of rebinned spectra */
        nf,
        nstart,
        ndata,
        nr_fit,
        nc_fit;

    cpl_matrix       *xss                = NULL,
        *yss                = NULL,
        *fit                = NULL,
        *curr_xres_limits   = NULL,
        *curr_xres_coeff    = NULL;

    cpl_image        *x_residuals_img    = NULL;

    cpl_matrix       *curr_subslit       = NULL;

    cxdouble      /*   *pd_curr_subslit    = NULL,*/
        *pd_abcissa         = NULL,
        *pd_xss             = NULL,
        *pd_yss             = NULL;

    cxdouble         *pd_locy            = NULL,
        *pd_locw            = NULL,
        *buffer             = NULL,
        *pd_fit             = NULL,
        *pd_x_residuals_img = NULL;


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

    if (locPos            ==NULL) { return NULL; }
    if (locPos->centroids ==NULL) { return NULL; }
    if (locPos->widths    ==NULL) { return NULL; }
    if (abcissa           ==NULL) { return NULL; }
    if (slitGeo           ==NULL) { return NULL; }
    if (xresiduals_limits ==NULL) { return NULL; }
    if (xresiduals_coeff  ==NULL) { return NULL; }

    nx  = cpl_image_get_size_y(locPos->centroids);
    ns  = cpl_image_get_size_x(locPos->centroids);
    nf  = cpl_image_get_size_x(abcissa);
    nwl = cpl_image_get_size_y(abcissa);

    cpl_msg_debug(
        fctid,
        "Computing pixel x residuals, using nr spec/nr lines/orig abcissa "
        "size: %d/%d/%d",
        ns,
        nwl,
        nx
        );

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

    x_residuals_img    = cpl_image_new(nf, nwl, CPL_TYPE_DOUBLE);
    pd_x_residuals_img = cpl_image_get_data_double(x_residuals_img);
    pd_abcissa         = cpl_image_get_data_double(abcissa);

    nstart = 0;

    for (subslit = 0; subslit<_giraffe_slitgeo_size(slitGeo); subslit++) {

        curr_subslit    = _giraffe_slitgeo_get(slitGeo, subslit);
        /*pd_curr_subslit = cpl_matrix_get_data(curr_subslit);*/

        giraffe_matrix_sort(curr_subslit);

        curr_xres_limits =
            cpl_matrix_duplicate(
                _giraffe_slitgeo_get(xresiduals_limits, subslit)
                );

        curr_xres_coeff =
            cpl_matrix_duplicate(
                _giraffe_slitgeo_get(xresiduals_coeff, subslit)
                );

        /* Get spectra/fibers range for current subslit */
        nfibers = cpl_matrix_get_nrow(curr_subslit);

        /*nmin    = (cxint) pd_curr_subslit[0];*/
        /*nmax    = (cxint) pd_curr_subslit[nfibers - 1];*/
        /*nlen    = nmax - nmin + 1;*/
        ndata   = nwl * nfibers;

        ymax    = 0.0;
        ymin    = CX_MAXDOUBLE;   /* has to be smaller than this one! */

        xss     = cpl_matrix_new(ndata, 1); /* X abcissas for subslit  */
        yss     = cpl_matrix_new(ndata, 1); /* Y ordinates             */

        pd_xss  = cpl_matrix_get_data(xss);
        pd_yss  = cpl_matrix_get_data(yss);

        pd_locy = cpl_image_get_data_double(locPos->centroids);
        pd_locw = cpl_image_get_data_double(locPos->widths);


        k = 0;

        for (m = 0, n = 0; n < nfibers; n++, m++) {

            i = 0;  /* running index for valid points */

            for (x = 0; x < nwl; x++) {

                j  = x * nf + (nstart + n);
                x0 = (cxint) floor(pd_abcissa[j]);
                xx = (cxint) ceil(pd_abcissa[j]);

                x0 = CX_MAX(CX_MIN(x0, nx - 1), 0);
                xx = CX_MAX(CX_MIN(xx, nx - 1), 0);

                l   = i  * nfibers + m;
                xxp = xx * ns   + cpl_matrix_get(curr_subslit, n, 0);
                x0p = x0 * ns   + cpl_matrix_get(curr_subslit, n, 0);

                pd_xss[l] = pd_abcissa[j];

                /*
                 * Corresponding Y centroid  and width using
                 * linear interpolation)
                 */

                yccd      = pd_locy[x0p];
                pd_yss[l] = yccd + ((pd_locy[xxp] - yccd) * (pd_xss[l] - x0));

                ywid = pd_locw[x0p];
                ywid = ywid + ((pd_locw[xxp] - ywid) * (pd_xss[l] - x0));

                /* Get y range for current subslit */
                yup = yccd + ywid;
                ylo = yccd - ywid;

                /* determine minimum and maximum */
                if (ymax < yup) {
                    ymax = yup;
                }

                if (ymin > ylo) {
                    ymin = ylo;
                }

                ++i;
                ++k;

            }
        }

        /* resize matrices to number of points found */
        cpl_matrix_set_size(xss, k, 1);
        cpl_matrix_set_size(yss, k, 1);
        pd_xss = cpl_matrix_get_data(xss);
        pd_yss = cpl_matrix_get_data(yss);

        xmin = cpl_matrix_get(curr_xres_limits, 0, 0);
        xmax = cpl_matrix_get(curr_xres_limits, 0, 1);
        ymin = cpl_matrix_get(curr_xres_limits, 0, 2);
        ymax = cpl_matrix_get(curr_xres_limits, 0, 3);

        xmin = xmin < 0. ? 0. : xmin;
        xmax = xmax < 0. ? (cxdouble)nx : xmax;

        ymin = ymin < 0. ? 0. : ymin;
        ymax = ymax < 0. ? 2048. : ymax;

        /* do chebyshev fit */
        fit =
            giraffe_chebyshev_fit2d(
                xmin, ymin,
                (xmax - xmin),
                (ymax - ymin + 1.0),
                curr_xres_coeff,
                xss,
                yss
                );

        cpl_matrix_delete(yss);
        cpl_matrix_delete(xss);
        cpl_matrix_delete(curr_xres_coeff);
        cpl_matrix_delete(curr_xres_limits);

        /* resize fit found to match image dimensions */
        buffer = cpl_matrix_get_data(fit);
        cpl_matrix_unwrap(fit);
        fit = NULL;

        fit = cpl_matrix_wrap(nwl, nfibers, buffer);
        pd_fit = cpl_matrix_get_data(fit);
        nr_fit = cpl_matrix_get_nrow(fit);
        nc_fit = cpl_matrix_get_ncol(fit);

        /* subslit fit goes into whole fit */
        for (x = 0; x < nr_fit; x++) {
            for (k = nstart, n = 0; n < nc_fit; n++, k++) {
                pd_x_residuals_img[x * nf + k] = pd_fit[x * nc_fit + n];
            }
        }

        cpl_matrix_delete(fit);
        fit = NULL;

        nstart += nfibers;

    } /* for each subslit */

    cpl_msg_debug(
        fctid,
        "Computed pixel x residuals, returning image [%d,%d]",
        ns,
        nwl
        );

    return x_residuals_img;

} /* end giraffe_rebin_compute_pixel_x_residuals() */


/**
 * @brief
 *   Compute rebinning X pixel abcissa for all fibers
 *
 * @param fiber_slit_position  fibre X and Y slit positions {mm}
 * @param locPos               localization mask position [nx,ns]*2
 * @param slitGeo              subslit fibre geometry
 * @param xresiduals_coeff     X residuals fitted coefficients per subslit
 * @param grat_params          grating parameters
 * @param opt_mod_params       optical model parameters
 * @param wloffsets            wavelength corrections to be added.
 * @param lmrq_opt_mod_x       optical model to use
 * @param binPrms              rebinning parameters
 *
 * @return wlen rebinned abcissa in pixels [nx,ns] image or NULL
 *         if an error occured
 *
 * @par Description:
 * Computes X abcissa in pixels for each wavelength spectrum using specified
 * global optical model @em lmrq_opt_mod_x with given parameters
 * @em opt_mod_params and @em grat_params.
 * X residuals are taken into account if specified and if fitted
 * coefficients @em xresiduals_coeff are provided.
 * The wavelength used are specified by @em binPrms.
 * The returned cpl_image @em abcissa must be freed using
 * cpl_image_delete().
 *
 * @see computePixelAbcissa()
 * @see giraffe_rebin_compute_pixel_x_residuals()
 *
 */

static cpl_image*
giraffe_rebin_compute_rebin_abcissa(GiFiberPosition* fiber_slit_position,
                                    GiLocPosition* locPos,
                                    GiSlitGeo* slitGeo,
                                    GiSlitGeo* xresiduals_limits,
                                    GiSlitGeo* xresiduals_coeff,
                                    cpl_matrix* grat_params,
                                    cpl_matrix* opt_mod_params,
                                    cpl_matrix* wloffsets,
                                    lmrq_model lmrq_opt_mod_x,
                                    GiRebinParams binPrms)
{

    /*************************************************************************
                                     Variables
    *************************************************************************/

    const cxchar* const fctid = "giraffe_rebin_compute_rebin_abcissa";

    cpl_matrix* wlengths    = NULL;
    cpl_matrix* temp_params = NULL;

    cpl_image* abcissa         = NULL;
    cpl_image* x_residuals_img = NULL;

    cxdouble* pd_wlengths       = NULL;
    cxdouble* pd_temp_params    = NULL;
    cxdouble* pd_opt_mod_params = NULL;
    cxdouble* pd_grat_params    = NULL;

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

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

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

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

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

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

    wlengths    = cpl_matrix_new(binPrms.size, 1);
    pd_wlengths = cpl_matrix_get_data(wlengths);

    temp_params = cpl_matrix_new(lmrq_opt_mod_x.nparams, 1);

    pd_temp_params    = cpl_matrix_get_data(temp_params);
    pd_opt_mod_params = cpl_matrix_get_data(opt_mod_params);
    pd_grat_params    = cpl_matrix_get_data(grat_params);

    pd_temp_params[OG_NX]    = pd_opt_mod_params[O_NX];
    pd_temp_params[OG_PXSIZ] = pd_opt_mod_params[O_PXSIZ];
    pd_temp_params[OG_FCOLL] = pd_opt_mod_params[O_FCOLL];
    pd_temp_params[OG_CFACT] = pd_opt_mod_params[O_CFACT];
    pd_temp_params[OG_THETA] = pd_grat_params[G_THETA];
    pd_temp_params[OG_ORDER] = pd_grat_params[G_ORDER];
    pd_temp_params[OG_SPACE] = pd_grat_params[G_SPACE];

    if (lmrq_opt_mod_x.nparams > OG_SOFFX) {
        pd_temp_params[OG_SOFFX] = pd_opt_mod_params[O_SOFFX];
        pd_temp_params[OG_SOFFY] = pd_opt_mod_params[O_SOFFY];
        pd_temp_params[OG_SPHI]  = pd_opt_mod_params[O_SPHI];
    }

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

    /*
     *  Compute lambda abcissa for rebinning
     */

    if (binPrms.log==TRUE) {
        /* lambda was specified in log(lambda) */
        cxint i;
        for (i = 0; i < binPrms.size; i++) {
            pd_wlengths[i] = exp(binPrms.min + (cxdouble) i * binPrms.step);
        }
    } else {
        cxint i;
        for (i = 0; i < binPrms.size; i++) {
            pd_wlengths[i] = binPrms.min + (cxdouble) i * binPrms.step;
        }
    }

    abcissa = _giraffe_compute_pixel_abscissa(wlengths, wloffsets,
                                              fiber_slit_position,
                                              temp_params, lmrq_opt_mod_x);


    /*
     *  If X residuals use, specified, calculate them and subtract
     *  them from the computed abcissa...
     */

    if ((binPrms.xres==TRUE) && (xresiduals_coeff!=NULL)) {

        x_residuals_img =
            giraffe_rebin_compute_pixel_x_residuals(
                locPos,
                abcissa,
                slitGeo,
                xresiduals_limits,
                xresiduals_coeff
                );

        cpl_image_subtract(abcissa, x_residuals_img);
        cpl_image_delete(x_residuals_img);

    }

    cpl_matrix_delete(wlengths);
    cpl_matrix_delete(temp_params);

    cpl_msg_debug(fctid, "Processing complete : returning image "
                  "[%" CPL_SIZE_FORMAT ", %" CPL_SIZE_FORMAT "]",
                  cpl_image_get_size_x(abcissa), cpl_image_get_size_y(abcissa));

    return abcissa;

} /* end giraffe_rebin_compute_rebin_abcissa() */

/**
 * @brief
 *   Resample the given extracted spectra to a wavelength grid.
 *
 * @param result          Rebsampled spectra.
 * @param extraction      Extracted spectra.
 * @param localization    Fiber localization.
 * @param fiber_position  Slit geometry (fiber positions in the focal
 *                                       plane [mm]).
 * @param subslits        Subslit configuration.
 * @param xres_limits     X residuals polynomial pixel domain.
 * @param xres_coeff      Coefficients of X residuals polynomial.
 * @param xres_order      Order of the X residuals polynomial.
 * @param grating_data    Grating parameters.
 * @param optical_model   Optical model parameters.
 * @param wlen_offsets    Additional wavelength offsets to apply.
 * @param rbstep          Resampling wavelength step.
 * @param method          Resampling method (linear or spline).
 * @param range           Resampling range (setup or common).
 * @param scale           Resampling scaling method (linear or logarithmic).
 *
 * @return
 *   The function returns @c 0 on success, or a non-zero value otherwise.
 *
 * TBD
 */

inline static cxint
_giraffe_resample_spectra(GiRebinning* result,
                          const GiExtraction* extraction,
                          const GiLocalization* localization,
                          GiFiberPosition* fiber_position,
                          GiSlitGeo* subslits,
                          GiSlitGeo* xres_limits,
                          GiSlitGeo* xres_coeff,
                          GiBinnParams* xres_order,
                          cpl_matrix* grating_data,
                          cpl_matrix* optical_model,
                          cpl_matrix* wlen_offsets,
                          cxdouble rbstep,
                          GiRebinMethod method,
                          GiRebinRange range,
                          GiRebinScale scale)
{

    const cxchar* const fctid = "_giraffe_resample_spectra";


    cxbool log_scale = FALSE;

    cxint i = 0;
    cxint status = 0;
    cxint om_sign = 0;
    cxint rbsize = 0;
    cxint nspectra = 0;

    cxdouble wlmin = 0.;
    cxdouble wlmax = 0.;
    cxdouble* _optical_model = NULL;

    cpl_matrix* wavelengths = NULL;

    cpl_image* _exspectra = NULL;
    cpl_image* _exerrors = NULL;
    cpl_image* _rbspectra = NULL;
    cpl_image* _rberrors = NULL;
    cpl_image* abscissa = NULL;

    cpl_propertylist* properties = NULL;

    GiImage* rbspectra = NULL;
    GiImage* rberrors = NULL;

    GiLocPosition locPos;

    GiRebinParams binPrms;

    GiRebinInfo setup;


    /*
     * default opt model is xoptmod2, python code
     * can not handle anything else anyway
     */

     lmrq_model_id opt_mod_id = LMRQ_XOPTMOD2;


    if (result == NULL || extraction == NULL || localization == NULL) {
        return 1;
    }

    if (result->spectra != NULL || result->errors != NULL) {
        return 1;
    }

    if (extraction->spectra == NULL) {
        return 1;
    }

    if (localization->locy == NULL || localization->locw == NULL) {
        return 1;
    }

    if (fiber_position == NULL) {
        return 1;
    }

    if (subslits == NULL) {
        return 1;
    }

    if (grating_data == NULL) {
        return 1;
    }

    if (optical_model == NULL) {
        return 1;
    }


    if (xres_coeff != NULL) {
        if (xres_limits == NULL || xres_order == NULL) {
            return 1;
        }
    }


    /*
     * FIXME: Should we set opt_mod_id to opt_mod_id used by wave
     *        calibration? Yes!!!
     */

    _exspectra = giraffe_image_get(extraction->spectra);

    if (extraction->error != NULL) {
        _exerrors = giraffe_image_get(extraction->error);
    }

    _optical_model = cpl_matrix_get_data(optical_model);

    nspectra = cpl_image_get_size_x(_exspectra);

    om_sign = (_optical_model[O_NX] >= 0) ? 1 : -1;

    if (scale == GIREBIN_SCALE_LOG) {
        log_scale = TRUE;
    }


    /*
     *  Find wavelength range
     */

     if (range == GIREBIN_RANGE_SETUP) {

        /*
         * Wavelength range taken from observed mode
         */

        wlmin = cpl_matrix_get(grating_data, 2, 0);
        wlmax = cpl_matrix_get(grating_data, 4, 0);

        if (log_scale == TRUE) {
            wlmin = log(wlmin);
            wlmax = log(wlmax);
        }

    }
    else {

        /*
         * Spectral range based on max/min wavelengths
         */

        status = giraffe_rebin_compute_lambda_range(fiber_position,
                                                    optical_model,
                                                    grating_data, log_scale,
                                                    TRUE, &wlmin, &wlmax);

    }

    rbsize = 1 + (cxint) ((wlmax - wlmin) / rbstep);

    if (rbsize < 0) {

        cpl_msg_debug(fctid, "Invalid dispersion direction [%d], changing "
                      "optical model orientation", om_sign);

        om_sign = -om_sign;
        _optical_model[O_NX] = -_optical_model[O_NX];

        status = giraffe_rebin_compute_lambda_range(fiber_position,
                                                    optical_model,
                                                    grating_data, log_scale,
                                                    TRUE, &wlmin, &wlmax);

        rbsize = 1 + (cxint) ((wlmax - wlmin) / rbstep);

    }

    if (rbsize < 1) {
        cpl_msg_error(fctid, "Invalid size %d of rebinned spectra, "
                      "aborting...", rbsize);
        return 2;
    }


    /*
     *  Calculate rebinned X positions...
     */

    locPos.type      = GILOCDATATYPE_FITTED_DATA;
    locPos.centroids = giraffe_image_get(localization->locy);
    locPos.widths    = giraffe_image_get(localization->locw);

    binPrms.min  = wlmin;
    binPrms.step = rbstep;
    binPrms.size = rbsize;
    binPrms.log  = log_scale;

    if (xres_coeff == NULL || (xres_order->xdeg == 0 &&
                               xres_order->ydeg == 0)) {
        binPrms.xres = FALSE;
    }
    else {
        binPrms.xres = TRUE;
    }

    abscissa = giraffe_rebin_compute_rebin_abcissa(fiber_position, &locPos,
                                                   subslits, xres_limits,
                                                   xres_coeff, grating_data,
                                                   optical_model, wlen_offsets,
                                                   lmrq_models[opt_mod_id],
                                                   binPrms);


    /*
     *  Perform rebinng of spectra and associated errors...
     */

    wavelengths = cpl_matrix_new(rbsize, 1);

    for (i = 0; i < rbsize; i++) {
        cpl_matrix_set(wavelengths, i, 0, wlmin + (cxdouble) i * rbstep);
    }

    rbspectra = giraffe_image_create(CPL_TYPE_DOUBLE, nspectra, rbsize);
    _rbspectra = giraffe_image_get(rbspectra);

    if (_exerrors != NULL) {
        rberrors = giraffe_image_create(CPL_TYPE_DOUBLE, nspectra, rbsize);
        _rberrors = giraffe_image_get(rberrors);
    }

    switch (method) {
    case GIREBIN_METHOD_LINEAR:
        status = _giraffe_resample_linear(_rbspectra, _rberrors, abscissa,
                                          _exspectra, _exerrors, om_sign);
        break;

    case GIREBIN_METHOD_SPLINE:
        status = _giraffe_resample_spline(_rbspectra, _rberrors, abscissa,
                                          _exspectra, _exerrors, om_sign);
        break;

    default:

        /* We should never get to this point! */

        gi_error("Invalid rebinning method!");
        break;
    }

    cpl_image_delete(abscissa);
    abscissa = NULL;

    if (status != 0) {
        cpl_msg_error(fctid, "Error during rebinning, aborting...");

        cpl_matrix_delete(wavelengths);
        wavelengths = NULL;

        giraffe_image_delete(rbspectra);
        rbspectra = NULL;

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

        return 3;
    }



    /*
     *  Add calculated image, keywords etc. to rebinned spectrum frame...
     */

    switch (scale) {
    case GIREBIN_SCALE_LOG:
        {
            cxsize nw = cpl_matrix_get_nrow(wavelengths);

            cxdouble mm2nm = log(GI_MM_TO_NM);

            setup.wmin = mm2nm + cpl_matrix_get(wavelengths, 0, 0);
            setup.wcenter = mm2nm + cpl_matrix_get(wavelengths, nw / 2, 0);
            setup.wmax = mm2nm + cpl_matrix_get(wavelengths, nw - 1, 0);
            setup.wstep  = rbstep;

            setup.units = "log(nm)";
            setup.offset = 0;
        }
        break;

    case GIREBIN_SCALE_LINEAR:
        {
            cxsize nw = cpl_matrix_get_nrow(wavelengths);

            setup.wmin = GI_MM_TO_NM * cpl_matrix_get(wavelengths, 0, 0);
            setup.wcenter = GI_MM_TO_NM * cpl_matrix_get(wavelengths,
                                                         nw / 2, 0);
            setup.wmax = GI_MM_TO_NM * cpl_matrix_get(wavelengths,
                                                      nw - 1, 0);
            setup.wstep  = GI_MM_TO_NM * rbstep;

            setup.units = "nm";
            setup.offset = 0;
        }
        break;

    default:

        /* We should never get here */

        gi_error("Invalid scaling option!");
        break;
    }

    cpl_matrix_delete(wavelengths);
    wavelengths = NULL;


    switch (method) {
    case GIREBIN_METHOD_LINEAR:
        setup.method = "linear";
        break;

    case GIREBIN_METHOD_SPLINE:
        setup.method = "spline";
        break;

    default:

        /* We should never get here */

        gi_error("Invalid rebinning method!");
        break;
    }


    switch (scale) {
    case GIREBIN_SCALE_LINEAR:
        setup.scale = "linear";
        break;

    case GIREBIN_SCALE_LOG:
        setup.scale = "logarithmic";
        break;

    default:

        /* We should never get here */

        gi_error("Invalid scaling option!");
        break;
    }

    switch (range) {
    case GIREBIN_RANGE_SETUP:
        setup.range = "setup";
        break;

    case GIREBIN_RANGE_COMMON:
        setup.range = "common";
        break;

    default:

        /* We should never get here */

        gi_error("Invalid range option!");
        break;
    }


    /*
     * Finalize resampled spectra
     */

    giraffe_error_push();

    properties = giraffe_image_get_properties(extraction->spectra);
    giraffe_image_set_properties(rbspectra, properties);

    if (cpl_error_get_code() != CPL_ERROR_NONE) {
        giraffe_image_delete(rbspectra);
        rbspectra = NULL;

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

        return 4;
    }

    giraffe_error_pop();

    status = _giraffe_resample_update_properties(rbspectra, &setup);

    if (status != 0) {
        giraffe_image_delete(rbspectra);
        rbspectra = NULL;

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

        return 4;
    }


    /*
     * Finalize resampled spectra errors if they are available
     */

    if (_rberrors != NULL) {

        giraffe_error_push();

        properties =  giraffe_image_get_properties(extraction->error);
        giraffe_image_set_properties(rberrors, properties);

        if (cpl_error_get_code() != CPL_ERROR_NONE) {
            giraffe_image_delete(rbspectra);
            rbspectra = NULL;

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

            return 5;
        }

        giraffe_error_pop();

        status = _giraffe_resample_update_properties(rberrors, &setup);

        if (status != 0) {
            giraffe_image_delete(rbspectra);
            rbspectra = NULL;

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

            return 5;
        }

    }

    result->spectra = rbspectra;
    result->errors = rberrors;

    return 0;

}


/**
 * @brief
 *   Compute the wavelenght range of spectra.
 *
 * TBD
 */

GiRange *
giraffe_rebin_get_wavelength_range(GiImage *spectra, GiTable *wlsolution,
                                   GiTable *grating, GiTable *slitgeometry,
                                   cxbool common)
{

    cxint status = 0;

    cxdouble min = 0.;
    cxdouble max = 0.;

    cpl_matrix *optical_model = NULL;
    cpl_matrix *grating_data = NULL;

    GiFiberPosition *positions = NULL;
    GiSlitGeo *subslits = NULL;

    GiWcalSolution *wcal = NULL;

    GiRange *range = NULL;


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

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

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


    wcal = _giraffe_wcalsolution_create(wlsolution);

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

    optical_model = _giraffe_rebin_setup_model(spectra, wcal);

    if (optical_model == NULL) {
        _giraffe_wcalsolution_delete(wcal);
        return NULL;
    }

    _giraffe_wcalsolution_delete(wcal);

    grating_data = _giraffe_rebin_setup_grating(spectra, grating,
                                                wlsolution);

    if (grating_data == NULL) {

        cpl_matrix_delete(grating_data);
        cpl_matrix_delete(optical_model);

        return NULL;

    }

    positions = _giraffe_fiberposition_new();
    subslits = _giraffe_slitgeo_new();

    status = _giraffe_slitgeo_setup(slitgeometry, positions, subslits,
                                    FALSE);

    if (status != 0) {

        _giraffe_slitgeo_delete(subslits);
        _giraffe_fiberposition_delete(positions);

        cpl_matrix_delete(grating_data);
        cpl_matrix_delete(optical_model);

        return NULL;

    }

    status = giraffe_rebin_compute_lambda_range(positions, optical_model,
                                                grating_data,
                                                GIREBIN_SCALE_LINEAR,
                                                common, &min, &max);

    /*
     * Convert wavelength from millimeters to nanometers.
     */

    min *= GI_MM_TO_NM;
    max *= GI_MM_TO_NM;

    _giraffe_slitgeo_delete(subslits);
    _giraffe_fiberposition_delete(positions);

    cpl_matrix_delete(grating_data);
    cpl_matrix_delete(optical_model);

    range = giraffe_range_create(min, max);

    return range;

}


/**
 * @brief
 *   Rebin an Extracted Spectra Frame and associated Errors Frame
 *
 * @param rebinning        Rebinned Extracted Spectrum and associated errors
 * @param extraction       Extraction containing Extracted Spectrum and
 *                         associated errors.
 * @param fibers           Table of available fibers
 * @param localization     Fiber spectra centroid positions and half-width.
 * @param grating          Grating data table.
 * @param slitgeo          Slit geometry table.
 * @param solution         Wavelength calibration solution.
 * @param config           Rebinning configuration data
 *
 * @return
 *   The function returns 0 on succes, and a value larger than 0 if an
 *   error occured.
 *
 * TBD
 *
 *
 * @par Status Description :
 * 1 - Input parameter empty (NULL value)
 * 2 - Missing FITS keyword in input frame
 * 3 - Unable to retrieve Grating Data
 * 4 - No wavelength calibration solution present in input
 * 5 - Invalid optical model in wavelength calibration solution
 * 6 - Error in optical model in wavelength calibration solution
 * 7 - Unable to retrieve Slit Geometry
 * 8 - Error during rebinning
 * 9 - Error converting wavelength calibration corrections.
 */

cxint
giraffe_rebin_spectra(GiRebinning *rebinning,
                      const GiExtraction *extraction,
                      const GiTable *fibers,
                      const GiLocalization *localization,
                      const GiTable *grating,
                      const GiTable *slitgeo,
                      const GiTable *solution,
                      const GiRebinConfig *config)
{

    const cxchar* const fctid = "giraffe_rebin_spectra";


    cxint status = 0;
    cxint ex_sp_extr_pixels = 0;
    cxint calc_rebinned_size = 0;
    cxint default_rebinned_size = GIREBIN_SIZE_Y_DEFAULT;

    cxdouble rbin_multiplier = 0.;
    cxdouble ex_sp_pixsize_x = 0.;
    cxdouble rbin_stepsize = 0.;

    cpl_matrix* grat_params    = NULL;
    cpl_matrix* opt_mod_params = NULL;
    cpl_matrix* wloffsets      = NULL;

    cpl_table* _fibers = NULL;

    cpl_propertylist* _pl_ext_sp = NULL;
    cpl_propertylist* _pl_wsol   = NULL;

    GiImage* ex_sp_frame = NULL;
    /*GiImage* ex_sp_err_frame = NULL;*/
    /*GiImage* loc_y_frame = NULL;*/
    /*GiImage* loc_w_frame = NULL;*/

    GiSlitGeo* subslit_fibers = NULL;
    GiSlitGeo* wav_coeffs = NULL;
    GiSlitGeo* wav_limits = NULL;

    GiGrat* grating_data = NULL;

    GiFiberPosition* fiber_slit_position  = NULL;

    GiWcalSolution* wcalib_solution = NULL;

    GiBinnParams xres_polynom_deg = {0, 0};



    /*
     * Preprocessing
     */

    if (extraction == NULL) {
        cpl_msg_error(fctid, "No extracted data, aborting...");
        return 1;
    }

    if (extraction->spectra == NULL) {
        cpl_msg_error(fctid, "No extracted spectra, aborting...");
        return 1;
    }

    if (fibers == NULL) {
        cpl_msg_error(fctid, "No fiber table, aborting ...");
        return 1;
    }

    if (localization == NULL) {
        cpl_msg_error(fctid, "No localization data, aborting...");
        return 1;
    }

    if (localization->locy == NULL) {
        cpl_msg_error(fctid, "No localization centroids, aborting...");
        return 1;
    }

    if (localization->locw == NULL) {
        cpl_msg_error(fctid, "No localization widths, aborting...");
        return 1;
    }

    if (grating == NULL) {
        cpl_msg_error(fctid, "No grating data, aborting...");
        return 1;
    }

    if (rebinning == NULL) {
        cpl_msg_error(fctid, "No rebinning results container, aborting...");
        return 1;
    }

    if (config == NULL) {
        cpl_msg_error(fctid, "No rebinning configuration data, aborting...");
        return 1;
    }

    if (solution == NULL) {
        cpl_msg_error(fctid, "No wavecalibration solution, aborting...");
        return 1;
    }

    ex_sp_frame          = extraction->spectra;
    /*ex_sp_err_frame      = extraction->error;*/
    /*loc_y_frame          = localization->locy;*/
    /*loc_w_frame          = localization->locw;*/

    _pl_ext_sp           = giraffe_image_get_properties(ex_sp_frame);
    _pl_wsol             = giraffe_table_get_properties(solution);


    _fibers = giraffe_table_get(fibers);
    cx_assert(_fibers != NULL);



    /*
     * Initialization
     */

    /*
     *  Retrieve necessary values from FITS keywords
     */

    if (cpl_propertylist_has(_pl_ext_sp, GIALIAS_PIXSIZX) == TRUE) {
        ex_sp_pixsize_x = cpl_propertylist_get_double(_pl_ext_sp,
                                                      GIALIAS_PIXSIZX);

    }
    else {
        cpl_msg_error(fctid, "%s Keyword missing in Extracted Spectra "
                      "Frame, aborting ...", GIALIAS_PIXSIZX);
        return 2;
    }


    /*
     * Convert pixel size to microns
     */

    if (ex_sp_pixsize_x > 1.) {
        ex_sp_pixsize_x /= 1000.;
    }


    /*
     * Determine rebinning step size multiplier
     */

    if (cpl_propertylist_has(_pl_ext_sp, GIALIAS_GRATNAME) == TRUE) {

        const cxchar* _string = NULL;

        _string = cpl_propertylist_get_string(_pl_ext_sp, GIALIAS_GRATNAME);

        if (strncmp(_string, "LR", 2) == 0) {
            rbin_multiplier = 4.;
        }
        else if (strncmp(_string, "HR", 2) == 0) {
            rbin_multiplier = 1.;
        }
        else {
            rbin_multiplier = 1.;
        }

    }
    else {
        cpl_msg_error(fctid, "%s Keyword missing in Extracted Spectra Frame, "
                      "aborting ...", GIALIAS_GRATNAME);
        return 2;
    }


    /*
     * find number of pixel per spectrum
     */

    if (cpl_propertylist_has(_pl_ext_sp, GIALIAS_EXT_NX) == TRUE) {
        ex_sp_extr_pixels = cpl_propertylist_get_int(_pl_ext_sp,
                                                     GIALIAS_EXT_NX);

    }
    else {
        cpl_msg_error(fctid, "%s Keyword missing in Extracted Spectra Frame, "
                      "aborting ...", GIALIAS_EXT_NX);
        return 2;
    }


    /*
     *  Retrieve Grating information
     */

    grating_data = _giraffe_grating_new();

    status = _giraffe_grating_setup(grating, ex_sp_frame, grating_data);

    if (status != 0) {
        cpl_msg_error(fctid, "Unable to retrieve grating information, "
                      "aborting...");
        _giraffe_grating_delete(grating_data);
        return 3;
    }

    /*
     *  Retrieve Slit Geometry Information
     */

    fiber_slit_position = _giraffe_fiberposition_new();
    subslit_fibers = _giraffe_slitgeo_new();

    status = _giraffe_slitgeo_setup(slitgeo, fiber_slit_position,
                                    subslit_fibers, FALSE);

    if (status != 0) {
        cpl_msg_error(fctid, "Unable to retrieve slit geometry information, "
                      "aborting...");
        _giraffe_grating_delete(grating_data);
        _giraffe_fiberposition_delete(fiber_slit_position);
        _giraffe_slitgeo_delete(subslit_fibers);
        return 7;
    }


    wcalib_solution = _giraffe_wcalsolution_create(solution);

    if (wcalib_solution == NULL) {
        cpl_msg_error(fctid, "Cannot create wavelength solution, "
                      "aborting ...");
        _giraffe_grating_delete(grating_data);
        _giraffe_fiberposition_delete(fiber_slit_position);
        _giraffe_slitgeo_delete(subslit_fibers);
        return 4;
    }

    if (cpl_propertylist_has(_pl_wsol, GIALIAS_WSOL_OMFCOLL) == TRUE) {
        grating_data->fcoll =
            cpl_propertylist_get_double(_pl_wsol, GIALIAS_WSOL_OMFCOLL);
    }

    if (cpl_propertylist_has(_pl_wsol, GIALIAS_WSOL_OMGCAM) == TRUE) {
        grating_data->gcam =
            cpl_propertylist_get_double(_pl_wsol, GIALIAS_WSOL_OMGCAM);
    }

    if (cpl_propertylist_has(_pl_wsol, GIALIAS_WSOL_OMGTHETA) == TRUE) {
        grating_data->theta =
            cpl_propertylist_get_double(_pl_wsol, GIALIAS_WSOL_OMGTHETA);
    }

    if (cpl_propertylist_has(_pl_wsol, GIALIAS_WSOL_OMSDX) == TRUE) {
        grating_data->slitdx =
            cpl_propertylist_get_double(_pl_wsol, GIALIAS_WSOL_OMSDX);
    }

    if (cpl_propertylist_has(_pl_wsol, GIALIAS_WSOL_OMSDY) == TRUE) {
        grating_data->slitdy =
            cpl_propertylist_get_double(_pl_wsol, GIALIAS_WSOL_OMSDY);
    }

    if (cpl_propertylist_has(_pl_wsol, GIALIAS_WSOL_OMSPHI) == TRUE) {
        grating_data->slitphi =
            cpl_propertylist_get_double(_pl_wsol, GIALIAS_WSOL_OMSPHI);
    }


    /*
     * If wavelength corrections were provided, convert the wavelength
     * offsets from nanometers to millimeters and store them as a
     * matrix (nfibers x 1).
     */

    if (cpl_table_has_column(_fibers, "WLRES") != 0) {

        cxint fiber = 0;
        cxint nfibers = cpl_table_get_nrow(_fibers);


        wloffsets  = cpl_matrix_new(nfibers, 1);

        for (fiber = 0; fiber < nfibers; ++fiber) {

            cxdouble wloffset = cpl_table_get_double(_fibers, "WLRES",
                fiber, NULL);


            wloffset *= -GI_NM_TO_MM;
            cpl_matrix_set(wloffsets, fiber, 0, wloffset);

        }

        cpl_msg_info(fctid, "Applying SIMCAL wavelength corrections ...");

    }



    /*
     * Processing
     */


    /*
     *  Determine rebinning stepsize and size in y direction after rebinning
     */

    if (config->scmethod == GIREBIN_SCALE_LOG) {

        cxint rebin_size;

        cxdouble wlenmax;
        cxdouble wlenmin;

        rebin_size = default_rebinned_size;

        wlenmin = log(grating_data->wlenmin / GI_MM_TO_NM);
        wlenmax = log(grating_data->wlenmax / GI_MM_TO_NM);

        if ((config->size != rebin_size) && (config->size != 0)) {
            rebin_size = config->size;
        }

        rbin_stepsize = (wlenmax - wlenmin) / rebin_size;

        calc_rebinned_size = 1 + (cxint) ((wlenmax-wlenmin)/ rbin_stepsize);

    }
    else {

        cxint rebin_size;

        cxdouble wlenmax;
        cxdouble wlenmin;

        wlenmin = grating_data->wlenmin / GI_MM_TO_NM;
        wlenmax = grating_data->wlenmax / GI_MM_TO_NM;

        rbin_stepsize = (config->lstep / GI_MM_TO_NM) * rbin_multiplier;
        rebin_size    = (wlenmax - wlenmin) / rbin_stepsize;

        if ((config->size != rebin_size) && (config->size != 0)) {
            rebin_size = config->size;
            rbin_stepsize = (wlenmax - wlenmin) / rebin_size;
        }

        calc_rebinned_size = 1 + (cxint) ((wlenmax-wlenmin) / rbin_stepsize);

    }

    /*
     *  Retrieve Wavecalibration Solution Physical Optical Paramters
     */

    if (wcalib_solution!=NULL) {

        if (wcalib_solution->opt_mod==LMRQ_XOPTMOD) {

            cxint    nrow;
            cxdouble opt_direction, fcoll, cfact;

            nrow = cpl_matrix_get_nrow(wcalib_solution->opt_mod_params);
            if (nrow==4) {
                opt_direction =
                    cpl_matrix_get(wcalib_solution->opt_mod_params, 0, 0);
                fcoll =
                    cpl_matrix_get(wcalib_solution->opt_mod_params, 1, 0);
                cfact =
                    cpl_matrix_get(wcalib_solution->opt_mod_params, 2, 0);

                opt_mod_params = cpl_matrix_new(4,1);
                cpl_matrix_set(opt_mod_params, 0, 0,
                               ex_sp_extr_pixels * opt_direction);
                cpl_matrix_set(opt_mod_params, 1, 0, ex_sp_pixsize_x);
                cpl_matrix_set(opt_mod_params, 2, 0, fcoll);
                cpl_matrix_set(opt_mod_params, 3, 0, cfact);
            }
            else {
                cpl_msg_error(fctid, "Invalid number of physical optical "
                              "parameters, aborting...");

                if (wloffsets != NULL) {
                    cpl_matrix_delete(wloffsets);
                }

                _giraffe_wcalsolution_delete(wcalib_solution);
                _giraffe_grating_delete(grating_data);
                _giraffe_fiberposition_delete(fiber_slit_position);
                _giraffe_slitgeo_delete(subslit_fibers);
                return 6;
            }

        }
        else if (wcalib_solution->opt_mod==LMRQ_XOPTMOD2) {

            cxint nrow;
            cxdouble opt_direction, fcoll, cfact, /*gtheta,*/ slitdx,
                slitdy, slitphi;

            nrow = cpl_matrix_get_nrow(wcalib_solution->opt_mod_params);
            if (nrow==7) {
                opt_direction =
                    cpl_matrix_get(wcalib_solution->opt_mod_params, 0, 0);
                fcoll   =
                    cpl_matrix_get(wcalib_solution->opt_mod_params, 1, 0);
                cfact   =
                    cpl_matrix_get(wcalib_solution->opt_mod_params, 2, 0);
                /*gtheta  =
                    cpl_matrix_get(wcalib_solution->opt_mod_params, 3, 0);*/
                slitdx  =
                    cpl_matrix_get(wcalib_solution->opt_mod_params, 4, 0);
                slitdy  =
                    cpl_matrix_get(wcalib_solution->opt_mod_params, 5, 0);
                slitphi =
                    cpl_matrix_get(wcalib_solution->opt_mod_params, 6, 0);

                opt_mod_params = cpl_matrix_new(7,1);
                cpl_matrix_set(opt_mod_params, 0, 0,
                               ex_sp_extr_pixels * opt_direction);
                cpl_matrix_set(opt_mod_params, 1, 0, ex_sp_pixsize_x);
                cpl_matrix_set(opt_mod_params, 2, 0, fcoll);
                cpl_matrix_set(opt_mod_params, 3, 0, cfact);
                cpl_matrix_set(opt_mod_params, 4, 0, slitdx);
                cpl_matrix_set(opt_mod_params, 5, 0, slitdy);
                cpl_matrix_set(opt_mod_params, 6, 0, slitphi);

            }
            else {
                cpl_msg_error(fctid, "Invalid number of physical optical "
                              "parameters, aborting...");

                if (wloffsets != NULL) {
                    cpl_matrix_delete(wloffsets);
                }

                _giraffe_wcalsolution_delete(wcalib_solution);
                _giraffe_grating_delete(grating_data);
                _giraffe_fiberposition_delete(fiber_slit_position);
                _giraffe_slitgeo_delete(subslit_fibers);
                return 6;
            }

        }
        else {
            cpl_msg_error(fctid, "Invalid optical model, aborting...");

            if (wloffsets != NULL) {
                cpl_matrix_delete(wloffsets);
            }

            _giraffe_wcalsolution_delete(wcalib_solution);
            _giraffe_grating_delete(grating_data);
            _giraffe_fiberposition_delete(fiber_slit_position);
            _giraffe_slitgeo_delete(subslit_fibers);
            return 5;
        }


        if (wcalib_solution->wav_coeffs != NULL) {

            GiSlitGeo* coeffs = wcalib_solution->wav_coeffs;

            xres_polynom_deg.xdeg =
                cpl_matrix_get_nrow(_giraffe_slitgeo_get(coeffs, 0));
            xres_polynom_deg.ydeg =
                cpl_matrix_get_ncol(_giraffe_slitgeo_get(coeffs, 0));
        }

    }
    else {
        cpl_msg_error(fctid, "No Wavelength Calibration solution found, "
                      "aborting...");

        if (wloffsets != NULL) {
            cpl_matrix_delete(wloffsets);
        }

        _giraffe_wcalsolution_delete(wcalib_solution);
        _giraffe_grating_delete(grating_data);
        _giraffe_fiberposition_delete(fiber_slit_position);
        _giraffe_slitgeo_delete(subslit_fibers);
        return 4;
    }

    if (config->xresiduals==FALSE) {
        xres_polynom_deg.xdeg = 0;
        xres_polynom_deg.ydeg = 0;
    }

    if (wcalib_solution->wav_coeffs!=NULL) {
        wav_coeffs = wcalib_solution->wav_coeffs;
    }

    if (wcalib_solution->wav_limits!=NULL) {
        wav_limits = wcalib_solution->wav_limits;
    }


    /*
     *  Setup Rebinning grating parameters
     */

    grat_params = cpl_matrix_new(7,1);

    cpl_matrix_set(grat_params, 0, 0, grating_data->theta);
    cpl_matrix_set(grat_params, 1, 0, grating_data->order);
    cpl_matrix_set(grat_params, 2, 0, grating_data->wlenmin / GI_MM_TO_NM);
    cpl_matrix_set(grat_params, 3, 0, grating_data->wlen0   / GI_MM_TO_NM);
    cpl_matrix_set(grat_params, 4, 0, grating_data->wlenmax / GI_MM_TO_NM);
    cpl_matrix_set(grat_params, 5, 0, grating_data->resol);
    cpl_matrix_set(grat_params, 6, 0, grating_data->space );


    /*
     *  Give user some feedback...
     */

    cpl_msg_info(fctid, "Performing Rebinning of spectra, stepsize=%.4f "
                 "[nm], resulting image size=%d, using x residuals : %s",
                 rbin_stepsize * GI_MM_TO_NM, calc_rebinned_size,
                 config->xresiduals ? "Yes" : "No");


    switch (config->rmethod) {
        case GIREBIN_METHOD_LINEAR:
            cpl_msg_info(fctid, "Rebinning method    : linear");
            break;

        case GIREBIN_METHOD_SPLINE:
            cpl_msg_info(fctid, "Rebinning method    : spline");
            break;

        default:
            cpl_msg_info(fctid, "Rebinning method    : undefined");
            break;
    }

    switch (config->scmethod) {
        case GIREBIN_SCALE_LOG:
            cpl_msg_info(fctid, "Scaling method      : logarithmic, "
                         "log(wavelength [nm]): min,max,range = %.3f, %.3f, %.3f",
                         log(grating_data->wlenmin),
                         log(grating_data->wlenmax),
                         log(grating_data->wlenmax) -
                         log(grating_data->wlenmin));
            break;

        case GIREBIN_SCALE_LINEAR:
            cpl_msg_info(fctid, "Scaling method      : linear, wavelength [nm]: "
                         "min,max,range = %.3f, %.3f, %.3f",
                         grating_data->wlenmin,
                         grating_data->wlenmax,
                         grating_data->wlenmax - grating_data->wlenmin);
            break;

        default:
            cpl_msg_info(fctid, "Scaling method      : undefined");
            break;
    }

    switch (config->range) {
        case GIREBIN_RANGE_SETUP:
            cpl_msg_info(fctid, "Wavelength range    : Setup");
            break;

        case GIREBIN_RANGE_COMMON:
            cpl_msg_info(fctid, "Wavelength range    : Common");
            break;

        default:
            cpl_msg_info(fctid, "Wavelength range    : undefined");
            break;
    }


    /*
     * Resample the spectra to the wavelength grid
     */

    status = _giraffe_resample_spectra(rebinning, extraction,
                                       localization, fiber_slit_position,
                                       subslit_fibers, wav_limits,
                                       wav_coeffs, &xres_polynom_deg,
                                       grat_params, opt_mod_params,
                                       wloffsets, rbin_stepsize,
                                       config->rmethod, config->range,
                                       config->scmethod);

    if (status != 0) {

        if (wloffsets != NULL) {
            cpl_matrix_delete(wloffsets);
        }

        cpl_matrix_delete(opt_mod_params);
        cpl_matrix_delete(grat_params);

        _giraffe_wcalsolution_delete(wcalib_solution);
        _giraffe_grating_delete(grating_data);
        _giraffe_fiberposition_delete(fiber_slit_position);
        _giraffe_slitgeo_delete(subslit_fibers);

        return 8;

    }


    /*
     *  Cleanup...
     */

    if (wloffsets != NULL) {
        cpl_matrix_delete(wloffsets);
    }

    cpl_matrix_delete(opt_mod_params);
    cpl_matrix_delete(grat_params);

    _giraffe_wcalsolution_delete(wcalib_solution);
    _giraffe_grating_delete(grating_data);
    _giraffe_fiberposition_delete(fiber_slit_position);
    _giraffe_slitgeo_delete(subslit_fibers);

    return 0;

} /* end giraffe_rebin_spectra() */


/**
 * @brief
 *   Create an empty rebinning results container.
 *
 * @return A newly allocated rebinning results container or NULL
 *         if an error occured
 *
 * @par Description :
 * The function allocales memory for a rebinning results container,
 * and initializes it's values to denote an empty container.
 */

GiRebinning*
giraffe_rebinning_new(void)
{

    GiRebinning *rebinn = cx_malloc(sizeof(GiRebinning));

    rebinn->spectra = NULL;
    rebinn->errors  = NULL;

    return rebinn;

}

/**
 * @brief
 *   Fills a rebinning results container
 *
 * @param spectra - Extracted spectra.
 * @param errors  - Errors of the extracted spectra.
 *
 * @return A newly allocated rebinning results container or NULL
 *         if an error occured
 *
 * @par Description :
 * The function allocales memory for a rebinning results container,
 * and initializes it's values using the parameters given.
 * Only a reference is stored!
 */

GiRebinning*
giraffe_rebinning_create(GiImage *spectra, GiImage *errors)
{

    GiRebinning *rebin = giraffe_rebinning_new();

    if (spectra) {
        rebin->spectra = spectra;
    }

    if (errors) {
        rebin->errors = errors;
    }

    return rebin;

}


/**
 * @brief
 *   Destroys a rebinning results container.
 *
 * @param rebinning - The rebinning results container to destroy.
 *
 * @return Nothing.
 *
 * @par Description :
 * The function deallocates the memory used for the rebinning results
 * container @em rebinning. Only the container itself is destroyed by
 * calling this function. Since the container stores only references to the
 * rebinning components its contents is left untouched and it is the
 * responsibility of the caller to ensure that other references for the
 * stored rebinning components exist.
 *
 */

void
giraffe_rebinning_delete(GiRebinning *rebinning)
{

    if (rebinning) {
        cx_free(rebinning);
    }

    return;

}


/**
 * @brief
 *   Destroys a rebinning results container and its contents.
 *
 * @param rebinning  The rebinning results container to destroy.
 *
 * @return Nothing.
 *
 * @par Description :
 * The function deallocates the memory used for the rebinning results
 * container @em rebinning and each rebinning component it may
 * contain.
 *
 */

void
giraffe_rebinning_destroy(GiRebinning *rebinning)
{

    if (rebinning) {

        if (rebinning->spectra) {
            giraffe_image_delete(rebinning->spectra);
            rebinning->spectra = NULL;
        }

        if (rebinning->errors) {
            giraffe_image_delete(rebinning->errors);
            rebinning->errors = NULL;
        }

        cx_free(rebinning);
    }

    return;

}


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

GiRebinConfig *
giraffe_rebin_config_create(cpl_parameterlist *list)
{

    const cxchar *fctid = "giraffe_rebin_config_create";

    const cxchar *s;

    cpl_parameter *p;

    GiRebinConfig *config = NULL;


    if (!list) {
        return NULL;
    }

    config = cx_calloc(1, sizeof *config);


    config->rmethod    = GIREBIN_METHOD_UNDEFINED;
    config->xresiduals = FALSE;
    config->lstep      = 0.0;
    config->scmethod   = GIREBIN_SCALE_UNDEFINED;
    config->size       = 0;
    config->range      = GIREBIN_RANGE_UNDEFINED;


    p = cpl_parameterlist_find(list, "giraffe.rebinning.method");
    s = cpl_parameter_get_string(p);
    if (strcmp(s, "linear")==0) {
        config->rmethod = GIREBIN_METHOD_LINEAR;
    } else if (strcmp(s, "spline")==0) {
        config->rmethod = GIREBIN_METHOD_SPLINE;
    }

    p = cpl_parameterlist_find(list, "giraffe.rebinning.xresiduals");
    config->xresiduals = cpl_parameter_get_bool(p);

    p = cpl_parameterlist_find(list, "giraffe.rebinning.lstep");
    config->lstep = cpl_parameter_get_double(p);

    p = cpl_parameterlist_find(list, "giraffe.rebinning.scalemethod");
    s = cpl_parameter_get_string(p);
    if (strcmp(s, "log")==0) {
        config->scmethod = GIREBIN_SCALE_LOG;
    } else if (strcmp(s, "linear")==0) {
        config->scmethod = GIREBIN_SCALE_LINEAR;
    }

    p = cpl_parameterlist_find(list, "giraffe.rebinning.size");
    config->size = cpl_parameter_get_int(p);

    p = cpl_parameterlist_find(list, "giraffe.rebinning.range");
    s = cpl_parameter_get_string(p);
    if (strcmp(s, "setup")==0) {
        config->range = GIREBIN_RANGE_SETUP;
    } else if (strcmp(s, "common")==0) {
        config->range = GIREBIN_RANGE_COMMON;
    }

    /* Validate */

    if (config->rmethod==GIREBIN_METHOD_UNDEFINED) {
        cpl_msg_info(fctid, "Invalid Rebinning method, aborting");
        cx_free(config);
        return NULL;
    }

    if (config->scmethod==GIREBIN_SCALE_UNDEFINED) {
        cpl_msg_info(fctid, "Invalid Rebinning scaling method, aborting");
        cx_free(config);
        return NULL;
    }

    if (config->range==GIREBIN_RANGE_UNDEFINED) {
        cpl_msg_info(fctid, "Invalid Rebinning range, aborting");
        cx_free(config);
        return NULL;
    }

    return config;

}


/**
 * @brief
 *   Destroys a spectrum extraction setup structure.
 *
 * @param config  The setup structure to destroy.
 *
 * @return Nothing.
 *
 * @par Description :
 * The function deallocates the memory used by the setup structure
 * @em config and all components contained in it.
 *
 */

void
giraffe_rebin_config_destroy(GiRebinConfig *config)
{

    if (config) {
        cx_free(config);
    }

    return;

}

/**
 * @brief
 *   Adds parameters for the rebinning.
 *
 * @param list  Parameter list to which parameters are added.
 *
 * @return Nothing.
 *
 * @par Description :
 * TBD
 */

void
giraffe_rebin_config_add(cpl_parameterlist *list)
{

    cpl_parameter *p;

    if (!list) {
        return;
    }

    p = cpl_parameter_new_enum("giraffe.rebinning.method",
                               CPL_TYPE_STRING,
                               "Method to use : `linear' or `spline'",
                               "giraffe.rebinning.method",
                               "linear", 2, "linear", "spline");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "rbin-method");
    cpl_parameterlist_append(list, p);

    p = cpl_parameter_new_value("giraffe.rebinning.xresiduals",
                                CPL_TYPE_BOOL,
                                "Use x residuals during rebinning? `true'/"
                                "`false'",
                                "giraffe.rebinning.xresiduals",
                                TRUE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "rbin-xresid");
    cpl_parameterlist_append(list, p);

    p = cpl_parameter_new_value("giraffe.rebinning.lstep",
                                CPL_TYPE_DOUBLE,
                                "Lambda step size, only used if "
                                "scaling method is 'linear'",
                                "giraffe.rebinning.lstep",
                                0.005);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "rbin-lstep");
    cpl_parameterlist_append(list, p);

    p = cpl_parameter_new_enum("giraffe.rebinning.scalemethod",
                               CPL_TYPE_STRING,
                               "Scaling method: `log' or `linear'",
                               "giraffe.rebinning.scalemethod",
                               "linear", 2, "linear", "log");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "rbin-scmethod");
    cpl_parameterlist_append(list, p);

    p = cpl_parameter_new_value("giraffe.rebinning.size",
                                CPL_TYPE_INT,
                                "Size of output rebinned spectra, 0 means "
                                "calculate size based on wavelength range "
                                "and lambda stepsize",
                                "giraffe.rebinning.size",
                                0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "rbin-size");
    cpl_parameterlist_append(list, p);

    p = cpl_parameter_new_enum("giraffe.rebinning.range",
                               CPL_TYPE_STRING,
                               "Rebinning range: `setup' or `common'",
                               "giraffe.rebinning.scalemethod",
                               "setup", 2, "setup", "common");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "rbin-range");
    cpl_parameterlist_append(list, p);

    return;

}
/**@}*/
