/*                                                                           *
 *   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-02-13 09:50:57 $
 * $Revision: 1.17 $
 * $Name: not supported by cvs2svn $
 */

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

/*---------------------------------------------------------------------------*/
/**
 * @defgroup xsh_data_order Orders
 * @ingroup data_handling
 */
/*---------------------------------------------------------------------------*/

/**@{*/


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

#include <math.h>
#include <xsh_data_spectrum1D.h>
#include <xsh_data_spectrum.h>
#include <xsh_utils.h>
#include <xsh_error.h>
#include <xsh_msg.h>
#include <xsh_pfits.h>
#include <xsh_dfs.h>
#include <cpl.h>

/*----------------------------------------------------------------------------
                                 Function implementation
 ----------------------------------------------------------------------------*/

/*---------------------------------------------------------------------------*/
/** 
 * @brief Create a 1D spectrum  structure
 * 
 * @param[in] lambda_min minimum wavelength of spectrum
 * @param[in] lambda_max maximum wavelength of spectrum
 * @param[in] lambda_step lambda binning 
 *
 * @return the spectrum1D structure
 */
/*---------------------------------------------------------------------------*/
xsh_spectrum1D* xsh_spectrum1D_create( double lambda_min, double lambda_max,    
  double lambda_step)
{
  xsh_spectrum1D* result = NULL;


  /* check input parameters */
  XSH_ASSURE_NOT_ILLEGAL( lambda_min >= 0.0 && lambda_min <= lambda_max);
  XSH_ASSURE_NOT_ILLEGAL( lambda_step >=0);

  XSH_CALLOC(result, xsh_spectrum1D,1);
   
  result->lambda_min = lambda_min;
  result->lambda_max = lambda_max;
  result->lambda_step = lambda_step;

  XSH_NEW_PROPERTYLIST( result->flux_header);
  check(xsh_pfits_set_wcs1(result->flux_header, 0.5, lambda_min, lambda_step));

  XSH_NEW_PROPERTYLIST( result->errs_header);
  check( xsh_pfits_set_extname ( result->errs_header, "ERRS"));
  XSH_NEW_PROPERTYLIST( result->qual_header);
  check( xsh_pfits_set_extname ( result->qual_header, "QUAL"));
  

  result->size = (int)((lambda_max-lambda_min)/lambda_step+0.5);

  check( result->flux = cpl_image_new( result->size, 1, CPL_TYPE_DOUBLE));
  check( result->errs = cpl_image_new( result->size, 1, CPL_TYPE_DOUBLE));
  check( result->qual = cpl_image_new( result->size, 1, CPL_TYPE_INT));

  cleanup:
    if (cpl_error_get_code() != CPL_ERROR_NONE) {
      xsh_spectrum1D_free(&result);
    }
    return result;
}


/*---------------------------------------------------------------------------*/
/** 
 * @brief Load a 1D spectrum  structure
 * 
 * @param[in] s1d_frame the 1D spectrum frame
 * @param[in] instr instrument structure
 *
 * @return the spectrum1D structure
 */
/*---------------------------------------------------------------------------*/
xsh_spectrum1D* xsh_spectrum1D_load( cpl_frame* s1d_frame, 
  xsh_instrument* instr)
{
  xsh_spectrum1D* result = NULL;
  const char *s1dname = NULL;

  XSH_ASSURE_NOT_NULL( s1d_frame);
  XSH_ASSURE_NOT_NULL( instr);

  XSH_ASSURE_NOT_ILLEGAL(cpl_frame_get_nextensions(s1d_frame) == 2);
  
  check( s1dname = cpl_frame_get_filename( s1d_frame));

  XSH_CALLOC(result, xsh_spectrum1D,1);

  check( result->flux_header = cpl_propertylist_load( s1dname,0));
  check( result->errs_header = cpl_propertylist_load( s1dname,1));
  check( result->qual_header = cpl_propertylist_load( s1dname,2));

  check( result->lambda_min = xsh_pfits_get_crval1( result->flux_header));
  check( result->lambda_step = xsh_pfits_get_cdelt1( result->flux_header));
  check( result->size =  xsh_pfits_get_naxis1( result->flux_header));
  result->lambda_max = result->lambda_min+
    (result->lambda_step*result->size-1);

  check( result->flux = cpl_image_load( s1dname, CPL_TYPE_DOUBLE, 0, 0));
  check( result->errs = cpl_image_load( s1dname, CPL_TYPE_DOUBLE, 0, 1));
  check( result->qual = cpl_image_load( s1dname, CPL_TYPE_INT, 0, 2));

  cleanup:
    if (cpl_error_get_code() != CPL_ERROR_NONE) {
      xsh_spectrum1D_free(&result);
    } 
    return result;
}
/*---------------------------------------------------------------------------*/
/** 
 * @brief Get size of spectrum
 * 
 * @param[in] s spectrum structure
 *
 * @return the size of flux data in spectrum
 */
/*---------------------------------------------------------------------------*/
int xsh_spectrum1D_get_size( xsh_spectrum1D* s)
{
  int res=0;

  XSH_ASSURE_NOT_NULL( s);

  res = s->size;

  cleanup:
    return res;
}

/*---------------------------------------------------------------------------*/
/** 
 * @brief Get minimum lambda of spectrum
 * 
 * @param[in] s spectrum structure
 *
 * @return minimum lambda spectrum
 */
/*---------------------------------------------------------------------------*/
double xsh_spectrum1D_get_lambda_min( xsh_spectrum1D* s)
{
  double res=0.0;

  XSH_ASSURE_NOT_NULL( s);

  res = s->lambda_min;

  cleanup:
    return res;
}

/*---------------------------------------------------------------------------*/
/** 
 * @brief Get maximum lambda of spectrum
 * 
 * @param[in] s spectrum structure
 *
 * @return maximum lambda of spectrum
 */
/*---------------------------------------------------------------------------*/
double xsh_spectrum1D_get_lambda_max( xsh_spectrum1D* s)
{
  double res=0.0;

  XSH_ASSURE_NOT_NULL( s);

  res = s->lambda_max;

  cleanup:
    return res;
}

/*---------------------------------------------------------------------------*/
/** 
 * @brief Get bin in lambda of spectrum
 * 
 * @param[in] s spectrum structure
 *
 * @return bin in lambda of spectrum
 */
/*---------------------------------------------------------------------------*/
double xsh_spectrum1D_get_lambda_step( xsh_spectrum1D* s)
{
  double res=0.0;

  XSH_ASSURE_NOT_NULL( s);

  res = s->lambda_step;

  cleanup:
    return res;
}


/*---------------------------------------------------------------------------*/
/** 
 * @brief Get flux of spectrum
 * 
 * @param[in] s spectrum structure
 *
 * @return the flux data of spectrum
 */
/*---------------------------------------------------------------------------*/
double* xsh_spectrum1D_get_flux( xsh_spectrum1D* s)
{
  double *res=NULL;

  XSH_ASSURE_NOT_NULL( s);

  check( res = cpl_image_get_data_double( s->flux));

  cleanup:
    return res;
}


/*---------------------------------------------------------------------------*/
/** 
 * @brief Get errs of spectrum
 * 
 * @param[in] s spectrum structure
 *
 * @return the errs data of spectrum
 */
/*---------------------------------------------------------------------------*/
double* xsh_spectrum1D_get_errs( xsh_spectrum1D* s)
{
  double *res=NULL;

  XSH_ASSURE_NOT_NULL( s);

  check( res = cpl_image_get_data_double( s->errs));

  cleanup:
    return res;
}


/*---------------------------------------------------------------------------*/
/** 
 * @brief Get qual of spectrum
 * 
 * @param[in] s spectrum structure
 *
 * @return the qual data of spectrum
 */
/*---------------------------------------------------------------------------*/
int* xsh_spectrum1D_get_qual( xsh_spectrum1D* s)
{
  int* res = NULL;

  XSH_ASSURE_NOT_NULL( s);

  check( res = cpl_image_get_data_int( s->qual));

  cleanup:
    return res;
}

/*---------------------------------------------------------------------------*/
/** 
 * @brief free memory associated to an 1D spectrum
 * 
 * @param[in] s spectrum structure to free
*/
/*---------------------------------------------------------------------------*/
void xsh_spectrum1D_free( xsh_spectrum1D** s)
{
  if (s && *s){
    xsh_free_propertylist( &((*s)->flux_header));
    xsh_free_propertylist( &((*s)->errs_header));
    xsh_free_propertylist( &((*s)->qual_header));
    xsh_free_image( &((*s)->flux));
    xsh_free_image( &((*s)->errs));
    xsh_free_image( &((*s)->qual));
    XSH_FREE( (*s));
  }
}

/*---------------------------------------------------------------------------*/
/** 
 * @brief save a 1D spectrum
 * 
 * @param[in] s spectrum structure to save
 * @param[in] filename name of the save file
 * 
 * @return 1D spectrum frame
 */
/*---------------------------------------------------------------------------*/
cpl_frame* xsh_spectrum1D_save( xsh_spectrum1D* s, const char* filename)
{
  cpl_frame *product_frame = NULL;

  XSH_ASSURE_NOT_NULL(s);
  XSH_ASSURE_NOT_NULL(filename);

 
  /* Save the file */
  check_msg (cpl_image_save ( s->flux, filename, XSH_SPECTRUM1D_DATA_BPP,
    s->flux_header, CPL_IO_DEFAULT),
    "Could not save data to %s extension 0", filename);
  check_msg (cpl_image_save ( s->errs, filename, XSH_SPECTRUM1D_ERRS_BPP,
    s->errs_header, CPL_IO_EXTEND),
    "Could not save errs to %s extension 1", filename);
  check_msg (cpl_image_save ( s->qual, filename, XSH_SPECTRUM1D_QUAL_BPP,
    s->qual_header, CPL_IO_EXTEND),
    "Could not save qual to %s extension 2", filename);
  /* Create product frame */

  check(product_frame=xsh_frame_product(filename,
				 "TAG",
				 CPL_FRAME_TYPE_IMAGE,
				 CPL_FRAME_GROUP_PRODUCT,
				 CPL_FRAME_LEVEL_FINAL));

cleanup:
  if (cpl_error_get_code () != CPL_ERROR_NONE) {
    xsh_free_frame(&product_frame);
    product_frame = NULL;
  }
  return product_frame;
}

static cpl_error_code
xsh_monitor_spectrum1D_flux_qc(xsh_spectrum* s,
                               const double wave_s,
                               const double wave_e,
                               const int index)
{

    int xstart=0;
    int xend=0;
    double flux=0;
    double err=0;
    double sn=0;
    double ws=wave_s;
    double we=wave_e;
    char comment[40];
    char qc_key[20];
    cpl_image* snr=NULL;

    if (index != 0) {
        xstart=((ws - s->lambda_min)/s->lambda_step+0.5);
        xend=  ((we - s->lambda_min)/s->lambda_step-0.5);
    } else {
        xstart=1;
        xend=s->size;
        ws=s->lambda_min;
        we=s->lambda_max;
    }
    xstart=(xstart < s->size) ? xstart: s->size;
    xend=(xend < s->size) ? xend: s->size;
    xstart=(xstart < xend) ? xstart: xend-1;

    check(flux=cpl_image_get_mean_window(s->flux,xstart,1,xend,1));
    if (index != 0) {
        sprintf(qc_key,"%s%d VAL",XSH_QC_FLUX,index);
    } else {
        sprintf(qc_key,"%s VAL",XSH_QC_FLUX);
    }
    sprintf(comment,"Flux in %4.0f-%4.0f nm",ws,we);

    if (isnan(flux)) {
        xsh_msg_warning("Calculated a NaN value for the '%s' keyword."
                        " Will force it to -1.0 instead.", qc_key);
        flux = -1.0;
    }
    check(cpl_propertylist_append_double(s->flux_header,qc_key,flux));
    check(cpl_propertylist_set_comment(s->flux_header,qc_key,comment));

    check(err=cpl_image_get_mean_window(s->errs,xstart,1,xend,1));
    if (index != 0) {
        sprintf(qc_key,"%s%d ERR",XSH_QC_FLUX,index);
    } else {
        sprintf(qc_key,"%s ERR",XSH_QC_FLUX);
    }
    sprintf(comment,"Error Flux in %4.0f-%4.0f nm",ws,we);

    if (isnan(err)) {
        xsh_msg_warning("Calculated a NaN value for the '%s' keyword."
                        " Will force it to -1.0 instead.", qc_key);
        err = -1.0;
    }
    cpl_propertylist_append_double(s->flux_header,qc_key,err);
    cpl_propertylist_set_comment(s->flux_header,qc_key,comment);
    if (index != 0) {
        sprintf(qc_key,"%s%d SN",XSH_QC_FLUX,index);
    } else {
        sprintf(qc_key,"%s SN",XSH_QC_FLUX);
    }
    sprintf(comment,"SNR in %4.0f-%4.0f nm",ws,we);
    /* because the extraction may give values of 0 for both flux and err
     * we make sure that flux and err are not both 0. For easy/speed we check
     * their squared value
     */
    double flux2=flux*flux;
    double err2=err*err;

    if( (flux2 >= 1.e-80) && (err2 >=1.e-80) ) {
        snr=cpl_image_duplicate(s->flux);
        cpl_image_divide(snr,s->errs);
        //xsh_msg("flux=%g err=%g",flux,err);
        //cpl_image_flag_bp(snr,s->qual,inst);
        sn=cpl_image_get_mean_window(snr,xstart,1,xend,1);
    }
    //cpl_image_flag_bp(snr,s->qual,inst);
    //sn = (fabs(err) > 1.e-37) ? flux/err : -999;

    if (isnan(sn)) {
        xsh_msg_warning("Calculated a NaN value for the '%s' keyword."
                        " Will force it to -1.0 instead.", qc_key);
        sn = -1.0;
    }
    cpl_propertylist_append_double(s->flux_header,qc_key,sn);
    cpl_propertylist_set_comment(s->flux_header,qc_key,comment);

    cleanup:

    xsh_free_image(&snr);
    return cpl_error_get_code();

}


cpl_error_code
xsh_monitor_spectrum1D_flux(cpl_frame* in_frm,xsh_instrument* instrument)
{

  xsh_spectrum* s1d=NULL;
  const char* s1dname=NULL;
  const char *tag = NULL;
  cpl_frame *product = NULL;

  check( s1dname=cpl_frame_get_filename(in_frm));
  check( s1d=xsh_spectrum_load(in_frm));
  check( tag = cpl_frame_get_tag(in_frm));

  if ( xsh_instrument_get_arm(instrument) == XSH_ARM_UVB){
    check( xsh_monitor_spectrum1D_flux_qc(s1d,450,550,0));

    check( xsh_monitor_spectrum1D_flux_qc(s1d,450,470,1));
    check( xsh_monitor_spectrum1D_flux_qc(s1d,510,530,2));

  }
  else if ( xsh_instrument_get_arm(instrument) == XSH_ARM_VIS){
    xsh_monitor_spectrum1D_flux_qc(s1d,600,999,0);
    xsh_monitor_spectrum1D_flux_qc(s1d,672,680,1);
    xsh_monitor_spectrum1D_flux_qc(s1d,745,756,2);
    xsh_monitor_spectrum1D_flux_qc(s1d,992,999,3);

  }
  else if ( xsh_instrument_get_arm(instrument) == XSH_ARM_NIR){
    xsh_monitor_spectrum1D_flux_qc(s1d,1100,2450,0);
    check(xsh_monitor_spectrum1D_flux_qc(s1d,1514,1548,1));
    if(!xsh_instrument_nir_is_JH(in_frm,instrument)) {
      check(xsh_monitor_spectrum1D_flux_qc(s1d,2214,2243,2));
    }
  }

  check( product = xsh_spectrum_save(s1d,s1dname, tag));

  cleanup:
    xsh_spectrum_free(&s1d);
    xsh_free_frame( &product);
    return cpl_error_get_code();
}

/**@}*/
