/* $Id: naco_util_img_std_cat.c,v 1.13 2013-03-12 08:03:07 llundin Exp $
 *
 * This file is part of the NACO Pipeline
 * Copyright (C) 2002,2003 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  02111-1307  USA
 */

/*
 * $Author: llundin $
 * $Date: 2013-03-12 08:03:07 $
 * $Revision: 1.13 $
 * $Name: not supported by cvs2svn $
 */

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

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

#include <string.h>

#include "naco_recipe.h"

#include <irplib_stdstar.h>
#include <irplib_wcs.h>

/*-----------------------------------------------------------------------------
                            Defines
 -----------------------------------------------------------------------------*/

#define NFILTERS 8

#define RECIPE_STRING "naco_util_img_std_cat"

/* Largest seen so far is 74 */
#ifndef NACO_ASCII_MAXLINELEN
#define NACO_ASCII_MAXLINELEN 1024
#endif

/*-----------------------------------------------------------------------------
                            Private Functions prototypes
 -----------------------------------------------------------------------------*/

NACO_RECIPE_DEFINE(naco_util_img_std_cat,
                   0,
                   "Standard star catalog creation",
                   RECIPE_STRING " -- Standard star catalog creation.\n"
                   "Convert ASCII-file(s) to a FITS standard star catalog.\n"
                   "This recipe generates a FITS standard star catalog for "
                   "imaging from one or more ASCII-files.\n"
                   "Each line in the text file must have "
                   "4+" IRPLIB_STRINGIFY(NFILTERS) " fields "
                   "separated by white-space.\n"
                   "The first field is the star name, e.g. 'HD108903' which "
                   "will be stored in a "
                   "table column labeled '" IRPLIB_STDSTAR_STAR_COL "'.\n"
                   "The next field is the Right Ascension [degrees] which will "
                   "be stored in a table column labeled '" IRPLIB_STDSTAR_RA_COL
                   "'.\n"
                   "The Right Ascension must be non-negative and less than "
                   "360.\n"
                   "The next field is the Declination [degrees] which will be "
                   "stored in a table column labeled '" IRPLIB_STDSTAR_DEC_COL
                   "'.\n"
                   "The Declination must be within the range -90 to 90.\n"
                   "The next field is the spectral type which will be "
                   "stored in a table column labeled '" IRPLIB_STDSTAR_TYPE_COL
                   "'.\n"
                   "The " IRPLIB_STRINGIFY(NFILTERS) " next fields are the "
                   "magnitudes for the " IRPLIB_STRINGIFY(NFILTERS) " supported "
                   "image filters.\n"
                   "Unknown magnitudes must be indicated by the "
                   "value " IRPLIB_STRINGIFY(IRPLIB_STDSTAR_NOMAG) ".\n"
                   "The filename (without path) of the ASCII file will for "
                   "each star be added in a table column labeled 'CAT_NAME'.\n"
                   "The " IRPLIB_STRINGIFY(NFILTERS) " filter names are "
                   "hard-coded in the recipe.\n"
                   "\n"
                   "Lines beginning with a hash (#) are treated as comments.\n"
                   "\n"
                   "The files listed in the Set Of Frames (sof-file) "
                   "must be tagged:\n" 
                   "NACO-ASCII-file " NACO_IMG_STD_ASCII "\n");

static IRPLIB_UTIL_SET_ROW(naco_util_img_std_set_row);
static IRPLIB_UTIL_CHECK(naco_util_img_std_check);

static cpl_error_code naco_util_img_std_cat_cmp(const cpl_table *,
                                                cpl_boolean *,
                                                double, double,
                                                const char *,
                                                const char *,
                                                const char *,
                                                int,
                                                const double *, double, double);

static double magmin = IRPLIB_STDSTAR_NOMAG;
static double magmax = -30.0;
static const char * filters[] = {"J","H", "K", "Ks", "L", "M", "Lp", "Mp"};

/*-----------------------------------------------------------------------------
                                Functions code
 -----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------*/
/**
  @brief    The recipe data reduction part is implemented here 
  @param    parlist     the parameters list
  @param    framelist   the frames list
  @return   0 iff everything is ok
 */
/*----------------------------------------------------------------------------*/
static int naco_util_img_std_cat(cpl_frameset            * framelist,
                                 const cpl_parameterlist * parlist)
{
    irplib_framelist * allframes = NULL;
    irplib_framelist * rawframes = NULL;
    cpl_frameset     * useframes = NULL;
    cpl_table        * self      = NULL;
    int                ifilt;


    bug_if(sizeof(filters) != NFILTERS * sizeof(char*));

    /* Identify the RAW and CALIB frames in the input frameset */
    skip_if (naco_dfs_set_groups(framelist));

    /* FIXME: Using framelists is the simplest way to extract the relevant
       frames :-( */
    allframes = irplib_framelist_cast(framelist);
    bug_if(allframes == NULL);

    rawframes = irplib_framelist_extract(allframes, NACO_IMG_STD_ASCII);
    skip_if(rawframes == NULL);

    irplib_framelist_empty(allframes);

    useframes = irplib_frameset_cast(rawframes);
    bug_if(allframes == NULL);

    /* At least one row per file */
    self = cpl_table_new(irplib_framelist_get_size(rawframes));

    irplib_framelist_empty(rawframes);


    /* Create the table columns - with units */
    bug_if (cpl_table_new_column(self, IRPLIB_STDSTAR_STAR_COL,
                                 CPL_TYPE_STRING));
    bug_if (cpl_table_new_column(self, IRPLIB_STDSTAR_TYPE_COL,
                                 CPL_TYPE_STRING));
    bug_if (cpl_table_new_column(self, "CAT_NAME", CPL_TYPE_STRING));
    bug_if (cpl_table_new_column(self, IRPLIB_STDSTAR_RA_COL,
                                 CPL_TYPE_DOUBLE));
    bug_if (cpl_table_new_column(self, IRPLIB_STDSTAR_DEC_COL,
                                 CPL_TYPE_DOUBLE));

    bug_if(cpl_table_set_column_unit(self, IRPLIB_STDSTAR_RA_COL,
                                     "Degrees"));
    bug_if(cpl_table_set_column_unit(self, IRPLIB_STDSTAR_DEC_COL,
                                     "Degrees"));
    for (ifilt=0 ; ifilt < NFILTERS ; ifilt++) {
        bug_if (cpl_table_new_column(self, filters[ifilt], CPL_TYPE_DOUBLE));
        bug_if(cpl_table_set_column_unit(self, filters[ifilt], "Magnitude"));
    }

    skip_if(irplib_dfs_table_convert(self, framelist, useframes, 
                                     NACO_ASCII_MAXLINELEN, '#', NULL,
                                     NACO_IMG_STD_CAT, parlist, RECIPE_STRING,
                                     NULL, NULL, NULL, "NACO", naco_pipe_id,
                                     naco_util_img_std_set_row,
                                     naco_util_img_std_check));

    end_skip;

    cpl_table_delete(self);
    cpl_frameset_delete(useframes);
    irplib_framelist_delete(allframes);
    irplib_framelist_delete(rawframes);

    return cpl_error_get_code();
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Cross-check a new star against the catalogue
  @param    self      the table to check
  @param    puse      On success, the to CPL_TRUE if the star is to be used
  @param    ra        RA [degrees]
  @param    dec       DEC [degrees]
  @param    sname     The star name
  @param    stype     The star spectral type
  @param    cat_name  The catalogue name
  @param    irow      The table row number
  @param    mags      The magnitudes in the same order as the filters
  @param    dist_max  The tolerance on ra, dec distance [degrees], e.g. 2.5e-5
  @param    mag_tol   The tolerance on the magnitude
  @return   0 iff OK

  Two standards stars nearer than twice IRPLIB_STDSTAR_MAXDIST means that
  the coordinates of an observed star could potentially be either.

 */
/*----------------------------------------------------------------------------*/
static cpl_error_code naco_util_img_std_cat_cmp(const cpl_table * self,
                                                cpl_boolean     * puse,
                                                double            ra,
                                                double            dec,
                                                const char      * sname,
                                                const char      * stype,
                                                const char      * cat_name,
                                                int               irow,
                                                const double    * mags,
                                                double            dist_max,
                                                double            mag_tol)
{

    const int nfilters = NFILTERS;
    int j;

    *puse = CPL_FALSE;

    bug_if(self     == NULL);
    bug_if(filters  == NULL);
    bug_if(mags     == NULL);
    bug_if(sname    == NULL);
    bug_if(stype    == NULL);
    bug_if(cat_name == NULL);

    for (j = 0; j < irow; j++) {
        const double raj
            = cpl_table_get_double(self, IRPLIB_STDSTAR_RA_COL, j, NULL);
        const double decj
            = cpl_table_get_double(self, IRPLIB_STDSTAR_DEC_COL, j, NULL);
        double dist;
        double mag_max_found = 0;
        int i;

        if (fabs(decj - dec) > dist_max) continue;

        dist = irplib_wcs_great_circle_dist(ra, dec, raj, decj);

        if (dist > dist_max) continue;

        for (i=0 ; i < nfilters ; i++) {
            const double mag
                = cpl_table_get_double(self, filters[i], j, NULL);
            double mag_dif;

            if (mags[i] > IRPLIB_STDSTAR_LIMIT) continue;

            if (mag > IRPLIB_STDSTAR_LIMIT) break;

            mag_dif = fabs(mags[i] - mag);

            if (mag_dif > mag_tol) break;

            if (mag_dif > mag_max_found) mag_max_found = mag_dif;
        }


        if (i == nfilters) {
            cpl_msg_debug(cpl_func, "Skipping star: '%s' at table row %d in "
                         "catalogue '%s' and the star %d have identical "
                         "magnitudes (within %g <= %g) and a distance: "
                          "%g <= %g", sname, 1+irow, cat_name, j+1,
                          mag_max_found, mag_tol, dist, dist_max);
            if (cpl_msg_get_level() <= CPL_MSG_DEBUG)
                cpl_table_dump(self, j, 1, stdout);
            break;
        }
        if (dist > 0.0) {
            cpl_msg_info(cpl_func, "The stars at rows %d and %d ('%s' in "
                         "catalogue '%s') have different magnitudes, but a "
                         "distance: %g <= %g",
                         1+j, 1+irow, sname, cat_name, dist, dist_max);
        } else {
            cpl_msg_info(cpl_func, "The stars at rows %d and %d ('%s' in "
                         "catalogue '%s') have different magnitudes, but "
                         "identical coordinates",
                         1+j, 1+irow, sname, cat_name);
        }
#if 0
        cpl_table_dump(self, j, 1, stderr);
        for (i=0 ; i < nfilters ; i++) {
            const double mag
                = cpl_table_get_double(self, filters[i], j, NULL);
            const double mag_dif = fabs(mags[i] - mag);

            if (mag_dif > mag_tol) 
                cpl_msg_warning(cpl_func, "%s(%d): |%g - %g| = %g > %g",
                                filters[i], i, mag, mags[i], mag_dif, mag_tol);
        }
#endif
    }

    if (j == irow) *puse = CPL_TRUE;

    end_skip;

    return CPL_ERROR_NONE;

}


/*----------------------------------------------------------------------------*/
/**
  @brief    Issue a message regarding the insterted FITS rows
  @param    self      The table to check
  @param    useframes The frames used to fill the table
  @param    parlist   The recipe parameters
  @return   CPL_ERROR_NONE on success, otherwise the relevant CPL error code
  
 */
/*----------------------------------------------------------------------------*/
static
cpl_error_code naco_util_img_std_check(cpl_table * self,
                                       const cpl_frameset * useframes,
                                       const cpl_parameterlist * parlist)
{

    bug_if(0);
    bug_if(self     == NULL);
    bug_if(parlist  == NULL);

    cpl_msg_info(cpl_func, "Created table of %d stars with "
                 "magnitudes from %g to %g from %d catalogues",
                 (int)cpl_table_get_nrow(self),
                 magmin, magmax, (int)cpl_frameset_get_size(useframes));

    end_skip;

    return cpl_error_get_code();
}


/*----------------------------------------------------------------------------*/
/**
  @brief    Convert one ASCII line and insert it into the atble
  @param    self      The table to set
  @param    line      The line to parse
  @param    irow      The row to set (zero for 1st)
  @param    parlist   The recipe parameters
  @return   CPL_TRUE iff the row was modified

  The input catalogs are ASCII files respecting this format:

  Star_name RA DEC Spec_type MAG_J MAG_H MAG_K MAG_KS MAG_L MAG_M MAG_LP MAG_MP

  Example:
  AS01-0 13.7912 0.720278 -- 10.716 10.507 10.470 99 99 99 99 99
  AS03-0 16.09 4.2275 -- 12.606 12.729 12.827 99 99 99 99 99
  AS04-1 28.6808 0.733056 -- 12.371 12.033 11.962 99 99 99 99 99
  ... 

  99 is the value for unknown magnitudes,
  IRPLIB_STDSTAR_NOMAG expands to 99.0.
  
 */
/*----------------------------------------------------------------------------*/
static cpl_boolean naco_util_img_std_set_row(cpl_table * self,
                                             const char * line,
                                             int irow,
                                             const cpl_frame * rawframe,
                                             const cpl_parameterlist * parlist)
{

    /* gcc can only check sscanf()s format when it is a string literal */
#define FORMAT "%" CPL_STRINGIFY(NACO_ASCII_MAXLINELEN) "s %lg %lg %"   \
        CPL_STRINGIFY(NACO_ASCII_MAXLINELEN) "s %lg %lg %lg %lg %lg %lg %lg %lg"

    char         sname[NACO_ASCII_MAXLINELEN + 1];
    char         stype[NACO_ASCII_MAXLINELEN + 1];
    double       mags[NFILTERS];
    double       ra, dec;
    int          nvals;
    int          ifilt;
    cpl_boolean  use = CPL_FALSE;

    bug_if(0);
    bug_if(self     == NULL);
    bug_if(line     == NULL);
    bug_if(irow   <  0);
    bug_if(rawframe == NULL);
    bug_if(parlist  == NULL);

    nvals = sscanf(line, FORMAT,
                   sname, &ra, &dec, stype, &(mags[0]), &(mags[1]), &(mags[2]),
                   &(mags[3]), &(mags[4]), &(mags[5]), &(mags[6]), &(mags[7]));

    error_if (nvals != NFILTERS+4, CPL_ERROR_BAD_FILE_FORMAT,
              "Line with length=%u has %d not 4+" IRPLIB_STRINGIFY(NFILTERS)
              " items formatted: %s", (unsigned)strlen(line), nvals, FORMAT);

    error_if (ra < 0.0, CPL_ERROR_BAD_FILE_FORMAT,
              "Negative RA=%g in line %s", ra, line);

    error_if (ra >=360.0, CPL_ERROR_BAD_FILE_FORMAT,
              "RA=%g is not less than 360 in line %s", ra, line);

    error_if (dec < -90.0, CPL_ERROR_BAD_FILE_FORMAT,
              "DEC=%g is not at least -90 in line %s", dec, line);

    error_if (dec > 90.0, CPL_ERROR_BAD_FILE_FORMAT,
              "DEC=%g is not at most 90 in line %s", dec, line);

    for (ifilt=0 ; ifilt < NFILTERS; ifilt++) {
        if (mags[ifilt] < IRPLIB_STDSTAR_LIMIT) break;
    }

    if (ifilt == NFILTERS) {
        cpl_msg_debug(cpl_func, "Not setting row without a valid magnitude: "
                      "(%d)", 1+irow);
    } else {
        const char * rawfile = cpl_frame_get_filename(rawframe);
        /* Skip path, if any */
        const char * cat_name = strrchr(rawfile, '/');

        cat_name = cat_name ? 1+cat_name : rawfile;

        bug_if(cat_name == NULL);

        skip_if(naco_util_img_std_cat_cmp(self, &use, ra, dec, sname,
                                          stype, cat_name, irow,
                                          mags, 0.25e-4, 1e-6));

        if (use) {
            bug_if (cpl_table_set_string(self, IRPLIB_STDSTAR_STAR_COL, irow,
                                         sname));

            bug_if (cpl_table_set_string(self, "CAT_NAME", irow, cat_name));

            bug_if (cpl_table_set_string(self, IRPLIB_STDSTAR_TYPE_COL, irow,
                                         stype));

            bug_if (cpl_table_set_double(self, IRPLIB_STDSTAR_RA_COL, irow,
                                         ra));

            bug_if (cpl_table_set_double(self, IRPLIB_STDSTAR_DEC_COL, irow,
                                         dec));

            for (ifilt=0 ; ifilt < NFILTERS; ifilt++) {
                if (mags[ifilt] > 27.0 && mags[ifilt] < IRPLIB_STDSTAR_LIMIT) {
                    /* 27 is about the limit of the UT */
                    cpl_msg_warning(cpl_func, "Setting faint (mag=%g) object "
                                    "(Filter-%s) in table row %d", mags[ifilt],
                                    filters[ifilt], 1+irow);
                }
                if (mags[ifilt] > magmax && mags[ifilt] < IRPLIB_STDSTAR_LIMIT)
                    magmax = mags[ifilt];
                if (mags[ifilt] < magmin) magmin = mags[ifilt];
                bug_if(cpl_table_set_double(self, filters[ifilt], irow,
                                            mags[ifilt]));
            }
        }
    }

    end_skip;

    return use;

}
