/* $Id: fors_setting.c,v 1.19 2010-09-14 07:49:30 cizzo 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: cizzo $
 * $Date: 2010-09-14 07:49:30 $
 * $Revision: 1.19 $
 * $Name: not supported by cvs2svn $
 */

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

#include <fors_setting.h>

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

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

/*----------------------------------------------------------------------------*/
/**
 * @defgroup fors_setting           Instrument setting
 */
/*----------------------------------------------------------------------------*/

/**@{*/

static fors_setting *
fors_setting_new_level(const cpl_frame *raw, cpl_msg_severity level);


/**
 * @brief  Create setting from FITS header
 * @param  raw       raw frame
 * @return newly allocated instrument setting
 *
 * The instrument setting is printed on the CPL_MSG_INFO level
 */
fors_setting *
fors_setting_new(const cpl_frame *raw)
{
    return fors_setting_new_level(raw, CPL_MSG_INFO);
}

#undef cleanup
#define cleanup \
do { \
    cpl_propertylist_delete(header); \
} while (0)

/**
 * @brief  Create setting from FITS header
 * @param  raw       raw frame
 * @param  level     print instrument setting on this message level
 * @return newly allocated instrument setting
 */
static fors_setting *
fors_setting_new_level(const cpl_frame *raw, cpl_msg_severity level)
{
    fors_setting *s = NULL;
    const char *filename;
    cpl_propertylist *header = NULL;

    assure( raw != NULL, return NULL, NULL );

    fors_msg(level, "Instrument setting:");
    cpl_msg_indent_more();

    /* Load header */
    filename = cpl_frame_get_filename(raw);
    assure( filename != NULL, return NULL, "Missing frame filename" );
    header = cpl_propertylist_load(filename, 0);
    assure( !cpl_error_get_code(), return NULL, 
            "Could not read %s primary header", filename );

    cpl_msg_debug(cpl_func, "Reading setting from %s", filename );
        
    /* Read relevant contents */
    s = cpl_malloc(sizeof(*s));

    s->filter_name = NULL;
    s->read_clock = NULL;
    s->chip_id = NULL;
    s->instrument = NULL;
    s->version = NULL;

    s->binx = cpl_propertylist_get_int(header, FORS_PFITS_BINX);
    if (CPL_ERROR_TYPE_MISMATCH == cpl_error_get_code()) {
        cpl_msg_error(cpl_func, 
                      "Keyword %s is not an integer", FORS_PFITS_BINX);
    }
    assure( !cpl_error_get_code(), return s,
            "Could not read %s from %s header", 
            FORS_PFITS_BINX, filename);
    
    fors_msg(level, "Detector x-binning (%s) = %d", FORS_PFITS_BINX, s->binx);

    s->biny = cpl_propertylist_get_int(header, FORS_PFITS_BINY);
    if (CPL_ERROR_TYPE_MISMATCH == cpl_error_get_code()) {
        cpl_msg_error(cpl_func, 
                      "Keyword %s is not an integer", FORS_PFITS_BINY);
    }
    assure( !cpl_error_get_code(), return s,
            "Could not read %s from %s header", 
            FORS_PFITS_BINY, filename);

    fors_msg(level, "Detector y-binning (%s) = %d", FORS_PFITS_BINY, s->biny);

    
    if (cpl_propertylist_has(header, FORS_PFITS_PRESCANX)) {
        
        s->prescan_x = cpl_propertylist_get_int(header, FORS_PFITS_PRESCANX);
        if (CPL_ERROR_TYPE_MISMATCH == cpl_error_get_code()) {
            cpl_msg_error(cpl_func, 
                          "Keyword %s is not an integer", FORS_PFITS_PRESCANX);
        }
        assure( !cpl_error_get_code(), return s,
                "Could not read %s from %s header", 
                FORS_PFITS_PRESCANX, filename);
        
    }
    else {
        s->prescan_x = 0;
    }
    
    fors_msg(level, "Detector x-prescan (%s) = %d",
             FORS_PFITS_PRESCANX, s->prescan_x);


    if (cpl_propertylist_has(header, FORS_PFITS_PRESCANY)) {
        
        s->prescan_y = cpl_propertylist_get_int(header, FORS_PFITS_PRESCANY);
        if (CPL_ERROR_TYPE_MISMATCH == cpl_error_get_code()) {
            cpl_msg_error(cpl_func, 
                          "Keyword %s is not an integer", FORS_PFITS_PRESCANY);
        }
        assure( !cpl_error_get_code(), return s,
                "Could not read %s from %s header", 
                FORS_PFITS_PRESCANY, filename);
        
    }
    else {
        s->prescan_y = 0;
    }
    
    fors_msg(level, "Detector y-prescan (%s) = %d",
             FORS_PFITS_PRESCANY, s->prescan_y);


    if (cpl_propertylist_has(header, FORS_PFITS_FILTER_NAME)) {
        
        s->filter_name = cpl_propertylist_get_string(header, 
                                                     FORS_PFITS_FILTER_NAME);
        if (CPL_ERROR_TYPE_MISMATCH == cpl_error_get_code()) {
            cpl_msg_error(cpl_func, 
                          "Keyword %s is not a string", 
                          FORS_PFITS_FILTER_NAME);
        }
        assure( !cpl_error_get_code(), return s,
                "Could not read %s from %s header", 
                FORS_PFITS_FILTER_NAME, filename);
    
        /* Allocate new string (after the check for NULL and
           before deallocating the cpl_propertylist) */
        s->filter_name = cpl_strdup(s->filter_name);

        fors_msg(level, "Filter name (%s) = %s", 
                     FORS_PFITS_FILTER_NAME, s->filter_name);
        
        cpl_errorstate  es = cpl_errorstate_get();
        s->filterband = fors_instrument_filterband_get_by_setting(s);
        /* for some reason, the old code did not set an error, so reset */
        cpl_errorstate_set(es);
        
        bool recognized = fors_instrument_filterband_is_defined(s->filterband);
        
/* %%% New part...  */

        if (!recognized) {
            cpl_msg_warning(cpl_func, "Non-standard filter...");
            /*s->filter = FILTER_U;*/ //fixme
            cpl_free(s->filter_name);
            s->filter_name = NULL;
        }

/***
        assure( recognized, return s, "%s: %s: Unrecognized filter name: '%s'",
                filename, FORS_PFITS_FILTER_NAME, s->filter_name );
***/

    }
    else {
        /* No raw frame filter (e.g. rawbias) */
        s->filterband = fors_instrument_filterband_value_unknown();
        /*s->filter = FILTER_U;*/ //fixme
        s->filter_name = NULL;
    }
    
    if (cpl_propertylist_has(header, FORS_PFITS_EXPOSURE_TIME)) {
        s->exposure_time = cpl_propertylist_get_double(header, 
                           FORS_PFITS_EXPOSURE_TIME);
        if (CPL_ERROR_TYPE_MISMATCH == cpl_error_get_code()) {
            cpl_msg_error(cpl_func, 
                          "Keyword %s is not a double precision type", 
                          FORS_PFITS_EXPOSURE_TIME);
        }
        assure( !cpl_error_get_code(), return s, 
                "Could not read %s from %s header", 
                FORS_PFITS_EXPOSURE_TIME, filename);
        
        fors_msg(level, "Exposure time (%s) = %f s", 
                     FORS_PFITS_EXPOSURE_TIME, s->exposure_time);
    }
    else {
        cpl_msg_debug(cpl_func, "%s: Missing keyword '%s'",
                      filename, FORS_PFITS_EXPOSURE_TIME);
    }

    s->pixel_scale = cpl_propertylist_get_double(header, FORS_PFITS_PIXSCALE);
    if (CPL_ERROR_TYPE_MISMATCH == cpl_error_get_code()) {
        cpl_msg_error(cpl_func, 
                      "Keyword %s is not a double precision type", 
                      FORS_PFITS_PIXSCALE);
    }
    assure( !cpl_error_get_code(), return s, 
            "Could not read %s from %s header", 
            FORS_PFITS_PIXSCALE, filename);

    fors_msg(level, "Pixel scale (%s) = %f arcsec/pixel", 
                 FORS_PFITS_PIXSCALE, s->pixel_scale);

    assure( s->pixel_scale > 0, return s,
            "%s: %s is non-positive (%f arcsec/pixel)",
            filename, FORS_PFITS_PIXSCALE, s->pixel_scale);
    
    s->version = fors_dfs_pipeline_version(header, &(s->instrument));
    assure( !cpl_error_get_code(), return s,
            "Could not read instrument version from %s header", 
            filename);

    fors_msg(level, "Instrument (%s) = %s", 
             FORS_PFITS_INSTRUME, s->instrument);

    {
	int outputs = cpl_propertylist_get_int(header, FORS_PFITS_OUTPUTS);
        if (CPL_ERROR_TYPE_MISMATCH == cpl_error_get_code()) {
            cpl_msg_error(cpl_func, 
                          "Keyword %s is not integer", 
                          FORS_PFITS_OUTPUTS);
        }
	assure( !cpl_error_get_code(), return s, 
		"Could not read %s from %s header", 
		FORS_PFITS_OUTPUTS, filename);

	fors_msg(level, "Output ports (%s) = %d", 
		 FORS_PFITS_OUTPUTS, outputs);
	
	/* Support only new and old FORS data, where the approximation
	   about using average ron/conad is fine. 
	*/
	assure( outputs == 1 || outputs == 4, return s,
		"1 or 4 output ports required");

	int i;
	s->average_gain = 0;
	s->ron = 0;
	for(i = 0; i < outputs; i++) {
	    
	    double conad = cpl_propertylist_get_double(header, 
                                                       FORS_PFITS_CONAD[i]);
            if (CPL_ERROR_TYPE_MISMATCH == cpl_error_get_code()) {
                cpl_msg_error(cpl_func, 
                              "Keyword %s is not a double precision type", 
                              FORS_PFITS_CONAD[i]);
            }
	    assure( !cpl_error_get_code(), return s, 
		    "Could not read %s from %s header", 
		    FORS_PFITS_CONAD[i], filename);
	    
	    fors_msg(level, "Gain factor (%s) = %.2f e-/ADU", 
		     FORS_PFITS_CONAD[i], conad);
	    
	    assure( conad > 0, return s, "%s: Illegal %s: %f, must be positive",
		    filename, FORS_PFITS_CONAD[i], conad);
	    
	    double ron = cpl_propertylist_get_double(header, FORS_PFITS_RON[i]);
            if (CPL_ERROR_TYPE_MISMATCH == cpl_error_get_code()) {
                cpl_msg_error(cpl_func, 
                              "Keyword %s is not a double precision type", 
                              FORS_PFITS_RON[i]);
            }
	    assure( !cpl_error_get_code(), return s, 
		    "Could not read %s from %s header", 
		    FORS_PFITS_RON[i], filename);
	    
	    assure( ron > 0, return s,
		    "%s: Illegal %s: %f, must be positive",
		    filename, FORS_PFITS_RON[i], ron);
	    
	    ron /= conad; /* electrons -> ADU */

	    fors_msg(level, "Nominal read-out-noise (%s) = %.2f ADU", 
		     FORS_PFITS_RON[i], ron);

	    /* accumulate */
	    s->ron += ron;
	    s->average_gain += 1.0/conad;
	}

	/* get average */
	s->ron          /= outputs;
	s->average_gain /= outputs;

	if (outputs > 1) {
	    fors_msg(level, "Average gain factor = %.2f e-/ADU", 
		     1.0/s->average_gain);

	    fors_msg(level, "Read-out-noise = %.2f ADU", 
		     s->ron);
	}
    }
    
    s->read_clock = cpl_propertylist_get_string(header, FORS_PFITS_READ_CLOCK);
    if (CPL_ERROR_TYPE_MISMATCH == cpl_error_get_code()) {
        cpl_msg_error(cpl_func, 
                      "Keyword %s is not a string", 
                      FORS_PFITS_READ_CLOCK);
    }
    assure( !cpl_error_get_code(), return s, 
            "Could not read %s from %s header", 
            FORS_PFITS_READ_CLOCK, filename);

    s->read_clock = cpl_strdup(s->read_clock);

    fors_msg(level, "Readout clock pattern (%s) = %s", 
                 FORS_PFITS_READ_CLOCK, s->read_clock);
    

    s->chip_id = cpl_propertylist_get_string(header, FORS_PFITS_CHIP_ID);
    if (CPL_ERROR_TYPE_MISMATCH == cpl_error_get_code()) {
        cpl_msg_error(cpl_func, 
                      "Keyword %s is not a string", 
                      FORS_PFITS_CHIP_ID);
    }
    assure( !cpl_error_get_code(), return s, 
            "Could not read %s from %s header", 
            FORS_PFITS_CHIP_ID, filename);

    s->chip_id = cpl_strdup(s->chip_id);


    fors_msg(level, "Chip ID (%s) = %s", 
                 FORS_PFITS_CHIP_ID, s->chip_id);

    cpl_msg_indent_less();
    
    cleanup;
    return s;
}

#undef cleanup
#define cleanup \
do { \
    fors_setting_delete(&input_setting); \
} while (0)

/**
 * @brief  Verify that instrument settings are compatible
 * @param  ref_setting     reference to compare with
 * @param  frame           input frame
 * @param  setting         (output) if non-NULL, the input frame setting is returned
 *
 * If the instrument settings are incompatible, a warning is printed but
 * the function does not fail
 */
void
fors_setting_verify(const fors_setting *ref_setting, const cpl_frame *frame,
		    fors_setting **setting)
{
    fors_setting *input_setting = NULL;

    assure( ref_setting != NULL, return, NULL );
    assure( frame       != NULL, return, NULL );
    assure( cpl_frame_get_filename(frame) != NULL, return, NULL );

    input_setting = fors_setting_new_level(frame, CPL_MSG_DEBUG);

    assure( !cpl_error_get_code(), return, 
            "Could not get %s instrument setting",
            cpl_frame_get_filename(frame));
    

    if (ref_setting->binx != input_setting->binx ||
        ref_setting->biny != input_setting->biny) {
        cpl_msg_warning(cpl_func, "Incompatible CCD binning: %dx%d",
                        input_setting->binx, input_setting->biny);
    }

    if (ref_setting->filter_name != NULL && 
        input_setting->filter_name != NULL &&
        strcmp(ref_setting->filter_name, input_setting->filter_name) != 0) {
        cpl_msg_warning(cpl_func, "Incompatible filter names: '%s'",
                        input_setting->filter_name);
    }

    if ((ref_setting->prescan_x != input_setting->prescan_x &&
         input_setting->prescan_x != 0) ||
        (ref_setting->prescan_y != input_setting->prescan_y &&
         input_setting->prescan_y != 0)) {
        cpl_msg_warning(cpl_func, "Incompatible CCD x-prescan areas: %dx%d",
                        input_setting->prescan_x,
                        input_setting->prescan_y);
    }

    /* no check on exposure time */

    if (fabs((ref_setting->average_gain - input_setting->average_gain) /
             ref_setting->average_gain) > 0.01) {

        cpl_msg_warning(cpl_func, "Incompatible gain factor: %f e-/ADU",
                        input_setting->average_gain);
    }


    if (fabs((ref_setting->ron - input_setting->ron) /
             ref_setting->ron) > 0.01) {
        cpl_msg_warning(cpl_func, "Incompatible read-out-noise: %f ADU",
                        input_setting->ron);
    }

    if (fabs((ref_setting->pixel_scale - input_setting->pixel_scale) /
             ref_setting->pixel_scale) > 0.01) {
        cpl_msg_warning(cpl_func, "Incompatible pixel scale: %f arcsec/pixel",
                        input_setting->pixel_scale);
    }

    if (strcmp(ref_setting->chip_id, input_setting->chip_id) != 0) {
        cpl_msg_warning(cpl_func, "Incompatible chip ID: '%s'",
                        input_setting->chip_id);
    }

    if (strcmp(ref_setting->read_clock, input_setting->read_clock) != 0) {
        cpl_msg_warning(cpl_func, "Incompatible readout clock pattern: '%s'",
                        input_setting->read_clock);
    }

    if (strcmp(ref_setting->instrument, input_setting->instrument) != 0) {
        cpl_msg_warning(cpl_func, "Incompatible instrument name: '%s'",
                        input_setting->instrument);
    }

    if (strcmp(ref_setting->version, input_setting->version) != 0) {
        cpl_msg_warning(cpl_func, "Incompatible version: '%s'",
                        input_setting->version);
    }   

    /* Return setting if requested */
    if (setting != NULL) {
        *setting = input_setting;
        input_setting = NULL;
    }

    cleanup;
    return;

}



/**
 * @brief  Deallocate and and set pointer to NULL
 * @param  s      setting to delete
 */
void fors_setting_delete(fors_setting **s)
{
    if (s && *s) {
        if ((*s)->filter_name != NULL) cpl_free((void *)((*s)->filter_name));
        cpl_free((void *)((*s)->read_clock));
        cpl_free((void *)((*s)->chip_id));
        cpl_free((void *)((*s)->version));
        cpl_free((void *)((*s)->instrument));
        cpl_free(*s); *s = NULL;
    }
    return;
}

/**@}*/
