/* $Id: fors_tools.c,v 1.22 2012-08-07 15:26:53 cgarcia Exp $
 *
 * This file is part of the FORS Library
 * Copyright (C) 2002-2010 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
 */

/*
 * $Author: cgarcia $
 * $Date: 2012-08-07 15:26:53 $
 * $Revision: 1.22 $
 * $Name: not supported by cvs2svn $
 */

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

#include <fors_tools.h>

#include <fors_pfits.h>
#include <fors_utils.h>

#include <cpl.h>
#include <math.h>
#include <stdbool.h>
#include <assert.h>

/*----------------------------------------------------------------------------*/
/**
 * @defgroup fors_tools       High level functions
 */
/*----------------------------------------------------------------------------*/

/**@{*/

#undef cleanup
#define cleanup \
do { \
    cpl_propertylist_delete(header); \
} while(0)
/**
 * @brief   Correct for extinction, gain, exposure time
 * @param   stars      with magnitudes to be corrected
 * @param   setting    instrument setting
 * @param   ext_coeff  extinction coefficient for this filter
 * @param   dext_coeff extinction coefficient error
 * @param   raw_frame  containing airmass, exposure time information
 * @return  average airmass as determined from raw frame
 */
double
fors_star_ext_corr(fors_star_list *stars, 
                   const fors_setting *setting,
                   double ext_coeff,
                   double dext_coeff,
                   const cpl_frame *raw_frame)
{
    cpl_propertylist *header = NULL;
    
    
    cpl_msg_info(cpl_func, "Extinction correction");
    
    assure( cpl_frame_get_filename(raw_frame) != NULL, return -1, NULL );
    
    header = cpl_propertylist_load(cpl_frame_get_filename(raw_frame), 0);
    assure( !cpl_error_get_code(), return -1,
            "Failed to load %s primary header",
            cpl_frame_get_filename(raw_frame));


    double avg_airmass = fors_get_airmass(header);
    assure( !cpl_error_get_code(), return -1,
            "%s: Could not read airmass",
            cpl_frame_get_filename(raw_frame));

    cpl_msg_indent_more();
    cpl_msg_info(cpl_func, "Exposure time = %f s", setting->exposure_time);
    cpl_msg_info(cpl_func, "Gain          = %f ADU/e-", setting->average_gain);
    cpl_msg_info(cpl_func, "Ext. coeff.   = %f +- %f mag/airmass", 
                 ext_coeff, dext_coeff);
    cpl_msg_info(cpl_func, "Avg. airmass  = %f airmass", avg_airmass);  
    /* The quantity and the unit are both 'airmass' */

    cpl_msg_indent_less();
    
    {
        fors_star *star;
        
        for (star = fors_star_list_first(stars);
             star != NULL;
             star = fors_star_list_next(stars)) {
            star->magnitude_corr = star->magnitude 
                + 2.5*log(setting->average_gain)/log(10)
                + 2.5*log(setting->exposure_time)/log(10)
                - ext_coeff * avg_airmass;

            /* Propagate error from ext.coeff.
               gain, exptime and airmass
               have zero error */
            star->dmagnitude_corr = sqrt(star->dmagnitude * star->dmagnitude
                                         + dext_coeff*dext_coeff * avg_airmass*avg_airmass);
        }
    }

    cleanup;
    return avg_airmass;
}

/**
 * @brief    Create product
 * @param    sources
 * @return   newly allocated table of sources properties
 *
 * The given list of objects may or may not be identified
 */
cpl_table *
fors_create_sources_table(fors_star_list *sources)
{
    cpl_table *t = NULL;
    
    t = cpl_table_new(fors_star_list_size(sources));
    cpl_table_new_column(t, "X", CPL_TYPE_DOUBLE);
    cpl_table_new_column(t, "Y", CPL_TYPE_DOUBLE);
    cpl_table_new_column(t, "FWHM", CPL_TYPE_DOUBLE);
    cpl_table_new_column(t, "A", CPL_TYPE_DOUBLE);
    cpl_table_new_column(t, "B", CPL_TYPE_DOUBLE);
    cpl_table_new_column(t, "THETA", CPL_TYPE_DOUBLE);
    cpl_table_new_column(t, "ELL", CPL_TYPE_DOUBLE);
    cpl_table_new_column(t, "INSTR_MAG", CPL_TYPE_DOUBLE);
    cpl_table_new_column(t, "DINSTR_MAG", CPL_TYPE_DOUBLE);
    cpl_table_new_column(t, "INSTR_CMAG", CPL_TYPE_DOUBLE);
    cpl_table_new_column(t, "DINSTR_CMAG", CPL_TYPE_DOUBLE);
    cpl_table_new_column(t, "CLASS_STAR", CPL_TYPE_DOUBLE);

    cpl_table_new_column(t, "OBJECT", CPL_TYPE_STRING);
    cpl_table_new_column(t, "RA", CPL_TYPE_DOUBLE);
    cpl_table_new_column(t, "DEC", CPL_TYPE_DOUBLE);
    cpl_table_new_column(t, "MAG", CPL_TYPE_DOUBLE);
    cpl_table_new_column(t, "DMAG", CPL_TYPE_DOUBLE);
    cpl_table_new_column(t, "CAT_MAG", CPL_TYPE_DOUBLE);
    cpl_table_new_column(t, "DCAT_MAG", CPL_TYPE_DOUBLE);
    cpl_table_new_column(t, "COLOR", CPL_TYPE_DOUBLE);
    cpl_table_new_column(t, "DCOLOR", CPL_TYPE_DOUBLE);
    cpl_table_new_column(t, "COV_CATM_COL", CPL_TYPE_DOUBLE);
    cpl_table_new_column(t, "USE_CAT", CPL_TYPE_INT);
    /* Shift in x and y between initial guess FITS header WCS position
       and measured position */
    cpl_table_new_column(t, "SHIFT_X", CPL_TYPE_DOUBLE); 
    cpl_table_new_column(t, "SHIFT_Y", CPL_TYPE_DOUBLE);
    cpl_table_new_column(t, "ZEROPOINT", CPL_TYPE_DOUBLE);
    cpl_table_new_column(t, "DZEROPOINT", CPL_TYPE_DOUBLE);
    cpl_table_new_column(t, "WEIGHT", CPL_TYPE_DOUBLE);

    {
        fors_star *s;
        int i;
        for (s = fors_star_list_first(sources), i = 0;
             s != NULL;
             s = fors_star_list_next(sources), i++) {

            const fors_std_star *id = s->id;
            
            cpl_table_set_double(t, "X", i, s->pixel->x);
            cpl_table_set_double(t, "Y", i, s->pixel->y);
            cpl_table_set_double(t, "FWHM", i, s->fwhm);
            cpl_table_set_double(t, "A", i, s->semi_major);
            cpl_table_set_double(t, "B", i, s->semi_minor);
            cpl_table_set_double(t, "THETA", i, s->orientation);
            cpl_table_set_double(t, "ELL", i, fors_star_ellipticity(s, NULL));
            cpl_table_set_double(t, "INSTR_MAG", i, s->magnitude);
            cpl_table_set_double(t, "DINSTR_MAG", i, s->dmagnitude);
            cpl_table_set_double(t, "INSTR_CMAG", i, s->magnitude_corr);
            cpl_table_set_double(t, "DINSTR_CMAG", i, s->dmagnitude_corr);
            cpl_table_set_double(t, "CLASS_STAR", i, s->stellarity_index);
            cpl_table_set_double(t, "WEIGHT", i, s->weight);
            
            if (id != NULL)
            {
                cpl_table_set_string(t, "OBJECT",       i, id->name); /* possibly NULL */
                cpl_table_set_double(t, "RA",           i, id->ra);
                cpl_table_set_double(t, "DEC",          i, id->dec);
                cpl_table_set_double(t, "MAG",          i, id->magnitude);
                cpl_table_set_double(t, "DMAG",         i, id->dmagnitude);
                cpl_table_set_double(t, "CAT_MAG",      i, id->cat_magnitude);
                cpl_table_set_double(t, "DCAT_MAG",     i, id->dcat_magnitude);
                cpl_table_set_double(t, "COLOR",        i, id->color);
                cpl_table_set_double(t, "DCOLOR",       i, id->dcolor);
                cpl_table_set_double(t, "COV_CATM_COL", i, id->cov_catm_color);
                cpl_table_set_double(t, "SHIFT_X",      i,  s->pixel->x
                                                            - id->pixel->x);
                cpl_table_set_double(t, "SHIFT_Y",      i,  s->pixel->y
                                                            - id->pixel->y);
                cpl_table_set_double(t, "ZEROPOINT",    i, 
                                     fors_star_get_zeropoint(s, NULL));
                cpl_table_set_double(t, "DZEROPOINT",   i, 
                                     fors_star_get_zeropoint_err(s, NULL));
                /* fit this magnitude in fors_photometry? (fit = !trusted) */
                cpl_table_set_int   (t, "USE_CAT",      i,
                                            ((id->trusted) ? 1 : 0));
            }
            else {
                cpl_table_set_invalid(t, "RA" , i);
                cpl_table_set_invalid(t, "DEC", i);
                cpl_table_set_invalid(t, "MAG", i);
                cpl_table_set_invalid(t, "DMAG", i);
                cpl_table_set_invalid(t, "SHIFT_X", i);
                cpl_table_set_invalid(t, "SHIFT_Y", i);
                cpl_table_set_invalid(t, "ZEROPOINT", i);
                cpl_table_set_invalid(t, "DZEROPOINT", i);
            }
        }
    }

    return t;
}

#undef cleanup
#define cleanup \
do { \
    fors_image_delete(&image); \
    fors_image_delete(&image2); \
} while(0)
/**
 * @brief    Compute fixed pattern noise in flat field
 * @param    master                master image
 * @param    convert_ADU           factor to convert from master units to ADU
 * @param    master_noise          master noise (ADU) for a shift of zero
 * @return   fixed pattern noise. The master noise is quadratically subtracted
 */
double
fors_fixed_pattern_noise(const fors_image *master,
                         double convert_ADU,
                         double master_noise)
{
    double master_fixed_pattern_noise;
    fors_image *image = NULL;
    fors_image *image2 = NULL;

    assure( master != NULL, return -1, NULL );

    /* Use central 101x101 window 
       and 101x101 window shifted (10, 10) from center
    */
    if (fors_image_get_size_x(master) >= 121 &&
        fors_image_get_size_y(master) >= 121) {
        
        int mid_x = (fors_image_get_size_x(master) + 1) / 2;
        int mid_y = (fors_image_get_size_y(master) + 1) / 2;
        
        image = fors_image_duplicate(master);       
        fors_image_crop(image, 
                        mid_x - 50, mid_y - 50,
                        mid_x + 50, mid_y + 50);
        
        image2 = fors_image_duplicate(master);       
        fors_image_crop(image2, 
                        mid_x + 10 - 50, mid_y + 10 - 50,
                        mid_x + 10 + 50, mid_y + 10 + 50);

        fors_image_subtract(image, image2);

        master_fixed_pattern_noise = 
            fors_image_get_stdev(image, NULL) / sqrt(2);

        /* Convert to ADU */
        master_fixed_pattern_noise *= convert_ADU;

        /* Subtract photon noise */
        if (master_fixed_pattern_noise >= master_noise) {
            
            master_fixed_pattern_noise = sqrt(master_fixed_pattern_noise*
                                              master_fixed_pattern_noise
                                              -
                                              master_noise*
                                              master_noise);
        }
        else {
            cpl_msg_warning(cpl_func,
                            "Zero-shift noise (%f ADU) is greater than "
                            "accumulated zero-shift and fixed pattern noise (%f ADU), "
                            "setting fixed pattern noise to zero",
                            master_noise,
                            master_fixed_pattern_noise);
            master_fixed_pattern_noise = 0;
        }
    }
    else {
        cpl_msg_warning(cpl_func,
                        "Master flat too small (%dx%d), "
                        "need size 121x121 to compute master flat "
                        "fixed pattern noise",
                        fors_image_get_size_x(master),
                        fors_image_get_size_y(master));
        master_fixed_pattern_noise = -1;
    }

    cleanup;
    return master_fixed_pattern_noise;
}


#undef cleanup
#define cleanup \
do { \
    fors_image_delete(&image); \
    fors_image_delete(&image2); \
} while(0)
/**
 * @brief    Compute fixed pattern noise in bias
 * @param    first_raw             First raw bias frame
 * @param    second_raw            Second raw bias frame
 * @param    ron                   Read out noise (ADU) for a shift of zero
 * @return   fixed pattern noise. The ron is quadratically subtracted
 */
double
fors_fixed_pattern_noise_bias(const fors_image *first_raw,
                              const fors_image *second_raw,
                              double ron)
{
    double bias_fixed_pattern_noise;
    fors_image *image = NULL;
    fors_image *image2 = NULL;
    int nx, ny;

    assure( first_raw != NULL, return -1, NULL );
    assure( second_raw != NULL, return -1, NULL );

    /* 
     * Extract the largest possible two windows shifted (10, 10) 
     */

    nx = fors_image_get_size_x(first_raw);
    ny = fors_image_get_size_y(first_raw);

    image = fors_image_duplicate(first_raw);       
    fors_image_crop(image, 
                    1, 1,
                    nx - 10, ny - 10);
        
    image2 = fors_image_duplicate(second_raw);       
    fors_image_crop(image2, 
                    11, 11,
                    nx, ny);

    fors_image_subtract(image, image2);

    bias_fixed_pattern_noise = fors_image_get_stdev_robust(image, 50, NULL) 
                             / sqrt(2);

    /* 
     * Subtract ron quadratically
     */

    if (bias_fixed_pattern_noise > ron) {
            
        bias_fixed_pattern_noise = sqrt(bias_fixed_pattern_noise *
                                        bias_fixed_pattern_noise
                                        -
                                        ron * ron);
    }
    else {
        cpl_msg_warning(cpl_func,
                        "Zero-shift noise (%f ADU) is greater than "
                        "accumulated zero-shift and fixed pattern "
                        "noise (%f ADU), "
                        "setting fixed pattern noise to zero",
                        ron,
                        bias_fixed_pattern_noise);
        bias_fixed_pattern_noise = 0;
    }

    cleanup;
    return bias_fixed_pattern_noise;
}


#undef cleanup
#define cleanup
/**
 * @brief    Compute average airmass
 * @param    header               header to read from
 * @return   average airmass
 */
double
fors_get_airmass(const cpl_propertylist *header)
{
  double airmass_start, airmass_end;
  airmass_start = cpl_propertylist_get_double(header, FORS_PFITS_AIRMASS_START);
  assure( !cpl_error_get_code(), return -1, 
          "Could not read %s from header", 
          FORS_PFITS_AIRMASS_START);
  
  airmass_end = cpl_propertylist_get_double(header, FORS_PFITS_AIRMASS_END);
  if(cpl_error_get_code())
  {
      cpl_msg_warning(cpl_func, "Could not read %s. Using only keyword %s",
                      FORS_PFITS_AIRMASS_END, FORS_PFITS_AIRMASS_START);
      cpl_error_reset();
      return airmass_start;
  }
  
  return 0.5 * (airmass_start + airmass_end);
}

/**@}*/
