/* $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: not supported by cvs2svn $
 */

#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"
                "\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 ;

}


/**@}*/
