/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set sw=2 sts=2 et cin: */
/*
 * This file is part of the MUSE Instrument Pipeline
 * Copyright (C) 2005-2018 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 Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

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

/*----------------------------------------------------------------------------*
 *                             Includes                                       *
 *----------------------------------------------------------------------------*/
#include <cpl.h>
#include <math.h>
#include <string.h>

#include "muse_pfits.h"
#include "muse_instrument.h"

/*----------------------------------------------------------------------------*/
/**
 * @defgroup muse_pfits        Protected access to FITS headers
 */
/*----------------------------------------------------------------------------*/

/**@{*/

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the arcfile
  @param    aHeaders       property list/headers to read from
  @return   pointer to statically allocated string or NULL on error

  Queries FITS header ARCFILE
 */
/*----------------------------------------------------------------------------*/
const char *
muse_pfits_get_arcfile(const cpl_propertylist *aHeaders)
{
  const char *value = cpl_propertylist_get_string(aHeaders, "ARCFILE");
  cpl_ensure(value, cpl_error_get_code(), NULL);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the origfile
  @param    aHeaders       property list/headers to read from
  @return   pointer to statically allocated string or NULL on error

  Queries FITS header ORIGFILE
 */
/*----------------------------------------------------------------------------*/
const char *
muse_pfits_get_origfile(const cpl_propertylist *aHeaders)
{
  const char *value = cpl_propertylist_get_string(aHeaders, "ORIGFILE");
  cpl_ensure(value, cpl_error_get_code(), NULL);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the pipefile
  @param    aHeaders       property list/headers to read from
  @return   pointer to statically allocated string or NULL on error

  Queries FITS header PIPEFILE
 */
/*----------------------------------------------------------------------------*/
const char *
muse_pfits_get_pipefile(const cpl_propertylist *aHeaders)
{
  const char *value = cpl_propertylist_get_string(aHeaders, "PIPEFILE");
  cpl_ensure(value, cpl_error_get_code(), NULL);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the ancestor of a file.
  @param    aHeaders       property list/headers to read from
  @return   the requested value or NULL on error

  Queries FITS header for ESO.PRO.ANCESTOR
 */
/*----------------------------------------------------------------------------*/
const char *
muse_pfits_get_ancestor(const cpl_propertylist *aHeaders)
{
  cpl_ensure(aHeaders, CPL_ERROR_NULL_INPUT, NULL);
  cpl_errorstate prestate = cpl_errorstate_get();
  const char *value = cpl_propertylist_get_string(aHeaders, "ESO PRO ANCESTOR");
  cpl_errorstate_set(prestate);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the DPR type
  @param    aHeaders       property list/headers to read from
  @return   the requested value or NULL on error

  Queries FITS header ESO DPR TYPE
 */
/*----------------------------------------------------------------------------*/
const char *
muse_pfits_get_dpr_type(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const char *value = cpl_propertylist_get_string(aHeaders, "ESO DPR TYPE");
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), NULL);
  return value;
} /* muse_pfits_get_dpr_type() */

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the DPR category
  @param    aHeaders       property list/headers to read from
  @return   the requested value or NULL on error

  Queries FITS header ESO DPR CATG
 */
/*----------------------------------------------------------------------------*/
const char *
muse_pfits_get_dpr_catg(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const char *value = cpl_propertylist_get_string(aHeaders, "ESO DPR CATG");
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), NULL);
  return value;
} /* muse_pfits_get_dpr_catg() */

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the PRO type
  @param    aHeaders       property list/headers to read from
  @return   the requested value or NULL on error

  Queries FITS header ESO PRO TYPE
 */
/*----------------------------------------------------------------------------*/
const char *
muse_pfits_get_pro_type(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const char *value = cpl_propertylist_get_string(aHeaders, "ESO PRO TYPE");
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), NULL);
  return value;
} /* muse_pfits_get_pro_type() */

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the PRO category
  @param    aHeaders       property list/headers to read from
  @return   the requested value or NULL on error

  Queries FITS header ESO PRO CATG
 */
/*----------------------------------------------------------------------------*/
const char *
muse_pfits_get_pro_catg(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const char *value = cpl_propertylist_get_string(aHeaders, "ESO PRO CATG");
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), NULL);
  return value;
} /* muse_pfits_get_pro_catg() */

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the i-th raw file name.
  @param    aHeaders       property list/headers to read from
  @param    idx            raw file index
  @return   the requested value or NULL on error

  Queries FITS header for ESO.PRO.REC1.RAW<idx>.NAME
 */
/*----------------------------------------------------------------------------*/
const char *
muse_pfits_get_raw_filename(const cpl_propertylist *aHeaders, unsigned int idx)
{
  cpl_ensure(aHeaders, CPL_ERROR_NULL_INPUT, NULL);
  char *key = cpl_sprintf("ESO PRO REC1 RAW%-u NAME", idx);
  cpl_errorstate prestate = cpl_errorstate_get();
  const char *value = cpl_propertylist_get_string(aHeaders, key);
  cpl_errorstate_set(prestate);
  cpl_free(key);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the value of the flux calibration flag
  @param    aHeaders       property list/headers to read from
  @param    idx            recipe index
  @return   the requested value or NULL on error

  Queries FITS header for ESO.PRO.REC<idx>.PIPE.ID
 */
/*----------------------------------------------------------------------------*/
const char *
muse_pfits_get_pipe_id(const cpl_propertylist *aHeaders, unsigned int idx)
{
  cpl_ensure(aHeaders, CPL_ERROR_NULL_INPUT, NULL);
  char *key = cpl_sprintf("ESO PRO REC%-u PIPE ID", idx);
  cpl_errorstate prestate = cpl_errorstate_get();
  const char *value = cpl_propertylist_get_string(aHeaders, key);
  cpl_errorstate_set(prestate);
  cpl_free(key);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the ESO program identification
  @param    aHeaders       property list/headers to read from
  @return   the requested value or NULL on error

  Queries FITS header for ESO.OBS.PROG.ID
 */
/*----------------------------------------------------------------------------*/
const char *
muse_pfits_get_progid(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const char *value = cpl_propertylist_get_string(aHeaders, "ESO OBS PROG ID");
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), NULL);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the ESO observation target name
  @param    aHeaders       property list/headers to read from
  @return   the requested value or NULL on error

  Queries FITS header for ESO.OBS.TARG.NAME
 */
/*----------------------------------------------------------------------------*/
const char *
muse_pfits_get_targname(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const char *value = cpl_propertylist_get_string(aHeaders, "ESO OBS TARG NAME");
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), NULL);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the observation block id
  @param    aHeaders       property list/headers to read from
  @return   the requested value or -1 on error

  Queries FITS header for ESO.OBS.ID
 */
/*----------------------------------------------------------------------------*/
long
muse_pfits_get_obsid(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const long value = cpl_propertylist_get_long(aHeaders, "ESO OBS ID");
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), -1);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    query the high-order loop status
  @param    aHeaders       property list/headers to read from
  @return   CPL_TRUE or CPL_FALSE;
            CPL_FALSE is also returned, if the header keyword does not exist

  Queries FITS header ESO AOS HO LOOP ST.
  CPL_TRUE signifies that AO was switched on.
 */
/*----------------------------------------------------------------------------*/
cpl_boolean
muse_pfits_get_ho_loop(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const cpl_boolean value = cpl_propertylist_get_bool(aHeaders,
                                                      "ESO AOS HO LOOP ST") == 0
                          ? CPL_FALSE : CPL_TRUE;
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), CPL_FALSE);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    query the WFM tit-tilt loop status
  @param    aHeaders       property list/headers to read from
  @return   CPL_TRUE or CPL_FALSE;
            CPL_FALSE is also returned, if the header keyword does not exist

  Queries FITS header ESO AOS TT LOOP ST.
  CPL_TRUE signifies that WFM AO was using the tip-tilt correction. For tip-tilt
  free operation, this will be CPL_FALSE.
 */
/*----------------------------------------------------------------------------*/
cpl_boolean
muse_pfits_get_tt_loop(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const cpl_boolean value = cpl_propertylist_get_bool(aHeaders,
                                                      "ESO AOS TT LOOP ST") == 0
                          ? CPL_FALSE : CPL_TRUE;
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), CPL_FALSE);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    query the NFM IRLOS loop status
  @param    aHeaders       property list/headers to read from
  @return   CPL_TRUE or CPL_FALSE;
            CPL_FALSE is also returned, if the header keyword does not exist

  Queries FITS header ESO AOS IR LOOP ST.
  CPL_TRUE signifies that NFM AO was using the tip-tilt correction as measured
  by the IRLOS sensor. For tip-tilt free operation, this will be CPL_FALSE.
 */
/*----------------------------------------------------------------------------*/
cpl_boolean
muse_pfits_get_ir_loop(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const cpl_boolean value = cpl_propertylist_get_bool(aHeaders,
                                                      "ESO AOS IR LOOP ST") == 0
                          ? CPL_FALSE : CPL_TRUE;
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), CPL_FALSE);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    query the AO mirror name
  @param    aHeaders       property list/headers to read from
  @param    aMirror        the mirror number (the "i" of the header keyword)
  @return   the mirror name or NULL

  Queries FITS header ESO.INS.AO.MIRRi.NAME

  Use aMirror==2 to query that the GALACSI AO instrument mode is the correct
  one (it should be either "WFM" or "NFM").
  Use aMirror==3 to check that the GALACSI dichroic is out of the light path;
  then the resulting string should be "OUT".
 */
/*----------------------------------------------------------------------------*/
const char *
muse_pfits_get_ao_mirror_name(const cpl_propertylist *aHeaders,
                              unsigned int aMirror)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  char keyword[KEYWORD_LENGTH];
  snprintf(keyword, KEYWORD_LENGTH, "ESO INS AO MIRR%u NAME", aMirror);
  const char *value = cpl_propertylist_get_string(aHeaders, keyword);
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), NULL);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    query the AO optical element name
  @param    aHeaders       property list/headers to read from
  @param    aElement       the element number (the "i" of the header keyword)
  @return   the mirror name or NULL

  Queries FITS header  ESO.INS.AO.OPTIi.NAME

  Use aElement==3 to check that the GALACSI dichroic is out of the light path;
  then the resulting string should be "OUT".
 */
/*----------------------------------------------------------------------------*/
const char *muse_pfits_get_ao_opti_name(const cpl_propertylist *aHeaders,
                                        unsigned int aElement)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  char keyword[KEYWORD_LENGTH];
  snprintf(keyword, KEYWORD_LENGTH, "ESO INS AO OPTI%u NAME", aElement);
  const char *value = cpl_propertylist_get_string(aHeaders, keyword);
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), NULL);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the observation mode
  @param    aHeaders       property list/headers to read from
  @return   The observing mode, on error this is MUSE_MODE_WFM_NONAO_N as the
            default MUSE setup.

  Queries header ESO INS MODE
  Uses @ref muse_pfits_get_insmode() to query the string of the header.
 */
/*----------------------------------------------------------------------------*/
muse_ins_mode
muse_pfits_get_mode(const cpl_propertylist *aHeaders)
{
  const char *value = muse_pfits_get_insmode(aHeaders);
  cpl_ensure(value, cpl_error_get_code(), MUSE_MODE_WFM_NONAO_N);
  if (!strncmp(value, "NFM", 3)) {
    return MUSE_MODE_NFM_AO_N;
  }
  if (!strncmp(value, "WFM-AO-N", 8)) {
    return MUSE_MODE_WFM_AO_N;
  }
  if (!strncmp(value, "WFM-AO-E", 8)) {
    return MUSE_MODE_WFM_AO_E;
  }
  if (!strncmp(value, "WFM-NOAO-N", 10) || !strncmp(value, "WFM-NONAO-N", 11)) {
    return MUSE_MODE_WFM_NONAO_N;
  }
  return MUSE_MODE_WFM_NONAO_E;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the observation mode
  @param    aHeaders       property list/headers to read from
  @return   The observing mode as string or NULL on error

  Queries header ESO INS MODE
  The difference to @ref muse_pfits_get_mode() is that this function returns
  the string content of the header, not the corresponding mode enum.
 */
/*----------------------------------------------------------------------------*/
const char *
muse_pfits_get_insmode(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const char *value = cpl_propertylist_get_string(aHeaders, "ESO INS MODE");
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), NULL);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Find out the whether this header related to a certain IFU.
  @param    aHeaders       property list/headers to read from
  @param    aIFU           the IFU number to query
  @return   True if the image has this number, false otherwise.

  Queries FITS header EXTNAME and parses the CHAN%02d string for the IFU
  (channel) number.

  Using this function may be faster than comparison with muse_utils_get_ifu().
 */
/*----------------------------------------------------------------------------*/
cpl_boolean
muse_pfits_has_ifu(const cpl_propertylist *aHeaders, unsigned char aIFU)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const char *extname = muse_pfits_get_extname(aHeaders);
  if (!cpl_errorstate_is_equal(prestate) || /* no EXTNAME */
      strncmp(extname, "CHAN", 4) ||        /* doesn't start with CHAN */
      strlen(extname) < 6) {                /* too short */
    cpl_errorstate_set(prestate); /* ignore the error */
    return CPL_FALSE;
  }
  unsigned char chan = atoi(extname + 4);
  return chan == aIFU;
} /* muse_pfits_has_ifu() */

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the MUSE derotator mode
  @param    aHeaders       property list/headers to read from
  @return   the requested value or 0.0 on error

  Queries FITS header ESO INS DROT MODE
 */
/*----------------------------------------------------------------------------*/
const char *
muse_pfits_get_drot_mode(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const char *value = cpl_propertylist_get_string(aHeaders, "ESO INS DROT MODE");
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), NULL);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the MUSE derotator position angle (in degrees)
  @param    aHeaders       property list/headers to read from
  @return   the requested value or 0.0 on error

  Queries FITS header ESO INS DROT POSANG
 */
/*----------------------------------------------------------------------------*/
double
muse_pfits_get_drot_posang(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const double value = cpl_propertylist_get_double(aHeaders, "ESO INS DROT POSANG");
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), 0.0);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the MUSE derotator rotation at exposure start (in degrees)
  @param    aHeaders       property list/headers to read from
  @return   the requested value or 0.0 on error

  Queries FITS header ESO INS DROT START
 */
/*----------------------------------------------------------------------------*/
double
muse_pfits_get_drot_start(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const double value = cpl_propertylist_get_double(aHeaders, "ESO INS DROT START");
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), 0.0);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the MUSE derotator rotation at exposure end (in degrees)
  @param    aHeaders       property list/headers to read from
  @return   the requested value or 0.0 on error

  Queries FITS header ESO INS DROT END
 */
/*----------------------------------------------------------------------------*/
double
muse_pfits_get_drot_end(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const double value = cpl_propertylist_get_double(aHeaders, "ESO INS DROT END");
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), 0.0);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the extension name
  @param    aHeaders       property list/headers to read from
  @return   the requested value or NULL on error

  Queries FITS header EXTNAME
 */
/*----------------------------------------------------------------------------*/
const char *
muse_pfits_get_extname(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const char *value = cpl_propertylist_get_string(aHeaders, "EXTNAME");
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), NULL);
  return value;
} /* muse_pfits_get_extname() */

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the unit string
  @param    aHeaders       property list/headers to read from
  @return   the requested value or NULL on error

  Queries FITS header BUNIT
 */
/*----------------------------------------------------------------------------*/
const char *
muse_pfits_get_bunit(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const char *value = cpl_propertylist_get_string(aHeaders, "BUNIT");
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), NULL);
  return value;
} /* muse_pfits_get_bunit() */

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the size of a given axis
  @param    aHeaders       property list/headers to read from
  @param    aAxis          the axis to read from, give 0 for NAXIS
  @return   the requested value or 0 on error

  Queries FITS header NAXIS or NAXISi
 */
/*----------------------------------------------------------------------------*/
cpl_size
muse_pfits_get_naxis(const cpl_propertylist *aHeaders, unsigned int aAxis)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  if (aAxis == 0) {
    cpl_size value = cpl_propertylist_get_long_long(aHeaders, "NAXIS");
    cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), 0);
    return value;
  }
  char keyword[KEYWORD_LENGTH];
  snprintf(keyword, KEYWORD_LENGTH, "NAXIS%u", aAxis);
  cpl_size value = cpl_propertylist_get_long_long(aHeaders, keyword);
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), 0);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the right ascension
  @param    aHeaders       property list/headers to read from
  @return   the requested value or 0.0 on error

  Queries FITS header RA
 */
/*----------------------------------------------------------------------------*/
double
muse_pfits_get_ra(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const double value = cpl_propertylist_get_double(aHeaders, "RA");
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), 0.0);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the declination
  @param    aHeaders       property list/headers to read from
  @return   the requested value or 0.0 on error

  Queries FITS header DEC
 */
/*----------------------------------------------------------------------------*/
double
muse_pfits_get_dec(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const double value = cpl_propertylist_get_double(aHeaders, "DEC");
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), 0.0);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the equinox
  @param    aHeaders       property list/headers to read from
  @return   the requested value or 0.0 on error

  Queries FITS header EQUINOX

  @note This keyword should be in floating point, but to be backward compatible,
        also fall back to reading it as int without error.
 */
/*----------------------------------------------------------------------------*/
double
muse_pfits_get_equinox(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  double value = cpl_propertylist_get_double(aHeaders, "EQUINOX");
  if (!cpl_errorstate_is_equal(prestate)) {
    cpl_errorstate_set(prestate);
    value = cpl_propertylist_get_long_long(aHeaders, "EQUINOX");
    cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), 0.0);
  } /* if error when reading as double */
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the local siderial time
  @param    aHeaders       property list/headers to read from
  @return   the requested value or 0.0 on error

  Queries FITS header LST
 */
/*----------------------------------------------------------------------------*/
double
muse_pfits_get_lst(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const double value = cpl_propertylist_get_double(aHeaders, "LST");
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), 0.0);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the Julian Date of the observation
  @param    aHeaders       property list/headers to read from
  @return   the requested value or 0.0 on error

  Queries FITS header MJD-OBS
 */
/*----------------------------------------------------------------------------*/
double
muse_pfits_get_mjdobs(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const double value = cpl_propertylist_get_double(aHeaders, "MJD-OBS");
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), 0.0);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the date of observations
  @param    aHeaders       property list/headers to read from
  @return   the requested value or NULL on error

  Queries FITS header DATE-OBS
 */
/*----------------------------------------------------------------------------*/
const char *
muse_pfits_get_dateobs(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const char *value = cpl_propertylist_get_string(aHeaders, "DATE-OBS");
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), NULL);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the exposure time
  @param    aHeaders       property list/headers to read from
  @return   the requested value or 0.0 on error

  Queries FITS header EXPTIME
 */
/*----------------------------------------------------------------------------*/
double
muse_pfits_get_exptime(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const double value = cpl_propertylist_get_double(aHeaders, "EXPTIME");
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), 0.0);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the WCS reference point
  @param    aHeaders       property list/headers to read from
  @param    aAxis          the axis (the "i" of CRPIXi)
  @return   the requested value or 0.0 on error

  Queries FITS header CRPIXi
 */
/*----------------------------------------------------------------------------*/
double
muse_pfits_get_crpix(const cpl_propertylist *aHeaders, unsigned int aAxis)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  char keyword[KEYWORD_LENGTH];
  snprintf(keyword, KEYWORD_LENGTH, "CRPIX%u", aAxis);
  const double value = cpl_propertylist_get_double(aHeaders, keyword);
  /* default to 0.0 as per FITS Standard v3.0 */
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), 0.0);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the WCS coordinate at the reference point
  @param    aHeaders       property list/headers to read from
  @param    aAxis          the axis (the "i" of CRVALi)
  @return   the requested value or 0.0 on error

  Queries FITS header CRVALi
 */
/*----------------------------------------------------------------------------*/
double
muse_pfits_get_crval(const cpl_propertylist *aHeaders, unsigned int aAxis)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  char keyword[KEYWORD_LENGTH];
  snprintf(keyword, KEYWORD_LENGTH, "CRVAL%u", aAxis);
  const double value = cpl_propertylist_get_double(aHeaders, keyword);
  /* default to 0.0 as per FITS Standard v3.0 */
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), 0.0);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the WCS coordinate at the reference point
  @param    aHeaders       property list/headers to read from
  @param    aAxisI         the first axis (the "i" of CDi_j)
  @param    aAxisJ         the second axis (the "j" of CDi_j)
  @return   the requested value or 0.0 on error

  Queries FITS header CDi_j
 */
/*----------------------------------------------------------------------------*/
double
muse_pfits_get_cd(const cpl_propertylist *aHeaders, unsigned int aAxisI,
                  unsigned int aAxisJ)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  char keyword[KEYWORD_LENGTH];
  snprintf(keyword, KEYWORD_LENGTH, "CD%u_%u", aAxisI, aAxisJ);
  const double value = cpl_propertylist_get_double(aHeaders, keyword);
  /* default to 0.0 as per FITS Standard v3.0 */
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), 0.0);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the WCS axis type string
  @param    aHeaders       property list/headers to read from
  @param    aAxis          the axis (the "i" of CTYPEi)
  @return   the requested value or " " on error

  Queries FITS header CTYPEi
 */
/*----------------------------------------------------------------------------*/
const char *
muse_pfits_get_ctype(const cpl_propertylist *aHeaders, unsigned int aAxis)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  char keyword[KEYWORD_LENGTH];
  snprintf(keyword, KEYWORD_LENGTH, "CTYPE%u", aAxis);
  const char *value = cpl_propertylist_get_string(aHeaders, keyword);
  /* default to single space as per FITS Standard v3.0 */
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), " ");
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the WCS axis unit string
  @param    aHeaders       property list/headers to read from
  @param    aAxis          the axis (the "i" of CUNITi)
  @return   the requested value or " " on error

  Queries FITS header CUNITi
 */
/*----------------------------------------------------------------------------*/
const char *
muse_pfits_get_cunit(const cpl_propertylist *aHeaders, unsigned int aAxis)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  char keyword[KEYWORD_LENGTH];
  snprintf(keyword, KEYWORD_LENGTH, "CUNIT%u", aAxis);
  const char *value = cpl_propertylist_get_string(aHeaders, keyword);
  /* default to single space as per FITS Standard v3.0 */
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), " ");
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the readout mode id
  @param    aHeaders       property list/headers to read from
  @return   the requested value or 0 on error

  Queries FITS header ESO DET READ CURID
 */
/*----------------------------------------------------------------------------*/
int
muse_pfits_get_read_id(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const int value = cpl_propertylist_get_int(aHeaders, "ESO DET READ CURID");
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), 0);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the readout mode name
  @param    aHeaders       property list/headers to read from
  @return   the requested value or NULL on error

  Queries FITS header ESO DET READ CURNAME
 */
/*----------------------------------------------------------------------------*/
const char *
muse_pfits_get_read_name(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const char *value = cpl_propertylist_get_string(aHeaders, "ESO DET READ CURNAME");
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), NULL);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the binning factor in x direction
  @param    aHeaders       property list/headers to read from
  @return   the requested value or 1 on error

  Queries FITS header ESO DET BINX
 */
/*----------------------------------------------------------------------------*/
int
muse_pfits_get_binx(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const int value = cpl_propertylist_get_int(aHeaders, "ESO DET BINX");
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), 1);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the binning factor in y direction
  @param    aHeaders       property list/headers to read from
  @return   the requested value or 1 on error

  Queries FITS header ESO DET BINY
 */
/*----------------------------------------------------------------------------*/
int
muse_pfits_get_biny(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const int value = cpl_propertylist_get_int(aHeaders, "ESO DET BINY");
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), 1);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the chip name
  @param    aHeaders       property list/headers to read from
  @return   the requested value or NULL on error

  Queries FITS header ESO DET CHIP NAME
 */
/*----------------------------------------------------------------------------*/
const char *
muse_pfits_get_chip_name(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const char *value = cpl_propertylist_get_string(aHeaders, "ESO DET CHIP NAME");
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), NULL);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the chip id
  @param    aHeaders       property list/headers to read from
  @return   the requested value or NULL on error

  Queries FITS header ESO DET CHIP ID
 */
/*----------------------------------------------------------------------------*/
const char *
muse_pfits_get_chip_id(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const char *value = cpl_propertylist_get_string(aHeaders, "ESO DET CHIP ID");
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), NULL);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the chip installation date
  @param    aHeaders       property list/headers to read from
  @return   the requested value or NULL on error

  Queries FITS header ESO DET CHIP DATE
 */
/*----------------------------------------------------------------------------*/
const char *
muse_pfits_get_chip_date(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const char *value = cpl_propertylist_get_string(aHeaders, "ESO DET CHIP DATE");
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), NULL);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out if the CCD was active (live)
  @param    aHeaders       property list/headers to read from
  @return   CPL_TRUE or CPL_FALSE;
            CPL_FALSE is also returned, if the header keyword does not exist

  Queries FITS header ESO DET CHIP LIVE
 */
/*----------------------------------------------------------------------------*/
cpl_boolean
muse_pfits_get_chip_live(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const cpl_boolean value = cpl_propertylist_get_bool(aHeaders,
                                                      "ESO DET CHIP LIVE") == 0
                          ? CPL_FALSE : CPL_TRUE;
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), CPL_FALSE);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find the detector read-out noise
  @param    aHeaders       property list/headers to read from
  @param    aQuadrant      the CCD quadrant to operate on (1 to 4)
  @return   the requested value or 1.0 on error

  Queries FITS header ESO DET OUTi RON
 */
/*----------------------------------------------------------------------------*/
double
muse_pfits_get_ron(const cpl_propertylist *aHeaders, unsigned char aQuadrant)
{
  char keyword[KEYWORD_LENGTH];
  snprintf(keyword, KEYWORD_LENGTH, "ESO DET OUT%d RON", aQuadrant);
  if (!cpl_propertylist_has(aHeaders, keyword)) {
    cpl_msg_warning(__func__, "Could not get %s, using RON=1.0",
                    keyword);
    return 1.0;
  }
  return cpl_propertylist_get_double(aHeaders, keyword);
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find the detector gain (in units of count/adu)
  @param    aHeaders       property list/headers to read from
  @param    aQuadrant      the CCD quadrant to operate on (1 to 4)
  @return   the requested value or 0.0 on error

  Queries FITS header ESO DET OUTi GAIN

  This function assumes that GAIN comes in units of [count/adu] (or electrons
  per ADU).
 */
/*----------------------------------------------------------------------------*/
double
muse_pfits_get_gain(const cpl_propertylist *aHeaders, unsigned char aQuadrant)
{
  char keyword[KEYWORD_LENGTH];
  snprintf(keyword, KEYWORD_LENGTH, "ESO DET OUT%d GAIN", aQuadrant);
  return cpl_propertylist_get_double(aHeaders, keyword);
} /* muse_pfits_get_gain() */

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the horizontal location of the output port of one quadrant
  @param    aHeaders       property list/headers to read from
  @param    aQuadrant      quadrant to look for
  @return   the requested value or -1 on error

  Queries FITS header ESO DET OUTi X
 */
/*----------------------------------------------------------------------------*/
int
muse_pfits_get_out_output_x(const cpl_propertylist *aHeaders,
                            unsigned char aQuadrant)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  char keyword[KEYWORD_LENGTH];
  snprintf(keyword, KEYWORD_LENGTH, "ESO DET OUT%d X", aQuadrant);
  const int value = cpl_propertylist_get_int(aHeaders, keyword);
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), -1);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the vertical location of the output port of one quadrant
  @param    aHeaders       property list/headers to read from
  @param    aQuadrant      quadrant to look for
  @return   the requested value or -1 on error

  Queries FITS header ESO DET OUTi Y
 */
/*----------------------------------------------------------------------------*/
int
muse_pfits_get_out_output_y(const cpl_propertylist *aHeaders,
                            unsigned char aQuadrant)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  char keyword[KEYWORD_LENGTH];
  snprintf(keyword, KEYWORD_LENGTH, "ESO DET OUT%d Y", aQuadrant);
  const int value = cpl_propertylist_get_int(aHeaders, keyword);
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), -1);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the horizontal size of the data section of one quadrant
  @param    aHeaders       property list/headers to read from
  @param    aQuadrant      quadrant to look for
  @return   the requested value or -1 on error

  Queries FITS header ESO DET OUTi NX
 */
/*----------------------------------------------------------------------------*/
int
muse_pfits_get_out_nx(const cpl_propertylist *aHeaders, unsigned char aQuadrant)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  char keyword[KEYWORD_LENGTH];
  snprintf(keyword, KEYWORD_LENGTH, "ESO DET OUT%d NX", aQuadrant);
  const int value = cpl_propertylist_get_int(aHeaders, keyword);
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), -1);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the vertical size of the data section of one quadrant
  @param    aHeaders       property list/headers to read from
  @param    aQuadrant      quadrant to look for
  @return   the requested value or -1 on error

  Queries FITS header ESO DET OUTi NY
 */
/*----------------------------------------------------------------------------*/
int
muse_pfits_get_out_ny(const cpl_propertylist *aHeaders, unsigned char aQuadrant)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  char keyword[KEYWORD_LENGTH];
  snprintf(keyword, KEYWORD_LENGTH, "ESO DET OUT%d NY", aQuadrant);
  const int value = cpl_propertylist_get_int(aHeaders, keyword);
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), -1);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the horizontal size of the prescan region of one quadrant
  @param    aHeaders       property list/headers to read from
  @param    aQuadrant      quadrant to look for
  @return   the requested value or -1 on error

  Queries FITS header ESO DET OUTi PRSCX
 */
/*----------------------------------------------------------------------------*/
int
muse_pfits_get_out_prescan_x(const cpl_propertylist *aHeaders,
                             unsigned char aQuadrant)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  char keyword[KEYWORD_LENGTH];
  snprintf(keyword, KEYWORD_LENGTH, "ESO DET OUT%d PRSCX", aQuadrant);
  const int value = cpl_propertylist_get_int(aHeaders, keyword);
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), -1);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the vertical size of the prescan region of one quadrant
  @param    aHeaders       property list/headers to read from
  @param    aQuadrant      quadrant to look for
  @return   the requested value or -1 on error

  Queries FITS header ESO DET OUTi PRSCY
 */
/*----------------------------------------------------------------------------*/
int
muse_pfits_get_out_prescan_y(const cpl_propertylist *aHeaders,
                             unsigned char aQuadrant)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  char keyword[KEYWORD_LENGTH];
  snprintf(keyword, KEYWORD_LENGTH, "ESO DET OUT%d PRSCY", aQuadrant);
  const int value = cpl_propertylist_get_int(aHeaders, keyword);
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), -1);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the horizontal size of the overscan region of one quadrant
  @param    aHeaders       property list/headers to read from
  @param    aQuadrant      quadrant to look for
  @return   the requested value or -1 on error

  Queries FITS header ESO DET OUTi OVSCX
 */
/*----------------------------------------------------------------------------*/
int
muse_pfits_get_out_overscan_x(const cpl_propertylist *aHeaders,
                              unsigned char aQuadrant)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  char keyword[KEYWORD_LENGTH];
  snprintf(keyword, KEYWORD_LENGTH, "ESO DET OUT%d OVSCX", aQuadrant);
  const int value = cpl_propertylist_get_int(aHeaders, keyword);
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), -1);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the vertical size of the overscan region of one quadrant
  @param    aHeaders       property list/headers to read from
  @param    aQuadrant      quadrant to look for
  @return   the requested value or -1 on error

  Queries FITS header ESO DET OUTi OVSCY
 */
/*----------------------------------------------------------------------------*/
int
muse_pfits_get_out_overscan_y(const cpl_propertylist *aHeaders,
                              unsigned char aQuadrant)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  char keyword[KEYWORD_LENGTH];
  snprintf(keyword, KEYWORD_LENGTH, "ESO DET OUT%d OVSCY", aQuadrant);
  const int value = cpl_propertylist_get_int(aHeaders, keyword);
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), -1);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the telescope's latitude
  @param    aHeaders       property list/headers to read from
  @return   the requested header value or -24.625278 by default

  Queries FITS header ESO TEL GEOLAT

  The default value is the UT4 entry from
  <http://www.eso.org/sci/facilities/paranal/site/paranal.html> (the value is
  the same as the "esovlt" location in the IRAF database, but there it's rounded
  to four digits).
 */
/*----------------------------------------------------------------------------*/
double
muse_pfits_get_geolat(const cpl_propertylist *aHeaders)
{
  const double value = cpl_propertylist_get_double(aHeaders, "ESO TEL GEOLAT");
  if (fabs(value) < DBL_EPSILON) {
    return -24.625278;
  }
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the telescope's longitude
  @param    aHeaders       property list/headers to read from
  @return   the requested header value or 70.402222 by default

  Queries FITS header ESO TEL GEOLON

  The default value is the UT4 entry from
  <http://www.eso.org/sci/facilities/paranal/site/paranal.html> (the value is
  the same as the "esovlt" location in the IRAF database, but there it's rounded
  to four digits).
 */
/*----------------------------------------------------------------------------*/
double
muse_pfits_get_geolon(const cpl_propertylist *aHeaders)
{
  const double value = cpl_propertylist_get_double(aHeaders, "ESO TEL GEOLON");
  if (fabs(value) < DBL_EPSILON) {
    return 70.402222;
  }
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the telescope's elevation
  @param    aHeaders       property list/headers to read from
  @return   the requested header value or 2648. by default

  Queries FITS header ESO TEL GEOELEV

  The default value is the UT4 entry taken from exposures taken during Comm2A
  (the value is the same as the "esovlt" location in the IRAF database).
 */
/*----------------------------------------------------------------------------*/
double
muse_pfits_get_geoelev(const cpl_propertylist *aHeaders)
{
  const double value = cpl_propertylist_get_double(aHeaders, "ESO TEL GEOELEV");
  if (fabs(value) < DBL_EPSILON) {
    return 2648.;
  }
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the scale in the VLT focal plane
  @param    aHeaders       property list/headers to read from
  @return   the requested value or 1.705 if the keyword is missing

  Queries FITS header ESO TEL FOCU SCALE, should be in units of [arcsec / mm].

  A super-precise value should be 1.70549 but to keep compatibility between
  the value in the header (if it's there) and the "error" case, lets return
  it with less digits.
 */
/*----------------------------------------------------------------------------*/
double
muse_pfits_get_focu_scale(const cpl_propertylist *aHeaders)
{
  double value = 1.705;
  if (aHeaders && cpl_propertylist_has(aHeaders, "ESO TEL FOCU SCALE")) {
    value = cpl_propertylist_get_double(aHeaders, "ESO TEL FOCU SCALE");
  }
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the airmass at start of exposure
  @param    aHeaders       property list/headers to read from
  @return   the requested value or 0.0 on error

  Queries FITS header ESO TEL AIRM START
 */
/*----------------------------------------------------------------------------*/
double
muse_pfits_get_airmass_start(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const double value = cpl_propertylist_get_double(aHeaders, "ESO TEL AIRM START");
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), 0.0);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the airmass at end of exposure
  @param    aHeaders       property list/headers to read from
  @return   the requested value or 0.0 on error

  Queries FITS header ESO TEL AIRM END
 */
/*----------------------------------------------------------------------------*/
double
muse_pfits_get_airmass_end(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const double value = cpl_propertylist_get_double(aHeaders, "ESO TEL AIRM END");
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), 0.0);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the altitude angle at start of the exposure (in degrees)
  @param    aHeaders       property list/headers to read from
  @return   the requested value or 0.0 on error

  Queries FITS header ESO TEL ALT
 */
/*----------------------------------------------------------------------------*/
double
muse_pfits_get_altang(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const double value = cpl_propertylist_get_double(aHeaders, "ESO TEL ALT");
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), 0.0);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the parallactic angle at start of exposure (in degrees)
  @param    aHeaders       property list/headers to read from
  @return   the requested value or 0.0 on error

  Queries FITS header ESO TEL PARANG START
 */
/*----------------------------------------------------------------------------*/
double
muse_pfits_get_parang_start(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const double value = cpl_propertylist_get_double(aHeaders, "ESO TEL PARANG START");
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), 0.0);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the parallactic angle at end of exposure (in degrees)
  @param    aHeaders       property list/headers to read from
  @return   the requested value or 0.0 on error

  Queries FITS header ESO TEL PARANG END
 */
/*----------------------------------------------------------------------------*/
double
muse_pfits_get_parang_end(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const double value = cpl_propertylist_get_double(aHeaders, "ESO TEL PARANG END");
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), 0.0);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the x-FWHM average value from the auto-guider (in arcsec)
  @param    aHeaders       property list/headers to read from
  @return   the requested value or 0.0 on error

  Queries FITS header ESO OCS SGS AG FWHMX AVG
 */
/*----------------------------------------------------------------------------*/
double
muse_pfits_get_agx_avg(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const double value = cpl_propertylist_get_double(aHeaders, "ESO OCS SGS AG FWHMX AVG");
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), 0.0);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the x-FWHM root mean square from the auto-guider (in arcsec)
  @param    aHeaders       property list/headers to read from
  @return   the requested value or 0.0 on error

  Queries FITS header ESO OCS SGS AG FWHMX RMS
 */
/*----------------------------------------------------------------------------*/
double
muse_pfits_get_agx_rms(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const double value = cpl_propertylist_get_double(aHeaders, "ESO OCS SGS AG FWHMX RMS");
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), 0.0);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the y-FWHM average value from the auto-guider (in arcsec)
  @param    aHeaders       property list/headers to read from
  @return   the requested value or 0.0 on error

  Queries FITS header ESO OCS SGS AG FWHMY AVG
 */
/*----------------------------------------------------------------------------*/
double
muse_pfits_get_agy_avg(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const double value = cpl_propertylist_get_double(aHeaders, "ESO OCS SGS AG FWHMY AVG");
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), 0.0);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the y-FWHM root mean square from the auto-guider (in arcsec)
  @param    aHeaders       property list/headers to read from
  @return   the requested value or 0.0 on error

  Queries FITS header ESO OCS SGS AG FWHMY RMS
 */
/*----------------------------------------------------------------------------*/
double
muse_pfits_get_agy_rms(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const double value = cpl_propertylist_get_double(aHeaders, "ESO OCS SGS AG FWHMY RMS");
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), 0.0);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the image analysis FWHM corrected by airmass (in arcsec)
  @param    aHeaders       property list/headers to read from
  @return   the requested value or 0.0 on error

  Queries FITS header ESO TEL IA FWHM
 */
/*----------------------------------------------------------------------------*/
double
muse_pfits_get_ia_fwhm(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const double value = cpl_propertylist_get_double(aHeaders, "ESO TEL IA FWHM");
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), 0.0);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the image analysis FWHM from a linear fit (in arcsec)
  @param    aHeaders       property list/headers to read from
  @return   the requested value or 0.0 on error

  Queries FITS header ESO TEL IA FWHMLIN
 */
/*----------------------------------------------------------------------------*/
double
muse_pfits_get_ia_fwhmlin(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const double value = cpl_propertylist_get_double(aHeaders, "ESO TEL IA FWHMLIN");
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), 0.0);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the ambient temperature (in degrees Celsius)
  @param    aHeaders       property list/headers to read from
  @return   the requested value or 0.0 on error

  Queries FITS header ESO TEL AMBI TEMP
 */
/*----------------------------------------------------------------------------*/
double
muse_pfits_get_temp(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const double value = cpl_propertylist_get_double(aHeaders, "ESO TEL AMBI TEMP");
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), 0.0);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the relavtive humidity (in %)
  @param    aHeaders       property list/headers to read from
  @return   the requested value or 0.0 on error

  Queries FITS header ESO TEL AMBI RHUM
 */
/*----------------------------------------------------------------------------*/
double
muse_pfits_get_rhum(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const double value = cpl_propertylist_get_double(aHeaders, "ESO TEL AMBI RHUM");
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), 0.0);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the ambient pressure at start of exposure (in mbar)
  @param    aHeaders       property list/headers to read from
  @return   the requested value or 0.0 on error

  Queries FITS header ESO TEL AMBI PRES START
 */
/*----------------------------------------------------------------------------*/
double
muse_pfits_get_pres_start(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const double value = cpl_propertylist_get_double(aHeaders, "ESO TEL AMBI PRES START");
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), 0.0);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the ambient pressure at end of exposure (in mbar)
  @param    aHeaders       property list/headers to read from
  @return   the requested value or 0.0 on error

  Queries FITS header ESO TEL AMBI PRES END
 */
/*----------------------------------------------------------------------------*/
double
muse_pfits_get_pres_end(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const double value = cpl_propertylist_get_double(aHeaders, "ESO TEL AMBI PRES END");
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), 0.0);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the ambient seeing at start of exposure (in arcsec)
  @param    aHeaders       property list/headers to read from
  @return   the requested value or 0.0 on error

  Queries FITS header ESO TEL AMBI FWHM START
 */
/*----------------------------------------------------------------------------*/
double
muse_pfits_get_fwhm_start(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const double value = cpl_propertylist_get_double(aHeaders, "ESO TEL AMBI FWHM START");
  cpl_ensure(cpl_errorstate_is_equal(prestate) && value > 0.,
             cpl_error_get_code(), 0.0);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    find out the ambient seeing at end of exposure (in arcsec)
  @param    aHeaders       property list/headers to read from
  @return   the requested value or 0.0 on error

  Queries FITS header ESO TEL AMBI FWHM END
 */
/*----------------------------------------------------------------------------*/
double
muse_pfits_get_fwhm_end(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const double value = cpl_propertylist_get_double(aHeaders, "ESO TEL AMBI FWHM END");
  cpl_ensure(cpl_errorstate_is_equal(prestate) && value > 0.,
             cpl_error_get_code(), 0.0);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    query the intensity measured by one photo diode (pico amplifier)
  @param    aHeaders       property list/headers to read from
  @param    aDiode         number of the photo diode to query
  @return   the requested value or 0.0 on error

  Queries FITS header ESO INS AMPLi CURR

  The FITS keyword is in units of mA, but the function returns the intensity in
  Ampere, if the unit in the header can be determined.

  This function ensures the unit by searching for "[mA]" in the FITS comment. If
  not found, an error is set, otherwise it converts the returned value from mA
  to A.
 */
/*----------------------------------------------------------------------------*/
double
muse_pfits_get_pam_intensity(const cpl_propertylist *aHeaders, int aDiode)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  char keyword[KEYWORD_LENGTH];
  snprintf(keyword, KEYWORD_LENGTH, "ESO INS AMPL%d CURR", aDiode);
  double value = cpl_propertylist_get_double(aHeaders, keyword);
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), 0.0);
  const char *comment = cpl_propertylist_get_comment(aHeaders, keyword);
  cpl_boolean ismilliampere = comment && strstr(comment, "[mA]")
                            ? CPL_TRUE : CPL_FALSE;
  if (!ismilliampere) {
    cpl_error_set_message(__func__, CPL_ERROR_INCOMPATIBLE_INPUT,
                          "Could not ensure that %s is in mA!", keyword);
  } else {
    value /= 1000.; /* convert from mA to A */
  }
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    query the intensity std. dev. of one photo diode (pico amplifier)
  @param    aHeaders       property list/headers to read from
  @param    aDiode         number of the photo diode to query
  @return   the requested value or 0.0 on error

  Queries FITS header ESO INS AMPLi STDEV
 */
/*----------------------------------------------------------------------------*/
double
muse_pfits_get_pam_stdev(const cpl_propertylist *aHeaders, int aDiode)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  char keyword[KEYWORD_LENGTH];
  snprintf(keyword, KEYWORD_LENGTH, "ESO INS AMPL%d STDEV", aDiode);
  const double value = cpl_propertylist_get_double(aHeaders, keyword);
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), 0.0);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    query the filter set up in front of photo diode (pico amplifier) 2
  @param    aHeaders       property list/headers to read from
  @return   the filter name or NULL

  Queries FITS header ESO INS AMPL2 FILTER
 */
/*----------------------------------------------------------------------------*/
const char *
muse_pfits_get_pam2_filter(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const char *value = cpl_propertylist_get_string(aHeaders,
                                                  "ESO INS AMPL2 FILTER");
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), NULL);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    query the number of lamps installed
  @param    aHeaders       property list/headers to read from
  @return   the number of lamps or 6 on error

  Queries FITS header ESO INS LAMPNUM
  The number returned on success can be used to enumerate both lamps and lamp
  shutters (keywords INS LAMPi and INS SHUTi).

  @note For backwards compatibility this function returns a reasonable default
        estimate and clears any errors if the FITS header is not present.
 */
/*----------------------------------------------------------------------------*/
int
muse_pfits_get_lampnum(const cpl_propertylist *aHeaders)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  const int value = cpl_propertylist_get_int(aHeaders, "ESO INS LAMPNUM");
  if (!cpl_errorstate_is_equal(prestate)) {
    /* header keyword not present (old data of some sort?!) */
    cpl_errorstate_set(prestate); /* be backward-compatible */
    return 6; /* return the default */
  }
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    query the name of one lamp
  @param    aHeaders       property list/headers to read from
  @param    aLamp          number of the lamp to query
  @return   the requested name or NULL on error

  Queries FITS header ESO INS LAMPi NAME
 */
/*----------------------------------------------------------------------------*/
const char *
muse_pfits_get_lamp_name(const cpl_propertylist *aHeaders, int aLamp)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  char keyword[KEYWORD_LENGTH];
  snprintf(keyword, KEYWORD_LENGTH, "ESO INS LAMP%d NAME", aLamp);
  const char *value = cpl_propertylist_get_string(aHeaders, keyword);
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), NULL);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    query the status of one lamp
  @param    aHeaders       property list/headers to read from
  @param    aLamp          number of the lamp to query
  @return   1 for lamp status on (T) or 0 for off (F) or error

  Queries FITS header ESO INS LAMPi ST
  @note This just checks the status of the lamp at the end of the exposure.
        Use the function @ref muse_pfits_get_lamp_ontime() to check, if the
        lamp was on at all during the given exposure.
 */
/*----------------------------------------------------------------------------*/
int
muse_pfits_get_lamp_status(const cpl_propertylist *aHeaders, int aLamp)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  char keyword[KEYWORD_LENGTH];
  snprintf(keyword, KEYWORD_LENGTH, "ESO INS LAMP%d ST", aLamp);
  const int value = cpl_propertylist_get_bool(aHeaders, keyword);
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), 0);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    query the time that a lamp was on during an exposure
  @param    aHeaders       property list/headers to read from
  @param    aLamp          number of the lamp to query
  @return   the time in seconds that the lamp was on or 0.0 on error.

  Queries FITS header ESO SEQ LAMPi ONTIME

  This is useful to check, if a lamp was on at all during an exposure, even if
  @ref muse_pfits_get_lamp_status() returns false.
 */
/*----------------------------------------------------------------------------*/
double
muse_pfits_get_lamp_ontime(const cpl_propertylist *aHeaders, int aLamp)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  char keyword[KEYWORD_LENGTH];
  snprintf(keyword, KEYWORD_LENGTH, "ESO SEQ LAMP%d ONTIME", aLamp);
  const double value = cpl_propertylist_get_double(aHeaders, keyword);
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), 0.0);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    query the name of one shutter
  @param    aHeaders       property list/headers to read from
  @param    aShutter       number of the shutter to query
  @return   the requested name or NULL on error

  Queries FITS header ESO INS SHUTi NAME
 */
/*----------------------------------------------------------------------------*/
const char *
muse_pfits_get_shut_name(const cpl_propertylist *aHeaders, int aShutter)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  char keyword[KEYWORD_LENGTH];
  snprintf(keyword, KEYWORD_LENGTH, "ESO INS SHUT%d NAME", aShutter);
  const char *value = cpl_propertylist_get_string(aHeaders, keyword);
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), NULL);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    query the status of one shutter
  @param    aHeaders       property list/headers to read from
  @param    aShutter       number of the shutter to query
  @return   1 for shutter status open (T) or 0 for closed (F) or error

  Queries FITS header ESO INS SHUTi ST
 */
/*----------------------------------------------------------------------------*/
int
muse_pfits_get_shut_status(const cpl_propertylist *aHeaders, int aShutter)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  char keyword[KEYWORD_LENGTH];
  snprintf(keyword, KEYWORD_LENGTH, "ESO INS SHUT%d ST", aShutter);
  const int value = cpl_propertylist_get_bool(aHeaders, keyword);
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), 0);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    query the absolute encoder position of one encoder
  @param    aHeaders       property list/headers to read from
  @param    aEncoder       number of the encoder to query
  @return   the absolute encoder position or 0 on error

  For possible values of aEncoder see @ref muse_pfits_get_pospos().

  Queries FITS header ESO INS POSi ENC
 */
/*----------------------------------------------------------------------------*/
int
muse_pfits_get_posenc(const cpl_propertylist *aHeaders, unsigned short aEncoder)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  char keyword[KEYWORD_LENGTH];
  snprintf(keyword, KEYWORD_LENGTH, "ESO INS POS%d ENC", aEncoder);
  const int value = cpl_propertylist_get_int(aHeaders, keyword);
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), 0);
  return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    query the position in user units of one encoder
  @param    aHeaders       property list/headers to read from
  @param    aEncoder       number of the encoder to query
  @return   the position in user units or 0.0 on error

  Queries FITS header ESO INS POSi POS. It returns the following properties,
  depending on the value of aEncoder:
  - 1:   Focusing stage position [mm]
  - 2:   Focal Mask Alignment X position [mm]
  - 3:   Focal Mask Alignment Y Position [mm]
  - 4:   Focal Mask Alignment Z Position [mm]

  There are no other POSi headers in MUSE headers, so for other values of
  aEncoder, this function will set an error and return 0.0.
 */
/*----------------------------------------------------------------------------*/
double
muse_pfits_get_pospos(const cpl_propertylist *aHeaders, unsigned short aEncoder)
{
  cpl_errorstate prestate = cpl_errorstate_get();
  char keyword[KEYWORD_LENGTH];
  snprintf(keyword, KEYWORD_LENGTH, "ESO INS POS%d POS", aEncoder);
  const double value = cpl_propertylist_get_double(aHeaders, keyword);
  cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), 0.0);
  return value;
}

/**@}*/
