/* $Id: fors_data.c,v 1.44 2013-08-07 13:26:50 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: 2013-08-07 13:26:50 $
 * $Revision: 1.44 $
 * $Name: not supported by cvs2svn $
 */

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

#include <fors_data.h>

#include <cpl_wcs.h>
#include <fors_star.h>
#include <fors_instrument.h>
#include <fors_std_star.h>
#include <fors_utils.h>

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


const char *const FORS_DATA_PHOT_FILTER    = "FILTER";
const char *const FORS_DATA_PHOT_EXTCOEFF  = "EXT";
const char *const FORS_DATA_PHOT_DEXTCOEFF  = "DEXT";
const char *const FORS_DATA_PHOT_ZEROPOINT = "ZPOINT";
const char *const FORS_DATA_PHOT_DZEROPOINT = "DZPOINT";
const char *const FORS_DATA_PHOT_COLORTERM = "COL";
const char *const FORS_DATA_PHOT_DCOLORTERM = "DCOL";


#undef cleanup
#define cleanup \
do { \
    cpl_wcs_delete(wcs); \
    cpl_matrix_delete(from); \
    cpl_matrix_delete(to); \
    cpl_array_delete(status); \
} while(0)

/**
 * @brief    Apply WCS solution to catalogue stars
 * @param    stars       list of stars
 */
void
fors_std_star_list_apply_wcs(               fors_std_star_list      *stars,
                                            const cpl_propertylist  *header)
{
    cpl_wcs *wcs = NULL;

    cpl_matrix *from = NULL;
    cpl_matrix *to = NULL;
    cpl_array *status = NULL;
    
    cassure_automsg(                        stars != NULL,
                                            CPL_ERROR_NULL_INPUT,
                                            return);
    cassure_automsg(                        header != NULL,
                                            CPL_ERROR_NULL_INPUT,
                                            return);

    if (fors_std_star_list_size(stars) == 0) {
        cleanup;
        return;
    }
    
    wcs = cpl_wcs_new_from_propertylist(header);
    assure( !cpl_error_get_code(), return,
            "Failed to get WCS from header");
    
    {
        fors_std_star *star;
        int i;

        from = cpl_matrix_new(fors_std_star_list_size(stars), 2);
        
        for (star = fors_std_star_list_first(stars), i = 0;
             star != NULL;
             star = fors_std_star_list_next(stars), i++) {
            
            cpl_matrix_set(from, i, 0, star->ra);
            cpl_matrix_set(from, i, 1, star->dec);
        }
        
        cpl_wcs_convert(wcs, from,
                             &to, &status,
                             CPL_WCS_WORLD2PHYS);

        /* The WCSLIB call sometimes fails with the error message
           "WCSLIB One or more input coordinates invalid",
           while all status flags are 0.
        */
        if (cpl_error_get_code() == CPL_ERROR_UNSPECIFIED) {
            cpl_msg_warning(cpl_func,
                            "Ignoring WCSLIB unspecified error");
            cpl_error_reset();
        }

        assure( !cpl_error_get_code(), return,
                "Failed to convert from world to physical coordinates");

        assure( cpl_matrix_get_ncol(to) == 2,
                return, "%"CPL_SIZE_FORMAT" columns, 2 expected",
                cpl_matrix_get_ncol(to));

        assure( cpl_matrix_get_nrow(to) == fors_std_star_list_size(stars),
                return, "%"CPL_SIZE_FORMAT" rows, %d expected",
                cpl_matrix_get_nrow(to), fors_std_star_list_size(stars));
        
        assure( status != NULL, return, NULL );

        assure( cpl_array_get_size(status) == fors_std_star_list_size(stars),
                return, "Status array size is %"CPL_SIZE_FORMAT", %d expected",
                cpl_array_get_size(status), fors_std_star_list_size(stars));
        
        for (star = fors_std_star_list_first(stars), i = 0;
             star != NULL;
             star = fors_std_star_list_next(stars), i++) {
            
            if (cpl_array_get_int(status, i, NULL) != 0) {
                cpl_msg_warning(cpl_func, "Catalogue star %d: "
                                "non-zero status = %d from WCSLIB",
                                i, cpl_array_get_int(status, i, NULL));
            }
            star->pixel->x = cpl_matrix_get(to, i, 0);
            star->pixel->y = cpl_matrix_get(to, i, 1);
        }
    }

#if 0 /* pre WCSLIB code */
    double deg_pr_pixel = 0.1/3600;
    fors_std_star *star;

    cpl_msg_warning(cpl_func,
                    "WCSLIB not available, "
                    "applying fake transformation");
    
    for (star = fors_std_star_list_first(stars);
         star != NULL;
         star = fors_std_star_list_next(stars)) {
        
        star->pixel->x = (star->ra  / deg_pr_pixel - 293000)/10;
        star->pixel->y = (star->dec / deg_pr_pixel / 10 + 169800)/10;
    }
#endif
    
    cleanup;
    return;
}


#undef cleanup
#define cleanup \
do { \
    cpl_table_delete(t); \
} while (0)

/**
 * @brief    Load photometry table data
 * @param    phot_table_frame    to load
 * @param    setting             expected instrument setting
 * @param    color_term          (output) if non-NULL, color term
 * @param    dcolor_term          (output) if non-NULL, color term error
 * @param    ext_coeff           (output) extinction coefficient
 * @param    dext_coeff           (output) extinction coefficient error
 * @param    expected_zeropoint  (output) if non-NULL, expected zeropoint
 * @param    dexpected_zeropoint  (output) if non-NULL, expected zeropoint error 
 */

void fors_phot_table_load(const cpl_frame *phot_table_frame,
                          const fors_setting *setting,
                          double *color_term,
                          double *dcolor_term,
                          double *ext_coeff,
                          double *dext_coeff,
                          double *expected_zeropoint,
                          double *dexpected_zeropoint)
{
    cpl_table *t = NULL;
    
    assure( setting != NULL, return, NULL );
/* %%%
    assure( setting->filter_name != NULL, return, "No filter name provided");
*/
    assure( phot_table_frame != NULL, return, NULL );
    /* color_term may be NULL */
    /* assure( ext_coeff != NULL, return, NULL ); it may also be NULL (Carlo) */
    /* expected_zeropoint may be NULL */

    assure( (color_term == NULL) == (dcolor_term == NULL), return, NULL );
    assure( (ext_coeff == NULL) == (dext_coeff == NULL), return, NULL );
    assure( (expected_zeropoint == NULL) == (dexpected_zeropoint == NULL), 
            return, NULL );

    assure( cpl_frame_get_filename(phot_table_frame) != NULL, return, NULL );

    if (color_term) {
        *color_term = 0.0;
        *dcolor_term = 0.0;
    }
    if (ext_coeff) {
        *ext_coeff = 0.0;
        *dext_coeff = 0.0;
    }
    if (expected_zeropoint) {
        *expected_zeropoint = 0.0;
        *dexpected_zeropoint = 0.0;
    }

    if (setting->filter_name == NULL) {
        cpl_msg_warning(cpl_func, "Zeropoint computation is not supported "
                        "for non-standard filters");
        return;
    }

    /* Verify instrument setting */
    //fixme: for user-friendliness we should verify the setting, except the filter
    //        (because this table contains data for all filters)
    if (0) {
    fors_setting_verify(setting, phot_table_frame, NULL);
    assure( !cpl_error_get_code(), return, 
            "Could not verify %s setting", 
            cpl_frame_get_filename(phot_table_frame));
    }

    t = cpl_table_load(cpl_frame_get_filename(phot_table_frame), 1, 1);
    
    assure( !cpl_error_get_code(), return, "Could not load %s",
            cpl_frame_get_filename(phot_table_frame));

    assure( cpl_table_get_nrow(t) > 0, return,
            "Empty table %s", cpl_frame_get_filename(phot_table_frame));

    {
        const char *const colname[] = {
            FORS_DATA_PHOT_FILTER,
            FORS_DATA_PHOT_EXTCOEFF,
            FORS_DATA_PHOT_DEXTCOEFF,
            FORS_DATA_PHOT_ZEROPOINT,
            FORS_DATA_PHOT_DZEROPOINT,
            FORS_DATA_PHOT_COLORTERM,
            FORS_DATA_PHOT_DCOLORTERM};
        
        const cpl_type coltype[] = {CPL_TYPE_STRING,
                                    CPL_TYPE_DOUBLE,
                                    CPL_TYPE_DOUBLE,
                                    CPL_TYPE_DOUBLE,
                                    CPL_TYPE_DOUBLE,
                                    CPL_TYPE_DOUBLE,
                                    CPL_TYPE_DOUBLE};

        int colrequired[7]; /* same # of elements as above */

        colrequired[0] = 1;
        colrequired[1] = ext_coeff ? 1 : 0;
        colrequired[2] = ext_coeff ? 1 : 0;
        colrequired[3] = expected_zeropoint ? 1 : 0;
        colrequired[4] = expected_zeropoint ? 1 : 0;
        colrequired[5] = color_term ? 1 : 0;
        colrequired[6] = color_term ? 1 : 0;

        unsigned i;
        for (i = 0; i < sizeof(colname) / sizeof(*colname); i++) {
            
            if (colrequired[i]) {
                assure( cpl_table_has_column(t, colname[i]), return,
                        "%s: Missing column %s",
                        cpl_frame_get_filename(phot_table_frame), colname[i]);
                
                assure( cpl_table_get_column_type(t, colname[i]) == coltype[i],
                        return,
                        "%s column %s type is %s, %s expected",
                        cpl_frame_get_filename(phot_table_frame),
                        colname[i],
                        fors_type_get_string(cpl_table_get_column_type(t, 
                        colname[i])),
                        fors_type_get_string(coltype[i]));
            
                assure( cpl_table_count_invalid(t, colname[i]) == 0, return,
                        "%s column %s has invalid values",
                        cpl_frame_get_filename(phot_table_frame),
                        colname[i]);
             }
        }
    }
    /* Input validation done */
    
    cpl_msg_debug(cpl_func, "Searching for filter: %s", setting->filter_name);
            
    bool found = false;
    int i;
    for (i = 0; i < cpl_table_get_nrow(t) && !found; i++) {
        const char *phot_filter = cpl_table_get_string(t, FORS_DATA_PHOT_FILTER, i);

        assure( phot_filter != NULL, return, "%s, row %d: Null %s",
                cpl_frame_get_filename(phot_table_frame), i+1, FORS_DATA_PHOT_FILTER);
        
        if (strcmp(setting->filter_name, phot_filter) == 0) {

            found = true;
            
            if (color_term != NULL) {
                *color_term  = cpl_table_get_double(t, FORS_DATA_PHOT_COLORTERM, i, NULL);
                *dcolor_term = cpl_table_get_double(t, FORS_DATA_PHOT_DCOLORTERM, i, NULL);
            }
            
            if (ext_coeff != NULL) {
                *ext_coeff  = cpl_table_get_double(t, FORS_DATA_PHOT_EXTCOEFF, i, NULL);
                *dext_coeff = cpl_table_get_double(t, FORS_DATA_PHOT_DEXTCOEFF, i, NULL);
            }

            if (expected_zeropoint != NULL) {
                *expected_zeropoint =
                    cpl_table_get_double(t, FORS_DATA_PHOT_ZEROPOINT, i, NULL);
                *dexpected_zeropoint =
                    cpl_table_get_double(t, FORS_DATA_PHOT_DZEROPOINT, i, NULL);
            }
        }
    }

    if (found == false) {
        cpl_msg_warning(cpl_func, "Entry for filter %s missing in input "
                        "photometric table (%s): assuming all photometric "
                        "coefficients Z, E, and color term, equal to zero.",
                        setting->filter_name, 
                        cpl_frame_get_filename(phot_table_frame));
        *color_term          = 0.0;
        *dcolor_term         = 0.0;
        *ext_coeff           = 0.0;
        *dext_coeff          = 0.0;
        *expected_zeropoint  = 0.0;
        *dexpected_zeropoint = 0.0;
    }
    
    /*
    assure( found, return, "%s: Missing entry for filter '%s'",
            cpl_frame_get_filename(phot_table_frame), setting->filter_name);
    */
    
    cleanup;
    return;
}


#undef cleanup
#define cleanup \
do { \
    cpl_table_delete(table); \
} while(0)

/**
 * @brief    Write photometry coefficients data to table
 * @param    setting             Working instrument setting
 * @param    color_term          Computed color term
 * @param    dcolor_term         Computed color term error
 * @param    ext_coeff           Extinction coefficient
 * @param    dext_coeff          Extinction coefficient error
 * @param    zeropoint           Computed zeropoint
 * @param    dzeropoint          Computed zeropoint error
 *
 * The output table is structured like a photometric table,
 * but only the columns corresponding to quantitites having
 * error > 0 are created. If no columns are created, the table
 * is not created and NULL is returned.
 */

cpl_table *fors_phot_coeff_create(const fors_setting *setting,
                                  double color_term,
                                  double dcolor_term,
                                  double ext_coeff,
                                  double dext_coeff,
                                  double zeropoint,
                                  double dzeropoint)
{
    cpl_table *table = cpl_table_new(1);

    if (table == NULL) {
        return NULL;
    }

    if (dcolor_term > 0.0 || dext_coeff > 0.0 || dzeropoint > 0.0) {
        cpl_table_new_column(table, FORS_DATA_PHOT_FILTER, CPL_TYPE_STRING);
        cpl_table_set_string(table, FORS_DATA_PHOT_FILTER, 
                             0, setting->filter_name);
    }
    else {
        cleanup;
        return NULL;
    }

    /*
     * Create only the necessary columns for the new table
     */

    if (dext_coeff > 0.0) {
        cpl_table_new_column(table, FORS_DATA_PHOT_EXTCOEFF, CPL_TYPE_DOUBLE);
        cpl_table_new_column(table, FORS_DATA_PHOT_DEXTCOEFF, CPL_TYPE_DOUBLE);
        cpl_table_set_double(table, FORS_DATA_PHOT_EXTCOEFF, 0, ext_coeff);
        cpl_table_set_double(table, FORS_DATA_PHOT_DEXTCOEFF, 0, dext_coeff);
    }

    if (dzeropoint > 0.0) {
        cpl_table_new_column(table, FORS_DATA_PHOT_ZEROPOINT, CPL_TYPE_DOUBLE);
        cpl_table_new_column(table, FORS_DATA_PHOT_DZEROPOINT, CPL_TYPE_DOUBLE);
        cpl_table_set_double(table, FORS_DATA_PHOT_ZEROPOINT, 0, zeropoint);
        cpl_table_set_double(table, FORS_DATA_PHOT_DZEROPOINT, 0, dzeropoint);
    }

    if (dcolor_term > 0.0) {
        cpl_table_new_column(table, FORS_DATA_PHOT_COLORTERM, CPL_TYPE_DOUBLE);
        cpl_table_new_column(table, FORS_DATA_PHOT_DCOLORTERM, CPL_TYPE_DOUBLE);
        cpl_table_set_double(table, FORS_DATA_PHOT_COLORTERM, 0, color_term);
        cpl_table_set_double(table, FORS_DATA_PHOT_DCOLORTERM, 0, dcolor_term);
    }

    return table;
}
