/* $Id: irplib_pfits.c,v 1.15 2013-05-13 16:05:04 jtaylor Exp $
 *
 * This file is part of the IRPLIB Package
 * 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: jtaylor $
 * $Date: 2013-05-13 16:05:04 $
 * $Revision: 1.15 $
 * $Name: not supported by cvs2svn $
 */

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

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

#include "irplib_utils.h"
#include "irplib_pfits.h"

#include <cpl.h>

#include <assert.h>
#include <string.h>
#include <sys/types.h>
#include <regex.h>

/*-----------------------------------------------------------------------------
                                 Private funcions
 -----------------------------------------------------------------------------*/

static cpl_error_code irplib_dfs_check_frame_tag(const cpl_frame *,
                                                 const cpl_propertylist *,
                                                 const char* (*)
                                                 (const char *,
                                                  const char *,
                                                  const char *));


/*----------------------------------------------------------------------------*/
/**
 * @defgroup irplib_pfits    Support for FITS header protected access
 *
 */
/*----------------------------------------------------------------------------*/

/**@{*/

/*-----------------------------------------------------------------------------
                              Function codes
 -----------------------------------------------------------------------------*/


/*----------------------------------------------------------------------------*/
/**
  @brief    The data category
  @param    self       property list to read from
  @return   pointer to statically allocated character string
 */
/*----------------------------------------------------------------------------*/
const char * irplib_pfits_get_dpr_catg(const cpl_propertylist * self)
{
    return irplib_pfits_get_string(self, "ESO DPR CATG");
}

/*----------------------------------------------------------------------------*/
/**
  @brief    The data technique
  @param    self       property list to read from
  @return   pointer to statically allocated character string
 */
/*----------------------------------------------------------------------------*/
const char * irplib_pfits_get_dpr_tech(const cpl_propertylist * self)
{
    return irplib_pfits_get_string(self, "ESO DPR TECH");
}

/*----------------------------------------------------------------------------*/
/**
  @brief    The data type
  @param    self       property list to read from
  @return   pointer to statically allocated character string
 */
/*----------------------------------------------------------------------------*/
const char * irplib_pfits_get_dpr_type(const cpl_propertylist * self)
{
    return irplib_pfits_get_string(self, "ESO DPR TYPE");
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Get the value of a property of type double
  @param    self     Property list to read from
  @param    key      Property key
  @param    function Function name (for error reporting)
  @param    file     Source file name (for error reporting)
  @param    line     Source line number (for error reporting)
  @return   The property value

 */
/*----------------------------------------------------------------------------*/
double irplib_pfits_get_double_macro(const cpl_propertylist * self,
                                     const char * key,
                                     const char * function,
                                     const char * file,
                                     unsigned line)
{
    double value;
    cpl_errorstate prestate = cpl_errorstate_get();

    value = cpl_propertylist_get_double(self, key);

    if (cpl_errorstate_is_equal(prestate)) {
        cpl_msg_debug(function, "FITS card '%s' [double]: %g", key, value);
    } else {
        /* Set the error location to that of the caller */
       (void)cpl_error_set_message_macro(function, cpl_error_get_code(), file,
                                         line, "Missing FITS card "
                                         " [double]: '%s' ", key);
    }

   return value;
}



/*----------------------------------------------------------------------------*/
/**
  @brief    Get the value of a property of type int
  @param    self     Property list to read from
  @param    key      Property key
  @param    function Function name (for error reporting)
  @param    file     Source file name (for error reporting)
  @param    line     Source line number (for error reporting)
  @return   The property value

 */
/*----------------------------------------------------------------------------*/
int irplib_pfits_get_int_macro(const cpl_propertylist * self,
                               const char * key, const char * function,
                               const char * file, unsigned line)
{
    int value;
    cpl_errorstate prestate = cpl_errorstate_get();

    value = cpl_propertylist_get_int(self, key);

    if (cpl_errorstate_is_equal(prestate)) {
        cpl_msg_debug(function, "FITS card '%s' [int]: %d", key, value);
    } else {
        /* Set the error location to that of the caller */
       (void)cpl_error_set_message_macro(function, cpl_error_get_code(), file,
                                         line, "Missing FITS card "
                                         " [int]: '%s' ", key);
    }
 
   return value;
}


/*----------------------------------------------------------------------------*/
/**
  @brief    Get the value of a property of type string
  @param    self     Property list to read from
  @param    key      Property key
  @param    function Function name (for error reporting)
  @param    file     Source file name (for error reporting)
  @param    line     Source line number (for error reporting)
  @return   The property value or NULL on errror

 */
/*----------------------------------------------------------------------------*/
const char * irplib_pfits_get_string_macro(const cpl_propertylist * self,
                                           const char * key,
                                           const char * function,
                                           const char * file,
                                           unsigned line)
{
    const char * value;
    cpl_errorstate prestate = cpl_errorstate_get();

    value = cpl_propertylist_get_string(self, key);

    if (cpl_errorstate_is_equal(prestate)) {
        cpl_msg_debug(function, "FITS card '%s' [string]: %s", key, value);
    } else {
        /* Set the error location to that of the caller */
       (void)cpl_error_set_message_macro(function, cpl_error_get_code(), file,
                                         line, "Missing FITS card "
                                         " [string]: '%s' ", key);
    }
 
    return value;
}




/*----------------------------------------------------------------------------*/
/**
  @brief    Check the tags in a frameset (group raw only)
  @param    self   The frameset to check
  @param    pfind  Function to find the DO category from a DPR-triplet
  @return   0 iff ok
 */
/*----------------------------------------------------------------------------*/
cpl_error_code irplib_dfs_check_framelist_tag(const irplib_framelist * self,
                                              const char* (*pfind)(const char *,
                                                                   const char *,
                                                                   const char *))
{

    int i;

    if (cpl_error_get_code()) return cpl_error_get_code();

    cpl_ensure_code(self  != NULL, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(pfind != NULL, CPL_ERROR_NULL_INPUT);

    for (i = 0; i < irplib_framelist_get_size(self); i++) {
        const cpl_frame * frame = irplib_framelist_get_const(self, i);
        const cpl_propertylist * plist
            = irplib_framelist_get_propertylist_const(self, i);

        cpl_ensure_code(frame != NULL, cpl_error_get_code());
        cpl_ensure_code(plist != NULL, cpl_error_get_code());

        cpl_ensure_code(!irplib_dfs_check_frame_tag(frame, plist, pfind),
                           cpl_error_get_code());
    }

    return cpl_error_get_code();

}


/*----------------------------------------------------------------------------*/
/**
   @brief Match a string with word(s) against a list of 1-word-regexps
   @param words     The string with word(s)
   @param format    A format string of space-separed %s, e.g. "%s %s %s"
   @param ...       Variable argument list of 1-word regular expressions
   @note The number of %s's in format must equal the number of 1-word-regexps,
         this allows the GNU C compiler to verify the count of regexps.
   @return 0 if matched, positive if not matched, negative on error.

 */
/*----------------------------------------------------------------------------*/
int irplib_dfs_find_words(const char * words, const char * format, ...)
{

    regex_t re;
    va_list ap;
    int error, status;


    if (cpl_error_get_code()) return -1;

    cpl_ensure(words  != NULL, CPL_ERROR_NULL_INPUT, -2);
    cpl_ensure(format != NULL, CPL_ERROR_NULL_INPUT, -3);

    /* format must consist of space separated %s */
    error = regcomp(&re, "^ *%s( +%s)* *$", REG_EXTENDED | REG_NOSUB);

    /* Should really be assert() */
    cpl_ensure(!error, CPL_ERROR_ILLEGAL_INPUT, -4);

    status = regexec(&re, format, (size_t)0, NULL, 0);

    regfree(&re);

    if (status != 0) {
        cpl_msg_error(cpl_func, "Regexp counter must consist of space-separated"
                      " %%s, not: %s", format);
        cpl_ensure(0, CPL_ERROR_ILLEGAL_INPUT, -5);
    }

    va_start(ap, format);

    /* Iterate through arguments, by searching for the '%' */
    for (; format != NULL; format = strchr(++format, '%')) {

        const char * regexp = va_arg(ap, const char *);

        if (regexp == NULL) {
            va_end(ap);
            cpl_ensure(0, CPL_ERROR_ILLEGAL_INPUT, -6);
        }

        error = regcomp(&re, regexp, REG_EXTENDED | REG_NOSUB);

        if (error) {
            va_end(ap);
            cpl_ensure(0, CPL_ERROR_ILLEGAL_INPUT, -7);
        }

        status = regexec(&re, words, (size_t)0, NULL, 0);

        regfree(&re);

        if (status != 0) break; /* Not matched */

    }

    va_end(ap);

    return format == NULL ? 0 : 1;

}

/*----------------------------------------------------------------------------*/
/**
   @brief Update/Set the AIRMASS property
   @param self      The propertylist to be updated
   @param rawframes The list of rawframes whose AIRMASSes to use
   @return 0 iff successfull

 */
/*----------------------------------------------------------------------------*/
cpl_error_code irplib_pfits_set_airmass(cpl_propertylist * self,
                                        const irplib_framelist * rawframes)
{

    char * newcomment = NULL;
    const int nframes = irplib_framelist_get_size(rawframes);
    int iframe;
    int nmass = 0;
    double astart0 = -1.0;
    double aend0 = -1.0;
    double airmass = 0.0;
    cpl_errorstate prestate = cpl_errorstate_get();

    skip_if(0);
    skip_if(self == NULL);

    for (iframe = 0; iframe < nframes; iframe++) {
        const cpl_propertylist * plist
            = irplib_framelist_get_propertylist_const(rawframes, iframe);
        double astart = DBL_MAX; /* Avoid (false) uninit warning */
        double aend = DBL_MAX; /* Avoid (false) uninit warning */
        double airmi;

        if (!cpl_errorstate_is_equal(prestate)) {
            irplib_error_recover(prestate, "No propertylist found for frame %d:",
                                 iframe);
            continue;
        }

        if (iframe == 0) {
            astart = irplib_pfits_get_double(plist, "ESO TEL AIRM START");
            if (cpl_errorstate_is_equal(prestate)) {
                astart0 = astart;
                aend = irplib_pfits_get_double(plist, "ESO TEL AIRM END");
            }
        } else {
            aend = irplib_pfits_get_double(plist, "ESO TEL AIRM END");
            if (cpl_errorstate_is_equal(prestate)) {
                if (iframe == nframes - 1) aend0 = aend;
                astart = irplib_pfits_get_double(plist, "ESO TEL AIRM START");
            }
        }

        if (cpl_errorstate_is_equal(prestate)) {
            airmi = 0.5 * (astart + aend);
        } else {
            const char * filename = cpl_frame_get_filename(
                                 irplib_framelist_get_const(rawframes, iframe));
            irplib_error_recover(prestate, "Could not get FITS key from %s",
                                 filename);

            airmi = irplib_pfits_get_double(plist, "AIRMASS");
            
            if (!cpl_errorstate_is_equal(prestate)) {
                irplib_error_recover(prestate, "Could not get FITS key from %s",
                                     filename);
                continue;
            }
        }

        airmass += airmi;
        nmass++;
    }

    bug_if(0);
    
    if (nmass == 0 && astart0 > 0.0 && aend0 > 0.0) {
        airmass = 0.5 * (astart0 + aend0);
        nmass = 1;
    }
    if (nmass > 0) {
        const char * key = "AIRMASS";
        const char * comment = cpl_propertylist_get_comment(self, key);

        cpl_errorstate_set(prestate);

        airmass /= (double)nmass;
        
        bug_if(cpl_propertylist_update_double(self, key, airmass));

        if (comment == NULL) {
            bug_if(cpl_propertylist_set_comment(self, key, "Averaged air mass "
                                                "(Recalculated)"));
        } else {
            newcomment = cpl_sprintf("%s (Recalculated)",
                                                       comment);
            bug_if(cpl_propertylist_set_comment(self, key, newcomment));
        }

    }

    end_skip;

    cpl_free(newcomment);

    return cpl_error_get_code();

}

/**@}*/


/*----------------------------------------------------------------------------*/
/**
  @brief    Check the tag in a frame
  @param    self   The frame to check
  @param    plist  The propertylist of the frame
  @param    pfind  Function to find the DO category from a DPR-triplet
  @return   0 iff ok
 */
/*----------------------------------------------------------------------------*/
static cpl_error_code irplib_dfs_check_frame_tag(const cpl_frame * self,
                                                 const cpl_propertylist * plist,
                                                 const char* (*pfind)
                                                 (const char *,
                                                  const char *,
                                                  const char *))
{

    const char   * file;
    const char   * tag;
    const char   * catg;
    const char   * type;
    const char   * tech;
    cpl_errorstate prestate = cpl_errorstate_get();


    cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT);

    file = cpl_frame_get_filename(self);

    cpl_ensure_code(file != NULL, cpl_error_get_code());

    tag = cpl_frame_get_tag(self);

    cpl_ensure_code(tag != NULL, cpl_error_get_code());

    catg = irplib_pfits_get_dpr_catg(plist);
    type = irplib_pfits_get_dpr_type(plist);
    tech = irplib_pfits_get_dpr_tech(plist);

    if (!cpl_errorstate_is_equal(prestate)) {
        if (cpl_msg_get_level() <= CPL_MSG_DEBUG) {
            cpl_msg_warning(cpl_func, "File %s has missing or incomplete DPR "
                            "triplet", file);
            cpl_errorstate_dump(prestate, CPL_FALSE, NULL);
        }                              
        cpl_errorstate_set(prestate);
    } else {
        const char   * docatg;

        cpl_ensure_code(pfind != NULL, CPL_ERROR_NULL_INPUT);

        docatg = (*pfind)(catg, type, tech);

        if (docatg == NULL) {
            if (cpl_msg_get_level() <= CPL_MSG_DEBUG)
                cpl_msg_warning(cpl_func, "File %s has tag %s but unknown DPR "
                                "triplet: (CATG;TYPE;TECH)=(%s;%s;%s)", file, tag,
                                catg, type, tech);
        } else if (strcmp(tag, docatg) != 0) {
            if (cpl_msg_get_level() <= CPL_MSG_DEBUG)
                cpl_msg_warning(cpl_func, "File %s has tag %s but DPR triplet of "
                                "%s: (CATG;TYPE;TECH)=(%s;%s;%s)", file, tag,
                                docatg, catg, type, tech);
        }
    }

    return CPL_ERROR_NONE;

}


