/*                                                                            *
 *   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-01-03 15:39:30 $
 * $Revision: 1.33 $
 * $Name: not supported by cvs2svn $
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
/*----------------------------------------------------------------------------
                                 Includes
 ----------------------------------------------------------------------------*/
#include <xsh_utils_table.h>
#include <xsh_utils.h>
#include <xsh_error.h>
#include <xsh_pfits.h>
#include <xsh_msg.h>
#include <xsh_data_resid_tab.h>
#include <xsh_data_arclist.h>
#include <math.h>

#define LINETAB_PIXELSIZE "Pixel"
#define TWOSQRT2LN2 2.35482004503095

/*----------------------------------------------------------------------------
                                 Variables
 ----------------------------------------------------------------------------*/

/*---------------------------------------------------------------------------*/
/**
 * @defgroup xsh_utils     Tools: Miscellaneous Utilities
 *
 * This module is for the stuff that don't fit anywhere else. It should be
 * kept as small as possible.
 *
 */
/*---------------------------------------------------------------------------*/

/**@{*/

/*-------------------------------------------------------------------------*/
/**
  @name        xsh_histogram
  @memo        computes histogram
  @param data  input data table
  @param nbins input size of histogram bin
  @param min   input min data to be considered in histogram
  @param max   input max data to be considered in histogram
  @param hist  output histogram: table with results:
       # H: histogram
       # X: array of data values corresponding to the center of each bin
       # Xmean: 1D array corresponding to the mean of the data values entering
                each histogram bin
  Returns in case of succes -1 else.
  @doc

       Compute the histogram with the IDL intrinsic function HISTOGRAM, using
       the input options specified by the parameters Dmin, Dmax, Bin. All
       the computations are performed in floating-point arithmetics.
       Then compute arrays of values corresponding to each histogram bin,
       useful for plots, fitting, etc.


 */
/*--------------------------------------------------------------------------*/

cpl_table*
xsh_histogram(const cpl_table* data,
              const char* cname,
                const int nbins,
                const double min,
                const double max)
{
  cpl_table* histo=NULL;
  cpl_table* tmp_tbl1=NULL;
  cpl_table* tmp_tbl2=NULL;
  int ntot=0;
  int i=0;
  int* phy=NULL;
  const double* pdt=NULL;
  /* double* phx=NULL; */

  double vtmp=0;
  double vstp=0;
  double vmax=0;
  double vmin=0;

  int h=0;


  cpl_table_and_selected_double(data,cname,CPL_NOT_GREATER_THAN,max);

  tmp_tbl1=cpl_table_extract_selected(data);
  cpl_table_and_selected_double(tmp_tbl1,cname,CPL_GREATER_THAN,min);
  tmp_tbl2=cpl_table_extract_selected(tmp_tbl1);
  xsh_free_table(&tmp_tbl1);

  ntot=cpl_table_get_nrow(tmp_tbl2);
  /* not necessry to sort:
    check_nomsg(sinfo_sort_table_1(tmp_tbl2,cname,FALSE));*/
  vmin=cpl_table_get_column_min(tmp_tbl2,cname);
  vmax=cpl_table_get_column_max(tmp_tbl2,cname);
  vstp=(vmax-vmin)/(nbins-1);

  histo=cpl_table_new(nbins);
  cpl_table_new_column(histo,"HL",CPL_TYPE_DOUBLE);
  cpl_table_new_column(histo,"HY",CPL_TYPE_INT);
  cpl_table_new_column(histo,"HX",CPL_TYPE_DOUBLE);

  cpl_table_fill_column_window(histo,"HL",0,nbins,0);
  cpl_table_fill_column_window(histo,"HY",0,nbins,0);

  phy=cpl_table_get_data_int(histo,"HY");

  pdt=cpl_table_get_data_double_const(data,cname);
  //cpl_table_save(data, NULL, NULL, "histo_data.fits", CPL_IO_DEFAULT);
  for(i=0;i<nbins;i++) {
    cpl_table_set_double(histo,"HX",i,(double)i);
    vtmp=vmin+i*vstp;
    cpl_table_set_double(histo,"HL",i,vtmp);
  }
  h=0;
  for(i=0;i<ntot;i++) {
    h=floor((pdt[i]-vmin)/vstp);
    if((h<nbins) && (h>-1)) {
      phy[h]++;
    }
  }
  //cpl_table_save(histo, NULL, NULL, "histogram.fits", CPL_IO_DEFAULT);

  xsh_free_table(&tmp_tbl2);


  return histo;


}


/*----------------------------------------------------------------------------*/
/**
   @brief    Find best matching catalogue wavelength
   @param    line_refer    Table to search
   @param    lambda        Find catalogue wavelength nearest to this wavelength
   @param    lo            First row (inclusive, counting from 0) of table to search
   @param    hi            Last row (inclusive, counting from 0) of table to search
   @return   The index of the row containing the wavelength closest to @em lambda

   This function returns the index of the nearest wavelength in the range {lo, ..., hi}
   The input table must be sorted according to the column @em "Wave" which is the column
   that is searched.

   @note The function implements a binary search (using recursion)
   which is why the input table must be sorted.
**/
/*----------------------------------------------------------------------------*/
static int
xsh_wavecal_find_nearest(const cpl_table *line_refer, const char* col_ref,double lambda, int lo, int hi)
{

    if (lo == hi) /* One-row interval */
    {
        return lo;
    }
    else if (lo + 1 == hi)
    {  /* Two-row interval */
        double llo, lhi;
        check(lhi = cpl_table_get_double(line_refer, col_ref, hi, NULL));
        check(llo = cpl_table_get_double(line_refer, col_ref, lo, NULL));
        
        /* Return the one of 'llo' and 'lhi' that is closest to 'lambda' */
        return ((llo-lambda)*(llo-lambda) < (lhi-lambda)*(lhi-lambda)) ? lo : hi;
    }
    else
    { /* Three or more rows to consider */ 
        double lmid;
        int mid = (lo + hi)/2;
        /* mid is different from both 'lo' and 'hi', so this will terminate */
        
        check(lmid = cpl_table_get_double(line_refer, col_ref, mid, NULL));
        
        if (lmid < lambda)
        {
	  return xsh_wavecal_find_nearest(line_refer, col_ref, lambda, mid, hi);
        }
        else
        {
	  return xsh_wavecal_find_nearest(line_refer, col_ref, lambda, lo, mid);
        }
    }

 cleanup:
    return -1;
}


/**
@brief computes intmon QC log
@param table    table on which QC log is computed
@param header   frame header of bright lines
@param element  atomic element the line refers to
*/
static cpl_error_code
xsh_wavecal_qclog_compute(cpl_table* temp,
                          cpl_propertylist** header_check,
                          const char* element)

{
   char qc_intavg_name[40];
   char qc_nlinint_name[40];
   char comment[40];

   double mean;
   int nrows=cpl_table_get_nrow(temp);
   if (nrows == 0)
   {
      xsh_msg_warning("No bright lines found!");
      mean = 0;
   }
   else
   {
      mean = cpl_table_get_column_mean(temp, "Intensity");
   }

   sprintf(qc_intavg_name,"%s %s",QC_WAVECAL_INTAVG,element);        
   sprintf(comment,QC_WAVECAL_INTAVG_C);
   check(cpl_propertylist_append_double(*header_check,qc_intavg_name,mean));
   check(cpl_propertylist_set_comment(*header_check,qc_intavg_name,comment));

   sprintf(qc_nlinint_name,"%s %s",QC_WAVECAL_NLININT,element);
   sprintf(comment,QC_WAVECAL_NLININT_C);
   check(cpl_propertylist_append_int(*header_check,qc_nlinint_name,nrows));
   check(cpl_propertylist_set_comment(*header_check,qc_nlinint_name,comment));

  cleanup:
  return cpl_error_get_code();

}

/**
@brief Match reference and actual table via wavelength column 
@param table_intmon    table on which QC log is computed
@param col_wave_intmon intmon table wavelength column
@param table_check     table on which QC log is computed
@param col_wave_check  check table wavelength column
*/
static cpl_table*
xsh_table_select_matching_waves(cpl_table* table_intmon,
                                const char* col_wave_intmon,
                                cpl_table* table_check,
                                const char* col_wave_intcheck,
                                const double exptime)

{


  double tolerance = 0.001; /* (A) 
			       The lines in the line table
			       and intmon table are considered
			       identical if the difference
			       is less than this number.
			    */
                      
  int N_bright = cpl_table_get_nrow(table_intmon);
 
  int i=0;
  double wmin=cpl_table_get_column_min(table_intmon,"WAVELENGTH");
  double wmax=cpl_table_get_column_max(table_intmon,"WAVELENGTH");
  check(cpl_table_and_selected_double(table_check,"Wavelength",CPL_LESS_THAN,wmax+tolerance));
  check(cpl_table_and_selected_double(table_check,"Wavelength",CPL_GREATER_THAN,wmin-tolerance));

  cpl_table* table_ext=cpl_table_extract_selected(table_check);

  check(cpl_table_new_column(table_ext, "Intensity", CPL_TYPE_DOUBLE)); 
  for (i = 0; i < cpl_table_get_nrow(table_ext); i++)
    {
      int is_null=0;
      double ident=0;
      check(ident=cpl_table_get_double(table_ext, "Wavelength", i, &is_null));
  
      if (!is_null)
        {
	 int bright_index = 0;
	 check(bright_index=xsh_wavecal_find_nearest(table_intmon, "WAVELENGTH",
						     ident, 0, N_bright-1));


	  double bright = 0;
	  check(bright=cpl_table_get_double(table_intmon, "WAVELENGTH", bright_index, NULL));

	  if (fabs(bright - ident) < tolerance)
            {
	      /*
		double peak = 0;
		check(peak=cpl_table_get_double(table_ext, "Peak", i, NULL));
                double pixelsize = 
                fabs(cpl_table_get_double(table_ext, LINETAB_PIXELSIZE, i, NULL));

		double lambda_fwhm = 0;
		check(lambda_fwhm = cpl_table_get_double(table_ext, "FwhmYGauss", i, NULL)
		* TWOSQRT2LN2 * pixelsize);
		*/
	      // Line FWHM in wlu 
	      double norm=0;
	      check(norm=cpl_table_get_double(table_ext, "NormGauss", i, NULL));


	      //double intensity = peak * lambda_fwhm / exptime;
	      double intensity=norm/exptime;
	      /* Same formula as in MIDAS */

	      check(cpl_table_set_double(table_ext, "Intensity", i, intensity));

            }
	  else
            {
	      check(cpl_table_set_invalid(table_ext, "Intensity", i));
            }
        }
      else
        {
	  check(cpl_table_set_invalid(table_ext, "Intensity", i));
        }
    }

  cleanup:
  return table_ext;
}



/**
@brief computes intmon QC log
@param table_intmon   table of bright lines
@param table_check    line detection table on which QC log is computed
@param exptime        frame exposure time
@param atom_name      atom name
@param header_check   frame header where to loh QC
*/
static cpl_error_code
xsh_wavecal_qclog_element(cpl_table* table_intmon,
                          cpl_table *table_check,
                          const double exptime,
                          const char* atom_name,
                          cpl_propertylist** header_check)
{
   cpl_table *ext_intmon = NULL;
   cpl_table* table_ext=NULL;
   cpl_table *temp = NULL;

   cpl_table_and_selected_string(table_intmon,"element",CPL_EQUAL_TO,atom_name);
   ext_intmon=cpl_table_extract_selected(table_intmon);

   check(table_ext=xsh_table_select_matching_waves(ext_intmon,
                                                   "WAVELENGTH",
                                                   table_check,
                                                   "Wavelength",exptime));
   xsh_free_table(&temp);
   temp = cpl_table_duplicate(table_ext);
   cpl_table_erase_invalid(temp);
   check(xsh_wavecal_qclog_compute(temp,header_check,atom_name));
   cpl_table_select_all(table_intmon);

  cleanup:
   xsh_free_table(&temp);
   xsh_free_table(&ext_intmon);
   xsh_free_table(&table_ext);
   return cpl_error_get_code();

}

/**
@brief computes intmon QC log
@param table_check    frame line detection table on which QC log is computed
@param line_intmon    frame table of bright lines
@param exptime        frame exposure time
@param inst           X-shooter instrument arm/lamp setting
*/
cpl_error_code
xsh_wavecal_qclog_intmon(cpl_frame* line_check,
                         const cpl_frame *line_intmon,
                         const double exptime,
                         xsh_instrument* inst)
{
   cpl_table *temp = NULL;
   cpl_table *table_check = NULL;
   cpl_table *table_intmon = NULL;
   const char* name_check=NULL;
   const char* name_intmon=NULL;
   cpl_propertylist* header_check=NULL;
   cpl_table* table_ext=NULL;

   check(name_check=cpl_frame_get_filename(line_check));
   check(table_check=cpl_table_load(name_check,1,0));
   check(header_check=cpl_propertylist_load(name_check,0));

   check(name_intmon=cpl_frame_get_filename(line_intmon));
   check(table_intmon=cpl_table_load(name_intmon,1,0));

   if( xsh_instrument_get_arm(inst) == XSH_ARM_NIR) {

      check(xsh_wavecal_qclog_element(table_intmon,table_check,exptime,
                                      "AR",&header_check));

      check(xsh_wavecal_qclog_element(table_intmon,table_check,exptime,
                                      "NE",&header_check));

      check(xsh_wavecal_qclog_element(table_intmon,table_check,exptime,
                                      "XE",&header_check));

   }  else {

      check(xsh_wavecal_qclog_element(table_intmon,table_check,exptime,
                                      "THAR",&header_check));

   }
   check(cpl_table_save(table_check,header_check,NULL,name_check,CPL_IO_DEFAULT));

 cleanup:
  xsh_free_table(&temp);
  xsh_free_propertylist(&header_check);
  xsh_free_table(&table_check);
  xsh_free_table(&table_ext);
  xsh_free_table(&table_intmon);

  return cpl_error_get_code();
}


/*---------------------------------------------------------------------------*/
/**
   @brief    Read a table value from a fits table
   @param    table table to read
   @param    colname Name of column to read
   @param    coltype Type of column
   @param    i row to read
   @param    result The value read

   @return   CPL_ERROR_NONE iff OK
   @note derived from UVES pipeline
*/
/*---------------------------------------------------------------------------*/
cpl_error_code xsh_get_table_value (const cpl_table* table,
  const char *colname, cpl_type coltype, int i, void* result)
{
  int flag = 0;
  /* Check input */
  XSH_ASSURE_NOT_NULL( table);
  XSH_ASSURE_NOT_NULL( colname);
  XSH_ASSURE_NOT_NULL( result);

  /* Read the column */
  
  switch (coltype) {
    case CPL_TYPE_INT:
      check_msg (*((int *) result) = cpl_table_get_int(table,colname,i,&flag),
        "Could not get (integer) value of %s at row %d", colname,i);
    break;
  case CPL_TYPE_FLOAT:
    check_msg (*((float *) result) = cpl_table_get_float( table, 
      colname,i,&flag),
      "Could not get (float) value of %s at row %d",colname,i);
    break;
  case CPL_TYPE_DOUBLE:
    check_msg (*((double *) result) = cpl_table_get_double (table,
      colname,i,&flag),
      "Could not get (double) value of %s at row %d",colname,i);
    break;
  case CPL_TYPE_STRING:
    check_msg (*((const char **) result) =
               cpl_table_get_string (table, colname,i),
               "Could not get (string) value of %s at row %d",colname,i);
    break;
  default:
    assure (false, CPL_ERROR_INVALID_TYPE, "Unknown type");
  }

cleanup:
  return cpl_error_get_code ();
}

XSH_TABLE_GET_ARRAY(int)
XSH_TABLE_GET_ARRAY(float)
XSH_TABLE_GET_ARRAY(double)

/*---------------------------------------------------------------------------*/
/**
   @brief    Sort a table by one column
   @param    t        Table
   @param    column   Column name
   @param    reverse  Flag indicating if column values are sorted descending
                      (CPL_TRUE) or ascending (CPL_FALSE)
   @return   CPL_ERROR_NONE iff OK
   @note derived from UVES pipeline

   This is a wrapper of @c cpl_table_sort().

*/
/*---------------------------------------------------------------------------*/
cpl_error_code
xsh_sort_table_1(cpl_table *t, const char *column, cpl_boolean reverse)
{
    cpl_propertylist *plist = NULL;
    
    assure(t != NULL, CPL_ERROR_NULL_INPUT, "Null table");
    assure(cpl_table_has_column(t, column), CPL_ERROR_ILLEGAL_INPUT, 
       "No column '%s'", column);

    check_msg(( plist = cpl_propertylist_new(),
        cpl_propertylist_append_bool(plist, column, reverse)),
       "Could not create property list for sorting");

    check_msg( cpl_table_sort(t, plist), "Could not sort table");

  cleanup:
    xsh_free_propertylist(&plist);
    return cpl_error_get_code();
}

/*---------------------------------------------------------------------------*/
/**
   @brief    Sort a table by two columns
   @param    t        Table
   @param    column1  1st column name
   @param    column2  2nd column name
   @param    reverse1  Flag indicating if 1st column values are sorted
                       descending (true) or ascending (CPL_FALSE)
   @param    reverse2  Flag indicating if 2nd column values are sorted
                       descending (true) or ascending (CPL_TRUE)
   @return   CPL_ERROR_NONE iff OK
   @note derived from UVES pipeline

   This is a wrapper of @c cpl_table_sort(). @em column1 is the more
   significant column (i.e. values in @em column2 are compared, only if the
   values in @em column1 are equal).
*/
/*---------------------------------------------------------------------------*/
cpl_error_code
xsh_sort_table_2(cpl_table *t, const char *column1, const char *column2, 
          cpl_boolean reverse1, cpl_boolean reverse2)
{
    cpl_propertylist *plist = NULL;
    
    assure(t != NULL, CPL_ERROR_NULL_INPUT, "Null table");
    assure(cpl_table_has_column(t, column1), CPL_ERROR_ILLEGAL_INPUT, 
       "No column '%s'", column1);
    assure(cpl_table_has_column(t, column2), CPL_ERROR_ILLEGAL_INPUT,
       "No column '%s'", column2);

    check_msg(( plist = cpl_propertylist_new(),
        cpl_propertylist_append_bool(plist, column1, reverse1),
        cpl_propertylist_append_bool(plist, column2, reverse2)),
       "Could not create property list for sorting");
    check_msg( cpl_table_sort(t, plist), "Could not sort table");
    
  cleanup:
    xsh_free_propertylist(&plist);
    return cpl_error_get_code();
}

/*---------------------------------------------------------------------------*/
/**
   @brief    Sort a table by two columns
   @param    t        Table
   @param    column1  1st column name
   @param    column2  2nd column name
   @param    column3  3rd column name
   @param    reverse1  Flag indicating if 1st column values are sorted
                       descending (CPL_TRUE) or ascending (CPL_FALSE)
   @param    reverse2  Flag indicating if 2nd column values are sorted
                       descending (CPL_TRUE) or ascending (CPL_FALSE)
   @param    reverse3  Flag indicating if 3rd column values are sorted
                       descending (CPL_TRUE) or ascending (CPL_FALSE)
   @return   CPL_ERROR_NONE iff OK
   @note derived from UVES pipeline

   This is a wrapper of @c cpl_table_sort(). @em column1 is the more
   significant column (i.e. values in @em column2 are compared, only if the
   values in @em column1 are equal).
*/
/*---------------------------------------------------------------------------*/
cpl_error_code
xsh_sort_table_3(cpl_table *t, const char *column1, const char *column2,
                 const char *column3,
          cpl_boolean reverse1, cpl_boolean reverse2,cpl_boolean reverse3)
{
    cpl_propertylist *plist = NULL;

    assure(t != NULL, CPL_ERROR_NULL_INPUT, "Null table");
    assure(cpl_table_has_column(t, column1), CPL_ERROR_ILLEGAL_INPUT,
       "No column '%s'", column1);
    assure(cpl_table_has_column(t, column2), CPL_ERROR_ILLEGAL_INPUT,
       "No column '%s'", column2);
    assure(cpl_table_has_column(t, column3), CPL_ERROR_ILLEGAL_INPUT,
           "No column '%s'", column3);

    check_msg(( plist = cpl_propertylist_new(),
        cpl_propertylist_append_bool(plist, column1, reverse1),
        cpl_propertylist_append_bool(plist, column2, reverse2),
        cpl_propertylist_append_bool(plist, column3, reverse3)),
       "Could not create property list for sorting");
    check_msg( cpl_table_sort(t, plist), "Could not sort table");

  cleanup:
    xsh_free_propertylist(&plist);
    return cpl_error_get_code();
}

/*---------------------------------------------------------------------------*/
/**
  @brief    Interpolate data points
  @param    wav       value at which is desired an interpolated value
  @param    nrow      number of data points
  @param    pw        pointer to wave array
  @param    pe        pointer to efficiency array
  @return   Interpolated data points
 */
/*---------------------------------------------------------------------------*/
double
xsh_data_interpolate(
		     double wav,
		     int nrow,
		     double* pw,
		     double* pe
		     )
{
  double y = 0;
  double w1=pw[0];
  double w2=pw[nrow-1];
  double y1_=pe[0]; /*was changed from y1 to y1_ due a warning from compiler - shadowed variable*/
  double y2=pe[nrow-1];
  if(wav < pw[0])
    {
      w1=pw[0];
      w2=pw[1];
      y1_=pe[0];
      y2=pe[1];
    }
  else if (wav > pw[nrow - 1])
    {
      w1=pw[nrow - 2];
      w2=pw[nrow - 1];
      y1_=pe[nrow - 2];
      y2=pe[nrow - 1];
    }
  else
    {
      int l = 0;
      int h = nrow - 1;
      int curr_row = 0;
      curr_row = (h-l) / 2;
      while( h-l >1 )
	{
	  if(wav < pw[curr_row])
	    {
	      h = curr_row;
	    }
	  else
	    {
	      l = curr_row;
	    }
	  curr_row = (h-l) / 2 + l;
	}
      w1=pw[curr_row];
      w2=pw[curr_row + 1];
      y1_=pe[curr_row];
      y2=pe[curr_row + 1];
    }
  y=y1_+(y2-y1_)/(w2-w1)*(wav-w1);
  return y;
}

/*---------------------------------------------------------------------------*/
/**
   @brief    Interpolate table columns
   @param    tbl        Table
   @param    wav    value at which is desired to get an interpolated value
   @param    colx   1st column name
   @param    coly   2nd column name

   @return   interpolated value if ok

*/
/*---------------------------------------------------------------------------*/
double
xsh_table_interpolate(cpl_table* tbl,
		  double wav,
		  const char* colx,
		  const char* coly)
{

  double y=0;
  double* pe=NULL;
  double* pw=NULL;
  int nrow=0;

  check(pw=cpl_table_get_data_double(tbl,colx));
  check(pe=cpl_table_get_data_double(tbl,coly));
  check(nrow=cpl_table_get_nrow(tbl));

  y = xsh_data_interpolate(wav, nrow, pw, pe);
 cleanup:
  return y;

}

/**
   @brief Computes statistics on spectrum for QC
   @param[in] table input table
   @param[in] ws    wavelength start value
   @param[in] we    wavelength end value
   @param[in] prefix prefix of QC parameter
   @param[in] index index to specify QC parameter name
   @param[out] header header containing computed QC key
   @return  CPL error code
*/
static cpl_error_code
xsh_table_monitor_flux_qc(cpl_table* table,
			  const double ws,
			  const double we,
			  const char* prefix,
			  const int index,
			  cpl_propertylist** header)
{

  double flux=0;
  cpl_table* ext=NULL;
  char comment[40];
  char qc_key[20];
  int next=0;
  
  check(cpl_table_and_selected_double(table,"W",CPL_GREATER_THAN,ws));
  check(next=cpl_table_and_selected_double(table,"W",CPL_LESS_THAN,we));
  if(next>0) {
    check(ext=cpl_table_extract_selected(table));
    check(cpl_table_select_all(table));
    if(cpl_table_has_valid(ext,"F")) {
       check(flux=cpl_table_get_column_mean(ext,"F"));
    } else {
       flux=-999;
    }
    sprintf(qc_key,"ESO QC %s%d",prefix,index);
    sprintf(comment,"Mean value of %s in %4.0f-%4.0f nm",prefix,ws,we);
    check(cpl_propertylist_append_double(*header,qc_key,flux));
    check(cpl_propertylist_set_comment(*header,qc_key,comment));
  }


 cleanup:
  xsh_free_table(&ext);

  return cpl_error_get_code();

}



/*---------------------------------------------------------------------------*/
/*---------------------------------------------------------------------------*/
/**
   @brief Computes statistics on spectrum for QC
   @param[in] table input table describing spectrum 
   @param[in] colw  wavelength column name
   @param[in] colf  flux column name
   @param[in] prefix prefix of QC parameter
   @param[in] instrument instrument arm setting
   @return  CPL error code
*/
static cpl_propertylist*
xsh_frame_table_monitor_flux_qc_ext(cpl_table* table,
				    const char* colw,
				    const char* colf,
				    const char* prefix,
				    xsh_instrument* instrument)
{
  cpl_propertylist* header=NULL;

  XSH_ASSURE_NOT_NULL_MSG(table,"Null input table");
  header=cpl_propertylist_new();
  check(cpl_table_cast_column(table,colw,"W",CPL_TYPE_DOUBLE));
  check(cpl_table_cast_column(table,colf,"F",CPL_TYPE_DOUBLE));

  if ( xsh_instrument_get_arm(instrument) == XSH_ARM_UVB){

    check( xsh_table_monitor_flux_qc(table,450,470,prefix,1,&header));
    check( xsh_table_monitor_flux_qc(table,510,530,prefix,2,&header));

  }
  else if ( xsh_instrument_get_arm(instrument) == XSH_ARM_VIS){

    check( xsh_table_monitor_flux_qc(table,672,680,prefix,1,&header));
    check( xsh_table_monitor_flux_qc(table,745,756,prefix,2,&header));
    check( xsh_table_monitor_flux_qc(table,992,999,prefix,3,&header));

  }
  else if ( xsh_instrument_get_arm(instrument) == XSH_ARM_NIR){

    check( xsh_table_monitor_flux_qc(table,1514,1548,prefix,1,&header));
    check( xsh_table_monitor_flux_qc(table,2214,2243,prefix,2,&header));

  }

  cpl_table_erase_column(table,"W");
  cpl_table_erase_column(table,"F");

cleanup:
  if(cpl_error_get_code() != CPL_ERROR_NONE) {
    return NULL;
  } else {
    return header;
  }
}


/*---------------------------------------------------------------------------*/
/**
   @brief Computes statistics on spectrum for QC
   @param[in] frm   spectrum frame
   @param[in] colw  wavelength column name
   @param[in] colf  flux column name
   @param[in] prefix prefix of QC parameter
   @param[in] instrument instrument arm setting
   @return  CPL error code
*/
/*---------------------------------------------------------------------------*/
cpl_error_code
xsh_frame_table_monitor_flux_qc(cpl_frame* frm,
				const char* colw,
				const char* colf,
				const char* prefix,
				xsh_instrument* instrument)
{

  const char* name=NULL;
  cpl_table* table=NULL;
  cpl_propertylist* header=NULL;
  cpl_propertylist* qc_header=NULL;
 
  int next=0;
  int ext=0;

  XSH_ASSURE_NOT_NULL_MSG(frm,"Null input spectrum frame");
  next=cpl_frame_get_nextensions(frm);
  name=cpl_frame_get_filename(frm);
 
  /* compute QC */
 
  if (next) {
    for(ext=1;ext<=next;ext++) {
    
      table=cpl_table_load(name,1,ext);
      check(header=cpl_propertylist_load(name,0));
      check(qc_header=xsh_frame_table_monitor_flux_qc_ext(table,colw,colf,
							  prefix,instrument));
      cpl_propertylist_append(header,qc_header);
      if(next==1) {
	check(cpl_table_save(table,header,NULL,name,CPL_IO_DEFAULT));
      } else {
	check(cpl_table_save(table,header,NULL,name,CPL_IO_EXTEND));
      }
      xsh_free_table(&table);
      xsh_free_propertylist(&qc_header);
      xsh_free_propertylist(&header);
    }
  } else {
    table=cpl_table_load(name,1,0);
    check(header=cpl_propertylist_load(name,0));
    check(qc_header=xsh_frame_table_monitor_flux_qc_ext(table,colw,colf,
							prefix,instrument));
    cpl_propertylist_append(header,qc_header);
    check(cpl_table_save(table,header,NULL,name,CPL_IO_DEFAULT));
    xsh_free_propertylist(&qc_header);
    xsh_free_propertylist(&header);
  }

 cleanup:
  xsh_free_table(&table);
  xsh_free_propertylist(&qc_header);
  xsh_free_propertylist(&header);

  return cpl_error_get_code();

}

cpl_error_code
xsh_table_merge_clean_and_resid_tabs(cpl_frame* frm_resid,cpl_frame* frm_clean)
{

  double* pwav_resid=NULL;
  float* pwav_clean=NULL;
  double* pwav_extra=NULL;
  cpl_table* resid=NULL;
  cpl_table* clean=NULL;

  int nrows_clean=0;
  int nrows_resid=0;
  int i=0;
  int k=0;
  double wave_acc=0.001; /* [nm] */
  const char* name=NULL;
  cpl_propertylist* rhead=NULL;
  cpl_propertylist* qc_head=NULL;
  //cpl_propertylist* chead=NULL;

  XSH_ASSURE_NOT_NULL_MSG(frm_resid, "Null input resid table frame");
  XSH_ASSURE_NOT_NULL_MSG(frm_clean, "Null input clean table frame");

  check(name = cpl_frame_get_filename(frm_clean));
  check(clean = cpl_table_load(name, 1, 0));
  check(nrows_clean = cpl_table_get_nrow(clean));
  check(name = cpl_frame_get_filename(frm_resid));
  resid = cpl_table_load(name, 1, 0);

  rhead = cpl_propertylist_load(name, 0);

  qc_head=cpl_propertylist_load_regexp(cpl_frame_get_filename(frm_clean),0,"^ESO QC",0);
  cpl_propertylist_append(rhead,qc_head);

  cpl_frame_get_nextensions(frm_resid);
  check(nrows_resid=cpl_table_get_nrow(resid));
  cpl_table_new_column(resid,"WavelengthClean",CPL_TYPE_DOUBLE);
  cpl_table_fill_column_window_double(resid,"WavelengthClean",0,nrows_resid,0);
  check(pwav_resid=cpl_table_get_data_double(resid,XSH_RESID_TAB_TABLE_COLNAME_WAVELENGTH));
  check(pwav_clean=cpl_table_get_data_float(clean,XSH_ARCLIST_TABLE_COLNAME_WAVELENGTH));
  check(pwav_extra=cpl_table_get_data_double(resid,"WavelengthClean"));

  for (i = 0; i < nrows_clean; i++) {
    for (k = 0; k < nrows_resid; k++) {
      if (fabs(pwav_resid[k] - pwav_clean[i]) < wave_acc) {
        pwav_extra[i] = pwav_clean[i];
      }
    }
  }

  check(cpl_table_save(resid,rhead,NULL,name,CPL_IO_DEFAULT));

  cleanup:

  xsh_free_table(&resid);
  xsh_free_table(&clean);
  xsh_free_propertylist(&rhead);
  xsh_free_propertylist(&qc_head);
  return cpl_error_get_code();
}


cpl_table*
xsh_table_shift_rv(cpl_table* orig, const char* col_wave,const double offset) {
  cpl_table* shifted = NULL;
  double* pwav = NULL;
  int nrows=0;
  int i=0;
  XSH_ASSURE_NOT_NULL_MSG(orig, "Null input table");

  shifted=cpl_table_duplicate(orig);
  pwav = cpl_table_get_data_double(shifted, col_wave);
  nrows=cpl_table_get_nrow(shifted);
  for(i=0;i<nrows;i++){
    pwav[i] *= (1.+offset);
  }
cleanup:
  return shifted;
}
/**@}*/

