/*                                                                            *
 *   This file is part of the ESO X-shooter Pipeline                          *
 *   Copyright (C) 2006 European Southern Observatory                         *
 *                                                                            *
 *   This library 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, 51 Franklin St, Fifth Floor, Boston, MA  02111-1307  USA     *
 *                                                                            */

/* 
 * $Author: amodigli $
 * $Date: 2013-10-13 07:37:56 $
 * $Revision: 1.122 $
*/

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


/*----------------------------------------------------------------------------*/
/**
 * @defgroup xsh_subtract_sky_single  Subtract Sky in single frame
 * @ingroup drl_functions
 *
 * Function ...
 */
/*----------------------------------------------------------------------------*/
/**@{*/

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

#include <math.h>
#include <xsh_drl.h>
#include <xsh_drl_check.h>

#include <xsh_badpixelmap.h>
#include <xsh_data_pre.h>
#include <xsh_data_order.h>
#include <xsh_data_wavemap.h>
#include <xsh_data_localization.h>
#include <xsh_data_rec.h>
#include <xsh_dfs.h>
#include <xsh_pfits.h>
#include <xsh_error.h>
#include <xsh_utils_image.h>
#include <xsh_msg.h>
#include <xsh_fit.h>

#include <cpl.h>

#include <math.h>
#include <gsl/gsl_bspline.h>
#include <gsl/gsl_linalg.h>
#ifdef DARWIN
	#include <vecLib/clapack.h>
#else
        /* The following redefinition of complex is necessary since f2c.h
         * implements its own version of this type, which otherwise breaks
         * complation. We do not however use the complex type in this source
         * file so this workaround should be harmless. */
        #ifdef complex
                #undef complex
        #endif
        #define complex f2c_complex

	#include <f2c.h>
	#include <clapack.h>
#endif 

#define REGDEBUG_MEDIAN_SPLINE 0
/*-----------------------------------------------------------------------------
  Functions prototypes
  -----------------------------------------------------------------------------*/
static void xsh_fit_spline( xsh_wavemap_list * wave_list, int idx,
			int nbkpts, int order, int niter, 
                        float kappa, float ron2, float gain,
			const char * sarm );
static xsh_wavemap_list* xsh_wavemap_list_new( cpl_image *wavemap,
  cpl_image *slitmap, xsh_localization *list,
  xsh_order_list *order_table, xsh_pre *pre_sci, int nbkpts,cpl_frame* usr_defined_break_points_frame,
  xsh_subtract_sky_single_param* sky_par, xsh_instrument *instrument);
static xsh_rec_list* xsh_wavemap_list_build_sky( xsh_wavemap_list* list,
						 xsh_instrument *instrument);

static int xsh_wave_compare( const void * un, const void * deux ) ;
/*
 * Not used
   static int find_flux( const void * key, const void * ptr ) ;

   static double get_fitted_flux_at_lambda( wavemap_item * pentry,
					 double lambda, int nb );
					 */
static cpl_frame* xsh_wavelist_subtract_sky( xsh_pre * pre_sci,
					     xsh_rec_list* sky_list, 
					     xsh_wavemap_list * wave_list,
					     xsh_instrument* instrument,
					     const char* rec_prefix);

static int xsh_bspline_eval_all(const double x, gsl_vector *B, size_t *idx,
                            gsl_bspline_workspace *w, size_t *start_i);

static size_t xsh_bspline_find_interval(const double x, int *flag,
                                           gsl_bspline_workspace *w, size_t *start_i);

static cpl_error_code
xsh_dump_sky_fit(const int level, const char* sarm,
                   const int order, const int idx,
                   const int nitem,xsh_wavemap_list* wave_list, float* yf)
{


    cpl_table* debug_fit=cpl_table_new(nitem);
    double* pw=NULL;
    double* pf=NULL;
    double* ps=NULL;
    double* px=NULL;
    double* py=NULL;
    double* pfit=NULL;
    double* perr=NULL;
    wavemap_item* pentry=NULL;
    char fname[128], dirname[128], cmd[128];
    //if (level >= XSH_DEBUG_LEVEL_MEDIUM) {


    cpl_table_new_column(debug_fit,"WAVE",CPL_TYPE_DOUBLE);
    cpl_table_new_column(debug_fit,"FLUX",CPL_TYPE_DOUBLE);
    cpl_table_new_column(debug_fit,"SIGMA",CPL_TYPE_DOUBLE);
    cpl_table_new_column(debug_fit,"FIT",CPL_TYPE_DOUBLE);
    cpl_table_new_column(debug_fit,"ERR",CPL_TYPE_DOUBLE);
    cpl_table_new_column(debug_fit,"X",CPL_TYPE_DOUBLE);
    cpl_table_new_column(debug_fit,"Y",CPL_TYPE_DOUBLE);

    cpl_table_fill_column_window_double(debug_fit,"WAVE",0,nitem,0.);
    cpl_table_fill_column_window_double(debug_fit,"FLUX",0,nitem,0.);
    cpl_table_fill_column_window_double(debug_fit,"SIGMA",0,nitem,0.);
    cpl_table_fill_column_window_double(debug_fit,"FIT",0,nitem,0.);
    cpl_table_fill_column_window_double(debug_fit,"ERR",0,nitem,0.);
    cpl_table_fill_column_window_double(debug_fit,"X",0,nitem,0.);
    cpl_table_fill_column_window_double(debug_fit,"Y",0,nitem,0.);

    pw=cpl_table_get_data_double(debug_fit,"WAVE");
    pf=cpl_table_get_data_double(debug_fit,"FLUX");
    ps=cpl_table_get_data_double(debug_fit,"SIGMA");
    pfit=cpl_table_get_data_double(debug_fit,"FIT");
    perr=cpl_table_get_data_double(debug_fit,"ERR");
    px=cpl_table_get_data_double(debug_fit,"X");
    py=cpl_table_get_data_double(debug_fit,"Y");

    //}
    FILE* fout = NULL;
    /* output the smoothed curve */
    if (level >= XSH_DEBUG_LEVEL_LOW) {

        xsh_msg_dbg_medium("Output fitted data");
        sprintf(dirname, "Wave_%s", sarm);
        if (access(dirname, 0) != 0) {
            sprintf(cmd, "mkdir %s", dirname);
            system(cmd);
        }
        sprintf(fname, "%s/wave_%02d.dat", dirname, order);
        fout = fopen(fname, "w");
    }
    pentry = wave_list->list[idx].sky;
    for (int i = 0; i < nitem; i++, pentry++) {
        double yerr = 0;
        double xi = pentry->lambda;
        double yi = yf[i];
        pentry->fitted = yi;
        pentry->fit_err = yerr;
        if (level >= XSH_DEBUG_LEVEL_LOW) {
            fprintf(fout, "%f %f %f %d %d ", pentry->lambda, pentry->flux,
                            pentry->sigma, pentry->ix, pentry->iy);
            fprintf(fout, "%lf %lf\n", yi, yerr);
        }
        //if (level >= XSH_DEBUG_LEVEL_HIGH) {
        pw[i] = pentry->lambda;
        pf[i] = pentry->flux;
        ps[i] = pentry->sigma;
        pfit[i] = pentry->fitted;
        perr[i] = pentry->fit_err;
        px[i] = pentry->ix;
        py[i] = pentry->iy;
        //}
    }


    if (level >= XSH_DEBUG_LEVEL_HIGH) {
         sprintf(fname,"debug_fit.fits");
         if(idx == 0) {
           cpl_table_save(debug_fit,NULL,NULL,fname,CPL_IO_DEFAULT);
         } else {
           cpl_table_save(debug_fit,NULL,NULL,fname,CPL_IO_EXTEND);
         }
       }

    xsh_free_table(&debug_fit);
    if ( fout ) fclose( fout ) ;
    return cpl_error_get_code();
}

/*-----------------------------------------------------------------------------
  Implementation
  ---------------------------------------------------------------------------*/	


/*****************************************************************************/
/*****************************************************************************/
static void
xsh_fit_spline( xsh_wavemap_list * wave_list, int idx,
            int nbkpts, int bs_order, int niter, 
            float kappa, float ron2, float gain,
            const char * sarm )
{

	
   gsl_bspline_workspace *bw;
   gsl_matrix *Bn, *Bn_full;
   gsl_vector *Bkpts;
   int *Bidx,*Bidx_full;
   float *y,*yf;
   double *lambda_fit;
   double tmp_val;
	
   double start, end ;
   int ncoeffs ;
   int i, j ;
   int ii,jj,kk;
   int iii;
   int order, nitem ;
   wavemap_item * pentry ;
	
   float *Xtp,*c;
	
	
   size_t idxb=0;	
   size_t start_i;
   /* allocate a cubic bspline workspace (k = 4) */
   int ord=bs_order;
   int ord_minus_1 = ord - 1;
   double *bwB_ptr;
   double *Bkpts_ptr;

   double *Bn_ptr;
   int Bn_strd;

   double *Bn_full_ptr;
   int Bn_full_strd;
   int level;


   //xsh_msg("niter=%d kappa=%f ron2=%f gain=%f",niter,kappa,ron2,gain);

   pentry = wave_list->list[idx].sky;
   XSH_ASSURE_NOT_NULL( pentry ) ;
   order = wave_list->list[idx].order ;
   nitem = wave_list->list[idx].sky_size;
   start = wave_list->list[idx].lambda_min ;
   end = wave_list->list[idx].lambda_max ;

   /* this is to debug Hence to speed up we comment out
   xsh_msg_dbg_medium( "Fit Spline(%d), Order %d, bs_order: %d, ndata: %d, nbkpts: %d", 
                       idx, order, bs_order, nitem, nbkpts ) ;
   xsh_msg_dbg_medium( "            Lambda Min: %lf, Max: %lf", start, end ) ;
   */

   bw = gsl_bspline_alloc(ord, nbkpts);
   ncoeffs = gsl_bspline_ncoeffs(bw) ;
	
   Bkpts = gsl_vector_alloc(nbkpts);
   Bn = gsl_matrix_alloc(ord,nitem);
   Bidx = calloc(nitem,sizeof(int));
	
   Bn_full = gsl_matrix_alloc(ord,nitem);
   Bidx_full = calloc(nitem,sizeof(int));
	
   int ncoeffs_times_ord = ncoeffs*ord;
   Xtp=calloc(ncoeffs_times_ord,sizeof(float));
   c=calloc(ncoeffs,sizeof(float));
	
   y=calloc(nitem,sizeof(float));
   yf=calloc(nitem,sizeof(float));
	
   lambda_fit=calloc(nitem,sizeof(double));
	
   bwB_ptr=gsl_vector_ptr(bw->B,0);
   Bkpts_ptr=gsl_vector_ptr(Bkpts,0);
	
   Bn_ptr=gsl_matrix_ptr(Bn,0,0);
   Bn_strd=Bn->tda;
	
   Bn_full_ptr=gsl_matrix_ptr(Bn_full,0,0);
   Bn_full_strd=Bn_full->tda;

   level = xsh_debug_level_get() ;


   /* Define the Bezier spline breakpoints, initially evenly spaced in lambda */
	
   double kappa2 = kappa*kappa;
   for (iii=0;iii<niter;iii++){
      int nfit=0;
      int err;
      char uplo='U';
          
      /* Mac OS X uses different clapack.h type definitions, 
       * which alter depending with 32/64 bit builds */
#ifdef DARWIN
      __CLPK_integer clpk_n=ncoeffs;
      __CLPK_integer kd=ord_minus_1;
      __CLPK_integer nrhs=1;
      __CLPK_integer ldab=ord;
      __CLPK_integer ldb=ncoeffs;
      __CLPK_integer info=0;
#else
      integer clpk_n=ncoeffs;
      integer kd=ord_minus_1;
      integer nrhs=1;
      integer ldab=ord;
      integer ldb=ncoeffs;
      integer info=0;
#endif

      /* Fill the basis function (Bn) and flux (y) matrices for the fitted lambda, 
         the _full versions span all lambda. */
      pentry = wave_list->list[idx].sky;
      for(i=0;i<ncoeffs_times_ord;i++) {
         Xtp[i]=0;
      }
      for(i=0;i<ncoeffs;i++) {
         c[i]=0;
      }
      if(iii==0) {
         gsl_bspline_knots_uniform(start, end, bw);
      }
      else{
         for ( i = 0 ; i<nitem ; i++, pentry++ ) {

            tmp_val = yf[i] - pentry->flux;
            if( (tmp_val*tmp_val) < kappa2*(ron2+(fabs(yf[i])/gain)) ){
               lambda_fit[nfit]=pentry->lambda;
               nfit++;
            }
         }
         int nfit_div_nbkpts=nfit/nbkpts;
         for(i=0;i<nbkpts-1;i++) {
            Bkpts_ptr[i]=lambda_fit[i*nfit_div_nbkpts];
	       }
         Bkpts_ptr[0]=start;
         Bkpts_ptr[nbkpts-1]=end;
         gsl_bspline_knots(Bkpts, bw);
      }


      pentry = wave_list->list[idx].sky;
      nfit=0;
      start_i=bw->k - 1;

      for ( i = 0 ; i<nitem ; i++, pentry++ ) {
        /* TODO: clarify why we do this
         if ( isnan( (double)pentry->lambda ) ||
              isnan( (double)pentry->flux )   ||
              isnan( (double)pentry->sigma ) ) {
            xsh_msg( "Nan in lambda, flux or sigma" ) ;
         }
			  */
         xsh_bspline_eval_all( (double)pentry->lambda, bw->B, &idxb,bw, &start_i);
         Bidx_full[i]=idxb;
         for (j = 0; j < ord; ++j) {
            Bn_full_ptr[j*Bn_full_strd+i] = bwB_ptr[j];
      	 }

         tmp_val = yf[i] - pentry->flux;
         if(iii==0 || ( (tmp_val*tmp_val) < kappa2*(ron2+(fabs(yf[i])/gain)) )){
            y[nfit]=(float)pentry->flux;   
            Bidx[nfit]=idxb;
            for (j = 0; j < ord; ++j) {
               Bn_ptr[j*Bn_strd+nfit] = bwB_ptr[j];
      	    }
            nfit++;
         }
      }
      if (nfit > 0) {
         xsh_msg_debug("niter %d nfit: %d %f %f", iii, nfit,y[0],y[nfit-1]);
      }
      else{
         xsh_msg_debug("niter %d nfit: %d ", iii, nfit);
      }

      /* Construct the input to the fit: Xtp=Transpose(Bn).Bn and c=Bn.y */
      for(ii=0;ii<nfit;ii++){
         idxb = Bidx[ii]- ord_minus_1;
         for(jj=0;jj<ord;jj++){
            for(kk=jj;kk<ord;kk++){
               Xtp[((idxb + kk)*ord) + ord_minus_1 - kk + jj] +=
                   Bn_ptr[jj*Bn_strd + ii]*Bn_ptr[kk*Bn_strd + ii];
            }
         }
      }
		
      for(ii=0;ii<nfit;ii++){
        idxb = Bidx[ii] - ord_minus_1;
        for(jj=0;jj<ord;jj++){
            c[idxb + jj] += Bn_ptr[jj*Bn_strd+ii]*y[ii];
         }
      }
		
      /* Do the fit, by Cholesky decomposition, using Lapack functions sptrf and spdtrs */
      err=spbtrf_(&uplo,&clpk_n,&kd,Xtp,&ldab,&info);
      err=spbtrs_(&uplo,&clpk_n,&kd,&nrhs,Xtp,&ldab,c,&ldb,&info);
		
      /* Fill y with the predicted flux from the fit, over all values (using Bn_full) */
		
      for(ii=0;ii<nitem;ii++) {
         yf[ii]=0;
      }
		
      for(ii=0;ii<nitem;ii++){
         idxb=Bidx_full[ii] - ord_minus_1;
         for(jj=0;jj<ord;jj++){
            if(!isnan(c[idxb + jj])) {
               yf[ii] += Bn_full_ptr[jj*Bn_full_strd + ii]*c[idxb + jj];
            }
         }
      }
      xsh_msg_debug("end of iter");	
   }
	
   /* output the smoothed curve */
    xsh_dump_sky_fit(level, sarm, order, idx, nitem, wave_list, yf);
   
  cleanup:

   free(Xtp);
   free(c);
   free(y);
   free(yf);
   free(Bidx);
   free(Bidx_full);
   free(lambda_fit);
	
   gsl_bspline_free(bw);
   gsl_vector_free( Bkpts);
   gsl_matrix_free(Bn);
   gsl_matrix_free(Bn_full);

   return ;
}
/*****************************************************************************/

/*****************************************************************************/
/*****************************************************************************/
static int xsh_wave_compare( const void * un, const void * deux )
{
  wavemap_item * one = (wavemap_item *)un ;
  wavemap_item * two = (wavemap_item *)deux ;

  if ( one->lambda < two->lambda ) return -1 ;
  else if ( one->lambda == two->lambda ) return 0 ;
  return 1 ;
}
/*****************************************************************************/

/*****************************************************************************/
/**
  @brief
    Create the wavemap list
*/
/*****************************************************************************/
double xsh_nbkpts_uvb[XSH_ORDERS_UVB]={2.0,2.0,2.0,2.0,
                                       2.0,2.0,2.0,2.0,
                                       2.0,2.0,2.0,2.0}; //XSH_ORDERS_UVB=12



                             /* order= 16   17  18  19  */
double xsh_nbkpts_vis[XSH_ORDERS_VIS]={1.5,0.8,0.8,0.7,
                                       0.8,0.8,0.7,0.7,
                                       0.7,0.75,0.7,0.6,
                                       0.6,0.6,0.6};     //XSH_ORDERS_VIS=15


/*
double xsh_nbkpts_vis[XSH_ORDERS_VIS]={1.5,0.8,0.8,0.8,
                                       0.8,0.8,0.9,0.9,
                                       0.9,0.8,0.9,0.9,
                                       0.8,0.8,0.6};     //XSH_ORDERS_VIS=15

*/
                               /* right */
//0.28,0.28,0.28,0.3, STD
//0.30,0.28,0.28,0.7
double xsh_nbkpts_nir[XSH_ORDERS_NIR]={0.30,0.28,0.28,0.7,
                                       0.9,0.8,0.9,0.9,
                                       0.5,0.7,1.0,0.5,
                                       0.7,0.7,0.7,0.4}; //XSH_ORDERS_NIR=16

static cpl_error_code
xsh_get_obj_and_sky_extraction_slits(xsh_subtract_sky_single_param *sky_par,
                                     xsh_localization* list,
                                     double* obj_slit_min,
                                     double* obj_slit_max,
                                     double* sky_slit_min,
                                     double* sky_slit_max)
{

    double pos1 = sky_par->pos1;
    double hheight1 = sky_par->hheight1;

    double pos2 = sky_par->pos2;
    double hheight2 = sky_par->hheight2;

    /* if hheight1 or hheight2 is defined */
    if ((hheight1 == 0) && (hheight2 == 0) && (list != NULL)) {
        *obj_slit_min = cpl_polynomial_eval_1d(list->edglopoly, 600, NULL);
        *obj_slit_max = cpl_polynomial_eval_1d(list->edguppoly, 600, NULL);
        xsh_msg_dbg_medium("Limit in slit given by localization %f => %f",
                        *obj_slit_min, *obj_slit_max);
    }
    else {
        if (hheight1 > 0) {
            *sky_slit_min = pos1 - hheight1;
            *sky_slit_max = pos1 + hheight1;

            if (hheight2 > 0) {
                double s2_min, s2_max, s1_min, s1_max;

                s1_min = pos1 - hheight1;
                s1_max = pos1 + hheight1;

                s2_min = pos2 - hheight2;
                s2_max = pos2 + hheight2;

                if (s2_min < s1_min) {
                    *sky_slit_min = s2_min;
                }
                else {
                    *sky_slit_min = s1_min;
                }

                if (s2_max > s1_max) {
                    *sky_slit_max = s2_max;
                }
                else {
                    *sky_slit_max = s1_max;
                }

                if (s2_min > s1_max) {
                    *obj_slit_min = s1_max;
                    *obj_slit_max = s2_min;
                }

                if (s1_min > s2_max) {
                    *obj_slit_min = s2_max;
                    *obj_slit_max = s1_min;
                }
            }
        }
        else {
            *sky_slit_min = pos2 - hheight2;
            *sky_slit_max = pos2 + hheight2;
        }
    }
    return cpl_error_get_code();
}

static cpl_error_code
xsh_get_sky_edges_min_max_and_size(xsh_subtract_sky_single_param *sky_par,
                                   const int miny, const int maxy,
                                   const int iorder, const int order,
                                   const int nx, const int wmap_xsize_diff,
                                   xsh_order_list* ord_tab,
                                   cpl_image* slitmap,
                                   double* sky_slit_min,
                                   double* sky_slit_max,
                                   int *max_size)
{
    /*
     For each Y calculate the nb of pixels in X.
     */
    *max_size = 0;
    double* pslit = cpl_image_get_data_double( slitmap);
    double sky_slit_min_edge=0;
    double sky_slit_max_edge=0;
    for (int iy = miny; iy < maxy; iy++) {

        int minx = xsh_order_list_eval_int(ord_tab,
                        ord_tab->list[iorder].edglopoly, iy) + 1;
        int maxx = xsh_order_list_eval_int(ord_tab,
                        ord_tab->list[iorder].edguppoly, iy) - 1;

        int offset = iy * (nx - wmap_xsize_diff);
        float slit_val = pslit[minx + offset];

        if (slit_val > sky_slit_max_edge) {
            sky_slit_max_edge = slit_val;
        }
        if (slit_val < sky_slit_min_edge) {
            sky_slit_min_edge = slit_val;
        }

        slit_val = pslit[maxx + offset];

        if (slit_val > sky_slit_max_edge) {
            sky_slit_max_edge = slit_val;
        }
        if (slit_val < sky_slit_min_edge) {
            sky_slit_min_edge = slit_val;
        }
        /* Add nb of pixels in the X range */
        *max_size += (maxx - minx + 1);
    }


    XSH_ASSURE_NOT_ILLEGAL( sky_slit_min_edge <= sky_slit_max_edge);
    XSH_CMP_INT( *max_size, >, 0, "Not enough points for the sky order %d",
                  ,order);
     double slit_edges_mask = sky_par->slit_edges_mask;
     sky_slit_min_edge = sky_slit_min_edge+slit_edges_mask;
     sky_slit_max_edge = sky_slit_max_edge-slit_edges_mask;

     double hheight1 = sky_par->hheight1;
     double hheight2 = sky_par->hheight2;


     if ( (hheight1 == 0) && (hheight2 == 0)){
         *sky_slit_min = sky_slit_min_edge;
         *sky_slit_max = sky_slit_max_edge;
     }
     else{
         if (sky_slit_min_edge > *sky_slit_min){
             *sky_slit_min = sky_slit_min_edge;
         }
         if (sky_slit_max_edge < *sky_slit_max){
             *sky_slit_max = sky_slit_max_edge;
         }
     }


    cleanup:

    return cpl_error_get_code();
}

static cpl_error_code
xsh_wavecal_list_pupulate(xsh_pre *pre_sci, const int iorder, const int miny,
                          const int maxy, const int wmap_xsize_diff,
                          const int decode_bp, const double obj_slit_min,
                          const double obj_slit_max, const double sky_slit_min,
                          const double sky_slit_max, const int order,
                          xsh_wavemap_list* wave_list, int* sky_size,
                          xsh_order_list* ord_tab,
                          cpl_image *wavemap, cpl_image *slitmap)
{
    /* Populate the wavecal list */
    wavemap_item * psky = wave_list->list[iorder].sky;
    wavemap_item * pobject = wave_list->list[iorder].object;
    double* plambda = cpl_image_get_data_double( wavemap);
    double* pslit = cpl_image_get_data_double( slitmap);

    float* pflux = cpl_image_get_data_float( xsh_pre_get_data( pre_sci));
    float* perrs = cpl_image_get_data_float( xsh_pre_get_errs( pre_sci));
    int* pqual = cpl_image_get_data_int( xsh_pre_get_qual( pre_sci));

    *sky_size = 0;
    int object_size = 0;
    double derrs;
    int idx;
    int sdx;
    double lambda_min = 10000., lambda_max = 0. ;
    int nx = xsh_pre_get_nx( pre_sci);

    float slit_val;
    for (int iy = miny; iy < maxy; iy++) {

        int minx = xsh_order_list_eval_int(ord_tab,
                        ord_tab->list[iorder].edglopoly, iy) + 1;
        int maxx = xsh_order_list_eval_int(ord_tab,
                        ord_tab->list[iorder].edguppoly, iy) - 1;

        int offset_idx = iy * nx;
        int offset_sdx = iy * (nx - wmap_xsize_diff);

        for (int ix = minx; ix <= maxx; ix++) {

            idx = ix + offset_idx;

            if ((pqual[idx] & decode_bp) == 0) {
                /* good pix */

                sdx = ix + offset_sdx;
                double lambda = plambda[sdx];
                if (lambda == 0) {
                    continue;
                }
                if (lambda < lambda_min) {
                    lambda_min = lambda;
                }
                if (lambda > lambda_max) {
                    lambda_max = lambda;
                }
                slit_val = pslit[sdx];

                if ( ( (slit_val >= obj_slit_min) && (slit_val <= obj_slit_max) ) ||
                       (slit_val <  sky_slit_min) || (slit_val >  sky_slit_max) ) {
                    pobject->lambda = lambda;
                    pobject->slit = slit_val;
                    pobject->flux = *(pflux + idx);
                    derrs = *(perrs + idx);
                    pobject->sigma = (double) 1. / (derrs * derrs);
                    pobject->ix = ix;
                    pobject->iy = iy;
                    object_size++;
                    pobject++;
                }
                else {
                    psky->lambda = lambda;
                    psky->slit = slit_val;
                    psky->flux = *(pflux + idx);
                    derrs = *(perrs + idx);
                    psky->sigma = (double) 1. / (derrs * derrs);
                    psky->ix = ix;
                    psky->iy = iy;
                    (*sky_size)++;
                    psky++;
                }
            }
        }
    }

    assure(*sky_size > 0, CPL_ERROR_ILLEGAL_INPUT,
           "On order %d sky_size 0. Order edge tab may "
           "over-estimate corresponding order size or "
           "localize-slit-hheight is too large or "
           "sky-slit-edges-mask too large or "
           "sky-hheight1 too small or too large ",
           order);
    wave_list->list[iorder].sky_size = *sky_size;
    wave_list->list[iorder].object_size = object_size;
    wave_list->list[iorder].lambda_min = lambda_min;
    wave_list->list[iorder].lambda_max = lambda_max;
    /* Sort by lambda */
    qsort(wave_list->list[iorder].sky, *sky_size, sizeof(wavemap_item),
          xsh_wave_compare);
    qsort(wave_list->list[iorder].object, object_size, sizeof(wavemap_item),
          xsh_wave_compare);

    cleanup:
    return cpl_error_get_code();
}

                                                     /* left */



static xsh_wavemap_list*
xsh_wavemap_list_new( cpl_image *wavemap, cpl_image *slitmap,
                      xsh_localization *list, xsh_order_list *order_table,
                      xsh_pre *pre_sci, int nbkpts,
                      cpl_frame* usr_defined_break_points_frame,
                      xsh_subtract_sky_single_param *sky_par,
                      xsh_instrument *instrument)
{
    xsh_wavemap_list *wave_list = NULL;
    int i, nx, ny, max_size, sky_size;
    int iorder ;

    int nrow=0;
    const double* pfactor=NULL;
    int median_hsize = 0;
    double nbkpts_arm=0;
    int nbkpts_ord=0;
    const char * name=NULL;
    cpl_table* usr_defined_break_points_tab=NULL;
    int wmap_xsize_diff=0;
    double obj_slit_min=0, obj_slit_max=0;
    double sky_slit_min=0, sky_slit_max=0;
    int decode_bp=instrument->decode_bp;
    double gain=sky_par->gain;
    double ron2=sky_par->ron*sky_par->ron;
    /* Check input parameters */
    XSH_ASSURE_NOT_NULL( wavemap);
    XSH_ASSURE_NOT_NULL( slitmap);
    XSH_ASSURE_NOT_NULL( order_table);
    XSH_ASSURE_NOT_NULL( pre_sci);
    XSH_ASSURE_NOT_NULL( sky_par);
    XSH_ASSURE_NOT_NULL( instrument);

    /* parameters */
    median_hsize = sky_par->median_hsize;

    /* order position */
    nx = xsh_pre_get_nx( pre_sci);
    ny = xsh_pre_get_ny( pre_sci);

    if( xsh_instrument_get_arm(instrument) == XSH_ARM_NIR){
        wmap_xsize_diff=pre_sci->nx-cpl_image_get_size_x(slitmap);
    }

    /* Create empty wavemap list to hold results */
    check( wave_list = xsh_wavemap_list_create( instrument));

    xsh_get_obj_and_sky_extraction_slits(sky_par,list,&obj_slit_min,
                                         &obj_slit_max,&sky_slit_min,
                                         &sky_slit_max);

    /* Loop over orders */
    for ( iorder = 0; iorder < order_table->size; iorder++){
        int miny, maxy;
        wavemap_item *psky = NULL;

        int order;

        /* Calculate x,y limits --> minx, maxx, miny, maxy */
        miny = xsh_order_list_get_starty( order_table, iorder);
        maxy = xsh_order_list_get_endy( order_table, iorder);
        order = order_table->list[iorder].absorder ;
        xsh_msg_dbg_medium( "Order %d, miny %d, maxy %d", order, miny, maxy ) ;
        /*
        For each Y calculate the nb of pixels in X.
         */
        xsh_get_sky_edges_min_max_and_size(sky_par, miny, maxy, iorder, order,
                                           nx, wmap_xsize_diff, order_table,
                                           slitmap, &sky_slit_min,
                                           &sky_slit_max, &max_size);
        xsh_msg( "SKY data slit range : [%f,%f],[%f,%f]",
                      sky_slit_min, obj_slit_min, obj_slit_max, sky_slit_max);


        /* Reserve space in wavecal list */
        check( xsh_wavemap_list_set_max_size( wave_list, iorder, order,
                        max_size));

        /* Populate the wavecal list */

        xsh_wavecal_list_pupulate(pre_sci, iorder, miny, maxy, wmap_xsize_diff,
                                  decode_bp, obj_slit_min, obj_slit_max,
                                  sky_slit_min, sky_slit_max, order, wave_list,
                                  &sky_size, order_table, wavemap, slitmap);


        if(usr_defined_break_points_frame!=NULL){
            /* if the user specifies different factors use those */
            name=cpl_frame_get_filename(usr_defined_break_points_frame);
            usr_defined_break_points_tab=cpl_table_load(name,1,0);
            nrow=cpl_table_get_nrow(usr_defined_break_points_tab);
            check(pfactor=cpl_table_get_data_double_const(usr_defined_break_points_tab,"FACTOR"));
            switch(xsh_instrument_get_arm(instrument)) {
            case XSH_ARM_UVB:
                XSH_ASSURE_NOT_ILLEGAL_MSG(nrow==XSH_ORDERS_UVB,
                                "Wrong number of factors for single frame sky subtraction table");
                xsh_nbkpts_uvb[iorder]=pfactor[iorder];
                break;
            case XSH_ARM_VIS:
                XSH_ASSURE_NOT_ILLEGAL_MSG(nrow==XSH_ORDERS_VIS,
                                "Wrong number of factors for single frame sky subtraction table");
                xsh_msg("iorder=%d factor=%f",iorder,pfactor[iorder]);
                xsh_nbkpts_vis[iorder]=pfactor[iorder];
                break;
            case XSH_ARM_NIR:
                XSH_ASSURE_NOT_ILLEGAL_MSG(nrow==XSH_ORDERS_NIR,
                                "Wrong number of factors for single frame sky subtraction table");
                xsh_nbkpts_nir[iorder]=pfactor[iorder];
                break;
            default:xsh_msg("Arm not supported"); break;
            }

        }

        /* Fit spline */
        if ( sky_par->method == BSPLINE_METHOD){
            switch(xsh_instrument_get_arm(instrument)) {
            case XSH_ARM_UVB:nbkpts_arm=xsh_nbkpts_uvb[iorder];break;
            case XSH_ARM_VIS:nbkpts_arm=xsh_nbkpts_vis[iorder];break;
            case XSH_ARM_NIR:nbkpts_arm=xsh_nbkpts_nir[iorder];break;
            default:xsh_msg("Arm not supported"); break;
            }
            if (sky_par->bspline_sampling == FINE) {
                nbkpts_ord=(int)(nbkpts*nbkpts_arm+0.5);
            } else {
                nbkpts_ord=nbkpts;
            }
            check( xsh_fit_spline(wave_list,iorder,nbkpts_ord,
                            sky_par->bezier_spline_order,
                            sky_par->niter, sky_par->kappa,
                            ron2, gain,
                            xsh_instrument_arm_tostring(instrument)));
        }
        else{
            cpl_vector* median_vector = NULL;
            int mediani;
            double median;
            int nb_points = 2*median_hsize+1;
            double m_pi_2 = 0.5*M_PI;

            median_vector = cpl_vector_new( nb_points);
            psky = wave_list->list[iorder].sky;

            for( i=median_hsize; i< (sky_size-median_hsize); i++){
                double median_err=0.0;
                int pixel = i-median_hsize;
                for(mediani=0; mediani< nb_points; mediani++){
                    cpl_vector_set( median_vector, mediani, psky[pixel+mediani].flux);
                    /* NB: psky[pixel+mediani].sigma contains actually the 1./(sigma*sigma) value
                     * Thus formula after is numerically correct despite "appears" wrong
                     *
                     */
                    median_err += 1.0/psky[pixel+mediani].sigma;
                }
                median = cpl_vector_get_median(median_vector);
                psky[i].fitted = median;
                psky[i].fit_err = sqrt((m_pi_2*median_err)/(nb_points*(nb_points- 1.)));
            }
            xsh_free_vector( &median_vector);

            /* TODO: this should be removed as not used: REGDEBUG_MEDIAN_SPLINE=0 */
#if REGDEBUG_MEDIAN_SPLINE
            {
                for( i=0; i< sky_size; i++){
                    psky[i].flux = psky[i].fitted;
                }
                XSH_REGDEBUG("fit median data");

                switch(xsh_instrument_get_arm(instrument)) {
                case XSH_ARM_UVB:nbkpts_arm=xsh_nbkpts_uvb[iorder];break;
                case XSH_ARM_VIS:nbkpts_arm=xsh_nbkpts_vis[iorder];break;
                case XSH_ARM_NIR:nbkpts_arm=xsh_nbkpts_nir[iorder];break;
                default:xsh_msg("Arm not supported"); break;
                }
                if (sky_par->bspline_sampling == FINE) {
                    nbkpts_ord=(int)(nbkpts*nbkpts_arm+0.5);
                } else {
                    nbkpts_ord=nbkpts;
                }
                check( xsh_fit_spline(wave_list,iorder,nbkpts_ord,
                                sky_par->bezier_spline_order,
                                sky_par->niter, sky_par->kappa,
                                ron2, gain,
                                xsh_instrument_arm_tostring(instrument)));
            }
#endif
        }
    }
    cleanup :
    return wave_list ;
}
/*****************************************************************************/

/*****************************************************************************/
/**
  @brief
    Build the sky spectrum list

*/
/*****************************************************************************/
xsh_rec_list*
xsh_wavemap_list_build_sky(xsh_wavemap_list* wave_list, xsh_instrument* instr) {
  xsh_rec_list* sky_list = NULL;
  int i, j;
  int level;

  /* check input parameters */
  XSH_ASSURE_NOT_NULL( wave_list);
  XSH_ASSURE_NOT_NULL( instr);

  xsh_msg("Build sky model");
  level = xsh_debug_level_get();

  check( sky_list = xsh_rec_list_create_with_size( wave_list->size, instr));

  /* Loop over orders */
  for (i = 0; i < wave_list->size; i++) {
    wavemap_item *psky = NULL;
    int order;
    int sky_size;
    double *lambdas = NULL;
    float *sky_flux = NULL;
    float *sky_err = NULL;
    float* pflat = NULL;
    int sx = 0;
    psky = wave_list->list[i].sky;
    sky_size = wave_list->list[i].sky_size;
    order = wave_list->list[i].order;

    check( xsh_rec_list_set_data_size( sky_list, i, order, sky_size, 1));
    check( lambdas = xsh_rec_list_get_lambda( sky_list, i));
    check( sky_flux = xsh_rec_list_get_data1( sky_list, i));
    check( sky_err = xsh_rec_list_get_errs1( sky_list, i));

    for (j = 0; j < sky_size; j++) {
        lambdas[j] = psky->lambda;
        sky_flux[j] = psky->fitted;
        sky_err[j] = psky->fit_err;
        psky++;
    }

    if (level >= XSH_DEBUG_LEVEL_MEDIUM) {
      FILE* debug = NULL;
      char debug_name[256];

      sprintf(debug_name, "fitted_data_sky_%d.log", order);
      debug = fopen(debug_name, "w");
      fprintf(debug, "# lambda flux err x y slit\n");
      psky = wave_list->list[i].sky;

      for (j = 0; j < sky_size; j++) {
        fprintf(debug, "%f %f %f %d %d %f\n", psky->lambda, psky->fitted,
            psky->fit_err, psky->ix, psky->iy, psky->slit);
        psky++;
      }
      fclose(debug);
    }
  }

  cleanup: if (cpl_error_get_code() != CPL_ERROR_NONE) {
    xsh_rec_list_free(&sky_list);
  }
  return sky_list;
}

static void
xsh_obj_fit_dump(int iorder, xsh_wavemap_list* wave_list)
{
    int level = xsh_debug_level_get();
    if (level >= XSH_DEBUG_LEVEL_MEDIUM) {
        for (iorder = 0; iorder < wave_list->size; iorder++) {
            wavemap_item* pobject = NULL;
            int j, order, object_size;
            FILE* debug = NULL;
            char debug_name[256];

            order = wave_list->list[iorder].order;
            object_size = wave_list->list[iorder].object_size;
            pobject = wave_list->list[iorder].object;

            sprintf(debug_name, "fitted_data_obj_%d.log", order);
            debug = fopen(debug_name, "w");
            fprintf(debug, "# lambda flux err x y slit\n");

            for (j = 0; j < object_size; j++) {
                fprintf(debug, "%f %f %f %d %d %f\n", pobject->lambda,
                                pobject->fitted, pobject->fit_err, pobject->ix,
                                pobject->iy, pobject->slit);
                pobject++;
            }
            fclose(debug);
        }
    }
    return;
}

static cpl_error_code
xsh_subtract_sky_at_pix(const int sky_size, const int nx, wavemap_item* psky,
                        float* pflux, float* pflat, float* perrs)
{
    for (int i = 0; i < sky_size; i++) {
        int x, y;
        float flux, fitted, err, fitted_err;

        x = psky->ix;
        y = psky->iy;
        int pixel = x + y * nx;
        flux = pflux[pixel];
        fitted = psky->fitted;
        fitted_err = psky->fit_err;
        pflux[pixel] = flux - fitted;
        err = perrs[pixel];
        perrs[pixel] = sqrt(fitted_err * fitted_err + err * err);
        psky++;
    }
    return cpl_error_get_code();
}

static cpl_error_code
xsh_subtract_sky_on_obj_at_pix(xsh_rec_list* sky_list, const int iorder,
                               const int object_size, const int nx,
                               wavemap_item** pobject, float* pflux,
                               float* perrs)
{
    int sky_lambdas_size = xsh_rec_list_get_nlambda( sky_list, iorder);
    double* sky_lambdas = xsh_rec_list_get_lambda( sky_list, iorder);
    float* sky_flux = xsh_rec_list_get_data1( sky_list, iorder);
    float* sky_err = xsh_rec_list_get_errs1( sky_list, iorder);
    int sky_lambdas_idx=0;
    for (int i = 0; i < object_size; i++) {
        int x, y;
        float flux, err, fitted, fitted_err;
        float lambda, lambda_min, lambda_max;
        float flux_min, flux_max, err_min, err_max;
        float cte_min, cte_max;
        x = (*pobject)->ix;
        y = (*pobject)->iy;
        lambda = (*pobject)->lambda;
        int pixel = x + y * nx;
        flux = pflux[pixel];
        while ((sky_lambdas_idx < sky_lambdas_size)
                        && (sky_lambdas[sky_lambdas_idx] <= lambda)) {
            (sky_lambdas_idx)++;
        }
        if (sky_lambdas_idx >= (sky_lambdas_size - 1)) {
            break;
        }
        lambda_max = sky_lambdas[sky_lambdas_idx];
        if (sky_lambdas_idx == 0) {
            (*pobject)++;
            continue;
        }
        lambda_min = sky_lambdas[sky_lambdas_idx - 1];
        flux_min = sky_flux[sky_lambdas_idx - 1];
        flux_max = sky_flux[sky_lambdas_idx];
        err_min = sky_err[sky_lambdas_idx - 1];
        err_max = sky_err[sky_lambdas_idx];
        cte_max = (lambda - lambda_min) / (lambda_max - lambda_min);
        cte_min = 1 - cte_max;
        fitted = flux_min * cte_min + flux_max * cte_max;
        fitted_err = sqrt(
                        err_min * err_min * cte_min
                                        + err_max * err_max * cte_max);
        (*pobject)->fitted = fitted;
        (*pobject)->fit_err = fitted_err;
        pflux[pixel] = flux - fitted;
        err = perrs[pixel];
        perrs[pixel] = sqrt(err * err + fitted_err * fitted_err);
        (*pobject)++;
    }

    return cpl_error_get_code();
}

/*****************************************************************************/

/*****************************************************************************/

/*****************************************************************************/
/* Not used
static int find_flux( const void * key, const void * ptr )
{
  double lambda = *(double *)key ;
  wavemap_item * wp = (wavemap_item *)ptr ;

  if ( lambda == wp->lambda ) return 0 ;
  else if ( lambda < wp->lambda ) return -1 ;
  return 1 ;
}
*/
/*****************************************************************************/

/*****************************************************************************/
/*****************************************************************************/
/* not used
static double get_fitted_flux_at_lambda( wavemap_item * pentry,
					 double lambda, int nb )
{
  wavemap_item * wp ;

  wp = (wavemap_item *)bsearch( &lambda, pentry, nb, sizeof( wavemap_item ),
				find_flux ) ;

  if ( wp == NULL ) return 0. ;
  return wp->fitted ;
}
*/
/*****************************************************************************/

/*****************************************************************************/
/**
  @brief
    subtract sky
*/
/*****************************************************************************/
static cpl_frame*
xsh_wavelist_subtract_sky(xsh_pre * pre_sci, xsh_rec_list* sky_list,
                          xsh_wavemap_list * wave_list, xsh_instrument* instrument,
                          const char* rec_prefix) {

  float* perrs = NULL;
  float* pflux = NULL;
  float* pflat = NULL;
  int* pqual = NULL;
  int nx, ny;
  xsh_pre* pre_res = NULL;
  cpl_image* res_image = NULL;
  cpl_frame* res_frame = NULL;
  int iorder;
  char tag[256];
  char fname[256];

  /* check input parameters */
  XSH_ASSURE_NOT_NULL( pre_sci);
  XSH_ASSURE_NOT_NULL( sky_list);
  XSH_ASSURE_NOT_NULL( wave_list);

  check( pre_res = xsh_pre_duplicate( pre_sci));
  check( res_image = xsh_pre_get_data( pre_res));
  check( pflux = cpl_image_get_data_float( res_image));
  check( perrs = cpl_image_get_data_float( xsh_pre_get_errs( pre_res)));
  check( pqual = cpl_image_get_data_int( xsh_pre_get_qual( pre_res)));

  nx = xsh_pre_get_nx(pre_res);
  ny = xsh_pre_get_ny(pre_res);

  for (iorder = 0; iorder < wave_list->size; iorder++) {
    wavemap_item* psky = NULL;
    wavemap_item* pobject = NULL;
    int order, sky_size, object_size;

    order = wave_list->list[iorder].order;
    psky = wave_list->list[iorder].sky;
    sky_size = wave_list->list[iorder].sky_size;

    xsh_msg_dbg_medium( "Subtract Sky - Order %d", order);

    xsh_subtract_sky_at_pix(sky_size, nx, psky, pflux, pflat, perrs);

    object_size = wave_list->list[iorder].object_size;
    pobject = wave_list->list[iorder].object;

    xsh_msg_dbg_medium(
        "Subtract Sky on object - Order %d size %d", order, object_size);

    xsh_subtract_sky_on_obj_at_pix(sky_list,iorder, object_size, nx, &pobject,
                                   pflux, perrs);
  }

  xsh_obj_fit_dump(iorder, wave_list);

  sprintf(tag, "%s_SUB_SKY_%s", rec_prefix,
      xsh_instrument_arm_tostring(instrument));
  sprintf(fname, "%s.fits", tag);

  if (strstr(fname, "TMPSKY") != NULL) {
    check( res_frame = xsh_pre_save( pre_res, fname, tag, 1 ));
  } else {
    check( res_frame = xsh_pre_save( pre_res, fname, tag, 0 ));
  }check(
      xsh_frame_config(fname,tag,CPL_FRAME_TYPE_IMAGE,CPL_FRAME_GROUP_CALIB, CPL_FRAME_LEVEL_FINAL,&res_frame));
  //xsh_add_temporary_file(fname);
  cleanup: xsh_pre_free(&pre_res);
  return res_frame;
}
/*****************************************************************************/

/*****************************************************************************/
/** 
  @brief
    Normalize the master flat to the value at the center of the slit on each order and Y position 
  @param[in] order_table 
    Edge order table list
  @param[in] pre_mflat 
    Master flat image
 @return 
   Normalized master flat image.
*/

static cpl_error_code
xsh_mflat_normalize(xsh_order_list * order_table, xsh_pre* pre_mflat)
{

  float* pflux=NULL;
  float* psmo=NULL;
  int* pqual=NULL;
  int iorder=0;
  //int abs_order=0;
  float flux_max=FLT_MIN;
  int minx=0;
  int miny=0;
  int maxx=0;
  int maxy=0;
  int sx=0;
  //int sy=0;


  int iy=0;
  int ix=0;
  int rad=2;
  float flux=0;
  cpl_image* data=NULL;
  cpl_image* smo=NULL;

  XSH_ASSURE_NOT_NULL( order_table);
  XSH_ASSURE_NOT_NULL( pre_mflat);
  data=xsh_pre_get_data(pre_mflat);

  smo=xsh_image_smooth_median_x(data,rad);
  
  check( pflux = cpl_image_get_data_float( data));
  check( psmo = cpl_image_get_data_float( smo));
  check( pqual = cpl_image_get_data_int  ( xsh_pre_get_qual( pre_mflat )));

  sx = xsh_pre_get_nx( pre_mflat);
  //sy = xsh_pre_get_ny( pre_mflat);

  for ( iorder = 0; iorder < order_table->size; iorder++){
    miny = xsh_order_list_get_starty( order_table, iorder);
    maxy = xsh_order_list_get_endy( order_table, iorder);
    //abs_order = order_table->list[iorder].absorder ;

    for( iy = miny ; iy < maxy ; iy++ ) {
      check( minx = xsh_order_list_eval_int(order_table,
					    order_table->list[iorder].edglopoly, iy)-3);
      check( maxx = xsh_order_list_eval_int(order_table,
					    order_table->list[iorder].edguppoly, iy)+2);

      /* compute local max at x */
      flux_max=FLT_MIN;
      for( ix = minx ; ix <= maxx; ix++){
        if(pqual[iy*sx+ix]==0) {
          flux=psmo[iy*sx+ix];
	  flux_max = (flux>flux_max)? flux : flux_max;
	}
      } /* end loop over ix */
      /* normalize flux */
      for( ix = minx ; ix <= maxx; ix++){
        pflux[iy*sx+ix]/=flux_max;
      }
    } /* end loop over iy */
  } /* end loop over orders */

 cleanup:
   xsh_free_image(&smo);
   return cpl_error_get_code();

}
/*****************************************************************************/
/** 
  @brief
    Subtract the sky background for single frame. If sky_spectrum is NOT NULL
    it is saved as a product, of type REC. 
  @param[in] sci_frame 
    Input Science frame after divide by flat
  @param[in] order_table_frame 
    Input Order Table Frame
  @param[in] slitmap_frame 
    Input Wavesolution frame
  @param[in] wavemap_frame 
    Wave map: each pixel content is the wave length
  @param[in] loc_table_frame 
    Localisation table (NULL if not applicable)
  @param[in] usr_defined_break_points_frame 
    user defined break points frame
  @param[in] instrument 
    Pointer to instrument description structure
  @param[in] nbkpts 
    Nb of break points used for Bezier Spline
  @param[in] sky_par
    parameters structure to control the sky subtraction
  @param[out] sky_spectrum  
   The 1D supersampled observed sky spectrum [REC] 
  @param[out] sky_spectrum_eso  
   The 1D supersampled observed sky spectrum [ESO format] 
  @param[in] rec_prefix
   Input recipe prefix

 @return 
   The frame (PRE) after sky subtraction.
 */
/* TODO
 * slitmap_frame,wavemap_frame may be replaced by the physical model
 * usr_defined_break_points_frame should be removed
 * nbkpts should be removed (made automatic optimisation or part of sky_params)
 * why two different parameters for order_table_frame, loc_table_frame?
 */
/*****************************************************************************/
cpl_frame * 
xsh_subtract_sky_single (cpl_frame *sci_frame, 
			 cpl_frame *order_table_frame, 
			 cpl_frame *slitmap_frame,
			 cpl_frame *wavemap_frame, 
			 cpl_frame *loc_table_frame,
                          cpl_frame* usr_defined_break_points_frame,
                         xsh_instrument *instrument, 
                         int nbkpts, 
                         xsh_subtract_sky_single_param* sky_par,
			 cpl_frame **sky_spectrum, 
                         cpl_frame** sky_spectrum_eso,
                         const char* rec_prefix,const int clean_tmp)
{
  cpl_frame *res_frame = NULL ;
  const char* wavemap_name = NULL;
  cpl_image *img_wavemap = NULL;
  const char* slitmap_name = NULL;
  cpl_image *img_slitmap = NULL;
  xsh_pre *pre_sci = NULL;
  xsh_order_list *order_table = NULL;
  xsh_wavemap_list *wave_list = NULL;
  xsh_localization *local_list = NULL;
  xsh_rec_list *sky_list = NULL;
  cpl_frame *sky_frame = NULL;
  cpl_frame *sky_frame2 = NULL;
  char fname[256];
  char tag[256];

  /* check input parameters */
  XSH_ASSURE_NOT_NULL_MSG( sci_frame,"Required science frame is missing");
  XSH_ASSURE_NOT_NULL_MSG( order_table_frame,"Required order table frame is missing");
  XSH_ASSURE_NOT_NULL_MSG( slitmap_frame, "Required slitmap frame is missing, provide it or set compute-map to TRUE");
  XSH_ASSURE_NOT_NULL_MSG( wavemap_frame,"Required wavemap frame is missing");
  XSH_ASSURE_NOT_NULL_MSG( instrument,"Instrument setting undefined");
  XSH_ASSURE_NOT_ILLEGAL( nbkpts > 1);
  XSH_ASSURE_NOT_NULL_MSG( sky_par,"Undefined input sky parameters");


  xsh_msg_dbg_low( "method %s edges slit mask %f", 
    SKY_METHOD_PRINT( sky_par->method), sky_par->slit_edges_mask);
  if (sky_par->method == BSPLINE_METHOD){
  xsh_msg_dbg_medium("start niter=%d kappa=%f",
    sky_par->niter, sky_par->kappa);
  }
  else{
    xsh_msg_dbg_low("median_hsize %d", sky_par->median_hsize);
  }

  if ( loc_table_frame == NULL ) {
    xsh_msg( "Subtract sky single no localization");
  }
  else {
    xsh_msg( "Subtract sky single using localization");
    check( local_list = xsh_localization_load( loc_table_frame));
  }

  check( slitmap_name = cpl_frame_get_filename( slitmap_frame));
  check( img_slitmap = cpl_image_load( slitmap_name, CPL_TYPE_DOUBLE,
    0, 0));

  check( pre_sci = xsh_pre_load( sci_frame, instrument));


    if (xsh_instrument_get_arm(instrument) == XSH_ARM_NIR) {
    sky_par->gain = 2.12;
  } else {
    sky_par->gain = xsh_pfits_get_gain(pre_sci->data_header);
  }



    if (xsh_instrument_get_arm(instrument) == XSH_ARM_NIR) {
    sky_par->ron = 10;/* this should come from a curve: 22 (DIT=2s)- 6 (DIT=100s) */
  } else {
    sky_par->ron = xsh_pfits_get_ron(pre_sci->data_header);
  }


  check( order_table = xsh_order_list_load (order_table_frame, instrument));

  check( wavemap_name = cpl_frame_get_filename( wavemap_frame));
  check( img_wavemap = cpl_image_load( wavemap_name, CPL_TYPE_DOUBLE,
    0, 0));


  /* Create data to build spectrum */
  check( wave_list = xsh_wavemap_list_new( img_wavemap, img_slitmap, 
    local_list, order_table, pre_sci, nbkpts, usr_defined_break_points_frame,sky_par, instrument));

  /* build sky spectrum */
  check (sky_list = xsh_wavemap_list_build_sky(wave_list,instrument));

  sprintf(tag,"%s_DRL_SKY_ORD1D_%s", rec_prefix,
	  xsh_instrument_arm_tostring(instrument));
  sprintf(fname,"%s.fits",tag);

  check( sky_frame = xsh_rec_list_save( sky_list,fname,tag, CPL_TRUE));
  xsh_add_temporary_file(fname);

  if ( sky_spectrum != NULL){
    *sky_spectrum = cpl_frame_duplicate( sky_frame);
  }

  sprintf( tag,"%s_SKY_ORD1D_%s",  rec_prefix,
	   xsh_instrument_arm_tostring(instrument));
  sprintf(fname,"%s.fits",tag);

  check( sky_frame2 = xsh_rec_list_save2( sky_list,fname,tag));
  xsh_msg("fname=%s",fname);
  if( (clean_tmp==1) || (strstr(rec_prefix,"TMPSKY")!=NULL) ) {
    xsh_add_temporary_file(fname);
  }

  if ( sky_spectrum != NULL){
    *sky_spectrum_eso = cpl_frame_duplicate( sky_frame2);
  }

  /* Now subtract sky */
  check( res_frame = xsh_wavelist_subtract_sky( pre_sci, sky_list,
						wave_list,instrument,
						rec_prefix));
  cleanup:
    xsh_free_frame( &sky_frame);
    xsh_free_frame( &sky_frame2);
    xsh_free_image( &img_slitmap);
    xsh_free_image( &img_wavemap);
    xsh_pre_free( &pre_sci);
    xsh_localization_free( &local_list);
    xsh_order_list_free( &order_table);
    xsh_wavemap_list_free( &wave_list);
    xsh_rec_list_free( &sky_list);
    return res_frame;
}
/*****************************************************************************/

/*****************************************************************************/
/*****************************************************************************/
static int xsh_bspline_eval_all(const double x, gsl_vector *B,
    size_t *idx, gsl_bspline_workspace *w, size_t *start_i) {
  if (B->size != w->k) {
    GSL_ERROR("B vector not of length k", GSL_EBADLEN);
  } else {
    size_t i; /* spline index */
    size_t j; /* looping */
    size_t ii; /* looping */
    int flg = 0; /* error flag */
    double saved;
    double term;

    double *knot_ptr;
    double *deltar_ptr;
    double *deltal_ptr;
    double *B_ptr;

    knot_ptr = gsl_vector_ptr(w->knots, 0);
    deltar_ptr = gsl_vector_ptr(w->deltar, 0);
    deltal_ptr = gsl_vector_ptr(w->deltal, 0);
    B_ptr = gsl_vector_ptr(B, 0);

    i = xsh_bspline_find_interval(x, &flg, w, start_i);

    if (flg == -1) {
      GSL_ERROR("x outside of knot interval", GSL_EINVAL);
    } else if (flg == 1) {
      if (x <= knot_ptr[i] + GSL_DBL_EPSILON) {
        --i;
      } else {
        GSL_ERROR("x outside of knot interval", GSL_EINVAL);
      }
    }

    *idx = i;

    B_ptr[0] = 1.0;

    int i_plus_1 = i + 1;
    for (j = 0; j < w->k - 1; ++j) {
      deltar_ptr[j] = knot_ptr[i_plus_1 + j] - x;
      deltal_ptr[j] = x - knot_ptr[i - j];

      saved = 0.0;

      for (ii = 0; ii <= j; ++ii) {
        term = B_ptr[ii] / (deltar_ptr[ii] + deltal_ptr[j - ii]);

        B_ptr[ii] = saved + deltar_ptr[ii] * term;

        saved = deltal_ptr[j - ii] * term;
      }

      B_ptr[j + 1] = saved;
    }

    return GSL_SUCCESS;
  }
} 
/*****************************************************************************/

/*****************************************************************************/
/*****************************************************************************/
static size_t
xsh_bspline_find_interval(const double x, int *flg,
                      gsl_bspline_workspace *w, size_t *start_i)
{
	size_t i;
	
	double *knot_ptr;
	
	knot_ptr=gsl_vector_ptr(w->knots,0);
	
	if (x < knot_ptr[0])
    {
		*flg = -1;
		return 0;
    }
	
	/* find i such that t_i <= x < t_{i+1} */
	for (i = *start_i; i < w->k + w->l - 1; ++i)
    {
		const double ti = knot_ptr[i];
		const double tip1 = knot_ptr[i + 1];
		
		/*if (tip1 < ti)
        {
			GSL_ERROR("knots vector is not increasing", GSL_EINVAL);
        }*/
		
		if (ti <= x && x < tip1)
			break;
		
		if (ti < x && x == tip1 &&
				   tip1 == knot_ptr[ w->k + w->l - 1])
			break;
		
    }
	
	if (i == w->k + w->l - 1)
		*flg = 1;
	else
		*flg = 0;
	
	*start_i=i;
	return i;
} 
/*****************************************************************************/




/*----------------------------------------------------------------------------*/
/*----------------------------------------------------------------------------*/
cpl_frame* xsh_save_sky_model( cpl_frame* obj_frame, cpl_frame* sub_sky_frame,
			       const char* sky_tag,xsh_instrument* instrument)
{
   char sky_name[256];
   cpl_frame* result=NULL;
   
   sprintf(sky_name,"%s.fits",sky_tag);
   result=xsh_pre_frame_subtract( obj_frame, sub_sky_frame, sky_name, instrument,0);

   cpl_frame_set_filename(result,sky_name);
   cpl_frame_set_tag(result,sky_tag);

   return result;
}
/*----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------*/
/*----------------------------------------------------------------------------*/
cpl_frame * xsh_add_sky_model( cpl_frame *subsky_frame, cpl_frame *sky_frame,
  xsh_instrument * instrument, const char* prefix)
{
  char result_tag[256];
  char result_name[256];
  cpl_frame *result = NULL;
  xsh_pre *pre_sci = NULL;
  const char *sky_name = NULL;
  cpl_image *sky_img = NULL;

  sprintf( result_tag, "%s_OBJ_AND_SKY_NOCRH_%s", prefix,
    xsh_instrument_arm_tostring(instrument));
  sprintf( result_name, "%s.fits", result_tag);


  check( pre_sci = xsh_pre_load( subsky_frame, instrument));
    
  check( sky_name = cpl_frame_get_filename(sky_frame));
  /* we put back just the sky level: for this reason we
   * correct only the data part, not the associated errors */
  check( sky_img = cpl_image_load( sky_name, CPL_TYPE_FLOAT, 0, 0));
  check( cpl_image_add( pre_sci->data, sky_img));
  check( result = xsh_pre_save( pre_sci, result_name, result_tag, 1));

  cleanup:
    xsh_free_image( &sky_img);
    xsh_pre_free( &pre_sci);

    return result;     
}
/*----------------------------------------------------------------------------*/

/**@}*/
