/*                                                                            *
 *   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: 2012-05-03 12:05:47 $
 * $Revision: 1.6 $
*/

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

/*----------------------------------------------------------------------------*/
/**
 * @defgroup xsh_calibrate_flux  Calibrate Flux
 * @ingroup drl_functions
 *
 * Function ...
 */
/*----------------------------------------------------------------------------*/
/**@{*/

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

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

#include <xsh_data_rec.h>
#include <xsh_data_localization.h>
#include <xsh_data_pre.h>
#include <xsh_data_order.h>
#include <xsh_dfs.h>
#include <xsh_pfits.h>
#include <xsh_error.h>
#include <xsh_msg.h>
#include <xsh_fit.h>
#include <xsh_ifu_defs.h>
#include <xsh_data_slice_offset.h>
#include <xsh_data_atmos_ext.h>
#include <cpl.h>
#include <xsh_utils.h>
#include <gsl/gsl_integration.h>
#include <xsh_data_star_flux.h>
#include <xsh_data_spectrum.h>

#define USE_SPLINE 
#if defined(USE_SPLINE)
#include <gsl/gsl_spline.h>
#endif

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



/*-----------------------------------------------------------------------------
  Implementation
  -----------------------------------------------------------------------------*/
#if defined(USE_SPLINE)
static gsl_interp_accel * AcceleratorResp, * AcceleratorAtmos ;
static gsl_spline * SplineResp, * SplineAtmos ;

static void init_interpolate( double * x, double * yf, int nb,
			      gsl_spline ** spline,
			      gsl_interp_accel ** accel)
{
  int ok ;

  //fout = fopen( "interpol.dat", "w" ) ;

  *accel = gsl_interp_accel_alloc();
  if ( *accel == NULL ) xsh_msg( "Accelerator = NULL" ) ;

  *spline = gsl_spline_alloc( gsl_interp_cspline, nb);

  if ( *spline == NULL ) xsh_msg( "Spline = NULL" ) ;

  ok = gsl_spline_init( *spline, x, yf, nb ) ;

  xsh_msg( "gsl_spline_init --> %d", ok ) ;

  return ;
}

static double do_interpolation( double x, gsl_spline * spline,
				gsl_interp_accel * accel )
{
  double y =0;

  y = gsl_spline_eval( spline, x, accel );

  return y ;
}

static void clear_interpolate( void )
{
  gsl_spline_free( SplineResp ) ;
  gsl_spline_free( SplineAtmos ) ;
  gsl_interp_accel_free( AcceleratorResp ) ;
  gsl_interp_accel_free( AcceleratorAtmos ) ;

}
#else

static int get_idx_by_lambda( double lambda, double * a_lambda, int from,
			      int to, double step )
{
  int idx =0;
  double * plambda=NULL ;

  for( idx = from, plambda = a_lambda+from ; idx<to ; idx++, plambda++ ) {
    if ( lambda >= (*plambda - step/2) &&
	 lambda < (*plambda + step/2) ) {
      return idx ;
    }
  }

  return -1 ;
}

#endif

static double myfunc (double x, void * params) {
  double alpha = *(double *) params;
  double thefunc = exp(alpha*x*x) ;

  return thefunc ;
}

static double compute_Lx( double slit_width, double seeing )
{
  gsl_integration_workspace * w 
    = gsl_integration_workspace_alloc (10000);
       
  double result, error;
  double alpha = -1.0;
     
  gsl_function F;
  double limit ;

  double Lx ;

  F.function = &myfunc ;
  F.params = &alpha;
  limit = (slit_width/2.)/((seeing*sqrt(2.))/2.35482) ;

  gsl_integration_qags (&F, 0, limit, 0, 1e-7, 10000,
                        w, &result, &error); 


  Lx = 1- (2/sqrt(M_PI))*result ;

  return Lx ;
}

static xsh_spectrum * do_calib_spectrum( xsh_spectrum * spectrum_in,
					 xsh_star_flux_list * response_list,
					 xsh_atmos_ext_list * atmos_ext_list,
					 double airmass_ratio,
					 double Lx )
{
  xsh_spectrum * spectrum =NULL;
  double * spectrum_flux = NULL ;
  int * spectrum_qual = NULL ;
  double spectrum_lambda_min=0;
  //double spectrum_lambda_max=0 ;
  int spectrum_nlambda=0, spectrum_nslit=0 ;
  double lambda=0 ;
  double lambda_step=0 ;

  double * atmos_lambda = NULL ;
  double * atmos_K = NULL ;
  //double atmos_step=0 ;
  int atmos_size=0 ;

  double * response_lambda = NULL ;
  double * response_flux = NULL ;
  //double response_step=0 ;
  int response_size=0 ;
  int ipix=0 ;
#if !defined(USE_SPLINE)
  int atmidx = 0, respidx = 0 ;
#endif

  check( spectrum = xsh_spectrum_duplicate( spectrum_in ) ) ;

  check( spectrum_nlambda = xsh_spectrum_get_size_lambda( spectrum ) ) ;
  check( spectrum_nslit = xsh_spectrum_get_size_slit( spectrum ) ) ;
  if ( spectrum_nslit > 1 ) {
    xsh_msg( "2D Spectrum" ) ;
  }
  else {
    xsh_msg( "1D Spectrum" ) ;
  }

  check( spectrum_flux = xsh_spectrum_get_flux( spectrum ) ) ;
  check( spectrum_qual = xsh_spectrum_get_qual( spectrum ) ) ;
  check( spectrum_lambda_min = xsh_spectrum_get_lambda_min( spectrum ) ) ;
  //check( spectrum_lambda_max = xsh_spectrum_get_lambda_max( spectrum ) ) ;
  check( lambda_step = xsh_spectrum_get_lambda_step( spectrum ) ) ;

  check( atmos_lambda = xsh_atmos_ext_list_get_lambda( atmos_ext_list ) ) ;
  check( atmos_K = xsh_atmos_ext_list_get_K( atmos_ext_list ) ) ;
  if (atmos_ext_list != NULL) {
     atmos_size = atmos_ext_list->size ;
  }
  //atmos_step = *(atmos_lambda+atmos_size-1) - *atmos_lambda ;
#if defined(USE_SPLINE)
  check( init_interpolate( atmos_lambda, atmos_K, atmos_size, &SplineAtmos,
			   &AcceleratorAtmos ) ) ;
#endif

  check( response_lambda = xsh_star_flux_list_get_lambda( response_list ) ) ;
  check( response_flux = xsh_star_flux_list_get_flux( response_list ) ) ;
  response_size = response_list->size ;
  //response_step = *(response_lambda+response_size-1) - *response_lambda ;
#if defined(USE_SPLINE)
  check( init_interpolate( response_lambda, response_flux, response_size,
			   &SplineResp,
			   &AcceleratorResp ) ) ;
#endif

  lambda = spectrum_lambda_min ;
  /* Loop over spectrum pixels */
  for( ipix = 0 ; ipix < spectrum_nlambda ; ipix++, lambda += lambda_step ) {
    double kvalue, vresp ;
    int islit ;

    /* Now correct with atmos extinction: how ? interpolate (how) ?
       other ? or just use the value corresponding to the current lambda ? */
    if ( atmos_ext_list != NULL ) {
#if defined(USE_SPLINE)
      double inter_k ;

      inter_k = do_interpolation( lambda, SplineAtmos, AcceleratorAtmos ) ;
      kvalue = pow( 10., -inter_k*0.4 ) ;
#else
     /* Trivial solution, use the table value in all the intervall */
     atmidx = get_idx_by_lambda( lambda, atmos_lambda, atmidx, atmos_size,
				  atmos_step ) ;
      kvalue = pow(10., -atmos_K[atmidx]*0.4 ) ;
#endif
    }
    else kvalue = 1. ;
    /* Now, more complicated, correct with response: how ? interpolate (how) ?
       other ? Could do the same as for atmos ext ?
    */
#if defined(USE_SPLINE)
    vresp = do_interpolation( lambda, SplineResp, AcceleratorResp ) ;
#else
    /* Trivial solution, use the table value in all the intervall */
    respidx = get_idx_by_lambda( lambda, response_lambda, respidx,
				 response_size, response_step ) ;
    vresp = response_flux[respidx] ;
#endif
    /* Loop over slit */
    for( islit = 0 ; islit < spectrum_nslit ; islit++ ) {
      int idx=0 ;

      if ( spectrum_qual[idx] !=  QFLAG_GOOD_PIXEL )
	continue ;
      idx = ipix + islit*spectrum_nlambda ;
      spectrum_flux[idx] *= (airmass_ratio*kvalue*vresp)/Lx ;
    }
  }

  clear_interpolate() ;

 cleanup:
  return spectrum ;
}

cpl_frame * xsh_calibrate_flux( cpl_frame * spectrum_frame,
				cpl_frame * respon_frame,
				cpl_frame * atmos_ext_frame,
				const char * fname,
				xsh_instrument * instrument )
{
  cpl_frame * result = NULL ;
  double s_airm_start, s_airm_end, r_airm_start, r_airm_end ;
  double s_airmass = 1., r_airmass = 1., airmass_ratio = 1. ;
  double slit_width, seeing_start, seeing_end, seeing ;
  cpl_propertylist * s_header = NULL, * r_header = NULL ;
  xsh_spectrum * spectrum = NULL;
  xsh_star_flux_list * response_list = NULL ;
  xsh_atmos_ext_list * atmos_ext_list = NULL ;
  double Lx ;

  XSH_ASSURE_NOT_NULL( spectrum_frame ) ;
  XSH_ASSURE_NOT_NULL( respon_frame ) ;
  XSH_ASSURE_NOT_NULL( instrument ) ;

  /* First get AIRMASS's */
  check( spectrum = xsh_spectrum_load( spectrum_frame)) ;
  check( s_header = spectrum->flux_header ) ;
  s_airm_start = xsh_pfits_get_airm_start( s_header ) ;
  s_airm_end = xsh_pfits_get_airm_end( s_header ) ;
  if ( cpl_error_get_code() != CPL_ERROR_NONE ) {
    cpl_error_reset() ;
    s_airmass = 1. ;
  }
  else s_airmass = (s_airm_start + s_airm_end)/2. ;

  check( response_list = xsh_star_flux_list_load( respon_frame ) ) ;
  check( r_header = response_list->header ) ;
  r_airm_start = xsh_pfits_get_airm_start( r_header ) ;
  r_airm_end = xsh_pfits_get_airm_end( r_header ) ;
  if ( cpl_error_get_code() != CPL_ERROR_NONE ) {
    cpl_error_reset() ;
    r_airmass = 1. ;
  }
  else r_airmass = (r_airm_start + r_airm_end)/2. ;
  airmass_ratio = s_airmass/r_airmass ;

  /* Get slit width and seeing */
  if ( xsh_instrument_get_mode( instrument ) == XSH_MODE_IFU ) {
    slit_width = WIDTH_SLIT_IFU ;
    xsh_msg( "IFU Mode Slit width: %.2lf", slit_width ) ;
  }
  else {
    check( slit_width = xsh_pfits_get_slit_width( s_header, instrument ) ) ;
    xsh_msg( "SLIT Mode Slit width: %.2lf", slit_width ) ;
  }
  check( seeing_start = xsh_pfits_get_seeing_start( s_header ) ) ;
  check( seeing_end = xsh_pfits_get_seeing_end( s_header ) ) ;
  seeing = (seeing_start + seeing_end)/2. ;
  xsh_msg_warning("SEEING=%g",seeing);

  Lx = compute_Lx( slit_width, seeing ) ;

  /* Load ATMOS EXT (if not NULL ) */
  if ( atmos_ext_frame != NULL ) {
    check( atmos_ext_list = xsh_atmos_ext_list_load( atmos_ext_frame ) ) ;
    xsh_msg( "ATMOS EXT Loaded" ) ;
  }

  /* Same function for 1D/2D spectra */
  check( do_calib_spectrum( spectrum, response_list, atmos_ext_list,
			    airmass_ratio, Lx ) ) ;
  /* How about IFU ? */

  check( result = xsh_phys_spectrum_save( spectrum, fname, instrument )) ;

 cleanup:

  return result ;
}
