/*                                                                            *
 *   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 $
 * $Revision: 1.25 $
*/

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

/*----------------------------------------------------------------------------*/
/**
 * @defgroup xsh_combine_nod  Combine rectified NOD Frames
 * @ingroup drl_functions
 *
 * Function ...
 */
/*----------------------------------------------------------------------------*/
/**@{*/

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

#include <math.h>
#include <xsh_drl.h>

#include <xsh_badpixelmap.h>
#include <xsh_data_pre.h>
#include <xsh_data_order.h>
#include <xsh_data_wavemap.h>
#include <xsh_data_localization.h>
#include <xsh_data_rec.h>
#include <xsh_dfs.h>
#include <xsh_pfits.h>
#include <xsh_error.h>
#include <xsh_msg.h>
#include <xsh_fit.h>
#include <xsh_badpixelmap.h>

#include <cpl.h>

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

/*-----------------------------------------------------------------------------
  Implementation
  -----------------------------------------------------------------------------*/


static void xsh_compute_slit_index( float slit_min, float slit_step, 
  xsh_rec_list **from, int *slit_index_tab, int size)
{
  int i;

  XSH_ASSURE_NOT_NULL( from);
  XSH_ASSURE_NOT_NULL( slit_index_tab);

  for( i=0; i< size; i++){
    xsh_rec_list *list = NULL;
    float* nod_slit = NULL;

    list = from[i];
    check( nod_slit = xsh_rec_list_get_slit( list, 0));
    slit_index_tab[i] = (int)xsh_round_double((nod_slit[0]-slit_min)/slit_step);
  }
  cleanup:
    return;  
}

static void xsh_rec_list_add(xsh_rec_list *dest, xsh_rec_list **from,
    int *slit_index, int nb_frames, int no, int method, const int decode_bp) {
  int nslit, from_slit, nlambda;
  float *dest_data1 = NULL, *dest_errs1 = NULL;
  int *dest_qual1 = NULL;
  float *from_data1 = NULL, *from_errs1 = NULL;
  int *from_qual1 = NULL;
  int ns, nl, nf;
  double *flux_tab = NULL;
  cpl_vector *flux_vect = NULL;
  double *err_tab = NULL;
  cpl_vector *err_vect = NULL;

  XSH_ASSURE_NOT_NULL( dest);
  XSH_ASSURE_NOT_NULL( from);
  XSH_ASSURE_NOT_NULL( slit_index);

  xsh_msg_dbg_medium(
      "xsh_rec_list_add: nb frames: %d, order: %d", nb_frames, no);

  check( nslit = xsh_rec_list_get_nslit( dest, no));
  check( from_slit = xsh_rec_list_get_nslit( from[0], no));
  check( nlambda = xsh_rec_list_get_nlambda( dest, no));

  check( dest_data1 = xsh_rec_list_get_data1( dest, no));
  check( dest_errs1 = xsh_rec_list_get_errs1( dest, no));
  check( dest_qual1 = xsh_rec_list_get_qual1( dest, no));

  XSH_MALLOC( flux_tab, double, nb_frames);
XSH_MALLOC  ( err_tab, double , nb_frames);

  for (nf = 0; nf < nb_frames; nf++) {
    xsh_msg_dbg_high(
        "slit index: max %d min=%d", slit_index[nf], slit_index[nf]+from_slit);
  }

  for (ns = 0; ns < nslit; ns++) {
    for (nl = 0; nl < nlambda; nl++) {
      int dest_idx;
      int good = 0, bad = 0;
      //double dest_err = 0, dest_data = 0. ;
      //double bad_err = 0, bad_data = 0;
      unsigned int qflag = QFLAG_GOOD_PIXEL;
      unsigned int badflag = QFLAG_GOOD_PIXEL;

      dest_idx = nl + ns * nlambda;

      for (nf = 0; nf < nb_frames; nf++) {
        int from_idx;

        if ((ns < slit_index[nf]) || (ns >= (slit_index[nf] + from_slit))) {
          continue;
        }
        from_idx = nl + (ns - slit_index[nf]) * nlambda;
        check( from_data1 = xsh_rec_list_get_data1( from[nf], no));
        check( from_errs1 = xsh_rec_list_get_errs1( from[nf], no));
        check( from_qual1 = xsh_rec_list_get_qual1( from[nf], no));

        if ((from_qual1[from_idx] & decode_bp) == 0) {
          qflag |= from_qual1[from_idx];
          flux_tab[good] = from_data1[from_idx];
          err_tab[good] = from_errs1[from_idx];
          good++;
        } else if ((from_qual1[from_idx] & decode_bp) > 0) {
          flux_tab[good + bad] = from_data1[from_idx];
          err_tab[good + bad] = from_errs1[from_idx];
          badflag |= from_qual1[from_idx];
          bad++;
        }
      }
      /* Dont calculate mean value, just sum */
      if (good == 0) {
        check( flux_vect = cpl_vector_wrap( bad, flux_tab));
        check( err_vect = cpl_vector_wrap( bad, err_tab));
        /* No good pixel */
        dest_qual1[dest_idx] |= badflag;
      } else {
        dest_qual1[dest_idx] |= qflag;
        check( flux_vect = cpl_vector_wrap( good, flux_tab));
        check( err_vect = cpl_vector_wrap( good, err_tab));
      }
      if (method == COMBINE_MEAN_METHOD) {
        check( dest_data1[dest_idx] = cpl_vector_get_mean( flux_vect));
        check( dest_errs1[dest_idx] = xsh_vector_get_err_mean( err_vect));
      } else {
        check( dest_data1[dest_idx] = cpl_vector_get_median( flux_vect));
        check( dest_errs1[dest_idx] = xsh_vector_get_err_median( err_vect));
      }
      xsh_unwrap_vector(&flux_vect);
      xsh_unwrap_vector(&err_vect);
    }
  }

  cleanup: if (cpl_error_get_code() != CPL_ERROR_NONE) {
    xsh_unwrap_vector(&flux_vect);
    xsh_unwrap_vector(&err_vect);
  }
  XSH_FREE( flux_tab);
  XSH_FREE( err_tab);
  return;
}

/*****************************************************************************/
/** 
 * Adds (combine) all the shifted rectified frames (nodding). Combination
 * is made for each order.
 * 
 * @param nod_frames Frameset of the shifted rectified frames
 * @param nod_par Parameters of combination
 * @param tag  pro catg of combined frame
 * @param instrument Instrument description
 * @param res_frame_ext combined frame in ESO format 
 * @return The combined frame, inf/upp suggested values for extraction
 */
/*****************************************************************************/
cpl_frame* xsh_combine_nod( cpl_frameset *nod_frames, 
                            xsh_combine_nod_param * nod_par, 
                            const char * tag,
                            xsh_instrument *instrument, 
                            cpl_frame** res_frame_ext)
{
  xsh_rec_list *result_list = NULL ;
  cpl_frame *result = NULL ;
  int nb_frames = 0 ;
  int i, no, nb_orders;
  float slit_min=999, slit_max=-999;
  double slit_step =0.0;
  double lambda_min, lambda_max, lambda_step;
  xsh_rec_list **rec_input_list = NULL ;
  xsh_rec_list *rec_first = NULL;
  //float *rec_first_slit = NULL;
  float *result_slit = NULL;
  int nslit;
  char *fname = NULL ;
  char *tag_drl = NULL ;
  char *fname_drl = NULL ;
  int *slit_index = NULL;
  int slit_ext_min=-9999;
  int slit_ext_max=9999;
  cpl_propertylist *header = NULL;

  XSH_ASSURE_NOT_NULL( nod_frames);
  XSH_ASSURE_NOT_NULL( nod_par);
  XSH_ASSURE_NOT_NULL( instrument);
  XSH_ASSURE_NOT_NULL( tag);
  XSH_ASSURE_NOT_NULL( res_frame_ext);

  /* Create list of_reclist */
  check( nb_frames = cpl_frameset_get_size( nod_frames));
  xsh_msg( "---xsh_combine_nod (method %s) - Nb frames = %d", 
    COMBINE_METHOD_PRINT( nod_par->method), nb_frames);

  /* create the list of pointers to input rec lists */
  XSH_CALLOC( rec_input_list, xsh_rec_list *, nb_frames);

  /* Populate the input lists */
  for ( i = 0 ; i < nb_frames ; i++ ) {
    cpl_frame * nod_frame = NULL ;
    xsh_rec_list * list = NULL;

    check( nod_frame = cpl_frameset_get_frame( nod_frames, i));
    check( list = xsh_rec_list_load( nod_frame, instrument));
    rec_input_list[i] = list;
  }

  /* Search for limits in slit */
  for ( i = 0 ; i < nb_frames ; i++ ) {
    float nod_slit_min, nod_slit_max;
    xsh_rec_list * list = NULL;
    nslit=0;
    float* nod_slit = NULL;
    
    list = rec_input_list[i];
    check( nslit = xsh_rec_list_get_nslit( list, 0)); 
    check( nod_slit = xsh_rec_list_get_slit( list, 0));
    nod_slit_min = nod_slit[0];
    nod_slit_max = nod_slit[nslit-1];

    if ( nod_slit_min < slit_min){
      slit_min = nod_slit_min;
    }
    if (nod_slit_max > slit_max){
      slit_max = nod_slit_max;
    }
  }
  rec_first = rec_input_list[0];
  //check( rec_first_slit = xsh_rec_list_get_slit( rec_first, 0));
  check( header = xsh_rec_list_get_header( rec_first));
  check( slit_step = xsh_pfits_get_rectify_bin_space( header));
  nslit = (slit_max-slit_min)/slit_step+1;
  
  /* Create result slit */
  XSH_CALLOC( result_slit, float, nslit);
  for ( i = 0 ; i < nslit; i++ ) {
    result_slit[i] = slit_min+i*slit_step;
  }

  xsh_msg("Combine nod slit : (%f,%f) step %f nslit %d", slit_min, slit_max, 
    slit_step, nslit);

  nb_orders = rec_first->size;
  check( result_list =  xsh_rec_list_create_with_size( nb_orders, 
    instrument));

  for ( no = 0 ; no < nb_orders ; no++ ) {
    int absorder, nlambda;
    double *dnew = NULL;
    double *dold = NULL;
    float *dslit = NULL;

    absorder = xsh_rec_list_get_order( rec_first, no);
    nlambda = xsh_rec_list_get_nlambda( rec_first, no);
    check( xsh_rec_list_set_data_size( result_list, no, absorder, nlambda, 
      nslit));
    /* copy lambda */
    dold = xsh_rec_list_get_lambda( rec_first, no);
    dnew = xsh_rec_list_get_lambda( result_list, no);
    memcpy( dnew, dold, nlambda*sizeof( double)); 
    /* copy slit */
    dslit = xsh_rec_list_get_slit( result_list, no);
    memcpy( dslit, result_slit, nslit*sizeof( float));
  }
  
  /* compute slit index */
  XSH_CALLOC( slit_index, int, nb_frames);
  check( xsh_compute_slit_index( slit_min,  slit_step, rec_input_list,
    slit_index, nb_frames));

  /* Now combine */
  for ( no = 0; no < nb_orders; no++) {
    check( xsh_rec_list_add( result_list, rec_input_list, slit_index, 
      nb_frames, no, nod_par->method,instrument->decode_bp) ) ;
  }

  /* Now save result list s frame */
  fname = xsh_stringcat_any( tag, ".fits", (void*)NULL);
  tag_drl = xsh_stringcat_any( tag, "_DRL", (void*)NULL);
  fname_drl = xsh_stringcat_any( "DRL_", fname, (void*)NULL);

  /* Set header from science header */
  check( cpl_propertylist_append( result_list->header, 
    rec_first->header));

  /* Add bin_lambda and bin_space to property list */
  check( lambda_step = xsh_pfits_get_rectify_bin_lambda( 
    result_list->header));
  check( lambda_min = xsh_pfits_get_rectify_lambda_min( 
    result_list->header));
  check( lambda_max = xsh_pfits_get_rectify_lambda_max( 
    result_list->header));
  check( xsh_pfits_set_rectify_bin_lambda( result_list->header,
    lambda_step));
  check( xsh_pfits_set_rectify_bin_space( result_list->header,
    slit_step));

   /* Add lambda min and max (global) */
  /* Lambda min is lambda_min of last order */
  /* Lambda Max is lambda_max of first order */
  check( xsh_pfits_set_rectify_lambda_min( result_list->header,
    lambda_min)) ;
  check( xsh_pfits_set_rectify_lambda_max( result_list->header,
    lambda_max));


  /* Now compute slit min/max for 1D extraction: AMO: should we control
     this bit of code via some parameters??
   */

  if(1) {
    int nf=0;
    int from_slit=0;
    for ( no = 0; no < nb_orders; no++) {
      check( from_slit = xsh_rec_list_get_nslit( rec_input_list[0], no));
  
      for( nf = 0 ; nf < nb_frames ; nf++ ) {
	slit_ext_min=(slit_index[nf]>slit_ext_min) ? slit_index[nf]: slit_ext_min;
	slit_ext_max=(slit_index[nf]+from_slit<slit_ext_max) ? slit_index[nf]+from_slit: slit_ext_max;

      }
    }
  }

  xsh_msg("slit index: min %f max=%f",
	  slit_min,slit_max);
  
  //result_list->slit_min = slit_min-slit_step/2.0;
  //result_list->slit_max = slit_max+slit_step/2.0;
  result_list->slit_min = slit_min;
  result_list->slit_max = slit_max;

  //xsh_rec_list_set_slit_min(result_slit,slit_min);
  //xsh_rec_list_set_slit_max(result_slit,slit_max);
  /*
  xsh_msg("slit index: min %f max=%f",
	  result_list->slit_min,result_list->slit_max);
  */


  check( xsh_pfits_set_rectify_space_min( result_list->header,
    result_list->slit_min));
  check( xsh_pfits_set_rectify_space_max( result_list->header,
    result_list->slit_max));
  check( xsh_pfits_set_pcatg( result_list->header, tag));

  xsh_pfits_set_extract_slit_min(result_list->header,slit_ext_min);
  xsh_pfits_set_extract_slit_max(result_list->header,slit_ext_max);
  check( *res_frame_ext=xsh_rec_list_save2( result_list, fname, tag));
  check( result = xsh_rec_list_save( result_list, fname_drl, tag_drl, 1));
  xsh_add_temporary_file(fname_drl);
  cleanup:
    if (cpl_error_get_code() != CPL_ERROR_NONE){
      xsh_free_frame( &result);
      xsh_free_frame( res_frame_ext);
    }
    xsh_rec_list_free( &result_list);
    if ( rec_input_list != NULL){
      for ( i = 0 ; i < nb_frames ; i++ ) {
        xsh_rec_list_free(  &rec_input_list[i]);
      }
    }
    XSH_FREE( result_slit);
    XSH_FREE( slit_index);
    XSH_FREE( tag_drl);
    XSH_FREE( fname_drl);
    XSH_FREE( fname);
    XSH_FREE( rec_input_list);
    return result ;
}
/*****************************************************************************/

/**@}*/
