/*                                                                            *
 *   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-04-26 14:10:08 $
 * $Revision: 1.13 $
 */
#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

/*--------------------------------------------------------------------------*/
/**
 * @defgroup test_xsh_flux Test Flux Conservation 
 * @ingroup unit_tests
 *
 */
/*--------------------------------------------------------------------------*/
/**@{*/

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


#include <cpl.h>
#include <xsh_data_instrument.h>
#include <xsh_pfits.h>
#include <xsh_msg.h>
#include <xsh_utils.h>
#include <xsh_utils_image.h>
#include <xsh_data_order.h>
#include <tests.h>
#include <math.h>

#include <xsh_cpl_size.h>

/*---------------------------------------------------------------------------
                            Defines
 ---------------------------------------------------------------------------*/
#define MODULE_ID "XSH_FLUX_CONSERVATION"
/*---------------------------------------------------------------------------
                         Functions prototypes
 ---------------------------------------------------------------------------*/

/*--------------------------------------------------------------------------*/
/**
  @brief    Unit test of XSH_DETECT_CONTINUUM
  @return   0 if tests passed successfully

  Test the Data Reduction Library function XSH_DETECT_CONTINUUM
 */
/*--------------------------------------------------------------------------*/

/*
   1 polynome (same for all orders)
   constant coeff incresed by 20 for each order
*/

static const double poly0_coeff[] = {
  280., 0.3, -0.0025 } ;
static const double step = 40. ;

static int nx = 400, ny = 400 ;
static int starty = 10, endy = 380 ;
static int dimension = 1, degree = 2, width = 10 ;
static int norder = 3 ;
static double exptime = 1. ;
static const char * img_name = "dtc_img.fits" ;
static const char * tbl_name = "dtc_tbl.fits" ;
static const char * dtc_pre_name = "dtc_img_pre.fits" ;

static double 
derivative_x(const double a1, const double a2, const double x){
  
   return a1+2*a2*x;

}

static double 
derivative_y(const double a1, const double a2, const double y){

   return a1+2*a2*y;

}
static void verify_order_table( cpl_frame * result )
{
  const char *hname ;

  cpl_propertylist * header = NULL ;
  double residavg = 0. ;

  xsh_msg( " ====== verify_order_tables" ) ;

  /* Get propertylist of new order_table
     check RESIDAVG, should be < 1 pixel
  */
  check( hname = cpl_frame_get_filename( result ) ) ;
  check( header = cpl_propertylist_load( hname, 0 ) ) ;
  check(xsh_get_property_value (header, QC_ORD_ORDERPOS_RESIDAVG,
				CPL_TYPE_DOUBLE, &residavg ) ) ;

  assure( residavg < 1., CPL_ERROR_ILLEGAL_INPUT, "Error Too large" ) ;
  xsh_msg( "   Orders Detected OK, RESIDAVG = %lf", residavg ) ;

 cleanup:
  xsh_free_propertylist(&header);
    return ;
}

int main(void)
{
  xsh_instrument* instrument = NULL;
  xsh_order_list * list = NULL ;
  cpl_polynomial * poly0 = NULL ;
  cpl_polynomial * poly1 = NULL ;
  cpl_polynomial * poly2 = NULL ;
  cpl_image *image = NULL ;
  cpl_image *bias = NULL;
  cpl_frame * img_frame = NULL, * tbl_frame = NULL, * dtc_frame = NULL,
    * result_frame = NULL ;
  xsh_pre * img_pre = NULL ;
  xsh_clipping_param dcn_clipping ;
  xsh_detect_continuum_param detect_param;

  XSH_INSTRCONFIG *iconfig ;
  cpl_propertylist * img_header ;


  cpl_vector* xprofile=NULL;
  cpl_vector* yprofile=NULL;
  cpl_polynomial* poly_u=NULL;
  cpl_polynomial* poly_v=NULL;
  const int dim=2;
  cpl_size pows[dim];
  cpl_image* warped=NULL;
  cpl_image* wone=NULL;
  cpl_image* one=NULL;
  cpl_image* dXdx=NULL;
  cpl_image* dYdy=NULL;
  cpl_image* dXdy=NULL;
  cpl_image* dYdx=NULL;
  cpl_image* corr=NULL;

  cpl_vector* sup=NULL;
  double* psup=NULL;
  cpl_vector* inf=NULL;
  double* pinf=NULL;
  
  float* pdXdx=NULL;
  float* pdYdy=NULL;
  float* pdXdy=NULL;
  float* pdYdx=NULL;
  float* pcor=NULL;

  int j=0;
  int i=0;
  cpl_size deg=0;
  double f_org=0;
  double f_cor=0;
  double f_add=0;
  double f_bad=0;
  double f_end=0;
  double add=0.1;
  cpl_frame* resid_frame=NULL;

  TESTS_INIT_WORKSPACE(MODULE_ID);
  TESTS_INIT(MODULE_ID);
  xsh_msg("detect_continuum");
  instrument = xsh_instrument_new() ;
  xsh_instrument_set_mode( instrument, XSH_MODE_SLIT ) ;
  xsh_instrument_set_arm( instrument, XSH_ARM_VIS ) ;
  xsh_instrument_set_lamp( instrument, XSH_LAMP_UNDEFINED ) ;
  xsh_instrument_set_recipe_id( instrument, "xsh_orderpos" ) ;

  xsh_msg( "   recipe_id: %s", instrument->recipe_id ) ;

  iconfig = xsh_instrument_get_config( instrument ) ;
  iconfig->orders = norder ;

  xsh_msg( "Create Order List with %d orders", norder ) ;
  check( list = create_order_list( norder, instrument ) ) ;

  xsh_msg( "Create polynomials of degree %d", degree ) ;
  poly0 = cpl_polynomial_new( dimension ) ;
  poly1 = cpl_polynomial_new( dimension ) ;
  poly2 = cpl_polynomial_new( dimension ) ;

  /* Set polynomial coefficients */
  {
    deg = 0 ;

    cpl_polynomial_set_coeff( poly0, &deg, poly0_coeff[deg] ) ;
    cpl_polynomial_set_coeff( poly1, &deg, poly0_coeff[deg] + step ) ;
    cpl_polynomial_set_coeff( poly2, &deg, poly0_coeff[deg] + 2*step ) ;

    for( deg = 1 ; deg<= degree ; deg++ ) {
      cpl_polynomial_set_coeff( poly0, &deg, poly0_coeff[deg] ) ;
      cpl_polynomial_set_coeff( poly1, &deg, poly0_coeff[deg] ) ;
      cpl_polynomial_set_coeff( poly2, &deg, poly0_coeff[deg] ) ;
    }
  }

  xsh_msg( "Add to order list" ) ;
  add_to_order_list( list, 0, 1, poly0, width, starty, endy ) ;
  add_to_order_list( list, 1, 2, poly1, width, starty, endy ) ;
  add_to_order_list( list, 2, 3, poly2, width, starty, endy ) ;

  xsh_order_list_dump( list, "orders.dmp" ) ;

  /* Save image and create Frame accordingly */
  img_frame = cpl_frame_new() ;
  img_header = mkHeader( iconfig, nx, ny, exptime ) ;

  check( image = create_order_image( list, nx, ny ) ) ;
  cpl_image_save( image, img_name, CPL_BPP_IEEE_DOUBLE, img_header,
		  CPL_IO_DEFAULT);

  cpl_frame_set_filename( img_frame, img_name ) ;
  cpl_frame_set_tag( img_frame, "ORDERDEF_VIS_D2" );
  cpl_frame_set_level( img_frame, CPL_FRAME_LEVEL_TEMPORARY);
  cpl_frame_set_group( img_frame, CPL_FRAME_GROUP_RAW ) ;

  /* Save this frame as a PRE frame */
  bias = xsh_test_create_bias_image( "BIAS.fits" ,nx, ny, instrument);
  check( img_pre = xsh_pre_create( img_frame, NULL, bias, instrument,0,CPL_FALSE));
  xsh_msg( "Saving PRE image \"%s\"", dtc_pre_name ) ;
  check_msg( dtc_frame = xsh_pre_save( img_pre, dtc_pre_name, "TEST",1 ),
	     "Cant save pre structure" ) ;
  cpl_frame_set_filename( dtc_frame, dtc_pre_name ) ;
  cpl_frame_set_tag( dtc_frame, "ORDERDEF_UVB_D2" );
  cpl_frame_set_level( dtc_frame, CPL_FRAME_LEVEL_TEMPORARY);
  cpl_frame_set_group( dtc_frame, CPL_FRAME_GROUP_RAW ) ;

  /* Save order table frame */
  check( tbl_frame = xsh_order_list_save( list, instrument, tbl_name,"ORDERDEF_UVB_D2",ny) ) ;

 
  // Now warps the image
  poly_u=cpl_polynomial_new(2);
  poly_v=cpl_polynomial_new(2);


  pows[0]=0;
  pows[1]=0;
  cpl_polynomial_set_coeff(poly_v,pows,poly0_coeff[0]);


  pows[0]=0;
  pows[1]=1;
  cpl_polynomial_set_coeff(poly_v,pows,-poly0_coeff[1]);


  pows[0]=0;
  pows[1]=2;
  cpl_polynomial_set_coeff(poly_v,pows,-poly0_coeff[2]);


  pows[0]=0;
  pows[1]=1;
  cpl_polynomial_set_coeff(poly_u,pows,1.);
  cpl_polynomial_dump(poly_u,stdout);
  cpl_polynomial_dump(poly_v,stdout);

  check(warped=cpl_image_new(nx,ny,CPL_TYPE_FLOAT));
  check(one=cpl_image_new(nx,ny,CPL_TYPE_FLOAT));
  check(cpl_image_add_scalar(one,1.));
  check(xprofile=cpl_vector_new(nx));
  check(yprofile=cpl_vector_new(ny));
  cpl_vector_fill_kernel_profile(xprofile, CPL_KERNEL_DEFAULT,
				 CPL_KERNEL_DEF_WIDTH);
  cpl_vector_fill_kernel_profile(yprofile, CPL_KERNEL_DEFAULT,
				 CPL_KERNEL_DEF_WIDTH);
/*
  check(cpl_image_warp_polynomial(warped,img_pre->data,poly_u,poly_v,
                                  xprofile,CPL_KERNEL_DEF_WIDTH,
                                  yprofile,CPL_KERNEL_DEF_WIDTH));
*/
  xsh_free_vector(&xprofile);
  xsh_free_vector(&yprofile);

  xsh_free_image(&warped);
  check(warped=xsh_warp_image_generic(img_pre->data,CPL_KERNEL_TANH,poly_u,poly_v));
  check(wone=xsh_warp_image_generic(one,CPL_KERNEL_TANH,poly_u,poly_v));
  check(cpl_image_divide(warped,wone));
  xsh_free_image(&wone);

  check(f_org=cpl_image_get_flux(img_pre->data));
  check(f_bad=cpl_image_get_flux_window(warped,1,140,nx,216));

  xsh_msg("Flux PRE frame: %g",f_org);  
  xsh_msg("Flux warp frame: %g",f_bad);

   // Construct derivatives images
  check(dXdx=cpl_image_new(nx,ny,CPL_TYPE_FLOAT));
  check(dYdy=cpl_image_new(nx,ny,CPL_TYPE_FLOAT));
  check(dXdy=cpl_image_new(nx,ny,CPL_TYPE_FLOAT));
  check(dYdx=cpl_image_new(nx,ny,CPL_TYPE_FLOAT));
  check(corr=cpl_image_new(nx,ny,CPL_TYPE_FLOAT));

  check(pdXdx=cpl_image_get_data_float(dXdx));
  check(pdYdy=cpl_image_get_data_float(dYdy));
  check(pdXdy=cpl_image_get_data_float(dXdy));
  check(pdYdx=cpl_image_get_data_float(dYdx));
  check(pcor=cpl_image_get_data_float(corr));

  check(sup=cpl_vector_new(2));
  check(psup=cpl_vector_get_data(sup));
  check(inf=cpl_vector_new(2));
  check(pinf=cpl_vector_get_data(inf));

  //Fill images with derivatives
  for(j=1;j<ny-1;j++) {
     for(i=1;i<nx-1;i++) {
        psup[0]=(double)(i+1);
        psup[1]=(double)j;

        pinf[0]=(double)(i-1);
        pinf[1]=(double)j;

        pdXdx[i+j*nx]=1.;
       /* In our case poly_v=1
        check(pdXdx[i+j*nx]=(cpl_polynomial_eval(poly_u,sup)-
                            cpl_polynomial_eval(poly_u,inf)));

       */

/*
        pdYdx[i+j*nx]=(cpl_polynomial_eval(poly_v,sup)-
                       cpl_polynomial_eval(poly_v,inf));
*/

        pdYdx[i+j*nx]=derivative_x(poly0_coeff[1],poly0_coeff[2],(double)i);

        psup[0]=(double)i;
        psup[1]=(double)(j+1);

        pinf[0]=(double)i;
        pinf[1]=(double)(j-1);

/*
        pdXdy[i+j*nx]=(cpl_polynomial_eval(poly_u,sup)-
                       cpl_polynomial_eval(poly_u,inf));
*/      
        pdXdy[i+j*nx]=0.;

        pdYdy[i+j*nx]=derivative_y(poly0_coeff[1],poly0_coeff[2],(double)j);

   
        //xsh_msg("i=%d j=%d pdYdx=%g pdXdy=%g",i,j,pdYdx[i+j*nx],pdXdy[i+j*nx]);

        /* In our case poly_v=1 
        check(pdYdy[i+j*nx]=(cpl_polynomial_eval(poly_v,sup)-
                             cpl_polynomial_eval(poly_v,inf)));

        */
/*
        pcor[i+j*nx]=0.25*fabs(pdXdx[i+j*nx]*pdYdy[i+j*nx]-
                               pdXdy[i+j*nx]*pdYdx[i+j*nx]);

*/

        pcor[i+j*nx]=fabs(pdXdx[i+j*nx]*pdYdy[i+j*nx]-
                               pdXdy[i+j*nx]*pdYdx[i+j*nx]);
        
     }
  }

  check(cpl_image_save(warped,"order_warped.fits",CPL_BPP_IEEE_FLOAT,NULL,
		       CPL_IO_DEFAULT));
  check(cpl_image_save(wone,"wone.fits",CPL_BPP_IEEE_FLOAT,NULL,
		       CPL_IO_DEFAULT));
  check(cpl_image_save(corr,"corr.fits",CPL_BPP_IEEE_FLOAT,NULL,
		       CPL_IO_DEFAULT));
  cpl_image_add_scalar(corr,add);
  check(cpl_image_divide(warped,corr));
  f_cor=cpl_image_get_flux_window(corr,1,140,nx,216);

  f_add=add*nx*ny;
  xsh_msg("Flux add frame: %g",f_add);
  f_end=cpl_image_get_flux_window(warped,1,140,nx,216);
  

  xsh_msg("Flux corrected frame: %g",f_end);
  xsh_msg("Flux predicted frame: %g",f_end*(f_add+f_cor)/f_cor);

  
  check(cpl_image_save(warped,"corrected.fits",CPL_BPP_IEEE_FLOAT,NULL,
		       CPL_IO_DEFAULT));

  /* Now detect continuum */
  dcn_clipping.sigma = 2.5 ;
  dcn_clipping.niter = 5 ;
  dcn_clipping.frac = 0.7 ;
  //The following value is crucial to the success of the test 
  //we use the min allowed to make the test pass
  dcn_clipping.res_max = 0.4 ;

  detect_param.search_window = 30;
  detect_param.running_window = 7;
  detect_param.fit_window = 10;
  detect_param.poly_degree = 2;
  detect_param.poly_step = 2;
  detect_param.fit_threshold = 1.0;

  xsh_debug_level_set( XSH_DEBUG_LEVEL_LOW ) ;
  //  cpl_msg_set_level( CPL_MSG_DEBUG ) ;
  check (result_frame = xsh_detect_continuum( dtc_frame, tbl_frame, NULL,
					      &detect_param,
					      &dcn_clipping,
					      instrument,&resid_frame) ) ;

  /* Now Verify order tables */
  check( verify_order_table( result_frame ) ) ;
 
  cleanup:
  xsh_free_frame(&resid_frame);
  xsh_free_propertylist(&img_header);
  xsh_free_polynomial(&poly0);
  xsh_free_polynomial(&poly1);
  xsh_free_polynomial(&poly2);
  xsh_free_polynomial(&poly_u);
  xsh_free_polynomial(&poly_v);

  xsh_free_frame(&img_frame);
  xsh_free_frame(&dtc_frame);
  xsh_free_frame(&tbl_frame);
  xsh_free_frame(&result_frame);
  xsh_order_list_free(&list);
  xsh_free_image(&image);
  xsh_free_image(&bias);
  xsh_free_image(&warped);
  xsh_free_image(&wone);
  xsh_free_image(&one);

   xsh_free_image(&dXdx);
   xsh_free_image(&dYdy);
   xsh_free_image(&dXdy);
   xsh_free_image(&dYdx);
   xsh_free_image(&corr);


  xsh_free_vector(&sup);
  xsh_free_vector(&inf);
  xsh_pre_free(&img_pre);
  xsh_free_vector(&xprofile);
  xsh_free_vector(&yprofile);
  xsh_instrument_free(&instrument);
  TESTS_CLEAN_WORKSPACE(MODULE_ID);
   if (cpl_error_get_code() != CPL_ERROR_NONE) {
      xsh_error_dump(CPL_MSG_ERROR);
      return 1;
    } 
    else {
      return 0;
    }

}

/**@}*/
