/*                                                                            *
 *   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-11-15 16:23:46 $
 * $Revision: 1.61 $
 */

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

/*----------------------------------------------------------------------------*/
/**
 * @defgroup xsh_remove_crh_single  Remove Cosmic Rays single (xsh_remove_crh_single)
 * @ingroup drl_functions
 *
 * Suppress Cosmic Rays by analysing on files
 */
/*----------------------------------------------------------------------------*/
/**@{*/

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

#include <math.h>

#include <xsh_drl.h>
#include <xsh_data_pre.h>
#include <xsh_dfs.h>
#include <xsh_pfits.h>
#include <xsh_error.h>
#include <xsh_msg.h>
#include <cpl.h>
#include <xsh_badpixelmap.h>
#include <xsh_parameters.h>
#include <xsh_utils_wrappers.h>
#include <xsh_qc_handling.h>
/*-----------------------------------------------------------------------------
  Functions prototypes
 -----------------------------------------------------------------------------*/

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

/* This should be defined in a more clever way, a parameter for example */
#define REGDEBUG_FULL 0





cpl_frame * xsh_abs_remove_crh_single( cpl_frame *sci_frame,
                                   xsh_instrument * instrument,
                                   xsh_remove_crh_single_param * single_par,
                                   const char * res_tag)
{
  cpl_frame *result_frame = NULL;
  cpl_frame *abs_frame = NULL;
  cpl_frame *sign_frame = NULL;
  cpl_frame *rmcrh_frame = NULL;
  const char* name = NULL;
  cpl_propertylist *header = NULL;
  int qc_ncrh =0;

  XSH_ASSURE_NOT_NULL( sci_frame);

  check( name = cpl_frame_get_filename( sci_frame));
  check( header = cpl_propertylist_load( name, 0));
  if(cpl_propertylist_has(header,XSH_QC_CRH_NUMBER)) {
     check( qc_ncrh = xsh_pfits_get_qc_ncrh( header));
  } else {
     qc_ncrh =0;
  }
  /* cosmics detection already done */
  if (qc_ncrh > 0){
    xsh_msg( "Not use remove crh single");
    check( result_frame = cpl_frame_duplicate( sci_frame));
  }
  else{
    check( abs_frame = xsh_frame_abs( sci_frame, instrument, &sign_frame));
    check( rmcrh_frame = xsh_remove_crh_single( abs_frame, instrument,NULL,
      single_par, res_tag));
    check( result_frame = xsh_frame_mult(  rmcrh_frame, instrument, sign_frame));
  }

  cleanup:
    if (cpl_error_get_code() != CPL_ERROR_NONE){
      xsh_free_frame( &result_frame);
    }
    xsh_free_propertylist( &header);
    xsh_free_frame( &abs_frame);
    xsh_free_frame( &sign_frame);
    xsh_free_frame( &rmcrh_frame);
    return result_frame;
}

/*****************************************************************************/
/** 
  @brief
    Remove cosmic rays from a single frame.
 @param[in] in_sci_frame 
   The science Frame
 @param[in] instrument 
   Pointer to instrument description
 @param[in] single_par 
   Parameters for remove crh single
 @param[in] res_tag 
   Parameters for result tag 
 @return 
   The science frame after removal of Cosmics
*/
/*****************************************************************************/
cpl_frame * xsh_remove_crh_single(cpl_frame * in_sci_frame,
    xsh_instrument * instrument, cpl_mask* sky_map,
    xsh_remove_crh_single_param * single_par,
    const char * res_tag) {
  int i, j,k,l,m;
  double sigma_lim, f_lim;
  /* Only pointers */
  cpl_frame* res_frame = NULL;
  char* res_name = NULL;
  /* Need to be free */
  cpl_image* laplacian_image = NULL;
  cpl_image* laplacian_redu_image = NULL; /* rebinned Laplacian */

  cpl_image* two_sub_sample = NULL;
  cpl_image* noise_image = NULL;
  cpl_image* s_median_image = NULL;
  cpl_image* s_image = NULL;
  cpl_image* sci_median3_image = NULL;
  cpl_image* sci_median3_7_image = NULL;
  cpl_image* f_image = NULL;
  cpl_image* r_image = NULL;
  int two_sub_sample_nx = 0;
  int two_sub_sample_ny = 0;
  xsh_pre* in_sci_pre = NULL; /**< Input PRE */
  xsh_pre* sci_pre = NULL; /**< Result PRE */

  cpl_image* sci_image = NULL; /**< Result image */
  cpl_image* sci_copy = NULL; /**< Result image */
  cpl_image* err_image = NULL; /**< Result image */
  cpl_image* qua_image = NULL; /**< Result image */

  /* Only pointers */
  float* sci_data = NULL;
  float* err_data = NULL;
  int* qua_data = NULL;
  cpl_binary* msk_data = NULL;

  float* two_sub_sample_data = NULL;
  float* laplacian_data = NULL;
  float* laplacian_redu_data = NULL;

  float* sci_median3_data = NULL;
  float* sci_median3_7_data = NULL;

  float* s_data = NULL;
  float* s_median_data = NULL;

  float* f_data = NULL;
  float* r_data = NULL;
  /* Need to be free */

  cpl_matrix* laplacian_kernel = NULL;
  cpl_matrix* median3_kernel = NULL;
  cpl_matrix* median5_kernel = NULL;
  cpl_matrix* median7_kernel = NULL;
  int new_crh = 1, nb_crh = 0;
  int nbiter = 1;
  int max_iter=1;
  cpl_vector* median = NULL;

  int nx = 0;
  int ny = 0;
  int j_nx=0;

  /* Check parameters */XSH_ASSURE_NOT_NULL_MSG( in_sci_frame,"Null input science frame" );
  XSH_ASSURE_NOT_NULL_MSG( single_par,"Null input parameters" );
  XSH_ASSURE_NOT_NULL_MSG( instrument,"Null instrument setting" );

  sigma_lim = single_par->sigma_lim;
  f_lim = single_par->f_lim;
  max_iter = single_par->nb_iter;
  xsh_msg_dbg_high(
      "  Params: sigma_lim %.2f f_lim %.2f, iter %d", sigma_lim, f_lim, max_iter);

  check( in_sci_pre = xsh_pre_load( in_sci_frame, instrument));
  check( sci_pre = xsh_pre_duplicate( in_sci_pre ) );
  xsh_pre_free(&in_sci_pre);
  check( sci_image = xsh_pre_get_data( sci_pre));
  check( sci_copy = cpl_image_duplicate( sci_image));
  check( err_image = xsh_pre_get_errs( sci_pre));
  check( qua_image = xsh_pre_get_qual( sci_pre));

  if (sky_map != NULL) {
     cpl_image_reject_from_mask(sci_copy,sky_map);
     msk_data = cpl_mask_get_data( sky_map);
  }


  nx = sci_pre->nx;
  ny = sci_pre->ny;

  /* Preparing different kernels */

  xsh_msg_dbg_low("--remove-crh-single : Preparing kernels");
  /* Laplacian */
  check( laplacian_kernel = cpl_matrix_new(3,3));
  cpl_matrix_set(laplacian_kernel, 0, 0, 0.0);
  cpl_matrix_set(laplacian_kernel, 0, 1, -1.0);
  cpl_matrix_set(laplacian_kernel, 0, 2, 0.0);
  cpl_matrix_set(laplacian_kernel, 1, 0, -1.0);
  cpl_matrix_set(laplacian_kernel, 1, 1, 4.0);
  cpl_matrix_set(laplacian_kernel, 1, 2, -1.0);
  cpl_matrix_set(laplacian_kernel, 2, 0, 0.0);
  cpl_matrix_set(laplacian_kernel, 2, 1, -1.0);
  cpl_matrix_set(laplacian_kernel, 2, 2, 0.0);

  /* Median 3x3*/
  check( median3_kernel = cpl_matrix_new(3,3));
  for (j = 0; j < 3; j++) {
    for (i = 0; i < 3; i++) {
      cpl_matrix_set(median3_kernel, i, j, 1.0);
    }
  }

  /* Median 5x5 */
  check( median5_kernel = cpl_matrix_new(5,5));
  for (j = 0; j < 5; j++) {
    for (i = 0; i < 5; i++) {
      cpl_matrix_set(median5_kernel, i, j, 1.0);
    }
  }

  /* Median 7x7 */
  check( median7_kernel = cpl_matrix_new(7,7));
  for (j = 0; j < 7; j++) {
    for (i = 0; i < 7; i++) {
      cpl_matrix_set(median7_kernel, i, j, 1.0);
    }
  }

  /* Allocate images and pointers */
  check(sci_data = cpl_image_get_data_float( sci_copy));
  check(err_data = cpl_image_get_data_float( err_image));
  check(qua_data = cpl_image_get_data_int( qua_image));

  two_sub_sample_nx = nx * 2;
  two_sub_sample_ny = ny * 2;
  check(
      two_sub_sample = cpl_image_new( two_sub_sample_nx, two_sub_sample_ny, CPL_TYPE_FLOAT));
  check(two_sub_sample_data = cpl_image_get_data_float( two_sub_sample));
  check( laplacian_redu_image = cpl_image_new(nx,ny, CPL_TYPE_FLOAT));
  check(laplacian_redu_data = cpl_image_get_data_float( laplacian_redu_image));
  check( noise_image = cpl_image_new(nx,ny, CPL_TYPE_FLOAT));

  check( s_image = cpl_image_new(nx,ny, CPL_TYPE_FLOAT));
  check( s_data = cpl_image_get_data_float( s_image));

 
  check( f_image = cpl_image_new(nx,ny, CPL_TYPE_FLOAT));
  check( f_data = cpl_image_get_data_float( f_image));
  check( r_image = cpl_image_new(nx,ny, CPL_TYPE_FLOAT));

  check( r_data = cpl_image_get_data_float( r_image));

  /* LGG - Added limit limit on nb iterations */
  while (new_crh > 0  && nbiter <= max_iter) {
    xsh_msg_dbg_low("Iteration %d", nbiter);

     
    /* Super sample data: 
       Create a 2n x 2n images like this 
     | 1 | 2 |  =>  | 1 | 1 | 2 | 2 |
     | 3 | 4 |      | 1 | 1 | 2 | 2 |
                    | 3 | 3 | 4 | 4 |
                    | 3 | 3 | 4 | 4 | */
    xsh_msg_dbg_medium("Create super-sampled image");
    int j_2_two_sub_sample_nx = 0;
    int pix = 0;
    for (j = 0; j < ny; j++) {
      j_2_two_sub_sample_nx = j * 2 * two_sub_sample_nx;
      j_nx = j*nx;
      for (i = 0; i < nx; i++) {
        float val = sci_data[i + j_nx];
        pix = i * 2 + j_2_two_sub_sample_nx;
        two_sub_sample_data[pix] = val;
        two_sub_sample_data[pix + two_sub_sample_nx] = val;
        pix++;
        two_sub_sample_data[pix] = val;
        two_sub_sample_data[pix + two_sub_sample_nx] = val;
      }
    }


    /* Doing the Laplacian convolution and set negative pixels to 
       0 in the Laplacian image

      0  -1   0
     -1   4  -1
      0  -1   0 */
    xsh_msg_dbg_medium("Doing Laplacian convolution");
    check(
        laplacian_image = xsh_image_filter_linear( two_sub_sample, laplacian_kernel));
 
    check(laplacian_data = cpl_image_get_data_float( laplacian_image));
    for (i = 0; i < two_sub_sample_nx * two_sub_sample_ny; i++) {
     if (laplacian_data[i] < 0.0) {
        laplacian_data[i] = 0.0;
     } else {
        laplacian_data[i] *= 8.0;
     }
    }


#if REGDEBUG_FULL
    xsh_msg_dbg_medium("Save Lpositive");
    cpl_image_save(laplacian_image, "Lpositive.fits", CPL_BPP_IEEE_FLOAT, NULL,
        CPL_IO_DEFAULT);
#endif



    /* resample to the original size 
     | 1 | 1 | 2 | 2 |    | 1 | 2 |
     | 1 | 1 | 2 | 2 |    | 3 | 4 |
     | 3 | 3 | 4 | 4 | =>
     | 3 | 3 | 4 | 4 |               */

    xsh_msg_dbg_medium("Resample to the original size");
    for (j = 0; j < ny; j++) {
      j_2_two_sub_sample_nx = j * 2 * two_sub_sample_nx;
      j_nx = j*nx;
      for (i = 0; i < nx; i++) {
        pix = i * 2 + j_2_two_sub_sample_nx;
        laplacian_redu_data[i + j_nx] = (laplacian_data[pix]
            + laplacian_data[pix + 1] + laplacian_data[pix + two_sub_sample_nx]
            + laplacian_data[pix + two_sub_sample_nx + 1]) * 0.25;
      }
    }

#if REGDEBUG_FULL
    cpl_image_save(laplacian_redu_image, "Lplus.fits", CPL_BPP_IEEE_FLOAT,
        NULL, CPL_IO_DEFAULT);
#endif


    /* compute S image */
    xsh_msg_dbg_medium("Compute S");
    for (i = 0; i < nx * ny; i++) {
      s_data[i] = 0.5 * laplacian_redu_data[i] / err_data[i];
    }

    /* compute S median image */
    xsh_msg_dbg_medium("Compute S median");
    check( s_median_image = xsh_image_filter_median( s_image, median5_kernel));
    check( s_median_data = cpl_image_get_data_float( s_median_image));


    /* compute s2 */
    xsh_msg_dbg_medium("Compute s2");
    for (i = 0; i < nx * ny; i++) {
      s_data[i] -= s_median_data[i];
    }
#if REGDEBUG_FULL
    cpl_image_save( s_image, "S2.fits", CPL_BPP_IEEE_FLOAT, NULL,
        CPL_IO_DEFAULT);
#endif


    /* Apply 3x3 median filter on data */
    xsh_msg_dbg_medium("Apply 3x3 filter");
    check(sci_median3_image = xsh_image_filter_median( sci_copy, median3_kernel));
    check( sci_median3_data = cpl_image_get_data_float( sci_median3_image));

    /* Apply 7x7 median filter */
    xsh_msg_dbg_medium("Apply 7x7 filter");
    check(
        sci_median3_7_image = xsh_image_filter_median( sci_median3_image, median7_kernel));
    check( sci_median3_7_data = cpl_image_get_data_float( sci_median3_7_image));



    /* compute F: fine structure image */
    xsh_msg_dbg_medium("Compute F");
    for (i = 0; i < nx * ny; i++) {
      f_data[i] = sci_median3_data[i] - sci_median3_7_data[i];
      if (f_data[i] < 0.01) {
        f_data[i] = 0.01;
      }
    }
#if REGDEBUG_FULL
    cpl_image_save( f_image, "F.fits", CPL_BPP_IEEE_FLOAT, NULL,
        CPL_IO_DEFAULT);
#endif

    /* compute R: the ratio of Laplacian to the fine structure image */
    xsh_msg_dbg_medium("Compute R");
    for (i = 0; i < nx * ny; i++) {
      r_data[i] = laplacian_redu_data[i] / f_data[i];
    }
#if REGDEBUG_FULL
    cpl_image_save( r_image, "R.fits", CPL_BPP_IEEE_FLOAT, NULL,
        CPL_IO_DEFAULT);
#endif


    /* Search for cosmics */
    xsh_msg_dbg_medium("Search for cosmic ray hits");
    new_crh = 0;
    median = cpl_vector_new(24);
    for (j = 0; j < ny - 1; j++) {

      double *data = NULL;
      cpl_vector* med_vect = NULL;

      j_nx = j*nx;
      for (i = 0; i < nx - 1 ; i++) {
         int  i_plus_j_nx = i + j_nx;
         if ( (msk_data != NULL)  && (msk_data[i + j_nx] != CPL_BINARY_1) ) {
             /* if the pixel was already flagged in input sky map
              * does not search for CRH
              */
             //xsh_msg("found a bad pixel at j=%d i=%d",j,i);

         } else {
          if ( s_data[i_plus_j_nx] > sigma_lim ) {
              if ( r_data[i_plus_j_nx] > f_lim ) {

                /* we flag the CRH in the science frame */
                qua_data[i + j_nx] |= QFLAG_COSMIC_RAY_UNREMOVED;
                new_crh++;

                /* we replace the CRH with median of the surroundings pixels 
                   in a box 5x5 pixels */
		int li, lj, ui, uj;
                li = i - 2;
                lj = j - 2;
                ui = i + 2;
                uj = j + 2;
                m = 0;

                if (li < 0)  { li = 0; }
                if (ui >= nx) { ui = nx - 1; }
                if (lj < 0)  { lj = 0; }
                if (uj >= ny) { uj = ny - 1;}

                for (k = lj; k <= uj; k++) {
                  int k_nx = k*nx;
                  for (l = li; l <= ui; l++) {
                    int l_plus_k_nx = l + k_nx;
                    if (s_data[l_plus_k_nx] <= sigma_lim) {
                      cpl_vector_set(median, m, sci_data[l_plus_k_nx]);
                      m++;
                      continue;
                    }
                    if (r_data[l_plus_k_nx] <= f_lim) {
                      cpl_vector_set(median, m, sci_data[l_plus_k_nx]);
                      m++;
                    }
                  }
                }
                /* if no good pixel has been found skip pixel value 
                   replacement */
                if(m==0) continue;
		/* Replace CRH value with median of computed values on 
                   good pixels */
                check( data = cpl_vector_get_data( median));
                check( med_vect = cpl_vector_wrap( m, data));
                check( sci_data[i_plus_j_nx] = cpl_vector_get_median(med_vect));
                cpl_vector_unwrap(med_vect);


              }/* end check on 2nd threshold */
          } /* end check on 1st threshold */
         }/* end check if given point should not be checked as flagged in map */
      } /* end loop over image columns */
    } /* end loop over image rows */

    xsh_free_vector(&median);

    nb_crh += new_crh;
    xsh_msg_dbg_low(
        "   new cosmics %d, total %d [%d pixels]", new_crh, nb_crh, nx*ny);
    nbiter++;
    xsh_free_image(&laplacian_image);
    xsh_free_image(&sci_median3_7_image);
    xsh_free_image(&sci_median3_image);
    xsh_free_image(&s_median_image);
  }
#if REGDEBUG_FULL
  cpl_image_save( qua_image, "CRH_SINGLE.fits", CPL_BPP_32_SIGNED, NULL,
      CPL_IO_DEFAULT);
#endif



  res_name = xsh_stringcat_any(res_tag, ".fits", (void*)NULL);
  xsh_msg_dbg_high( "Saving Result Frame '%s'", res_name);
  check( xsh_add_qc_crh( sci_pre, nb_crh, 1) );
  check( res_frame = xsh_pre_save( sci_pre, res_name, res_tag,0 ) );
  check( cpl_frame_set_tag( res_frame, res_tag ) );
  XSH_FREE( res_name);

  cleanup: 
  xsh_pre_free(&sci_pre);

  /* free kernel */
  xsh_free_matrix(&laplacian_kernel);
  xsh_free_matrix(&median3_kernel);
  xsh_free_matrix(&median5_kernel);
  xsh_free_matrix(&median7_kernel);
  /* free images */
  xsh_free_image(&laplacian_image);
  xsh_free_image(&laplacian_redu_image);
  xsh_free_image(&two_sub_sample);
  xsh_free_image(&noise_image);
  xsh_free_image(&s_median_image);
  xsh_free_image(&s_image);
  xsh_free_image(&sci_median3_image);
  xsh_free_image(&sci_median3_7_image);
  xsh_free_image(&sci_copy);
  xsh_free_image(&f_image);
  xsh_free_image(&r_image);
  /* free tab */
  return res_frame;
}


/**@}*/

