/*                                                                            *
 *   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: 2013-03-18 11:40:35 $
 * $Revision: 1.98 $
*/

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

/*----------------------------------------------------------------------------*/
/**
 * @defgroup xsh_orderpos   xsh_orderpos
 * @ingroup recipes
 *
 * This recipe calculates the position of the orders from a flat illumination
 * See man-page for details.
 */
/*----------------------------------------------------------------------------*/
/**@{*/

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


/* DRL steps */
/* Error handling */
#include <xsh_error.h>
/* Utility fonctions */
#include <xsh_pfits_qc.h>
#include <xsh_utils.h>
#include <xsh_msg.h>
/* DFS functions */
#include <xsh_dfs.h>
/* DRL functions */
#include <xsh_drl.h>
#include <xsh_drl_check.h>
#include <xsh_data_instrument.h>
#include <xsh_model_arm_constants.h>

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

/*-----------------------------------------------------------------------------
  Defines
  ----------------------------------------------------------------------------*/

#define RECIPE_ID "xsh_orderpos"
#define RECIPE_AUTHOR "L.Guglielmi,R.Haigron,P.Goldoni,F.Royer, A. Modigliani"
#define RECIPE_CONTACT "amodigli@eso.org"

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

/*
 *   Plugin initalization, execute and cleanup handlers
 */

static int xsh_orderpos_create(cpl_plugin *);
static int xsh_orderpos_exec(cpl_plugin *);
static int xsh_orderpos_destroy(cpl_plugin *);

/* The actual executor function */
static void xsh_orderpos(cpl_parameterlist *, cpl_frameset *);

/*-----------------------------------------------------------------------------
  Static variables
  ----------------------------------------------------------------------------*/
static char xsh_orderpos_description_short[] =
"Create the orders centre traces table file";

static char xsh_orderpos_description[] =
"This recipe creates the orders centre traces table.\n\
Input Frames for UVB and VIS:\n\
  Raw file (Tag = ORDERDEF_arm_D2)\n\
  Master Dark (Tag = MASTER_DARK_arm)\n\
  Master Bias (Tag = MASTER_BIAS_arm)\n\
Input Frames for NIR:\n\
  Raw file ON(Tag = ORDERDEF_NIR_ON)\n\
  Raw file OFF(Tag = ORDERDEF_NIR_OFF)\n\
Input Frames for all arms\n\
  Guess order table (Tag = ORDER_TAB_GUESS_arm)\n\
  Spectral format table (Tag = SPECTRAL_FORMAT_TAB_arm)\n\
  - [OPTIONAL] A map of non linear bad pixels (Format=QUP, Tag = BP_MAP_NL_arm)\n\
  - [OPTIONAL] A map of reference bad pixels (Format = QUP,RAW, Tag = BP_MAP_RP_arm)\n\
Prepare PRE structures.\n\
For NIR, subtract NIR-OFF from NIR-ON.\n\
For UVB and NIR, Substract the master Bias and master dark.\n\
Detect Orders and calculate the order table.\n\
The final products are:\n\
   An updated Order Table, PRO.CATG=ORDER_TABLE_CENTR_arm.\n\
   A order trace residuals Table, PRO.CATG=ORDERPOS_RESID_TAB_arm.\n\
   The order pos frame bias subtracted, PRO.CATG=ORDERDEF_ON_arm.\n";


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

   Create the recipe instance and make it available to the application using the
   interface. This function is exported.
*/
/*----------------------------------------------------------------------------*/

int cpl_plugin_get_info(cpl_pluginlist *list) {
  cpl_recipe *recipe = NULL;
  cpl_plugin *plugin = NULL;

  recipe = cpl_calloc(1, sizeof(*recipe));
  if ( recipe == NULL ){
    return -1;
  }

  plugin = &recipe->interface ;

  cpl_plugin_init(plugin,
                  CPL_PLUGIN_API,                    /* Plugin API */
                  XSH_BINARY_VERSION,             /* Plugin version */
                  CPL_PLUGIN_TYPE_RECIPE,            /* Plugin type */
                  RECIPE_ID,                         /* Plugin name */
                  xsh_orderpos_description_short,       /* Short help */
                  xsh_orderpos_description,             /* Detailed help */
                  RECIPE_AUTHOR,                     /* Author name */
                  RECIPE_CONTACT,                    /* Contact address */
                  xsh_get_license(),                 /* Copyright */
                  xsh_orderpos_create,
                  xsh_orderpos_exec,
                  xsh_orderpos_destroy);

  cpl_pluginlist_append(list, plugin);

  return (cpl_error_get_code() != CPL_ERROR_NONE);
}

/*----------------------------------------------------------------------------*/
/**
   @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 xsh_orderpos_create(cpl_plugin *plugin){
  cpl_recipe *recipe = NULL;
  xsh_detect_continuum_param param = { 5, 0, 5,
				       DETECT_CONTINUUM_POLYNOMIAL_DEGREE,
				       1, 0.,
				       /* fmtchk*/ 20, 50, 140., 2., 0 } ;

  /* Reset library state */
  xsh_init();

  /* Check input */
  assure( plugin != NULL, CPL_ERROR_NULL_INPUT, "Null plugin");

  /* Get the recipe out of the plugin */
  assure( cpl_plugin_get_type(plugin) == CPL_PLUGIN_TYPE_RECIPE,
          CPL_ERROR_TYPE_MISMATCH,
          "Plugin is not a recipe");

  recipe = (cpl_recipe *)plugin;

  /* Create the parameter list in the cpl_recipe object */
  recipe->parameters = cpl_parameterlist_new();
  assure( recipe->parameters != NULL,
          CPL_ERROR_ILLEGAL_OUTPUT,
          "Memory allocation failed!");

  /* Set generic parameters (common to all recipes) */
  check( xsh_parameters_generic( RECIPE_ID, recipe->parameters ) ) ;
  xsh_parameters_decode_bp(RECIPE_ID,recipe->parameters,-1);
  check( xsh_parameters_pre_overscan( RECIPE_ID, recipe->parameters ) ) ;

  check(xsh_parameters_detect_continuum_create(RECIPE_ID,
					       recipe->parameters,
					       param));
  check( xsh_parameters_clipping_dcn_create( RECIPE_ID,
					     recipe->parameters ) ) ;
 cleanup:
  if ( cpl_error_get_code() != CPL_ERROR_NONE ){
    xsh_error_dump(CPL_MSG_ERROR);
    return 1;
  }
  else {
    return 0;
  }
}

/*----------------------------------------------------------------------------*/
/**
   @brief    Execute the plugin instance given by the interface
   @param    plugin  the plugin
   @return   0 if everything is ok
*/
/*----------------------------------------------------------------------------*/

static int xsh_orderpos_exec(cpl_plugin *plugin) {
  cpl_recipe *recipe = NULL;

  /* Check parameter */
  assure( plugin != NULL, CPL_ERROR_NULL_INPUT, "Null plugin" );

  /* Get the recipe out of the plugin */
  assure( cpl_plugin_get_type(plugin) == CPL_PLUGIN_TYPE_RECIPE,
          CPL_ERROR_TYPE_MISMATCH, "Plugin is not a recipe");

  recipe = (cpl_recipe *)plugin;

  /* Check recipe */
  xsh_orderpos(recipe->parameters, recipe->frames);
  

 cleanup:
  if ( cpl_error_get_code() != CPL_ERROR_NONE ) {
    xsh_error_dump(CPL_MSG_ERROR);
    /* RESTORE the error context */
    cpl_error_reset();
    return 1;
  }
  else {
    return 0;
  }
}

/*----------------------------------------------------------------------------*/
/**
   @brief    Destroy what has been created by the 'create' function
   @param    plugin  the plugin
   @return   0 if everything is ok
*/
/*----------------------------------------------------------------------------*/
static int xsh_orderpos_destroy(cpl_plugin *plugin)
{
  cpl_recipe *recipe = NULL;

  /* Check parameter */
  assure( plugin != NULL, CPL_ERROR_NULL_INPUT, "Null plugin" );

  /* Get the recipe out of the plugin */
  assure( cpl_plugin_get_type(plugin) == CPL_PLUGIN_TYPE_RECIPE,
	  CPL_ERROR_TYPE_MISMATCH, "Plugin is not a recipe");

  recipe = (cpl_recipe *)plugin;

  xsh_free_parameterlist(&recipe->parameters);

 cleanup:
  if (cpl_error_get_code() != CPL_ERROR_NONE)
    {
      return 1;
    }
  else
    {
      return 0;
    }
}

/*----------------------------------------------------------------------------*/
/**
   @brief    Interpret the command line options and execute the data processing
   @param    parameters     the parameters list
   @param    frameset   the frames list

   In case of failure the cpl_error_code is set.
*/
/*----------------------------------------------------------------------------*/
static void xsh_orderpos(cpl_parameterlist* parameters,
			 cpl_frameset* frameset)
{
  const char* recipe_tags[1] = {XSH_ORDERDEF};
  int recipe_tags_size = 1;

  /* Intermediate frames */  
  cpl_frameset* raws = NULL;
  cpl_frameset* calib = NULL;
  cpl_frame* bpmap = NULL;
  cpl_frame* master_bias = NULL;
  cpl_frame* master_dark = NULL;
  cpl_frame * order_tab_guess = NULL ;
  cpl_frame * orderframe = NULL ;
  cpl_frame * spectralformat_frame = NULL ;
  cpl_frame * rmbias = NULL ;
  cpl_frame * intFrame = NULL ;	/**< The frame where to find orders */
  cpl_frame * nir_on = NULL ;	/**< Do NOT deallocated this pointer ! */
  cpl_frame * nir_off = NULL ;	/**< Do NOT deallocated this pointer ! */

  /* Product frames */
  cpl_frame * resFrame = NULL ;

  /* Other */
  xsh_detect_continuum_param * detect_param = NULL ;
  xsh_instrument* instrument = NULL;
  xsh_clipping_param * dcn_clipping_param = NULL;  
  cpl_frame* resid_tab=NULL;
  char tag[128];
  char fname[128] ;
  int pre_overscan_corr=0;

  /**************************************************************************/
  /* DFS management */
  /**************************************************************************/
  check( xsh_begin( frameset, parameters, &instrument, &raws, &calib,
                    recipe_tags, recipe_tags_size,
                    RECIPE_ID, XSH_BINARY_VERSION,
                    xsh_orderpos_description_short ) ) ;

  /* Why */
  check( xsh_instrument_update_lamp( instrument, XSH_LAMP_UNDEFINED));

  xsh_recipe_params_check(parameters,instrument,RECIPE_ID);
  /**************************************************************************/
  /* Recipe frames */
  /**************************************************************************/
  check( spectralformat_frame = xsh_find_frame_with_tag( calib,
    XSH_SPECTRAL_FORMAT, instrument)); 

  check(bpmap=xsh_check_load_master_bpmap(calib,instrument,RECIPE_ID));
  if ( xsh_instrument_get_arm(instrument) == XSH_ARM_NIR ) {
    check( nir_on = xsh_find_raw_orderdef_nir ( raws ) ) ;
    check( nir_off = xsh_find_raw_orderdef_nir_off ( raws ) ) ;
  }
  else {
    check( orderframe = xsh_find_raw_orderdef_vis_uvb( raws ) ) ;
    if((master_bias = xsh_find_frame_with_tag(calib,XSH_MASTER_BIAS,
					      instrument)) == NULL) {

      xsh_msg_warning("Frame %s not provided",XSH_MASTER_BIAS);
      xsh_error_reset();
    }

    if((master_dark = xsh_find_frame_with_tag(calib,XSH_MASTER_DARK,
					      instrument)) == NULL){
      xsh_msg_warning("Frame %s not provided",XSH_MASTER_DARK);
      xsh_error_reset();
    }
  }

  if(NULL== (order_tab_guess = xsh_find_frame_with_tag(calib,
						       XSH_ORDER_TAB_GUESS,
						       instrument)) ) {
    xsh_msg_error("you must provide an input %s_%s table",XSH_ORDER_TAB_GUESS,
		  xsh_instrument_arm_tostring(instrument));
    goto cleanup;
  }


  check( xsh_instrument_update_from_spectralformat( instrument,
    spectralformat_frame));

  /**************************************************************************/
  /* Recipe parameters */
  /**************************************************************************/
  check( pre_overscan_corr = xsh_parameters_get_int( parameters, RECIPE_ID,
						     "pre-overscan-corr"));

  check(detect_param=xsh_parameters_detect_continuum_get( RECIPE_ID,
							  parameters)) ;
 

  xsh_msg_dbg_low("Search Window: %d, Running Window: %d, Fit Window: %d",
	  detect_param->search_window, detect_param->running_window,
	  detect_param->fit_window ) ;
  xsh_msg_dbg_low( "Polynomial degree: %d, Step: %d", detect_param->poly_degree,
	   detect_param->poly_step ) ;

  check( dcn_clipping_param = xsh_parameters_clipping_dcn_get( RECIPE_ID,
							       parameters ));

  /* not needed as Daniel says current defaults are robust for any arm
  check(xsh_params_set_defaults(parameters,instrument,detect_param,
				dcn_clipping_param));
				*/


  /**************************************************************************/
  /* Recipe code */
  /**************************************************************************/
  xsh_msg( "Working on Arm %s", xsh_instrument_arm_tostring(instrument ) ) ;

  check(xsh_prepare(raws, bpmap, master_bias, XSH_ORDERDEF, instrument,pre_overscan_corr,CPL_TRUE));
  /*
    from raws frameset one should use only the single (or the 2 in case of
    NIR) flat frames "ORDERDEF_arm_D2"
  */

  if ( xsh_instrument_get_arm(instrument) == XSH_ARM_NIR ) {
   
    /* subtract Off frame */
    check(intFrame=xsh_pre_frame_subtract( nir_on, nir_off,
					   "ON-OFF_NIR.fits",instrument,1 ) ) ;
  }
  else {
    /* UVB and VIS */
    if(master_bias!= NULL) {
    /* correct bias level */
    xsh_msg( "Substract bias" ) ;
    check(rmbias = xsh_subtract_bias( orderframe, master_bias, instrument,"ORDERDEF_",pre_overscan_corr,0));
    } else {
      rmbias=cpl_frame_duplicate(orderframe);
    }

    /* correct dark level */
    if(master_dark!= NULL) {
    xsh_msg( "Substract dark" ) ;
    sprintf( fname, "ORDERPOS_%s_DARK.fits",
	     xsh_instrument_arm_tostring(instrument ) ) ;
    check(intFrame = xsh_subtract_dark(rmbias, master_dark,
      fname, instrument));
    } else {
      intFrame=cpl_frame_duplicate(rmbias);
    }
  }
  /* check on binning: operations takes orderdef frames also in bin mode but the pipeline supports only
   * unbinned orderdef frame input */
  if ( xsh_instrument_get_arm(instrument) != XSH_ARM_NIR){
     check(xsh_check_input_is_unbinned(intFrame));
  }

  /*
    TODO:
      Add an option "--recover" using a function "xsh_find_order" instead
      of detect_continuum to create an order table.
      xsh_find_order does NOT use any order table, but seraches for
      maximum along Y for x = nx/2, x = nx/2 - step, ... x = nx/2 + step ...
  */
  /* Detect Continuum */
  xsh_msg("Calling detect continuum" ) ;
  /* the following generate a leak */
  check_msg( resFrame = xsh_detect_continuum( intFrame, order_tab_guess,
					      spectralformat_frame,
					      detect_param,
					      dcn_clipping_param,
					      instrument, &resid_tab ),
	     "Error in xsh_detect_continuum, try to increase detectcontinuum-fit-win-hsize or detectcontinuum-ordertab-deg-y or detectcontinuum-clip-sigma or detectcontinuum-search-win-hsize" ) ;
  /* monitor input frame flux */
  check(xsh_monitor_flux( intFrame, resFrame, instrument));
  /**************************************************************************/
  /* Products */
  /**************************************************************************/
  xsh_msg( "Save Order Table product" ) ;
  check(xsh_add_product_table( resFrame, frameset, parameters, RECIPE_ID,
                                     instrument,NULL));

  check(xsh_add_product_table(resid_tab, frameset, parameters, RECIPE_ID,
                                    instrument,NULL));

  if ( xsh_instrument_get_arm(instrument) == XSH_ARM_NIR ) {
    sprintf(tag,"ORDERDEF_ON");
    check(xsh_add_product_image(intFrame,frameset,parameters,RECIPE_ID,instrument,tag));
  } else {
    sprintf(tag,"ORDERDEF_ON");
    check(xsh_add_product_image(rmbias,frameset,parameters,RECIPE_ID,instrument,tag));
  }
 cleanup:
  xsh_end( RECIPE_ID, frameset, parameters );
  XSH_FREE( dcn_clipping_param );
  XSH_FREE( detect_param );
  xsh_instrument_free(&instrument );
  xsh_free_frameset(&raws);
  xsh_free_frameset(&calib);
  xsh_free_frame( &resFrame) ;
  xsh_free_frame( &resid_tab) ;
  xsh_free_frame(&rmbias);
  xsh_free_frame(&bpmap);
  xsh_free_frame(&intFrame);
  return;
}

/**@}*/
