/* $Id: sinfo_utl_cube_create.c,v 1.6 2013/08/15 11:40:17 amodigli Exp $
 *
 * This file is part of the SINFONI Pipeline
 * Copyright (C) 2002,2003 European Southern Observatory
 *
 * This program 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, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/*
 * $Author: amodigli $
 * $Date: 2013/08/15 11:40:17 $
 * $Revision: 1.6 $
 * $Name: sinfo-2_4_0 $
 */

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

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


/* cpl */
#include <cpl.h>

/* irplib */
#include <irplib_utils.h>

#include <sinfo_tpl_utils.h>
#include <sinfo_pfits.h>
#include <sinfo_tpl_dfs.h>
#include <sinfo_msg.h>
#include <sinfo_error.h>
#include <sinfo_utils_wrappers.h>
#include <sinfo_image_ops.h>
#include <sinfo_wave_calibration.h>
#include <sinfo_pro_save.h>
#include <sinfo_raw_types.h>
#include <sinfo_pro_types.h>
#include <sinfo_coltilt.h>
#include <sinfo_utilities_scired.h>
/*-----------------------------------------------------------------------------
                            Functions prototypes
 ----------------------------------------------------------------------------*/

static int sinfo_utl_cube_create_create(cpl_plugin *) ;
static int sinfo_utl_cube_create_exec(cpl_plugin *) ;
static int sinfo_utl_cube_create_destroy(cpl_plugin *) ;

static int 
sinfo_cube_create(cpl_parameterlist * parameters, cpl_frameset * frames);

/*-----------------------------------------------------------------------------
                            Static variables
 ----------------------------------------------------------------------------*/

static char sinfo_utl_cube_create_description[] =
"This recipe perform cubes creation.\n"
"The input files are:\n"
"A raw frame on tagged as RAW_ON\n"
"[optional] A raw frame off RAW_OFF\n"
"A wavelength map, tagged as WAVE_MAP\n"
"A wavelength map, tagged as WAVE_MAP\n"
"A distortion table, tagged as DISTORTION\n"
"A slitlets position table, tagged as SLIT_POS\n"
"Information on relevant parameters can be found with\n"
"esorex --params sinfo_utl_cube_create\n"
"esorex --help sinfo_utl_cube_create\n"
"\n";

/*-----------------------------------------------------------------------------
                                Functions code
 ----------------------------------------------------------------------------*/
/*---------------------------------------------------------------------------*/
/**
 * @defgroup sinfo_utl_cube_create  Recipe to coadd cubes
 */
/*---------------------------------------------------------------------------*/

/**@{*/
/*---------------------------------------------------------------------------*/
/**
  @brief    Build the list of available plugins, for this module. 
  @param    list    the plugin list
  @return   0 if everything is ok

  This function is exported.
 */
/*---------------------------------------------------------------------------*/
int cpl_plugin_get_info(cpl_pluginlist * list)
{
    cpl_recipe  *   recipe = cpl_calloc(1, sizeof *recipe ) ;
    cpl_plugin  *   plugin = &recipe->interface ;

    cpl_plugin_init(plugin,
                    CPL_PLUGIN_API,
                    SINFONI_BINARY_VERSION,
                    CPL_PLUGIN_TYPE_RECIPE,
                    "sinfo_utl_cube_create",
                    "Generate a cube",
                    sinfo_utl_cube_create_description,
                    "Andrea Modigliani",
                    "Andrea.Modigliani@eso.org",
                    sinfo_get_license(),
                    sinfo_utl_cube_create_create,
                    sinfo_utl_cube_create_exec,
                    sinfo_utl_cube_create_destroy) ;

    cpl_pluginlist_append(list, plugin) ;
    
    return 0;
}

/*---------------------------------------------------------------------------*/
/**
  @brief    Setup the recipe options    
  @param    plugin  the plugin
  @return   0 if everything is ok

  Create the recipe instance and make it available to the application using the 
  interface. 
 */
/*---------------------------------------------------------------------------*/
static int sinfo_utl_cube_create_create(cpl_plugin * plugin)
{
    cpl_recipe      * recipe ;
    cpl_parameter   * p ;
    cpl_parameterlist   * list ;

    /* Get the recipe out of the plugin */
    if (cpl_plugin_get_type(plugin) == CPL_PLUGIN_TYPE_RECIPE) 
        recipe = (cpl_recipe *)plugin ;
    else return -1 ;
    cpl_error_reset();
    irplib_reset();

    /* Create the parameters list in the cpl_recipe object */
    recipe->parameters = cpl_parameterlist_new() ; 

    list=recipe->parameters;
  /* jitter mode indicator: yes: Auto-Jitter, no: user defined jitter
     The next three parameters are only used if jitterInd is set to yes, 
     that means in auto-jittering mode!
  */
  p = cpl_parameter_new_value("sinfoni.objnod.jitter_index",
                  CPL_TYPE_BOOL,
                              "jitter mode indicator: "
                              "TRUE: Auto-Jitter, "
                              "FALSE: user defined jitter. "
                              "The size_x size_y kernel_type parameters "
                              "are only used if jitterInd is set to yes, "
                              "that means in auto-jittering mode.",
                              "sinfoni.objnod",
                              TRUE);

  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI,"objnod-jit_ind");
  cpl_parameterlist_append(list, p);


  /* Kernel Type */
  /* the name of the interpolation kernel to shift the single cubes  to the 
     correct places inside the big combined cube. That you want to generate 
     using the eclipse routine sinfo_generate_interpolation_kernel()
     Supported kernels are:

                                     NULL:      default kernel, currently tanh
                                     default: dito
                                     tanh:    Hyperbolic tangent
                                     sinc2:   Square sinc
                                     lanczos: Lanczos2 kernel
                                     hamming: Hamming kernel
                                     hann:    Hann kernel
  */
  p = cpl_parameter_new_enum("sinfoni.objnod.kernel_type",
                  CPL_TYPE_STRING,
                              "Kernel Type:"
                              "the name of the interpolation kernel to shift "
                              "the single cubes  to the correct places inside "
                             "the big combined cube",
                              "sinfoni.objnod",
                              "tanh",
                               7,
                              "NULL","default","tanh","sinc2",
                              "lanczos","hamming","hann");

  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI,"objnod-kernel_typ");
  cpl_parameterlist_append(list, p);

  /*Resampling */
 /* number of coefficients for the polynomial interpolation */
  p = cpl_parameter_new_value("sinfoni.objnod.n_coeffs",
                  CPL_TYPE_INT,
                              "number of coefficients for the polynomial "
                              "interpolation ",
                              "sinfoni.objnod",
                               3);

  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI,"objnod-no_coeffs");
  cpl_parameterlist_append(list, p);

  /* Cube Creation */
  /*indicates if the slitlet distances are determined by a north-south-test 
     (yes) or slitlet edge fits (no)
  */
  p = cpl_parameter_new_value("sinfoni.objnod.nord_south_index",
                  CPL_TYPE_BOOL,
                              "Nord South Index Switch: "
                              "indicates if the slitlet distances are "
                              "determined by a north-south-test (TRUE) "
                              "or slitlet edge fits (FALSE)",
                              "sinfoni.objnod",
                              TRUE);


  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI,"objnod-ns_ind");
  cpl_parameterlist_append(list, p);


  /* Cube Creation */
  /*indicates if the slitlet distances are determined by a north-south-test 
     (yes) or slitlet edge fits (no)
  */
  p = cpl_parameter_new_value("sinfoni.objnod.flux_cor",
                              CPL_TYPE_BOOL,
                              "Flux correction: ",
                              "sinfoni.objnod",
                              FALSE);


  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI,"objnod-flux_cor");
  cpl_parameterlist_append(list, p);




 /* Fine tuning */
  /* Method */
  p = cpl_parameter_new_enum("sinfoni.objnod.fine_tuning_method",
                  CPL_TYPE_STRING,
                              "Fine Tuning Method: "
                              "indicator for the shifting method to use "
                              "(P: polynomial interpolation, "
                 /* " F: FFT, " */
                              " S: cubic spline interpolation)",
                              "sinfoni.objnod",
                              "P",
                               2,
                              "P","S");

  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI,"objnod-fine_tune_mtd");
  cpl_parameterlist_append(list, p);

  p = cpl_parameter_new_value("sinfoni.objnod.order",
                  CPL_TYPE_INT,
                              "Fine Tuning polynomial order: " 
                              "order of the polynomial if the polynomial "
                              "interpolation shifting method is used.",
                              "sinfoni.objnod",
                              2);

  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI,"objnod-order");
  cpl_parameterlist_append(list, p);

    /* Fill the parameters list */
    /* --stropt */

  
    /* Return */
    return 0;
}

/*---------------------------------------------------------------------------*/
/**
  @brief    Execute the plugin instance given by the interface
  @param    plugin  the plugin
  @return   0 if everything is ok
 */
/*---------------------------------------------------------------------------*/
static int sinfo_utl_cube_create_exec(cpl_plugin * plugin)
{
    cpl_recipe  *   recipe ;
    int result=0;    
    cpl_errorstate initial_errorstate = cpl_errorstate_get();

    /* Get the recipe out of the plugin */
    if (cpl_plugin_get_type(plugin) == CPL_PLUGIN_TYPE_RECIPE) 
        recipe = (cpl_recipe *)plugin ;
    else return -1 ;
       sinfo_msg("Welcome to SINFONI Pipeline release %d.%d.%d",
        SINFONI_MAJOR_VERSION,SINFONI_MINOR_VERSION,SINFONI_MICRO_VERSION);

    result=sinfo_cube_create(recipe->parameters, recipe->frames) ;
    if (!cpl_errorstate_is_equal(initial_errorstate)) {                      
        /* Dump the error history since recipe execution start.                
           At this point the recipe cannot recover from the error */           
        cpl_errorstate_dump(initial_errorstate, CPL_FALSE, NULL);              
    } 
    return result;
}

/*---------------------------------------------------------------------------*/
/**
  @brief    Destroy what has been created by the 'create' function
  @param    plugin  the plugin
  @return   0 if everything is ok
 */
/*---------------------------------------------------------------------------*/
static int sinfo_utl_cube_create_destroy(cpl_plugin * plugin)
{
    cpl_recipe  *   recipe ;
    
    /* Get the recipe out of the plugin */
    if (cpl_plugin_get_type(plugin) == CPL_PLUGIN_TYPE_RECIPE) 
        recipe = (cpl_recipe *)plugin ;
    else return -1 ;

    cpl_parameterlist_delete(recipe->parameters) ; 
    return 0 ;
}


/*---------------------------------------------------------------------------*/
/**
  @brief    Create the cube
  @param    parameters recipe input parameters
  @param    frames recipe input frames
  @return   0 if everything is ok
 */
/*---------------------------------------------------------------------------*/
static int 
sinfo_cube_create(cpl_parameterlist * parameters, cpl_frameset * frames)
{

   cpl_frame* frm_raw_on=NULL;
   cpl_frame* frm_raw_off=NULL;
   cpl_frame* frm_wav_map=NULL;
   cpl_frame* frm_mflat=NULL;
   cpl_frame* frm_distortion=NULL;
   cpl_frame* frm_slit_pos=NULL;
   cpl_frame* frm_slitlets_distance=NULL;

   cpl_image* ima_raw_on=NULL;
   cpl_image* ima_raw_off=NULL;
   cpl_image* ima_wav_map=NULL;
   cpl_image* ima_mflat=NULL;
   cpl_image* ima_mflat_dist=NULL;
   cpl_image* ima_obj_mflat=NULL;
   cpl_image* ima_obj_dist=NULL;
   cpl_image* ima_obj_res=NULL;
   cpl_image* ima_wav_res=NULL;
   cpl_image* ima_wav_dif=NULL;

   cpl_imagelist* cube=NULL;
   cpl_imagelist* outcube=NULL;
   cpl_imagelist* outcube2=NULL;

   //cpl_table* tab_distortion=NULL;
   //cpl_table* tab_slit_pos=NULL;

   float mi=0;
   float ma=0;
   double dis=0 ;
   double cwav=0 ;
   int cpix=0 ;
   int nx=0;
   int ny=0;
    float fcol=0;
   float* pd=NULL;
   float* pw=NULL;
   int i=0;
   int j=0;
   float** slit_edges=NULL;
   float* distances=NULL;
   float* correct_dist=NULL;
   char kernel[80];
   char poly_file[80];
   char pos_list[80];
   char dist_list[80];


   int ns_index=0;
   int n_coeffs=0;
   int nrows=0;
   cpl_parameter* p=NULL;
   int nslits=32;
   int flux_cor=0;
   const char* fine_tuning_method=NULL;
   int fine_tuning_pol_order=0;
   float center_x=0;
   float center_y=0;

   /* Identify the RAW and CALIB frames in the input frameset */
    check(sinfo_dfs_set_groups(frames),
         "Cannot identify RAW and CALIB frames") ;

   check_nomsg(p=cpl_parameterlist_find(parameters,
                                        "sinfoni.objnod.flux_cor"));
   check_nomsg(flux_cor=cpl_parameter_get_bool(p));

   check_nomsg(p=cpl_parameterlist_find(parameters,
                                        "sinfoni.objnod.kernel_type"));
   check_nomsg(strcpy(kernel,cpl_parameter_get_string(p)));

   check_nomsg(p=cpl_parameterlist_find(parameters,"sinfoni.objnod.n_coeffs"));
   check_nomsg(n_coeffs=cpl_parameter_get_int(p));


   check_nomsg(p=cpl_parameterlist_find(parameters,
                                        "sinfoni.objnod.nord_south_index"));
   check_nomsg(ns_index=cpl_parameter_get_bool(p));


   check_nomsg(p=cpl_parameterlist_find(parameters,
                                        "sinfoni.objnod.fine_tuning_method"));
   check_nomsg(fine_tuning_method=cpl_parameter_get_string(p));

   check_nomsg(p=cpl_parameterlist_find(parameters,
                                        "sinfoni.objnod.order"));
   check_nomsg(fine_tuning_pol_order=cpl_parameter_get_int(p));

 
   cknull(frm_raw_on=cpl_frameset_find(frames,RAW_ON),
          "Missing required input %s",RAW_ON);
   cknull(frm_raw_off=cpl_frameset_find(frames,RAW_OFF),
          "Missing required input %s",RAW_OFF);
   cknull(frm_wav_map=cpl_frameset_find(frames,PRO_WAVE_MAP),
                "Missing required input %s",PRO_WAVE_MAP);
   cknull(frm_mflat=cpl_frameset_find(frames,PRO_MASTER_FLAT_LAMP),
                "Missing required input %s",PRO_WAVE_MAP);

   check_nomsg(frm_distortion=cpl_frameset_find(frames,PRO_DISTORTION));
   check_nomsg(frm_slit_pos=cpl_frameset_find(frames,PRO_SLIT_POS));
   check_nomsg(frm_slitlets_distance=
               cpl_frameset_find(frames,PRO_SLITLETS_DISTANCE));

   strcpy(pos_list,cpl_frame_get_filename(frm_slit_pos));
   strcpy(dist_list,cpl_frame_get_filename(frm_slitlets_distance));
   strcpy(poly_file,cpl_frame_get_filename(frm_distortion));

 
   check_nomsg(ima_raw_on=cpl_image_load(cpl_frame_get_filename(frm_raw_on),
                                         CPL_TYPE_FLOAT,0,0));
 
   check_nomsg(ima_raw_off=cpl_image_load(cpl_frame_get_filename(frm_raw_off),
                                          CPL_TYPE_FLOAT,0,0));

 
   check_nomsg(ima_wav_map=cpl_image_load(cpl_frame_get_filename(frm_wav_map),
                                          CPL_TYPE_FLOAT,0,0));
 
   check_nomsg(ima_mflat=cpl_image_load(cpl_frame_get_filename(frm_mflat),
                                          CPL_TYPE_FLOAT,0,0));

 

   check_nomsg(cpl_image_subtract(ima_raw_on,ima_raw_off));
   cpl_image_save(ima_raw_on,"ima_sub.fits", CPL_BPP_IEEE_FLOAT, 
                  NULL,CPL_IO_DEFAULT);

   cknull_nomsg(ima_obj_mflat=sinfo_new_div_images_robust(ima_raw_on,
                                                         ima_mflat));
   cpl_image_save(ima_obj_mflat,"ima_obj_mflat.fits", CPL_BPP_IEEE_FLOAT, 
                  NULL,CPL_IO_DEFAULT);
 

//The following is not needed
   cknull_nomsg(ima_mflat_dist=sinfo_new_image_warp_fits(ima_mflat,kernel,
                                                         poly_file));

   cpl_image_save(ima_mflat_dist,"ima_mflat_dist.fits", CPL_BPP_IEEE_FLOAT, 
                  NULL,CPL_IO_DEFAULT);

   cknull_nomsg(ima_obj_dist=sinfo_new_image_warp_fits(ima_obj_mflat, 
                                                       kernel, 
                                                       poly_file));

   cpl_image_save(ima_obj_dist,"ima_obj_dist.fits", CPL_BPP_IEEE_FLOAT, 
                  NULL,CPL_IO_DEFAULT);
 
   cknull(ima_obj_res = sinfo_new_defined_resampling(ima_obj_dist, 
						    ima_wav_map, 
						    n_coeffs,
						    &nrows,
						    &dis,
						    &mi,
						    &ma,
						    &cwav,
						    &cpix),
	     " sinfo_definedResampling() failed" ) ;



  cpl_image_save(ima_obj_res,"ima_obj_res.fits", CPL_BPP_IEEE_FLOAT, 
                  NULL,CPL_IO_DEFAULT);


    //We create an image with the derivatives
  nx=cpl_image_get_size_x(ima_wav_map);
  ny=cpl_image_get_size_y(ima_wav_map);

      check_nomsg(ima_wav_dif=cpl_image_new(nx,ny,CPL_TYPE_FLOAT));
      pw=cpl_image_get_data(ima_wav_map);
      pd=cpl_image_get_data(ima_wav_dif);

    
      for(i=1;i<nx-1;i++) {
	for(j=1;j<ny-1;j++) {
	  if(!isnan(pd[nx*j+i])) {
	    pd[nx*j+i]=2.0*dis/(pw[nx*(j+1)+i]-pw[nx*(j-1)+i]);
	  }
	}
	if(!isnan(pd[i])) {
	  pd[i]=dis/(pw[nx+i]-pw[i]);
	}
	if(!isnan(pd[nx*(ny-1)+i])) {
	  pd[nx*(ny-1)+i]=dis/(pw[nx*(ny-1)+i]-pw[nx*(ny-2)+i]);
	}
      }
    

      //cpl_image_save(ima_wav_dif,"diff.fits", 
      //CPL_BPP_IEEE_FLOAT,NULL,CPL_IO_DEFAULT);



      cknull(ima_wav_res = sinfo_new_defined_resampling(ima_wav_dif, 
						    ima_wav_map, 
						    n_coeffs,
						    &nrows,
						    &dis,
						    &mi,
						    &ma,
						    &cwav,
						    &cpix),
	     " sinfo_definedResampling() failed" ) ;

      if(flux_cor) {
	sinfo_msg("Apply flux correction");
	cpl_image_divide(ima_obj_res,ima_wav_res);
      }


      //cpl_image_save(ima_wav_res,"res_diff.fits", 
      //CPL_BPP_IEEE_FLOAT,NULL,CPL_IO_DEFAULT);



  cpl_image_save(ima_wav_res,"ima_wav_res.fits", CPL_BPP_IEEE_FLOAT, 
                  NULL,CPL_IO_DEFAULT);


      //To rescale in flux we divide the resampled image and 
      //the resampled derivatives. At this point ima_obj_res should have same 
      //flux as input image (im.diff)

      //ima_obj_res=cpl_image_duplicate(ima_wav_res);
      //sinfo_free_image(&ima_wav_res);
      if(flux_cor) {
	sinfo_msg("Apply flux correction");
	cpl_image_divide(ima_obj_res,ima_wav_res);
      }




      /*---select north-south-test or fitting of slitlet edges--*/
      if (ns_index == 0) {
	sinfo_msg("cfg->northsouthInd == 0");
	cknull(slit_edges=sinfo_read_slitlets_edges(nslits,pos_list),
	       "error reading slitlets edges");
      } else {
	sinfo_msg("cfg->northsouthInd != 0");
	cknull(distances = sinfo_read_distances(nslits,dist_list),
	       "error reading distances");
      }
 
      cknull(correct_dist = (float*) cpl_calloc(nslits, sizeof (float)),
	     " could not allocate memory!") ;

      sinfo_msg("Create cube object");
      if (ns_index ==0 ) {

        cknull(cube = sinfo_new_make_cube_spi(ima_obj_res,slit_edges,
					      correct_dist),
	       "could not construct data cube!") ;

      }  else {
	cknull(cube = sinfo_new_make_cube_dist(ima_obj_res,fcol,distances,
					       correct_dist),
	       "could not construct a data cube!") ;
      }
      sinfo_free_image(&ima_obj_res);


     cknull(outcube2=sinfo_new_fine_tune(cube,
					  correct_dist,
					  fine_tuning_method,
					  fine_tuning_pol_order,
					  nslits),
	     " could not fine tune the data cube") ;

      sinfo_msg("Stretch output cube along Y direction");
      cknull(outcube = sinfo_new_bin_cube(outcube2,1,2,0,63,0,63),
	     "Error binning cube");
      sinfo_free_imagelist(&cube);

      ck0(sinfo_pro_save_ims(outcube,frames,frames,"out_cube.fits","CUBE",NULL,
                             "sinfo_utl_cube_create",parameters),
          "cannot save cube %s", "out_cube.fits");


      check_nomsg(center_x = cpl_image_get_size_x(
						  cpl_imagelist_get(outcube,0))/2.+0.5) ;
      check_nomsg(center_y = cpl_image_get_size_y(
						  cpl_imagelist_get(outcube,0))/2.+0.5 );

     sinfo_new_set_wcs_cube(outcube,"out_cube.fits", cwav, dis, 
			     cpix, center_x, center_y);


  cleanup:      

    if (ns_index ==0 ) {
      if(slit_edges != NULL) {
	sinfo_new_destroy_2Dfloatarray(&slit_edges,nslits);
      }
    } else {
      if (distances != NULL ) {
	sinfo_new_destroy_array(&distances);
      }
    }



   
      sinfo_free_float(&correct_dist) ;
      sinfo_free_imagelist(&cube);
      sinfo_free_imagelist(&outcube);
      sinfo_free_imagelist(&outcube2);
      sinfo_free_image(&ima_raw_on);
      sinfo_free_image(&ima_raw_off);
      sinfo_free_image(&ima_wav_map);
      sinfo_free_image(&ima_mflat);
      sinfo_free_image(&ima_mflat_dist);
      sinfo_free_image(&ima_obj_res);
      sinfo_free_image(&ima_obj_mflat);
      sinfo_free_image(&ima_obj_dist);
      sinfo_free_image(&ima_obj_res);
      sinfo_free_image(&ima_wav_res);
      sinfo_free_image(&ima_wav_dif);
    
      return 0 ;

}


/**@}*/
