/* $Id: fors_std_star.c,v 1.20 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.20 $
 * $Name: not supported by cvs2svn $
 */

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

#include <fors_star.h>
#include <fors_utils.h>

#include <cpl.h>

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

/*-----------------------------------------------------------------------------
    Prototypes
 -----------------------------------------------------------------------------*/

static
double          _get_optional_table_value(  const cpl_table *tab,
                                            unsigned int    row,
                                            const char      *colname);
static
double          _square(                    double  a);

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

/*----------------------------------------------------------------------------*/
#undef cleanup
#define cleanup
/**
 * @brief   Get a double value from a table
 * @param   row     Input row
 * @param   name    (Optional) Column name, can be NULL
 * @return  The value, NaN if no column name was specified or on error
 */
/*----------------------------------------------------------------------------*/
static
double          _get_optional_table_value(  const cpl_table *tab,
                                            unsigned int    row,
                                            const char      *colname)
{
    int             null;
    cpl_errorstate  errstat = cpl_errorstate_get();
    cpl_error_code  errc;
    
    if (colname != NULL && colname[0] != '\0')
    {
        double  d;
        d = cpl_table_get(                  tab, colname, row, &null);
        if (!cpl_errorstate_is_equal(errstat))
        {
            switch (errc = cpl_error_get_code())
            {
                case CPL_ERROR_DATA_NOT_FOUND:
                    cpl_error_set_message(  cpl_func,
                                            errc,
                                            "Column \"%s\" not found",
                                            colname);
                    break;
                case CPL_ERROR_INVALID_TYPE:
                    cpl_error_set_message(  cpl_func,
                                            errc,
                                            "Column \"%s\" is not numeric",
                                            colname);
                    break;
                default:    break;
            }
            cleanup;
        }
        else
        {
            return d;
        }
    }
    
    {
        int n;
        union _nant {
            unsigned char   bytes[8];
            double          val;
        } nan;
        for (n = 0; n < 8; n++)
            nan.bytes[n] = (unsigned char)255;
        return nan.val;
    }
}

/*----------------------------------------------------------------------------*/
/**
 * @brief   Just square a number.
 * @param   a   Number
 * @return  Square
 */
/*----------------------------------------------------------------------------*/
static
double          _square(                    double  a)
{
    return  a*a;
}


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

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

/*----------------------------------------------------------------------------*/
/**
 * @brief   Constructor
 * @param   ra       right ascension
 * @param   dec      declination
 * @param   m        magnitude (color corrected)
 * @param   dm       magnitude (color corrected) error
 * @param   cat_m    magnitude
 * @param   dcat_m   magnitude error
 * @param   col      color (in magnitude)
 * @param   name     name or NULL
 * @return  newly allocated standard star
 * 
 * @par Further initialization values:
 * - fors_std_star.pixel = (-1, -1)
 * - fors_std_star.trusted = true
 */
/*----------------------------------------------------------------------------*/
fors_std_star   *fors_std_star_new(         double ra, double dec,
                                            double m, double dm,
                                            double cat_m, double dcat_m,
                                            double col, double dcol,
                                            double cov_catm_col,
                                            const char *name)
{
    fors_std_star *s = cpl_malloc(sizeof(*s));

    s->ra               = ra;
    s->dec              = dec;
    s->magnitude        = m;
    s->dmagnitude       = dm;
    s->cat_magnitude    = cat_m;
    s->dcat_magnitude   = dcat_m;
    s->color            = col;
    s->dcolor           = dcol;
    s->cov_catm_color   = cov_catm_col;

    s->pixel     = fors_point_new(-1, -1);
    
    if (name != NULL) {
        s->name = cpl_strdup(name);
    }
    else {
        s->name = NULL;
    }
    
    s->trusted = true;
    
    return s;
}

/*----------------------------------------------------------------------------*/
#undef cleanup
#define cleanup fors_std_star_delete(&s)
/**
 * @brief   Create a standard star from a table WITHOUT checking
 * @param   tab         Input table
 * @param   row         Input row index
 * @param   ra_col      (Optional) right ascension column name
 * @param   dec_col     (Optional) declination column name
 * @param   mag_col     (Optional) color corrected magnitude column name
 * @param   dmag_col    (Optional) color corrected magnitude error column name
 * @param   catmag_col  (Optional) catalog magnitude column name
 * @param   dcatmag_col (Optional) catalog magnitude error column name
 * @param   color_col   (Optional) color index column name
 * @param   x_col       (Optional) x column name
 * @param   y_col       (Optional) y column name
 * @param   name_col    (Optional) object name column name
 * @return  newly allocated star
 * 
 * @par Constraints:
 * - Every specified column must exist.
 * - If the object's name in the table is a NULL entry, then also the standard
 *   star's name will be NULL (not an empty string).
 * - the column @a name_col must be of type string, every other column must be
 *   of a numerical type (no array).
 * 
 * @par Initialization values:
 * - Non-specified columns will produce NaN values, except:
 * - fors_std_star.pixel->x = -1
 * - fors_std_star.pixel->y = -1
 * - If the pixel values x, y are < 1, then they are set to -1.
 * - fors_std_star.trusted = true
 */
/*----------------------------------------------------------------------------*/
fors_std_star   *fors_std_star_new_from_table(
                                            const cpl_table *tab,
                                            unsigned int    row,
                                            const char      *ra_col,
                                            const char      *dec_col,
                                            const char      *mag_col,
                                            const char      *dmag_col,
                                            const char      *catmag_col,
                                            const char      *dcatmag_col,
                                            const char      *color_col,
                                            const char      *dcolor_col,
                                            const char      *cov_catm_color_col,
                                            const char      *x_col,
                                            const char      *y_col,
                                            const char      *name_col)
{
    cpl_errorstate  errstat = cpl_errorstate_get();
    double          x,
                    y;
    
    fors_std_star *s = cpl_malloc(sizeof(*s));
    s->name = NULL; /* assign this before calling assure (that calls delete) */

    s->ra =                     _get_optional_table_value(tab, row, ra_col);
    assure(cpl_errorstate_is_equal(errstat), return s, NULL);
    s->dec =                    _get_optional_table_value(tab, row, dec_col);
    assure(cpl_errorstate_is_equal(errstat), return s, NULL);
    s->magnitude =              _get_optional_table_value(tab, row, mag_col);
    assure(cpl_errorstate_is_equal(errstat), return s, NULL);
    s->dmagnitude =             _get_optional_table_value(tab, row, dmag_col);
    assure(cpl_errorstate_is_equal(errstat), return s, NULL);
    s->cat_magnitude  =       _get_optional_table_value(tab, row, catmag_col);
    assure(cpl_errorstate_is_equal(errstat), return s, NULL);
    s->dcat_magnitude =       _get_optional_table_value(tab, row, dcatmag_col);
    assure(cpl_errorstate_is_equal(errstat), return s, NULL);
    s->color =                  _get_optional_table_value(tab, row, color_col);
    assure(cpl_errorstate_is_equal(errstat), return s, NULL);
    s->dcolor =                 _get_optional_table_value(tab, row, dcolor_col);
    assure(cpl_errorstate_is_equal(errstat), return s, NULL);
    s->cov_catm_color =         _get_optional_table_value(tab, row,
                                            cov_catm_color_col);
    assure(cpl_errorstate_is_equal(errstat), return s, NULL);
    
    x = _get_optional_table_value(tab, row, x_col);
    y = _get_optional_table_value(tab, row, y_col);
    s->pixel = fors_point_new(  (isnan(x) ? -1 : x),
                                (isnan(y) ? -1 : y));
    assure(cpl_errorstate_is_equal(errstat), return s, NULL);
    if (s->pixel->x < 1) s->pixel->x = -1;
    if (s->pixel->y < 1) s->pixel->y = -1;
    
    s->name = NULL;
    if (name_col != NULL)
    {
        const char  *str;
        str = cpl_table_get_string(tab, name_col, row);
        if (!cpl_errorstate_is_equal(errstat))
        {
            cpl_error_code  errc;
            switch (errc = cpl_error_get_code())
            {
                case CPL_ERROR_DATA_NOT_FOUND:
                    cpl_error_set_message(  cpl_func,
                                            errc,
                                            "Column \"%s\" not found",
                                            name_col);
                    break;
                case CPL_ERROR_INVALID_TYPE:
                    cpl_error_set_message(  cpl_func,
                                            errc,
                                            "Column \"%s\" is not string type",
                                            name_col);
                    break;
                default:    break;
            }
            cleanup;
            return s;
        }
        if (str != NULL)
            s->name = cpl_strdup(str);
    }
    
    s->trusted = true;
    
    return s;
}

/*----------------------------------------------------------------------------*/
/**
 * @brief    Destructor
 * @param    s      to delete
 */
/*----------------------------------------------------------------------------*/
void            fors_std_star_delete(       fors_std_star **s){
    if (s && *s) {
        fors_point_delete(&(*s)->pixel);
        if ((*s)->name != NULL) {
            cpl_free((void *)(*s)->name); (*s)->name = NULL;
        }
        cpl_free(*s); *s = NULL;
    }
    return;
}

/*----------------------------------------------------------------------------*/
/**
 * @brief    Destructor
 * @param    s      to delete
 */
/*----------------------------------------------------------------------------*/
void            fors_std_star_delete_const( const fors_std_star **s){
    fors_std_star_delete((fors_std_star **)s);
    return;
}

/*----------------------------------------------------------------------------*/
#undef cleanup
#define cleanup
/**
 * @brief   Copy constructor
 * @param   s         star to duplicate
 * @return  newly allocated standard star
 */
/*----------------------------------------------------------------------------*/
fors_std_star   *fors_std_star_duplicate(   const fors_std_star *s)
{
    fors_std_star *d = NULL;

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

    d = cpl_malloc(sizeof(*d));

    d->ra             = s->ra;
    d->dec            = s->dec;
    d->magnitude      = s->magnitude;
    d->dmagnitude     = s->dmagnitude;
    d->cat_magnitude  = s->cat_magnitude;
    d->dcat_magnitude = s->dcat_magnitude;
    d->color          = s->color;
    d->dcolor         = s->dcolor;
    d->cov_catm_color = s->cov_catm_color;
    
    d->pixel          = fors_point_duplicate(s->pixel);
    d->name           = s->name != NULL ? cpl_strdup(s->name) : NULL;
    
    d->trusted        = s->trusted;
    
    return d;    
}

/*----------------------------------------------------------------------------*/
#undef cleanup
#define cleanup
/**
 * @brief   Set the name of a std star, NULL is allowed.
 * @param   s       Standard star
 * @param   name    New name, can be NULL
 * @return  Nothing
 */
/*----------------------------------------------------------------------------*/
void            fors_std_star_set_name(     fors_std_star   *s,
                                            const char      *name)
{
    assure( s != NULL, return, NULL );
    
    cpl_free(s->name);
    s->name = (name != NULL) ? cpl_strdup(name) : NULL;
    
    return;
}

/*----------------------------------------------------------------------------*/
/**
 * @brief    Test for equality
 * @param    s         1st star
 * @param    t         2nd star
 * @return   true if and only if the two stars are equal
 *
 * The contents, not the pointers, are compared
 */
/*----------------------------------------------------------------------------*/
bool            fors_std_star_equal(        const fors_std_star *s,
                                            const fors_std_star *t)
{
    assure( s != NULL && t != NULL, return true, NULL );

    return( s->trusted && t->trusted &&
            fabs(s->ra  - t->ra ) < DBL_EPSILON &&
            fabs(s->dec - t->dec) < DBL_EPSILON);
}

/*----------------------------------------------------------------------------*/
#undef cleanup
#define cleanup
/**
 * @brief   Print star
 * @param   level    message level
 * @param   star     to print
 */
/*----------------------------------------------------------------------------*/
void            fors_std_star_print(        cpl_msg_severity level,
                                            const fors_std_star *star)
{
    if (star == NULL) {
        fors_msg(level, "NULL std.star");
    }
    else {
        fors_msg(level, "(%7.4f, %7.4f): %sm = %g +- %g "
                        "(col = %g +- %g)%s, (x=%7.2f, y=%7.2f) %s",
                 star->ra, star->dec,
                 (star->trusted ? "" : "untrusted magnitude (values are: "),
                 star->magnitude, star->dmagnitude,
                 star->color, star->dcolor,
                 (star->trusted ? "" : ")"),
                 star->pixel->x, star->pixel->y,
                 ((star->name != NULL) ? star->name : ""));
    }

    return;                  
}

/*----------------------------------------------------------------------------*/
#undef cleanup
#define cleanup
/**
 * @brief   Print list of stars
 * @param   level        message level
 * @param   sl           list to print
 */
/*----------------------------------------------------------------------------*/
void            fors_std_star_print_list(   cpl_msg_severity level,
                                            const fors_std_star_list *sl)
{
    if (sl == NULL) fors_msg(level, "Null list");
    else {
        const fors_std_star *s;
        
        for (s = fors_std_star_list_first_const(sl);
             s != NULL;
             s = fors_std_star_list_next_const(sl)) {
            
            fors_std_star_print(level, s);
        }
    }
    return;
}

/*----------------------------------------------------------------------------*/
/**
 * @brief    Compare brightness
 * @param    s         1st star
 * @param    t         2nd star
 * @param    data      not used
 * @return   true if s is brighter than t
 */
/*----------------------------------------------------------------------------*/
bool            fors_std_star_brighter_than(const fors_std_star *s,
                                            const fors_std_star *t,
                                            void *data){
    data = data;
    return (s->trusted && t->trusted &&
            s->magnitude < t->magnitude);
}

/*----------------------------------------------------------------------------*/
#undef cleanup
#define cleanup
/**
 * @brief   Distance between stars
 * @param   s         1st star
 * @param   t         2nd star
 * @return  spherical distance between s and t in arcseconds
 */
/*----------------------------------------------------------------------------*/
double          fors_std_star_dist_arcsec(  const fors_std_star *s,
                                            const fors_std_star *t)
{
    assure( s != NULL, return -1, NULL );
    assure( t != NULL, return -1, NULL );

    /* Convert to radians, use stock formula for angular separation,
       convert */
    double s_ra  = s->ra  * 2*M_PI / 360;
    double s_dec = s->dec * 2*M_PI / 360;
    double t_ra  = t->ra  * 2*M_PI / 360;
    double t_dec = t->dec * 2*M_PI / 360;
    
    double cos_separation = 
        sin(s_dec)*sin(t_dec) +  
        cos(s_dec)*cos(t_dec) * cos(s_ra - t_ra);
    
    if (cos_separation < -1) cos_separation = -1;
    if (cos_separation >  1) cos_separation =  1;
    
    /* Note: result is always positive */
    return (acos(cos_separation) * 360 / (2*M_PI)) * 3600;
}

/*----------------------------------------------------------------------------*/
/**
 * @brief   Compute the color corrected magnitude of a standard star.
 * @param   s           Standard star
 * @param   color_term  Color correction term
 * @param   dcolor_term Color correction term error
 * @return  Nothing
 * 
 * This function ignores the "trusted" flag.
 */
/*----------------------------------------------------------------------------*/
void            fors_std_star_compute_corrected_mag(
                                            fors_std_star   *s,
                                            double          color_term,
                                            double          dcolor_term)
{
    cassure( s != NULL, CPL_ERROR_NULL_INPUT, return, NULL);
    
    s->magnitude = s->cat_magnitude - color_term * s->color;
    s->dmagnitude = sqrt(   _square(s->dcat_magnitude)
                            - 2.0 * color_term * s->cov_catm_color
                            + _square(color_term * s->dcolor)
                            + _square(s->color * dcolor_term));
}

/*----------------------------------------------------------------------------*/
#define LIST_DEFINE
#undef LIST_ELEM
#define LIST_ELEM fors_std_star
#include <list.h>

/**@}*/
