/*                                                                            *
 *   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-01-16 21:09:42 $
 * $Revision: 1.14 $
 * $Name: not supported by cvs2svn $
 */
#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

/*--------------------------------------------------------------------------*/
/**
 * @defgroup tests_c Tests Tools
 * @ingroup unit_tests
 *
 * Module including several functions used by various test programs.
 *
 */
/*--------------------------------------------------------------------------*/
/**@{*/

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

#include <xsh_data_pre.h>
#include <xsh_error.h>
#include <xsh_msg.h>
#include <xsh_data_instrument.h>
#include <xsh_drl.h>
#include <xsh_pfits.h>
#include <xsh_badpixelmap.h>
#include <cpl.h>
#include <stdbool.h>
#include <xsh_data_order.h>
#include <xsh_utils.h>
#include <xsh_pfits.h>
#include <string.h>
#include <tests.h>
#include <xsh_cpl_size.h>

/*---------------------------------------------------------------------------
  Defines
  ---------------------------------------------------------------------------*/
/*----------------------------------------------------------------------------*/
/**
 * AMO added (useful in unit tests)
  @brief    generates random data with Poisson distribution
  @param    expectedValue random generator goal value
  @return   random value
 */
/*----------------------------------------------------------------------------*/
static int xsh_poisson_random(double expectedValue) {
    int n = 0; //counter of iteration
    double limit;
    double x;  //pseudo random number
    limit = exp(-expectedValue);
    x = (double)rand() / (double)INT_MAX;
    while (x > limit) {
      n++;
      x *= (double)rand() / (double)INT_MAX;
    }
    return n;
  }


/*---------------------------------------------------------------------------
  Functions implementation
  ---------------------------------------------------------------------------*/
cpl_image* xsh_test_create_bias_image( const char* name, int nx, int ny,
				       xsh_instrument* instrument)
{
  cpl_image* res = NULL;
  cpl_propertylist* header = NULL;
  XSH_INSTRCONFIG * config = NULL;
  double ron = 0.0;
  double conad = 0.0;
  double ron_adu = 0.0;
  double medval = 85.0;

  xsh_msg("config update %d", instrument->update);
  config = xsh_instrument_get_config( instrument);
  ron = config->ron;
  conad = config->conad;
  ron_adu = ron / conad;
  res = cpl_image_new( nx, ny, XSH_PRE_DATA_TYPE) ;

  header = cpl_propertylist_new();
  setHeader(header, config, nx, ny, 1. );
  /* the parameters depends of the RON/CONAD KW for produce BIAS frame
     dispersion around (Bgmin+Bgmax)/2  */
  cpl_image_fill_noise_uniform ( res, medval-ron_adu, medval+ron_adu); 
  cpl_image_save( res, name, XSH_PRE_DATA_BPP, header, CPL_IO_DEFAULT);

  xsh_free_propertylist(&header);
  return res;

}

cpl_frame* xsh_test_create_frame(const char* name,int nx, int ny,
				 const char* tag, cpl_frame_group group, xsh_instrument* instrument)
{
  
  XSH_INSTRCONFIG *iconfig = NULL ;
  cpl_propertylist *header = NULL;
  cpl_image *data = NULL;
  cpl_frame *frame = NULL;
  float* pdata=NULL;
  int size=nx*ny;
  int i,j;
  XSH_ASSURE_NOT_NULL( instrument);
  XSH_ASSURE_NOT_NULL( name);

  iconfig = xsh_instrument_get_config( instrument);

  header = cpl_propertylist_new();
  setHeader(header, iconfig, nx, ny, 1.);

  data = cpl_image_new(nx,ny,XSH_PRE_DATA_TYPE);
  cpl_image_fill_gaussian(data,
			  nx / 3.0, ny / 3.0,     /* center */
			  50,     /* norm */
			  nx, ny / 8.0); /* sigma */

  /* add random noise, centered at 100 */
  pdata=cpl_image_get_data_float(data);
  for(j=0;j<size;j++) {
      pdata[j]+=xsh_poisson_random(100.);
  }
  check( cpl_image_save(data, name, XSH_PRE_DATA_BPP,header,
    CPL_IO_DEFAULT));
 
  /* Create test frame */
  frame = cpl_frame_new();
  cpl_frame_set_filename(frame,name);
  cpl_frame_set_group(frame,group);
  cpl_frame_set_tag(frame,tag);

  cleanup:
    xsh_free_propertylist(&header);
    xsh_free_image(&data);
    return frame;
}
/*--------------------------------------------------------------------------*/
/**
   @brief    Set unset parameters to default value
   @param    parlist              A parameter list
   @return   0 iff success

   The function initializes the provided parameter list by setting the current
   parameter values to the default parameter values.
*/
/*--------------------------------------------------------------------------*/
void tests_set_defaults(cpl_parameterlist * parlist)
{
  cpl_parameter *p = NULL;

  p = cpl_parameterlist_get_first(parlist);
  while (p != NULL) {
    bool parameter_is_set = cpl_parameter_get_default_flag(p);

    if (!parameter_is_set) {
      cpl_type ptype = cpl_parameter_get_type(p);
      switch (ptype) {
      case CPL_TYPE_BOOL:
	cpl_parameter_set_bool(p, cpl_parameter_get_default_bool(p));
	break;
      case CPL_TYPE_INT:
	cpl_parameter_set_int(p, cpl_parameter_get_default_int(p));
	break;
      case CPL_TYPE_DOUBLE:
	cpl_parameter_set_double(p, cpl_parameter_get_default_double(p));
	break;
      case CPL_TYPE_STRING:
	cpl_parameter_set_string(p, cpl_parameter_get_default_string(p));
	break;
      default:
	assure(false, CPL_ERROR_ILLEGAL_INPUT,
	       "Unknown type of parameter '%s'", cpl_parameter_get_name(p));
      }
    }
    p = cpl_parameterlist_get_next(parlist);
  }

 cleanup:
  return;
}

/** 
 * Create a header and Set the Basic Keywords of xsh images according 
 * to the instrument configuration.
 * Some parameters of the instrument are overrriden (nx, ny,
 * exptime).
 * 
 * @param iconfig Structure describing the instrument configuration
 * @param nx Nb of pixels in X (overrides the default configuration)
 * @param ny Nb of pixels in Y (overrides the default configuration)
 * @param exptime Exposure time
 * 
 * @return Pointer to the created propertylist
 */
cpl_propertylist * mkHeader( XSH_INSTRCONFIG *iconfig,
			     int nx, int ny, double exptime )
{
  cpl_propertylist * header = cpl_propertylist_new() ;

  /* set a minimum of KW */
  /* Note that NAXIS, NAXIS1 and NAXIS2 are automtically set */
  check_msg( cpl_propertylist_append_double( header, XSH_CRPIX1, 1. ),
	     "Cant append CRPIX1" ) ;
  check_msg( cpl_propertylist_append_double( header, XSH_CRPIX2, 1. ),
	     "Cant append CRPIX2" ) ;
  check_msg( cpl_propertylist_append_double( header, XSH_CRVAL1, 1. ),
	     "Cant append CRVAL1" ) ;
  check_msg( cpl_propertylist_append_double( header, XSH_CRVAL2, 1. ),
	     "Cant append CRVAL2" ) ;


  check_msg( cpl_propertylist_append_int( header, XSH_OUT_NX, nx ),
	     "Cant append NX" ) ;
  check_msg( cpl_propertylist_append_int( header, XSH_OUT_NY, ny ),
	     "Cant append NY" ) ;
  check_msg( cpl_propertylist_append_int( header, XSH_OVSCX, iconfig->ovscx ),
	     "Cant append OVSCX" ) ;
  check_msg( cpl_propertylist_append_int( header, XSH_OVSCY, iconfig->ovscy ),
	     "Cant append OVSCY" ) ;
  check_msg( cpl_propertylist_append_int( header, XSH_PRSCX, iconfig->prscx ),
	     "Cant append PRSCX" ) ;
  check_msg( cpl_propertylist_append_int( header, XSH_PRSCY, iconfig->prscy ),
	     "Cant append PRSCY" ) ;
  check_msg( cpl_propertylist_append_double( header, XSH_RON, iconfig->ron ),
	     "Cant append RON" ) ;
  check_msg( cpl_propertylist_append_double( header, XSH_CONAD,
					     iconfig->conad ) ,
	     "Cant append CONAD" ) ;
  check_msg( cpl_propertylist_append_double( header, XSH_PSZX, 15. ),
	     "Cant append PSZX" ) ;
  check_msg( cpl_propertylist_append_double( header, XSH_PSZY, 15. ),
	     "Cant append PSZY" ) ;
  check_msg( cpl_propertylist_append_double( header, XSH_EXPTIME, exptime ),
	     "Cant append EXPTIME" ) ;
  check_msg( cpl_propertylist_append_double( header, XSH_DET_GAIN,2.78 ),
             "Cant append GAIN" ) ;
  check_msg( cpl_propertylist_append_int( header, XSH_WIN_BINX,1 ),
             "Cant append BINX" ) ;
  check_msg( cpl_propertylist_append_int( header, XSH_WIN_BINY,1 ),
             "Cant append BINY" ) ;
  return header ;
 cleanup:
  return NULL ;
}

/** 
 * Set the Basic Keywords of xsh images according to the instrument
 * configuration. Some parameters of the instrument are overrriden (nx, ny,
 * exptime).
 *
 * @param header the property list to update 
 * @param iconfig Structure describing the instrument configuration
 * @param nx Nb of pixels in X (overrides the default configuration)
 * @param ny Nb of pixels in Y (overrides the default configuration)
 * @param exptime Exposure time
 * 
 */
void setHeader( cpl_propertylist * header, XSH_INSTRCONFIG *iconfig,
		int nx, int ny, double exptime)
{

  XSH_ASSURE_NOT_NULL( header);
  /* set a minimum of KW */
  /* Note that NAXIS, NAXIS1 and NAXIS2 are automtically set */
  check_msg( cpl_propertylist_append_int( header, XSH_OUT_NX, nx ),
	     "Cant append NX" ) ;
  check_msg( cpl_propertylist_append_int( header, XSH_OUT_NY, ny ),
	     "Cant append NY" ) ;
  check_msg( cpl_propertylist_append_int( header, XSH_WIN_BINX,1 ),
             "Cant append BINX" ) ;
  check_msg( cpl_propertylist_append_int( header, XSH_WIN_BINY,1 ),
             "Cant append BINY" ) ;

  check_msg( cpl_propertylist_append_int( header, XSH_CHIP_NX, nx ),
	     "Cant append NX" ) ;
  check_msg( cpl_propertylist_append_int( header, XSH_CHIP_NY, ny ),
	     "Cant append NY" ) ;

  check_msg( cpl_propertylist_append_double( header, XSH_PSZX, 15. ),
	     "Cant append PSZX" ) ;
  check_msg( cpl_propertylist_append_double( header, XSH_PSZY, 15. ),
	     "Cant append PSZY" ) ;
  check_msg( cpl_propertylist_append_double( header, XSH_EXPTIME, exptime ),
	     "Cant append EXPTIME" ) ;
  check_msg( cpl_propertylist_append_double( header, XSH_DET_GAIN,2.78 ),
             "Cant append GAIN" ) ;

  check_msg( cpl_propertylist_append_int( header, XSH_OVSCX, iconfig->ovscx ),
             "Cant append OVSCX" ) ;
  check_msg( cpl_propertylist_append_int( header, XSH_OVSCY, iconfig->ovscy ),
             "Cant append OVSCY" ) ;
  check_msg( cpl_propertylist_append_int( header, XSH_PRSCX, iconfig->prscx ),
             "Cant append PRSCX" ) ;
  check_msg( cpl_propertylist_append_int( header, XSH_PRSCY, iconfig->prscy ),
             "Cant append PRSCY" ) ;
  check_msg( cpl_propertylist_append_double( header, XSH_RON, iconfig->ron ),
             "Cant append RON" ) ;
  check_msg( cpl_propertylist_append_double( header, XSH_CONAD,
                                             iconfig->conad ) ,
             "Cant append CONAD" ) ;
  /* Special NIR */
  if ( iconfig->bitpix == 32){
    check_msg( cpl_propertylist_append_double( header, XSH_DET_PXSPACE,
					     iconfig->pxspace),
             "Cant append PXSPACE keyword" ) ;
  }

  cleanup:
    return;
}

/* 
   @brief
   Create an image with a (small) number of arclines.
   @param list
   order list
   @param nx
   image size in x
   @param ny
   image size in y

   Calculer les pixels en fonction des polynomes upper et lower
   avec une gaussienne de parametres fixes et bien connus.

*/

cpl_image * create_order_image( xsh_order_list* list, 
				int nx, int ny )
{
  int i ;
  double pixmax = 100. ;
  cpl_image * image = NULL ;

  check( image = cpl_image_new( nx, ny, CPL_TYPE_DOUBLE ) ) ;

  for( i = 0 ; i<list->size ; i++ ) {
    /* Create line according to polynomial coeff */
    int ixl, ixu, ixc, iy;
    cpl_size zero = 0 ;
    double cxl, cxu ;
    double pixstep ;

    cxl = cpl_polynomial_get_coeff( list->list[i].edglopoly, &zero ) ;
    cxu = cpl_polynomial_get_coeff( list->list[i].edguppoly, &zero ) ;
    pixstep = (2*pixmax)/(cxu-cxl) ;

    //xsh_msg("starty=%d endy=%d",list->list[i].starty,list->list[i].endy);

    for( iy = list->list[i].starty ; iy < list->list[i].endy ;
	 iy++ ) {
      int k ;
      double pixval ;

      /* Calculate x = f(y) */
      ixc = cpl_polynomial_eval_1d( list->list[i].cenpoly,
				    (double)iy, NULL ) ;
      ixl = cpl_polynomial_eval_1d( list->list[i].edglopoly,
				    (double)iy, NULL ) ;
      ixu = cpl_polynomial_eval_1d( list->list[i].edguppoly,
				    (double)iy, NULL ) ;
      /* Set values (gaussian) around x (x-delta to x+delta) */
      //xsh_msg("ixl=%d ixu=%d",ixl,ixu);
      for( pixval = 0., k = ixl ; k<ixc ; k++, pixval += pixstep) {
	cpl_image_set( image, k, iy, pixval ) ;
      }
      cpl_image_set( image, ixc+1, iy+1, pixmax ) ;
      for( pixval = pixval, k = ixc ; k<=ixu ; k++, pixval -= pixstep) {
	cpl_image_set( image, k, iy, pixval ) ;
      }
    }
  }
 cleanup:
  return image ;
}

xsh_order_list * create_order_list( int norder, xsh_instrument* instrument )
{
  xsh_order_list* result = NULL ;

  XSH_CALLOC(result, xsh_order_list, 1);
  result->size = norder ;
  result->instrument = instrument ;
  XSH_CALLOC( result->list, xsh_order, result->size);  
  XSH_NEW_PROPERTYLIST( result->header );

 cleanup:
  if (cpl_error_get_code() != CPL_ERROR_NONE) {
    xsh_order_list_free(&result);
  }  
  return result;

}

/*
  Calculer les polynomes upper/lower edge en fonction de delta 
*/

void add_to_order_list( xsh_order_list * list, int order,
			int absorder,
			cpl_polynomial *poly, int xdelta,
			int starty, int endy )
{
  cpl_size i = 0 ;
  double coeff0 ;

  coeff0 = cpl_polynomial_get_coeff( poly, &i ) ;

  list->list[order].order = absorder ;
  list->list[order].absorder = absorder ;
  list->list[order].cenpoly = cpl_polynomial_duplicate(  poly ) ;
  list->list[order].edguppoly = cpl_polynomial_duplicate(  poly ) ;
  cpl_polynomial_set_coeff( list->list[order].edguppoly, &i, 
			    coeff0 + (double)xdelta ) ;
  list->list[order].edglopoly = cpl_polynomial_duplicate(  poly ) ;
  cpl_polynomial_set_coeff( list->list[order].edglopoly, &i, 
			    coeff0 - (double)xdelta ) ;
  list->list[order].starty = starty ;
  list->list[order].endy = endy ;
}

/* Special creation of rectified frames */

#define NB_LAMBDA 100
#define FIRST_LAMBDA 500.0
#define LAST_LAMBDA 510.0

#define NB_SLIT 40
#define FIRST_SLIT -5
#define LAST_SLIT 5

#define POS_PLUS 30
#define COEFF0_PLUS 30.
#define COEFF1_PLUS 0.03
#define POS_CENTER 30
#define POS_MINUS 5
#define COEFF0_MINUS 10.
#define COEFF1_MINUS 0.03

#define WIDTH 5
#define HALF_WIDTH 2

static float Flux[WIDTH] = {
  20., 40., 100., 40., 20. } ;

static cpl_polynomial * poly_plus = NULL, * poly_minus = NULL ;

cpl_frame * create_rectify_nod_list( int sign, const char * fname,
				     xsh_instrument * instr )
{
  xsh_rec_list* result = NULL;
  int i, j, k, nb, order ;
  double * plambda ;
  float * pslit ;
  double step ;
  const char * tag = NULL ;
  cpl_frame * res_frame = NULL ;

  XSH_CALLOC(result, xsh_rec_list, 1 );
  result->size = instr->config->orders ;
  XSH_CALLOC(result->list, xsh_rec, result->size);  
  XSH_NEW_PROPERTYLIST(result->header);

  for( nb = 0, order = instr->config->order_min ;
       order <= instr->config->order_max ;
       order++, nb++ ) {
    result->list[nb].order = order ;
    result->list[nb].nlambda = NB_LAMBDA ;
    result->list[nb].nslit = NB_SLIT ;
    XSH_CALLOC( result->list[nb].slit, float, NB_SLIT ) ;
    XSH_CALLOC( result->list[nb].lambda, double, NB_LAMBDA ) ;
    XSH_CALLOC( result->list[nb].data1, float, NB_LAMBDA*NB_SLIT ) ;
    XSH_CALLOC( result->list[nb].errs1, float, NB_LAMBDA*NB_SLIT ) ;
    XSH_CALLOC( result->list[nb].qual1, int, NB_LAMBDA*NB_SLIT ) ;

    xsh_msg_dbg_high( "Fill lambda" ) ;
    // Now fill slits, lambda and data1
    plambda = result->list[nb].lambda ;
    step = (double)(LAST_LAMBDA-FIRST_LAMBDA)/(double)NB_LAMBDA ;
    for ( i = 0 ; i<NB_LAMBDA ; i++, plambda++ )
      *plambda = FIRST_LAMBDA + i*step ;

    pslit = result->list[nb].slit ;
    step = (double)(LAST_SLIT-FIRST_SLIT)/(double)NB_SLIT ;
    xsh_msg_dbg_high( "Fill slit (%lf)", step ) ;
    for ( i = 0 ; i<NB_SLIT ; i++, pslit++ )
      *pslit = FIRST_SLIT + i*step ;
    /* Create the 2 polynomials */
    check( poly_plus = cpl_polynomial_new( 1 ) ) ;
    check( poly_minus = cpl_polynomial_new( 1 ) ) ;
    /* Set the coeff */
    {
      cpl_size kc ;
      /* PLUS */
      kc = 0 ;
      cpl_polynomial_set_coeff( poly_plus, &kc, COEFF0_PLUS ) ;
      kc = 1 ;
      cpl_polynomial_set_coeff( poly_plus, &kc, COEFF1_PLUS ) ;
      /* Minus */
      kc = 0 ;
      cpl_polynomial_set_coeff( poly_minus, &kc, COEFF0_MINUS ) ;
      kc = 1 ;
      cpl_polynomial_set_coeff( poly_minus, &kc, COEFF1_MINUS ) ;
    }

    plambda = result->list[nb].lambda ;
    for( j = 0 ; j<NB_LAMBDA ; j++, plambda++ ) {
      float datump, datumm ;
      int ns ;

      check( datump = cpl_polynomial_eval_1d( poly_plus, (double)j, NULL ) ) ;
      check( datumm = cpl_polynomial_eval_1d( poly_minus, (double)j, NULL ) ) ;

      for ( k = 0, ns = (int)datump - HALF_WIDTH ; k<WIDTH ; k++, ns++ ) {
	float val ;
	if ( sign > 0 ) val = Flux[k] ;
	else val = -Flux[k] ;
	result->list[nb].data1[j + (int)ns*NB_LAMBDA] = val ;
      }
      for ( k = 0, ns = (int)datumm - HALF_WIDTH ; k<WIDTH ; k++, ns++ ) {
	float val ;
	if ( sign > 0 ) val = -Flux[k] ;
	else val = Flux[k] ;
	result->list[nb].data1[j + (int)ns*NB_LAMBDA] = val ;
      }
    }
  }
  result->instrument = instr;

  xsh_msg_dbg_high( "Save frame" ) ;
  res_frame = xsh_rec_list_save( result, fname, "TAG", 1 ) ;
  tag = XSH_GET_TAG_FROM_ARM( XSH_ORDER2D,instr);
  check(cpl_frame_set_tag(res_frame, tag )) ;

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


cpl_frameset * sof_to_frameset( const char* sof_name)
{
  FILE *sof_file = NULL;
  cpl_frameset *result = NULL;
  char sof_line[200];
  
  XSH_ASSURE_NOT_NULL( sof_name);

  check( result = cpl_frameset_new());

  sof_file = fopen( sof_name, "r");

  if (sof_file != NULL){

  while ( fgets( sof_line, 200, sof_file)){
    char name[200];
    char tag[200];
    cpl_frame *frame = NULL;
    
    if ( sof_line[0] == '#'){
      xsh_msg("skip line %s", sof_line);
    }
    else{
      /* Note: update the string format (the %__s) if name(tag) changes size.
       * Remember: field width must equal 1 less than sizeof(name(tag)). */
      sscanf( sof_line, "%199s %199s", name, tag);
      check( frame = cpl_frame_new());
      check( cpl_frame_set_filename( frame, name));
      check( cpl_frame_set_tag( frame, tag));
      check( cpl_frameset_insert( result, frame));
    }
  }
  fclose( sof_file);
  }
  else{
    xsh_msg("File %s not found", sof_name);
    XSH_ASSURE_NOT_NULL( sof_file);
  }

  cleanup:
    if (cpl_error_get_code() != CPL_ERROR_NONE) {
      xsh_free_frameset( &result);
    }
    return result;
}

xsh_instrument * create_instrument( const char *filename)
{
  xsh_instrument *instr = NULL;
  cpl_propertylist *header = NULL;
  const char *catg = NULL;
  
  check( header = cpl_propertylist_load( filename, 0));
  check( catg = xsh_pfits_get_pcatg( header));
  
  check( instr = xsh_instrument_new());
  
  if (strstr( catg, "UVB") != NULL){
    xsh_instrument_set_arm( instr, XSH_ARM_UVB);
  }
  else {
    if (strstr( catg, "VIS") != NULL){
      xsh_instrument_set_arm( instr, XSH_ARM_VIS);
    }
    else{
      if (strstr( catg, "NIR") != NULL){
        xsh_instrument_set_arm( instr, XSH_ARM_NIR);
      } 
    }
  }
  
  cleanup:
    xsh_free_propertylist( &header);
    return instr;
}
/**@}*/
