/*
 * This file is part of the KMOS Pipeline
 * Copyright (C) 2002,2003 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

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

/*-----------------------------------------------------------------------------
          Functions Hierarchy (o : public / x : static)

    kmos_priv_sky_tweak() (o)
        - kmos_priv_sky_tweak_get_spectra() (x)
        - kmos_priv_sky_tweak_correct_vibrational_trans() (x) 
        - kmos_get_thermal_background() (x)
 *----------------------------------------------------------------------------*/

/*-----------------------------------------------------------------------------
 *                              Includes
 *----------------------------------------------------------------------------*/

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

#include <cpl.h>

#include "kmclipm_constants.h"
#include "kmclipm_priv_splines.h"
#include "kmclipm_math.h"

#include "kmo_error.h"
#include "kmos_priv_sky_tweak.h"
#include "kmo_priv_lcorr.h"
#include "kmo_utils.h"

/*-----------------------------------------------------------------------------
 *                             Glaobal vars !!
 *----------------------------------------------------------------------------*/

#define HC_K 14387.7512979   // h*c/k in [micrometer*K]
#define PLANCK(lambda,t) (pow((lambda),-5.0) / (exp(HC_K/((lambda) * fabs(t))) - 1.))

int         cont_region_size,
            line_region_size;
double  *   cont_sky_intensities=NULL,
        *   line_sky_intensities=NULL,
        *   cont_object_intensities=NULL,
        *   line_object_intensities=NULL,
        *   cont_lambda=NULL,
        *   line_lambda=NULL;

int spectrum_size;
double *spectrum_lambda=NULL;
double *spectrum_value=NULL;
double *thermal_background=NULL;

/*-----------------------------------------------------------------------------
                                Functions prototypes
 -----------------------------------------------------------------------------*/

static void amoeba(double**, double y[], int, double, 
        double (*funk)(double []), int *) ;
static double amotry(double **, double y[], double psum[], int, 
        double (*funk)(double []), int, double); 
static double fitsky(double *) ;
static double fitbkd(double *) ;
static cpl_bivector * kmos_get_thermal_background(cpl_bivector *, double) ;
static double get_average_disregarding_outliers(const cpl_vector *) ;
static cpl_error_code kmos_priv_sky_tweak_get_spectra(const cpl_imagelist *, 
        const cpl_imagelist *, const cpl_vector *, const cpl_image *, 
        const int, cpl_bivector **, cpl_bivector **) ;
static cpl_bivector* kmos_priv_sky_tweak_correct_vibrational_trans(
        cpl_bivector *, cpl_bivector *, int) ;

/*----------------------------------------------------------------------------*/
/**
    @defgroup kmos_priv_sky_tweak    Sky tweaking algorithm
 */
/*----------------------------------------------------------------------------*/

/**@{*/

/*----------------------------------------------------------------------------*/
/**
  @brief    Main sky tweak function
  @param
  @param
  @param
  @param
  @param
  @return
  

    Possible _cpl_error_code_ set in this function:
      @li CPL_ERROR_NULL_INPUT if input is NULL.
      @li CPL_ERROR_ILLEGAL_INPUT if input is incorrect.
 */
/*----------------------------------------------------------------------------*/
cpl_imagelist * kmos_priv_sky_tweak(
        cpl_imagelist           *   object, 
        cpl_imagelist           *   sky,
        const cpl_propertylist  *   header, 
        float                       min_frac, 
        int                         tbsub,
        int                         ifu_nr,
        int                         skip_last) 
{
    cpl_imagelist   *   result ;
    cpl_image       *   mask ;
    cpl_bivector    *   obj1_spectrum ;
    cpl_bivector    *   sky_spectrum ;
    cpl_bivector    *   thermal_backg ;
    cpl_bivector    *   vscales ;
    cpl_vector      *   lambda ;
    cpl_imagelist   *   modified_sky ;
    int                 nx, ny, nz, ix, kx, snx, sny, snz, kmax ;
    int ifu_plot = -1 ;

    /* Check Entries */
    if (object == NULL || sky==NULL || header==NULL) {
        cpl_msg_error(__func__, "NULL inputs") ;
        cpl_error_set(__func__, CPL_ERROR_NULL_INPUT) ;
        return NULL ;
    }
    nx = cpl_image_get_size_x(cpl_imagelist_get_const(object, 0));
    ny = cpl_image_get_size_y(cpl_imagelist_get_const(object, 0));
    nz = cpl_imagelist_get_size(object);
    snx = cpl_image_get_size_x(cpl_imagelist_get_const(sky, 0));
    sny = cpl_image_get_size_y(cpl_imagelist_get_const(sky, 0));
    snz = cpl_imagelist_get_size(sky);
    if (nx!=snx || ny!=sny || nz!=snz) {
        cpl_msg_error(__func__, "Illegal inputs") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return NULL ;
    }

    /* Create wavelengths vector corresponding to the input cubes */
    lambda = kmo_lcorr_create_lambda_vector(header);
    if (nz != cpl_vector_get_size(lambda)) {
        cpl_msg_error(__func__, "Illegal inputs") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return NULL ;
    }

    /* mask is a binary image : 0 -> object / 1 -> background  */
    mask = kmo_lcorr_create_object_mask(object, min_frac, NULL, NULL);
    if (ifu_nr == ifu_plot) {
        cpl_image_save(mask,"mask.fits",CPL_TYPE_DOUBLE,NULL,CPL_IO_CREATE);
    }

    /* Get the background spectrum (using mask) from obj and sky cubes */
    kmos_priv_sky_tweak_get_spectra(object, sky, lambda, mask, 1, 
            &obj1_spectrum, &sky_spectrum);
    cpl_image_delete(mask);
    if (ifu_nr == ifu_plot) {
        cpl_plot_bivector("set title \"OBJ\";", "w lines","",obj1_spectrum) ;
        cpl_plot_bivector("set title \"SKY\";", "w lines", "",sky_spectrum) ;
    }

    /* Get the corrections factors for each sub-band */
    vscales = kmos_priv_sky_tweak_correct_vibrational_trans(obj1_spectrum,
                sky_spectrum, skip_last);
    if (ifu_nr == ifu_plot) {
        cpl_plot_bivector("set title \"SCALES\";", "w lines", "",vscales) ;
    }

    /* Get the thermal bgd */
    thermal_backg = kmos_get_thermal_background(obj1_spectrum, 1.0);

    /* Re-try if the computation failed (PIPE-5528) */
    if (thermal_backg == NULL) {
        cpl_error_reset() ;
        cpl_msg_indent_more() ;
        cpl_msg_warning(__func__, 
                "Thermal Bgd failed, re-try by clipping highest values") ;
        thermal_backg = kmos_get_thermal_background(obj1_spectrum, 0.98);
        if (thermal_backg == NULL) {
            cpl_msg_warning(__func__, 
                    "Recovery failed - skip the thermal background correction");
        } else {
            cpl_msg_info(__func__, "Recovery succeeded");
        }
        cpl_msg_indent_less() ;
    }

    if (ifu_nr == ifu_plot) {
        cpl_plot_bivector("set title \"Th. bgd\";","w lines", "",thermal_backg);
    }

    /* Correct using the computes scales */
    result = cpl_imagelist_duplicate(object);
    modified_sky = cpl_imagelist_duplicate(sky);
    kmax = cpl_vector_get_size(cpl_bivector_get_x_const(obj1_spectrum));
    kx = 0;
    for (ix=0; ix<nz; ix++) {
        if ((kx < kmax ) && (cpl_vector_get(lambda,ix)==cpl_vector_get(
                        cpl_bivector_get_x_const(obj1_spectrum),kx))) {
            /* Subtract thermal background from new sky cube */
            if (thermal_backg != NULL) {
                cpl_image_subtract_scalar(cpl_imagelist_get(modified_sky,ix),
                        cpl_vector_get(cpl_bivector_get_y_const(thermal_backg),
                            kx));
            }
            
            /* Multiply new sky cube with scaling factors from  */
            /* OH line fitting */
            cpl_image_multiply_scalar(cpl_imagelist_get(modified_sky,ix),
                    cpl_vector_get(cpl_bivector_get_y_const(vscales), kx));
            /* Subtract new sky cube from object cube copy to  */
            /* get new object cube */
            cpl_image_subtract(cpl_imagelist_get(result, ix), 
                    cpl_imagelist_get(modified_sky,ix));

            /* Subtract thermal background from new object cube as well */
            if (tbsub && thermal_backg != NULL) {
                cpl_image_subtract_scalar(cpl_imagelist_get(result, ix),
                        cpl_vector_get(cpl_bivector_get_y_const(
                                thermal_backg), kx));
            }
            kx++;
        }
    }
    cpl_vector_delete(lambda);
    cpl_bivector_delete(obj1_spectrum);
    cpl_bivector_delete(sky_spectrum);
    if (thermal_backg != NULL) cpl_bivector_delete(thermal_backg);
    cpl_bivector_delete(vscales);
    cpl_imagelist_delete(modified_sky);
    return result;
}

/** @} */

static cpl_bivector * kmos_get_thermal_background(
        cpl_bivector    *   spectrum,
        double              clip_rate)
{
    cpl_bivector    *   result ;
    cpl_vector      *   spectrum_v ;
    cpl_vector      *   thermal_v ;
    cpl_vector      *   tmp_v ;
    cpl_vector      *   sorted_y ;
    const int           ndim = 3;
    const int           nsimplex = ndim + 1;
    double          **  p ;
    double              p_init[ndim+1];
    double          *   lspectrum = NULL,
                    *   vspectrum = NULL;
    double          *   x_amoeba ;
    double          *   y_amoeba ;
    int                 i, j, ix, loop, niter, new_size, skip, input_size ;
    double              min, max, tmp, diff, limit, clip_limit ;

    /* Check entries */
    if (spectrum == NULL) {
        cpl_msg_error(__func__, "NULL inputs") ;
        cpl_error_set(__func__, CPL_ERROR_NULL_INPUT) ;
        return NULL ;
    }
    if (clip_rate < 0.5 || clip_rate > 1.0) {
        cpl_msg_error(__func__, "Invalid clip rate: %g", clip_rate) ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return NULL ;
    }

    /* Initialise */
    min = +1.e30;
    max = -1.e30;
    niter = 0 ;
    new_size = 0;
    skip = 11;
    input_size = cpl_bivector_get_size(spectrum) ;

    lspectrum = cpl_bivector_get_x_data(spectrum);
    vspectrum = cpl_bivector_get_y_data(spectrum);
    spectrum_lambda=cpl_malloc(input_size*sizeof(double));
    spectrum_value=cpl_malloc(input_size* sizeof(double));
    thermal_background= cpl_malloc(input_size*sizeof(double));

    /* Get rid off spikes - skip highest values in the first iteration */ 
    if (clip_rate < 1.0) {
        sorted_y = cpl_vector_duplicate(cpl_bivector_get_y(spectrum));
        cpl_vector_sort(sorted_y,  CPL_SORT_ASCENDING);
        clip_limit = cpl_vector_get(sorted_y, 
                cpl_vector_get_size(sorted_y) * clip_rate);
        cpl_vector_delete(sorted_y) ;
    } else {
        clip_limit = +1.e30;
    }

    /* Move to the first non-zero value */
    for (ix=0; ix < input_size ; ix++) if (vspectrum[ix] != 0.0) break;
    /* Store the following non-zero/nan/inf values  */
    /* Also omit the clipped ones - Store the min of the retained values */
    /* Skip the 11 first values */
    for (i=ix+skip; i<input_size; i++) {
        if ((vspectrum[i] != 0.0) && (kmclipm_is_nan_or_inf(vspectrum[i])==0) 
                && (vspectrum[i] <= clip_limit) ) {
            spectrum_lambda[new_size] = lspectrum[i];
            spectrum_value[new_size] = vspectrum[i];
            if (vspectrum[i] < min) { min = vspectrum[i]; }
            new_size++;
        }
    }
    /* Skip the last 0.0 values if any */
    for (ix=new_size; ix >= 0; ix--) if (vspectrum[ix] != 0.0) break;
    /* Skip the 11 last values */
    spectrum_size = ix-skip;

    y_amoeba=cpl_malloc((nsimplex+1)*sizeof(double));
    x_amoeba=cpl_malloc((ndim+1)*sizeof(double));
    p = matrix(nsimplex+1,ndim+1);

    p_init[1] = min;
    p_init[2] = spectrum_value[spectrum_size-1];
    p_init[3] = 280.;
    p[1][1] = p_init[1];
    p[1][2] = p_init[2];
    p[1][3] = p_init[3];

    for (loop=0; loop<20; loop++) {
        /*
        printf("p initial : %d %g %g %g\n", 
                loop, p[1][1], p[1][2], p[1][3]) ;
        */
        for (i=2; i<nsimplex+1; i++) {
            for (j=1; j<ndim+1; j++) p[i][j] = p[1][j];
        }
        for (i=2; i<nsimplex+1; i++) p[i][i-1] = p[i][i-1] * 1.2;
        for (i=1; i<nsimplex+1; i++) {
            for (j=1; j<ndim+1; j++) x_amoeba[j] = p[i][j];
            y_amoeba[i] = fitbkd(x_amoeba);
        }
        amoeba(p, y_amoeba, 3, 1.e-5, fitbkd, &niter  );

        spectrum_v = cpl_vector_wrap(spectrum_size, spectrum_value);
        thermal_v = cpl_vector_wrap(spectrum_size, thermal_background);
        tmp_v =  cpl_vector_duplicate(spectrum_v);
        cpl_vector_subtract(tmp_v, thermal_v);
        limit = cpl_vector_get_median(tmp_v) + 2. * cpl_vector_get_stdev(tmp_v);
        cpl_vector_delete(tmp_v);
        cpl_vector_unwrap(spectrum_v);
        cpl_vector_unwrap(thermal_v);

        min = +1.e30;
        new_size = 0;
        for (i=0; i<spectrum_size; i++) {
            diff = spectrum_value[i] - thermal_background[i];
            if (diff < limit) {
                spectrum_lambda[new_size] = spectrum_lambda[i];
                spectrum_value[new_size] = spectrum_value[i];
                if (spectrum_value[i] < min) min = spectrum_value[i];
                new_size++;
            }
        }
        spectrum_size = new_size;
    }

    for (i=0; i<spectrum_size; i++) {
        tmp = PLANCK(spectrum_lambda[i], p[1][3]);
        if (tmp > max) max = tmp;
    }
    if (fabs(max) < 1e-20) {
        cpl_msg_error(__func__, "Cannot determine thermal Background") ;
        free_matrix(p, 5);
        cpl_free(x_amoeba); 
        cpl_free(y_amoeba);
        cpl_free(spectrum_lambda);
        cpl_free(spectrum_value);
        cpl_free(thermal_background);
        cpl_error_set(__func__, CPL_ERROR_NULL_INPUT) ;
        return NULL ;
    }

    for (i=0; i<input_size; i++) {
        spectrum_lambda[i] = lspectrum[i];
        spectrum_value[i] = vspectrum[i];
        tmp = PLANCK(spectrum_lambda[i], p[1][3]);
        thermal_background[i] = p[1][1] + tmp / max * p[1][2];
    }
    thermal_v = cpl_vector_wrap(input_size, thermal_background);
    tmp_v =  cpl_vector_duplicate(thermal_v);
    cpl_vector_unwrap(thermal_v);

    result = cpl_bivector_wrap_vectors(
            cpl_vector_duplicate(cpl_bivector_get_x_const(spectrum)), tmp_v);

    free_matrix(p, 5);
    cpl_free(x_amoeba); 
    cpl_free(y_amoeba);
    cpl_free(spectrum_lambda);
    cpl_free(spectrum_value);
    cpl_free(thermal_background);
    return result;
}

static double get_average_disregarding_outliers(const cpl_vector * vdata) 
{
    cpl_vector      *   tmpv1 ;
    cpl_vector      *   tmpv2 ;
    cpl_vector      *   tmpv3 ;
    cpl_vector      *   tmpv4 ;
    const double    *   data ;
    double          *   tmpv1_data;
    double              avg, median, stdev, clip;
    int                 nr_data, i, nr_i;

    nr_data = cpl_vector_get_size(vdata);
    data = cpl_vector_get_data_const(vdata);
    tmpv1 = cpl_vector_new(nr_data);
    tmpv1_data = cpl_vector_get_data(tmpv1);

    median = cpl_vector_get_median_const(vdata);
    for (i=0; i<nr_data; i++) tmpv1_data[i] = fabs(data[i] - median);
    cpl_vector_sort(tmpv1, CPL_SORT_ASCENDING);
    clip = cpl_vector_get(tmpv1, (int) .8*nr_data);
    tmpv2 = kmo_idl_where(tmpv1, clip*5., le);
    tmpv3 = kmo_idl_values_at_indices(vdata, tmpv2);
    median = cpl_vector_get_median_const(tmpv3);
    stdev = cpl_vector_get_stdev(tmpv3);
    nr_i = 0;
    for (i=0; i<nr_data; i++) {
        if ((data[i] < median + 3. * stdev) && (data[i] > median - 3. * stdev)){
            tmpv1_data[nr_i] = data[i];
            nr_i++;
        }
    }
    tmpv4 = cpl_vector_wrap(nr_i, tmpv1_data);
    avg = cpl_vector_get_mean(tmpv4);

    cpl_vector_delete(tmpv1);
    cpl_vector_delete(tmpv2);
    cpl_vector_delete(tmpv3);
    cpl_vector_unwrap(tmpv4);
    return avg;
}

static cpl_error_code kmos_priv_sky_tweak_get_spectra(
        const cpl_imagelist     *   object,
        const cpl_imagelist     *   sky,
        const cpl_vector        *   lambda,
        const cpl_image         *   mask,
        const int                   no_nans,
        cpl_bivector            **  obj_spectrum_ptr,
        cpl_bivector            **  sky_spectrum_ptr) 
{
    cpl_bivector    *   obj_spectrum ; 
    cpl_bivector    *   sky_spectrum ; 
    cpl_vector      *   intobj ;
    cpl_vector      *   intsky ;
    cpl_vector      *   new_lambda ;
    cpl_vector      *   new_intobj ;
    cpl_vector      *   new_intsky ;
    const cpl_image *   oimg ;
    const cpl_image *   simg ;
    double          *   intobj_data ;
    double          *   intsky_data ;
    double          *   vo ;
    double          *   vs ;
    double          *   new_lambda_d ;
    double          *   new_intobj_d ;
    double          *   new_intsky_d ;
    const double    *   lambda_d ;
    cpl_vector      *   ovec ;
    cpl_vector      *   svec ;
    double              mpix, opix, spix;
    int                 nx, ny, nz, snx, sny, snz, ix, iy, iz, ni, found, 
                        nr_valid_int, m_is_rejected, o_is_rejected, 
                        s_is_rejected;

    nx = cpl_image_get_size_x(cpl_imagelist_get_const(object, 0));
    ny = cpl_image_get_size_y(cpl_imagelist_get_const(object, 0));
    nz = cpl_imagelist_get_size(object);
    snx = cpl_image_get_size_x(cpl_imagelist_get_const(sky, 0));
    sny = cpl_image_get_size_y(cpl_imagelist_get_const(sky, 0));
    snz = cpl_imagelist_get_size(sky);

    /* Check entries */
    if (nx!=snx || ny!=sny || nz!=snz) {
        cpl_msg_error(__func__, "Illegal inputs") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return CPL_ERROR_ILLEGAL_INPUT ;
    }
    if (nz != cpl_vector_get_size(lambda)) {
        cpl_msg_error(__func__, "Illegal inputs") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return CPL_ERROR_ILLEGAL_INPUT ;
    }

    intobj = cpl_vector_new(cpl_vector_get_size(lambda));
    intsky = cpl_vector_new(cpl_vector_get_size(lambda));
    intobj_data = cpl_vector_get_data(intobj);
    intsky_data = cpl_vector_get_data(intsky);

    vo = cpl_malloc(nx * ny * sizeof(double));
    vs = cpl_malloc(nx * ny * sizeof(double));

    nr_valid_int = 0;
    for (iz = 0; iz <nz; iz++) {
        oimg = cpl_imagelist_get_const(object, iz);
        simg = cpl_imagelist_get_const(sky, iz);
        found = 0;
        for (ix = 1; ix<=nx; ix++) {
            for (iy = 1; iy<=ny; iy++) {
                mpix = cpl_image_get(mask, ix, iy, &m_is_rejected);
                opix = cpl_image_get(oimg, ix, iy, &o_is_rejected);
                spix = cpl_image_get(simg, ix, iy, &s_is_rejected);
                if ( mpix > .5 && m_is_rejected == 0 && o_is_rejected == 0 &&
                        s_is_rejected == 0 ) {
                    vo[found] = opix;
                    vs[found] = spix;
                    found++;
                }
            }
        }
        if (found >= nx*ny/4.) {
            nr_valid_int++;
            ovec = cpl_vector_wrap(found, vo);
            svec = cpl_vector_wrap(found, vs);

            if (found < nx*ny/2. ) {
                intobj_data[iz] = cpl_vector_get_median(ovec);
                intsky_data[iz] = cpl_vector_get_median(svec);
            } else {
                intobj_data[iz] = get_average_disregarding_outliers(ovec);
                intsky_data[iz] = get_average_disregarding_outliers(svec);
            }
            cpl_vector_unwrap(ovec);
            cpl_vector_unwrap(svec);
        } else {
            intobj_data[iz] = 0./0.;
            intsky_data[iz] = 0./0.;
        }
    }

    if (no_nans) {
        new_lambda = cpl_vector_new(nr_valid_int);
        new_intobj = cpl_vector_new(nr_valid_int);
        new_intsky = cpl_vector_new(nr_valid_int);
        new_lambda_d=cpl_vector_get_data(new_lambda);
        new_intobj_d=cpl_vector_get_data(new_intobj);
        new_intsky_d=cpl_vector_get_data(new_intsky);
        lambda_d=cpl_vector_get_data_const(lambda);

        ni = 0;
        for (iz=0 ; iz<nz; iz++) {
            if ((! isnan(intobj_data[iz])) && ni < nr_valid_int) {
                new_lambda_d[ni] = lambda_d[iz];
                new_intobj_d[ni] = intobj_data[iz];
                new_intsky_d[ni] = intsky_data[iz];
                ni++;
            }
        }
        obj_spectrum = cpl_bivector_wrap_vectors(new_lambda, new_intobj) ;
        sky_spectrum = cpl_bivector_wrap_vectors(
                cpl_vector_duplicate(new_lambda), new_intsky);

        cpl_vector_delete(intobj);
        cpl_vector_delete(intsky);
    } else {
        obj_spectrum = cpl_bivector_wrap_vectors(cpl_vector_duplicate(lambda), 
                intobj);
        sky_spectrum = cpl_bivector_wrap_vectors(cpl_vector_duplicate(lambda), 
                intsky);
    }

    cpl_free(vo);
    cpl_free(vs);
    *obj_spectrum_ptr = obj_spectrum;
    *sky_spectrum_ptr = sky_spectrum;
    return CPL_ERROR_NONE;
}

static cpl_bivector * kmos_priv_sky_tweak_correct_vibrational_trans(
        cpl_bivector    *   obj_spectrum,
        cpl_bivector    *   sky_spectrum,
        int                 skip_last)
{
    cpl_bivector    *   result ;
    double          *   scalings ;
    double          *   lambda_boundaries ;
    char            **  labels ;
    const double    *   lambda ;
    const double    *   intobj ;
    const double    *   intsky ;
    double          *   tmp1_l ;
    double          *   tmp1_o ;
    double          *   tmp1_s ;
    double          *   tmp3_s ;
    double          *   tmp4_d ;
    double          *   line_interpolate_object ;
    double          *   line_interpolate_sky ;
    double          *   flineres; 
    cpl_vector      *   tmp1_l_v ;
    cpl_vector      *   tmp1_o_v ;
    cpl_vector      *   tmp1_s_v ;
    cpl_vector      *   tmp2_s_v ;
    cpl_vector      *   tmp3_s_v ;
    cpl_vector      *   tmp4_v  ;
    double              median, stdev ;
    int                 i, ix, il, nr_lambda, nr_transitions,
                        nr_samples, nr_boundaries ;

    /* Check Entries */
    if (obj_spectrum == NULL || sky_spectrum == NULL) {
        cpl_msg_error(__func__, "NULL inputs") ;
        cpl_error_set(__func__, CPL_ERROR_NULL_INPUT) ;
        return NULL;
    }

    /* Initialise */
    median = stdev = 0.0 ;
    tmp4_v = NULL ;
    if (skip_last) {
        nr_boundaries = 20 ;
        lambda_boundaries = cpl_malloc(nr_boundaries * sizeof(double)) ;
        labels = cpl_malloc((nr_boundaries-1) * sizeof(char*)) ;
    } else {
        nr_boundaries = 21 ;
        lambda_boundaries = cpl_malloc(nr_boundaries * sizeof(double)) ;
        labels = cpl_malloc((nr_boundaries-1) * sizeof(char*)) ;
    }
    lambda_boundaries[0]  = 0.780 ;     labels[0]  = "" ;
    lambda_boundaries[1]  = 0.824 ;     labels[1]  = "" ;
    lambda_boundaries[2]  = 0.873 ;     labels[2]  = "" ;
    lambda_boundaries[3]  = 0.926 ;     labels[3]  = "" ;
    lambda_boundaries[4]  = 0.964 ;     labels[4]  = "" ;
    lambda_boundaries[5]  = 1.014 ;     labels[5]  = "4-1 transitions" ;
    lambda_boundaries[6]  = 1.067 ;     labels[6]  = "5-2 transitions" ;
    lambda_boundaries[7]  = 1.125 ;     labels[7]  = "6-3 transitions" ;
    lambda_boundaries[8]  = 1.196 ;     labels[8]  = "7-4 transitions" ;
    lambda_boundaries[9]  = 1.252 ;     labels[9]  = " 02 transitions" ;
    lambda_boundaries[10] = 1.289 ;     labels[10] = "8-5 transitions" ;
    lambda_boundaries[11] = 1.400 ;     labels[11] = "2-0 transitions" ;
    lambda_boundaries[12] = 1.472 ;     labels[12] = "3-1 transitions" ;
    lambda_boundaries[13] = 1.5543 ;    labels[13] = "4-2 transitions" ;
    lambda_boundaries[14] = 1.6356 ;    labels[14] = "5-3 transitions" ;
    lambda_boundaries[15] = 1.7253 ;    labels[15] = "6-4 transitions" ;
    lambda_boundaries[16] = 1.840 ;     labels[16] = "7-5 transitions" ;
    lambda_boundaries[17] = 1.9570 ;    labels[17] = "8-6 transitions" ;
    lambda_boundaries[18] = 2.095 ;     labels[18] = "9-7 transitions" ;
    if (skip_last) {
        lambda_boundaries[19] = 2.460 ; 
    } else {
        lambda_boundaries[19] = 2.30 ;  labels[19] = "final bit" ;
        lambda_boundaries[20] = 2.460 ;
    }

    /* Allocate scalings */
    scalings = cpl_malloc(nr_boundaries * sizeof(double)) ;

    nr_lambda = cpl_bivector_get_size(obj_spectrum);
    if (nr_lambda != cpl_bivector_get_size(sky_spectrum)) {
        cpl_msg_error(__func__, "Illegal inputs") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        cpl_free(scalings) ;
        cpl_free(labels) ;
        cpl_free(lambda_boundaries) ;
        return NULL;
    }
    lambda = cpl_vector_get_data_const(cpl_bivector_get_x_const(obj_spectrum));
    intobj = cpl_vector_get_data_const(cpl_bivector_get_y_const(obj_spectrum));
    intsky = cpl_vector_get_data_const(cpl_bivector_get_y_const(sky_spectrum));

    for (ix=0; ix<nr_boundaries; ix++) scalings[ix] = 0.;
    for (ix=0; ix<nr_lambda; ix++) {
        double tol = 1.e-7;
        if (fabs(lambda[ix] - 
                    cpl_vector_get(cpl_bivector_get_x_const(sky_spectrum), ix))
                >= tol) {
            cpl_msg_error(__func__, "Illegal inputs") ;
            cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
            cpl_free(scalings) ;
            cpl_free(labels) ;
            cpl_free(lambda_boundaries) ;
            return NULL;
        }
    }

    tmp1_l_v = cpl_vector_new(nr_lambda);
    tmp1_l = cpl_vector_get_data(tmp1_l_v);
    tmp1_o_v = cpl_vector_new(nr_lambda);
    tmp1_o = cpl_vector_get_data(tmp1_o_v);
    tmp1_s_v = cpl_vector_new(nr_lambda);
    tmp1_s = cpl_vector_get_data(tmp1_s_v);

    char dbg_message[4096];
    dbg_message[0] = '\0';
    char *tmp_dbg_message = NULL;
    sprintf(dbg_message, "> ");
    nr_transitions = 0;
    /* Loop on the SUB BANDS */
    /* The SUB BANDS are handled separately as one SUB BAND is */
    /* driven by the same physical conditions  */
    for (ix = 0; ix<nr_boundaries-1; ix++) {
        /* do following free in case continue-statement  */
        /* has been reached in if-staement below */
        if (tmp4_v != NULL) { cpl_vector_delete(tmp4_v);  tmp4_v = NULL; }

        tmp_dbg_message = cpl_sprintf("Checking %2d: %s", ix, labels[ix]);
        strcat(dbg_message, tmp_dbg_message);
        cpl_free(tmp_dbg_message); tmp_dbg_message = NULL;

        int nr_nans_sky = 0 ;
        int nr_nans_obj = 0 ;
        nr_samples = 0;
        for (il=0; il<nr_lambda; il++) {
            if ((lambda[il] >= lambda_boundaries[ix] ) &&
                (lambda[il] < lambda_boundaries[ix+1]) ) {

                if (kmclipm_is_nan_or_inf(intsky[il]) || 
                        kmclipm_is_nan_or_inf(intobj[il])) 
                    continue ;

                tmp1_l[nr_samples] = lambda[il];
                tmp1_o[nr_samples] = intobj[il];
                tmp1_s[nr_samples] = intsky[il];
                if (kmclipm_is_nan_or_inf(intobj[il])) nr_nans_obj++ ;
                if (kmclipm_is_nan_or_inf(intsky[il])) nr_nans_sky++ ;
                nr_samples++;
            }
        }
        tmp_dbg_message = cpl_sprintf("   Found %d samples", nr_samples);
        strcat(dbg_message, tmp_dbg_message);
        cpl_free(tmp_dbg_message); tmp_dbg_message = NULL;

        if (nr_samples > 20) {
            tmp2_s_v = cpl_vector_wrap(nr_samples, tmp1_s);
            median = cpl_vector_get_median_const(tmp2_s_v);
            stdev = cpl_vector_get_stdev(tmp2_s_v);
            tmp3_s_v = cpl_vector_duplicate(tmp2_s_v);
            cpl_vector_unwrap(tmp2_s_v);
            tmp3_s = cpl_vector_get_data(tmp3_s_v);
            for (i=0; i<cpl_vector_get_size(tmp3_s_v); i++) {
                if (tmp1_s[i] > median+stdev)   tmp3_s[i] =10.;
                else                            tmp3_s[i] = 0;
            }

            tmp4_v = cpl_vector_filter_lowpass_create(tmp3_s_v,
                    CPL_LOWPASS_LINEAR,2);
            if (tmp3_s_v != NULL) cpl_vector_delete(tmp3_s_v); 
            tmp4_d = cpl_vector_get_data(tmp4_v);

            /* Make sure cont/line regions start and end with  */
            /* a continuum region */
            int first_cont_region = -1;
            int last_cont_region = -1;
            int previous_line_region = 0;
            cont_region_size = 0;
            line_region_size = 0;
            for (i=0; i<cpl_vector_get_size(tmp4_v); i++) {
                if (tmp4_d[i] > 0.) {
                    //regions have to start with continuum
                    if (first_cont_region == -1) continue;  
                    previous_line_region++;
                } else {
                    if (first_cont_region == -1) first_cont_region = i;
                    last_cont_region = i;
                    cont_region_size++;
                    line_region_size += previous_line_region;
                    previous_line_region = 0;
                }
            }
            tmp_dbg_message = cpl_sprintf(
                    " cont region size: %d,  first line region size: %d",
                    cont_region_size, line_region_size);
            strcat(dbg_message, tmp_dbg_message);
            cpl_free(tmp_dbg_message); tmp_dbg_message = NULL;

            if (line_region_size == 0) {
                tmp_dbg_message = cpl_sprintf(
                        ", either no line region found or a single which is not surrounded by continuum");
                strcat(dbg_message, tmp_dbg_message);
                cpl_free(tmp_dbg_message); tmp_dbg_message = NULL;
                cpl_msg_debug(__func__, "%s", dbg_message);
                dbg_message[0] = '\0';
                continue;
            }

            /* if (cont_region_size < 20) continue ; */

            /* Allocate arrays to hold concatenated line and  */
            /* continuum regions */
            cont_sky_intensities=cpl_malloc(cont_region_size*sizeof(double));
            cont_object_intensities=cpl_malloc(cont_region_size*sizeof(double));
            cont_lambda = cpl_malloc(cont_region_size * sizeof(double));
            line_sky_intensities=cpl_malloc(line_region_size * sizeof(double));
            line_object_intensities=cpl_malloc(line_region_size*sizeof(double));
            line_lambda = cpl_malloc(line_region_size * sizeof(double));

            /* Sort input spectra to line and continuum arrays  */
            /* (for this spectrum range) */
            int cx = 0;
            int lx = 0;
            for(i=first_cont_region; i<=last_cont_region; i++) {
                if (tmp4_d[i] > 0.) {
                    line_sky_intensities[lx] = tmp1_s[i];
                    line_object_intensities[lx] = tmp1_o[i];
                    line_lambda[lx] = tmp1_l[i];
                    lx++;
                } else {
                    cont_sky_intensities[cx] = tmp1_s[i];
                    cont_object_intensities[cx] = tmp1_o[i];
                    cont_lambda[cx] = tmp1_l[i];
                    cx++;
                }
            }

            /* Minimize object - sky difference (line regions -  */
            /* interpolated background) using a single factor */
            const int ndim = 1;
            const int nsimplex = ndim + 1;
            double **p = NULL;
            double *x_amoeba  = NULL,
                   *y_amoeba  = NULL;
            int niter;
            int ip,jp;
            double scale;
            y_amoeba = cpl_malloc((nsimplex+1) * sizeof(double));
            x_amoeba = cpl_malloc((ndim+1) * sizeof(double));
            p = matrix(nsimplex+1,ndim+1);

            p[1][1] = 1.;
            p[2][1] = 1.25;
            for (ip=1; ip<nsimplex+1; ip++) {
                for (jp=1; jp<ndim+1; jp++) {
                    x_amoeba[jp] = p[ip][jp];
                }
                y_amoeba[ip] = fitsky(x_amoeba);
            }
            amoeba(p, y_amoeba, ndim, 1.e-5, fitsky, &niter  );
            scale = p[1][1];
            flineres = cpl_malloc(line_region_size * sizeof(double));

            line_interpolate_object = polynomial_irreg_irreg_nonans(
                    cont_region_size, cont_lambda, cont_object_intensities,
                    line_region_size, line_lambda, 2);
            line_interpolate_sky = polynomial_irreg_irreg_nonans(
                    cont_region_size, cont_lambda, cont_sky_intensities,
                    line_region_size, line_lambda, 2);

            for (i=0; i<line_region_size; i++) {
                flineres[i] = (line_object_intensities[i] - 
                        line_interpolate_object[i]) - 
                    (line_sky_intensities[i] - line_interpolate_sky[i]) * 
                    scale;
            }

            cpl_vector *tmpx_v = NULL;
            tmpx_v = cpl_vector_wrap(line_region_size, flineres);
            median = cpl_vector_get_median_const(tmpx_v);
            stdev = cpl_vector_get_stdev(tmpx_v);
            if (tmpx_v != NULL) { cpl_vector_unwrap(tmpx_v); tmpx_v = NULL;}

            int clip_cnt=0;
            for (i=0; i<line_region_size; i++) {
                if ( fabs(flineres[i] - median) <= (3 * stdev) ) {
                    clip_cnt++;
                }
            }
            tmp_dbg_message = cpl_sprintf("  outliers: %d", 
                    line_region_size-clip_cnt);
            strcat(dbg_message, tmp_dbg_message);
            cpl_free(tmp_dbg_message); tmp_dbg_message = NULL;

            if ((clip_cnt != line_region_size) && (clip_cnt >= 3)) {
                lx = 0;
                for (i=0; i<line_region_size; i++) {
                    if ( fabs(flineres[i] - median) <= (3 * stdev) ) {
                        line_sky_intensities[lx] = line_sky_intensities[i];
                        line_object_intensities[lx] = 
                            line_object_intensities[i];
                        line_lambda[lx] = line_lambda[i];
                        lx++;
                    }
                }
                line_region_size = lx;
                tmp_dbg_message=cpl_sprintf("second line region size: %d", 
                        line_region_size);
                strcat(dbg_message, tmp_dbg_message);
                cpl_free(tmp_dbg_message); tmp_dbg_message = NULL;

                p[1][1] = 1.;
                p[2][1] = 1.5;
                for (ip=1; ip<nsimplex+1; ip++) {
                    for (jp=1; jp<ndim+1; jp++) x_amoeba[jp] = p[ip][jp];
                    y_amoeba[ip] = fitsky(x_amoeba);
                }
                amoeba(p, y_amoeba, ndim, 1.e-5, fitsky, &niter  );
                scale = p[1][1];
            }
            tmp_dbg_message = cpl_sprintf("   scale: %f",scale);
            strcat(dbg_message, tmp_dbg_message);
            cpl_free(tmp_dbg_message); tmp_dbg_message = NULL;
            scalings[ix] = scale;

            if (tmp4_v != NULL) { cpl_vector_delete(tmp4_v); tmp4_v = NULL;}
            if (p != NULL) { free_matrix(p, nsimplex+1); p = NULL; }
            if (x_amoeba != NULL) { cpl_free(x_amoeba); x_amoeba = NULL; }
            if (y_amoeba != NULL) { cpl_free(y_amoeba); y_amoeba = NULL; }
            if (cont_sky_intensities != NULL) {
                cpl_free(cont_sky_intensities); cont_sky_intensities = NULL;
            }
            if (line_sky_intensities != NULL) {
                cpl_free(line_sky_intensities); line_sky_intensities = NULL;
            }
            if (cont_object_intensities != NULL) {
                cpl_free(cont_object_intensities); 
                cont_object_intensities = NULL; }
            if (line_object_intensities != NULL) {
                cpl_free(line_object_intensities); 
                line_object_intensities = NULL; }
            if (cont_lambda != NULL) {
                cpl_free(cont_lambda); cont_lambda = NULL; }
            if (line_lambda != NULL) {
                cpl_free(line_lambda); line_lambda = NULL; }
            if (flineres != NULL) {
                cpl_free(flineres); flineres = NULL; }
            if (line_interpolate_object != NULL) {
                cpl_free(line_interpolate_object); 
                line_interpolate_object = NULL; }
            if (line_interpolate_sky != NULL) {
                cpl_free(line_interpolate_sky); 
                line_interpolate_sky = NULL; }
            nr_transitions++;
        } // if (nr_samples != 0)
        cpl_msg_debug(__func__, "%s", dbg_message);
        dbg_message[0] = '\0';
    } // end for (ix = 0; ix<nr_boundaries-1; ix++)
    cpl_free(labels) ;

    // check for outliers in the scaling factors
    int nr_valid = 0,
        v_ix = 0;
    double valid_scales[nr_boundaries];
    cpl_vector *valid_scales_v = NULL;
    for (ix=0; ix<nr_boundaries; ix++) {
        if (scalings[ix] != 0.0 ) {
            valid_scales[nr_valid] = scalings[ix];
            nr_valid++;
        }
    }
    valid_scales_v = cpl_vector_wrap(nr_valid, valid_scales);
    median = cpl_vector_get_median_const(valid_scales_v);
    stdev = cpl_vector_get_stdev(valid_scales_v);
    v_ix = 0;
    for (ix=0; ix<nr_boundaries; ix++) {
        if (scalings[ix] != 0.0 ) {
            if (fabs(valid_scales[v_ix] - median) > 2 * stdev) {
                scalings[ix] = 0.;
            }
            v_ix++;
        }
    }
    if (valid_scales_v != NULL) {
        cpl_vector_unwrap(valid_scales_v); 
        valid_scales_v = NULL; 
    }

    int scale0_size;
    double *scale0_lambda = NULL,
           *scale0_value = NULL,
           *scale = NULL;
    cpl_vector *scale_v = NULL;

    scale0_lambda = cpl_malloc(nr_lambda * sizeof(double));
    scale0_value = cpl_malloc(nr_lambda * sizeof(double));

    scale0_size = 0;
    for (il=0; il<nr_lambda; il++) {
        for (ix = 0; ix<nr_boundaries-1; ix++) {
            if (scalings[ix] != 0.) {
                if ((lambda[il] >= lambda_boundaries[ix] ) &&
                        (lambda[il] < lambda_boundaries[ix+1]) ) {
                    scale0_lambda[scale0_size] = lambda[il];
                    scale0_value[scale0_size] = scalings[ix];
                    scale0_size++;
                }
            }
        }
    }
    cpl_free(scalings) ;
    cpl_free(lambda_boundaries) ;

    scale = polynomial_irreg_irreg_nonans(scale0_size, scale0_lambda, 
            scale0_value, nr_lambda, lambda, 1);

    scale_v = cpl_vector_wrap(nr_lambda, scale);

    if (scale0_lambda != NULL) cpl_free(scale0_lambda); 
    if (scale0_value != NULL) cpl_free(scale0_value); 
    result = cpl_bivector_wrap_vectors(
            cpl_vector_duplicate(cpl_bivector_get_x_const(obj_spectrum)),
            scale_v);

    cpl_vector_delete(tmp1_l_v);
    cpl_vector_delete(tmp1_o_v);
    cpl_vector_delete(tmp1_s_v);
    cpl_vector_delete(tmp4_v);

    return result;
}

/* UNDER LICENSE !!!!!!! */

#define GET_PSUM \
                  for (j=1;j<=ndim;j++) {\
                       for (sum=0.0,i=1;i<=mpts;i++) sum += p[i][j];\
                       psum[j]=sum;}
#define SWAP(a,b) {swap=(a);(a)=(b);(b)=swap;}
static void amoeba(
        double  **  p, 
        double      y[], 
        int         ndim, 
        double      ftol, 
        double (*funk)(double []), 
        int     *   nfunk)
{
    int     i,ihi,ilo,inhi,j,mpts=ndim+1;
    double  rtol, sum, swap, ysave, ytry, *psum = NULL, d;

    psum=vector(ndim+1);
    *nfunk=0;
    GET_PSUM
    for (;;) {
        ilo=1;
        ihi = y[1]>y[2] ? (inhi=2,1) : (inhi=1,2);
        for (i=1;i<=mpts;i++) {
            if (y[i] <= y[ilo]) ilo=i;
            if (y[i] > y[ihi]) {
                inhi=ihi;
                ihi=i;
            } else if (y[i] > y[inhi] && i != ihi) inhi=i;
        }
        d = fabs(y[ihi])+fabs(y[ilo]);
        if (d == 0.0) {
            rtol = ftol / 2. ; //succeeds next if statement -> breaks loop
        } else {
            rtol=2.0*fabs(y[ihi]-y[ilo])/d;
        }
        if (rtol < ftol) {
            SWAP(y[1],y[ilo])
                for (i=1;i<=ndim;i++) SWAP(p[1][i],p[ilo][i])
                    break;
            }
        if (*nfunk >= 5000) printf("5000 exceeded\n");
        *nfunk += 2;
        ytry=amotry(p,y,psum,ndim,funk,ihi,-1.0);
        if (ytry <= y[ilo])
            ytry=amotry(p,y,psum,ndim,funk,ihi,2.0);
        else if (ytry >= y[inhi]) {
            ysave=y[ihi];
            ytry=amotry(p,y,psum,ndim,funk,ihi,0.5);
            if (ytry >= ysave) {
                for (i=1;i<=mpts;i++) {
                    if (i != ilo) {
                        for (j=1;j<=ndim;j++)
                            p[i][j]=psum[j]=0.5*(p[i][j]+p[ilo][j]);
                        y[i]=(*funk)(psum);
                    }
                }
                *nfunk += ndim;
                GET_PSUM
            }
        } else --(*nfunk);
    }
    free_vector(psum);
}
#undef SWAP
#undef GET_PSUM

static double amotry(
        double  **  p, 
        double      y[], 
        double      psum[], 
        int         ndim,
        double (*funk)(double []), 
        int         ihi, 
        double      fac)
{
    int j;
    double fac1,fac2,ytry,*ptry=NULL;

    ptry=vector(ndim+1);
    fac1=(1.0-fac)/ndim;
    fac2=fac1-fac;
    for (j=1;j<=ndim;j++) ptry[j]=psum[j]*fac1-p[ihi][j]*fac2;
    ytry=(*funk)(ptry);
    if (ytry < y[ihi]) {
        y[ihi]=ytry;
        for (j=1;j<=ndim;j++) {
            psum[j] += ptry[j]-p[ihi][j];
            p[ihi][j]=ptry[j];
        }
    }
    free_vector(ptry);
    return ytry;
}

static double fitsky(double *p) {
    double result = 0.;
    double *cont_sky = NULL,
           *cont_object = NULL;
    double avg = 0.;
    int ix;

    cont_sky = polynomial_irreg_irreg_nonans(cont_region_size, cont_lambda,
            cont_sky_intensities,
            line_region_size, line_lambda, 1);
    cont_object = polynomial_irreg_irreg_nonans(cont_region_size, cont_lambda,
            cont_object_intensities,
            line_region_size, line_lambda, 1);
    for (ix=0; ix<line_region_size; ix++) {
        double diff = (line_object_intensities[ix] - cont_object[ix]) -
                      (line_sky_intensities[ix] - cont_sky[ix]) * p[1];
        avg += diff * diff;
    }
    avg /= line_region_size;
    result = sqrt(avg);

    if (cont_sky != NULL)       free_vector(cont_sky); 
    if (cont_object != NULL)    free_vector(cont_object); 

    return result;
}

static double fitbkd(double *p) {
    double result = 0.;
    double *tmp = NULL;
    double max    = 0.;
    int i = 0;

    tmp = cpl_malloc(spectrum_size * sizeof(double));

    max = -1.;
    for (i=0; i<spectrum_size; i++) {
        tmp[i] = PLANCK(spectrum_lambda[i], p[3]);
        if (tmp[i] > max) {
            max = tmp[i];
        }
    }
    p[2]=fabs(p[2]);  // make sure scaling factor is positive
    if (max > 0.) {
        for (i=0; i<spectrum_size; i++) {
            thermal_background[i] = p[1] + tmp[i] / max * fabs(p[2]);
        }
    } else {
        for (i=0; i<spectrum_size; i++) {
            thermal_background[i] = tmp[i];
        }
    }

    result = 0.;
    for (i=0; i<spectrum_size; i++) {
        result += (spectrum_value[i] - thermal_background[i]) *
                  (spectrum_value[i] - thermal_background[i]);
    }

    if (tmp != NULL) cpl_free(tmp);
    return result;
}

