/*                                                                            *
 *   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 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_pre* pre_mflat,
						 xsh_instrument *instrument);

static int 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_pre* pre_mflat,
					     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);

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


/*****************************************************************************/
/*****************************************************************************/
void 
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* pw=NULL;
   double* pf=NULL;
   double* ps=NULL;
   double* px=NULL;
   double* py=NULL;
   double* pfit=NULL;
   double* perr=NULL;

   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;
   FILE* fout = NULL;
   cpl_table* debug_fit=NULL;

   char fname[128], dirname[128], cmd[128];

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

   //if (level >= XSH_DEBUG_LEVEL_MEDIUM) {
      debug_fit=cpl_table_new(nitem);

      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");
      //}
   /* 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 */
   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 (i = 0; i < nitem ; i++, pentry++ ) {
      double xi, yi=0, yerr=0;
		
      xi = pentry->lambda ;
      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) {
       check(cpl_table_save(debug_fit,NULL,NULL,fname,CPL_IO_DEFAULT));
     } else {
       check(cpl_table_save(debug_fit,NULL,NULL,fname,CPL_IO_EXTEND));
     }
   }
   

  cleanup:
   xsh_free_table(&debug_fit);
 

   if ( fout ) fclose( fout ) ;

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

   xsh_free_table( &debug_fit);
   return ;
}
/*****************************************************************************/

/*****************************************************************************/
/*****************************************************************************/
static int 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


                                                     /* 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)
{
  cpl_image *sci = NULL;
  xsh_wavemap_list *wave_list = NULL;
  float *pflux = NULL ;
  float *perrs = NULL ;
  int *pqual = NULL ;
  double *plambda = NULL ;
  double *pslit = NULL;
  int i, ix, iy, nx, ny, max_size, sky_size, object_size;
  int iorder ;

  int nrow=0;
  const double* pfactor=NULL;
  float slit_edges_mask = 0.;

  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 pos1=0, hheight1=0;
  double pos2=0, hheight2=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;
  slit_edges_mask = sky_par->slit_edges_mask;
  pos1 = sky_par->pos1;
  hheight1 = sky_par->hheight1;

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

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


  check( pflux = cpl_image_get_data_float( xsh_pre_get_data( pre_sci)));
  check( perrs = cpl_image_get_data_float( xsh_pre_get_errs( pre_sci)));
  check( pqual = cpl_image_get_data_int( xsh_pre_get_qual( pre_sci)));
  check( plambda = cpl_image_get_data_double( wavemap));
  check( pslit = cpl_image_get_data_double( slitmap));

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


  /* 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;
    } 
  }

  /* Loop over orders */
  for ( iorder = 0; iorder < order_table->size; iorder++){
    int miny, maxy, minx, maxx;
    double lambda ;
    wavemap_item *psky = NULL;
    wavemap_item *pobject = NULL;
    int order;
    double lambda_min = 10000., lambda_max = 0. ;
    double sky_slit_min_edge=0, sky_slit_max_edge=0; 
 
    /* 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.
    */
    max_size = 0;

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

      check( minx = xsh_order_list_eval_int( order_table,
        order_table->list[iorder].edglopoly, iy)+1);
      check( maxx = xsh_order_list_eval_int( order_table,
	order_table->list[iorder].edguppoly, iy)-1);
      int offset=iy*(nx-wmap_xsize_diff);
      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);
    sky_slit_min_edge = sky_slit_min_edge+slit_edges_mask;
    sky_slit_max_edge = sky_slit_max_edge-slit_edges_mask;    

    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;
      }
    }
    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 */
    psky = wave_list->list[iorder].sky;
    pobject = wave_list->list[iorder].object; 
    sky_size = 0;
    object_size = 0;

    double derrs;
    int idx;
    int sdx;
    float slit_val;
    for( iy = miny ; iy < maxy ; iy++ ) {
      check( minx = xsh_order_list_eval_int(order_table,
        order_table->list[iorder].edglopoly, iy)+1);
      check( maxx = xsh_order_list_eval_int(order_table,
        order_table->list[iorder].edguppoly, iy)-1);

      int offset_idx=iy*nx;
      int offset_sdx=iy*(nx-wmap_xsize_diff);
      for( ix = minx ; ix <= maxx; ix++){

        idx = ix+offset_idx;

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

          sdx = ix+offset_sdx;
          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),
      wave_compare);
    qsort( wave_list->list[iorder].object, object_size, sizeof( wavemap_item),
      wave_compare);


    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( 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( 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_pre* pre_mflat,
    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));

    /* TODO if mflat input is dropped this can go away */
    if (pre_mflat != NULL) {
      check( pflat = cpl_image_get_data_float( xsh_pre_get_data( pre_mflat)));
      sx = cpl_image_get_size_x(xsh_pre_get_data(pre_mflat));

      for (j = 0; j < sky_size; j++) {
        lambdas[j] = psky->lambda;
        sky_flux[j] = psky->fitted * pflat[psky->ix + (psky->iy) * sx];
        sky_err[j] = psky->fit_err;
        psky++;
      }
    } else {
      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;
}
/*****************************************************************************/

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

/*****************************************************************************/
/* 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_pre* pre_mflat, xsh_rec_list* sky_list, xsh_wavemap_list * wave_list,
    xsh_instrument* instrument, const char* rec_prefix) {
  int level;
  float* perrs = NULL;
  float* pflux = NULL;
  float* pflat = NULL;
  int* pqual = NULL;
  int i, 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)));

  /* TODO: if mflat is not required input this can be dropped */
  if (pre_mflat != NULL) {
    check( pflat = cpl_image_get_data_float( xsh_pre_get_data( pre_mflat)));
  }

  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;
    double* sky_lambdas = NULL;
    int sky_lambdas_idx = 0;
    int sky_lambdas_size;
    float* sky_flux = NULL;
    float* sky_err = NULL;
    float mflat = 0;
    order = wave_list->list[iorder].order;
    psky = wave_list->list[iorder].sky;
    sky_size = wave_list->list[iorder].sky_size;

    check( sky_lambdas_size = xsh_rec_list_get_nlambda( sky_list, iorder));
    check( sky_lambdas = xsh_rec_list_get_lambda( sky_list, iorder));
    check( sky_flux = xsh_rec_list_get_data1( sky_list, iorder));
    check( sky_err = xsh_rec_list_get_errs1( sky_list, iorder));

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

    for (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;
      /* TODO: if mflat is not required input this can be dropped */
      if (pre_mflat != NULL) {
        mflat = pflat[pixel];
        pflux[pixel] = flux - fitted * mflat;
      } else {
        pflux[pixel] = flux - fitted;
      }
      err = perrs[pixel];
      perrs[pixel] = sqrt(fitted_err * fitted_err + err * err);
      psky++;
    }
    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);

    for (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++;
    }
  }

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

  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] mflat_frame 
    Master Flat frame
  @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.
 */
/*****************************************************************************/
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 *mflat_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];
  //const char* mflat_name=NULL;
  xsh_pre* pre_mflat=NULL;

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

  /* TODO: remove this beautiful piece of code! rescale master flat frame to correct sky model */
  if(mflat_frame!=NULL) {
    xsh_msg("--subtract_sky_single : normalize master flat");
    //mflat_name=cpl_frame_get_filename( mflat_frame);
    pre_mflat=xsh_pre_load( mflat_frame, instrument);
    check( xsh_mflat_normalize( order_table, pre_mflat));

    check(cpl_image_save( xsh_pre_get_data( pre_mflat),"mflat_norm.fits",
		       CPL_BPP_IEEE_FLOAT,NULL,CPL_IO_DEFAULT));
  }
  /* 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,pre_mflat,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, pre_mflat,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_mflat);
    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 inline 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;     
}
/*----------------------------------------------------------------------------*/

/**@}*/
