/* -*- 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-2014 European Southern Observatory
 *           (C) 2001 Enrico Marchetti, 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_rvcorrect.h"

#include "muse_astro.h"

/*----------------------------------------------------------------------------*
 *                             Debugging/feature Macros                       *
 *----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------*/
/**
 * @defgroup muse_rvcorrect    Radial velocity corrections
 */
/*----------------------------------------------------------------------------*/

/**@{*/

/* corresponds to muse_rvcorrect_type, keep in sync with those values! */
static const char *muse_rvcorrect_typestring[] = {
  "none",
  "bary",
  "helio",
  "geo",
  "unknown"
};

/* Keyword values of the supported spectral reference frames, ordered         *
 * according to the definition of the muse_rvcorrect_type enumeration values. */
static const char *const
muse_specsys_id[] = {
 "TOPOCENT",
 "BARYCENT",
 "HELIOCEN",
 "GEOCENTR",
 "UNKNOWN"
};

/*----------------------------------------------------------------------------*/
/**
  @brief   Select type of radial velocity correction to be done from type string.
  @param   aTypeString   type string of velocity correction to perform
  @return  the corresponding muse_rvcorrect_type or MUSE_RVCORRECT_UNKNOWN on
           error

  @error{set CPL_ERROR_NULL_INPUT\, return MUSE_RVCORRECT_UNKNOWN,
         aTypeString is NULL}
  @error{set CPL_ERROR_ILLEGAL_INPUT\, return MUSE_RVCORRECT_UNKNOWN,
         an unknown aType was given}
 */
/*----------------------------------------------------------------------------*/
muse_rvcorrect_type
muse_rvcorrect_select_type(const char *aTypeString)
{
  cpl_ensure(aTypeString, CPL_ERROR_NULL_INPUT, MUSE_RVCORRECT_UNKNOWN);
  muse_rvcorrect_type type = MUSE_RVCORRECT_NONE;
  if (!strncmp(aTypeString, muse_rvcorrect_typestring[MUSE_RVCORRECT_BARY],
               strlen(muse_rvcorrect_typestring[MUSE_RVCORRECT_BARY]) + 1)) {
    type = MUSE_RVCORRECT_BARY;
  } else if (!strncmp(aTypeString, muse_rvcorrect_typestring[MUSE_RVCORRECT_HELIO],
               strlen(muse_rvcorrect_typestring[MUSE_RVCORRECT_HELIO]) + 1)) {
    type = MUSE_RVCORRECT_HELIO;
  } else if (!strncmp(aTypeString, muse_rvcorrect_typestring[MUSE_RVCORRECT_GEO],
               strlen(muse_rvcorrect_typestring[MUSE_RVCORRECT_GEO]) + 1)) {
    type = MUSE_RVCORRECT_GEO;
  } else if (strncmp(aTypeString, muse_rvcorrect_typestring[MUSE_RVCORRECT_NONE],
               strlen(muse_rvcorrect_typestring[MUSE_RVCORRECT_NONE]) + 1)) {
    cpl_error_set_message(__func__, CPL_ERROR_ILLEGAL_INPUT, "Unknown type of "
                          "radial velocity correction requested:" " \"%s\"",
                          aTypeString);
    type = MUSE_RVCORRECT_UNKNOWN;
  } /* else */
  return type;
} /* muse_rvcorrect_select_type() */

/*----------------------------------------------------------------------------*/
/**
  @brief   Correct the wavelengths of all pixels of a given pixel table for
           radial velocity shift.
  @param   aPixtable   the input pixel table to correct for DAR
  @param   aType       type of velocity correction to perform
  @return  CPL_ERROR_NONE on success another CPL error code on failure
  @remark  The resulting correction is directly applied to the input pixel
           table.
  @remark  This function adds a FITS header (@ref MUSE_HDR_PT_RVCORR) with the
           value of the computed correction to the pixel table; its comment
           contains the type of correction that was applied. Then users and
           other functions can determine, if and which correction was applied.
           If such a pixel table is passed to this function, it will then
           immediately return.

  @error{return CPL_ERROR_NULL_INPUT,
         the input pixel table and/or one of its components are NULL}
  @error{output info message\, skip the correction\, return CPL_ERROR_NONE,
         the input pixel table was already corrected for non-zero radial velocity}
  @error{return CPL_ERROR_ILLEGAL_INPUT, an unknown aType was given}
 */
/*----------------------------------------------------------------------------*/
cpl_error_code
muse_rvcorrect(muse_pixtable *aPixtable, muse_rvcorrect_type aType)
{
  cpl_ensure_code(aPixtable && aPixtable->table && aPixtable->header,
                  CPL_ERROR_NULL_INPUT);
  const char *specsys = NULL;
  if (aType == MUSE_RVCORRECT_NONE) {
    specsys = muse_specsys_id[MUSE_RVCORRECT_NONE];
  } else {
    if (cpl_propertylist_has(aPixtable->header, MUSE_HDR_PT_RVCORR) &&
        fabs(cpl_propertylist_get_double(aPixtable->header, MUSE_HDR_PT_RVCORR)) > 0.) {
      cpl_msg_info(__func__, "pixel table already corrected: skipping radial "
          "velocity correction");
      return CPL_ERROR_NONE;
    }

    cpl_errorstate state = cpl_errorstate_get();
    muse_astro_rvcorr rvcorr = muse_astro_rvcorr_compute(aPixtable->header);
    if (!cpl_errorstate_is_equal(state)) {
      return cpl_error_set_message(__func__, cpl_error_get_code(), "Computing "
                                   "radial velocity correction failed: %s",
                                   cpl_error_get_message());
    }
    double rvoffset = 0.;
    if (aType == MUSE_RVCORRECT_BARY) {
      rvoffset = rvcorr.bary;
      specsys = muse_specsys_id[MUSE_RVCORRECT_BARY];
    } else if (aType == MUSE_RVCORRECT_HELIO) {
      rvoffset = rvcorr.helio;
      specsys = muse_specsys_id[MUSE_RVCORRECT_HELIO];
    } else if (aType == MUSE_RVCORRECT_GEO) {
      rvoffset = rvcorr.geo;
      specsys = muse_specsys_id[MUSE_RVCORRECT_GEO];
    } else { /* unknown type */
      return cpl_error_set_message(__func__, CPL_ERROR_ILLEGAL_INPUT, "Unknown "
                                   "type of radial velocity correction, no "
                                   "correction performed!");
    }
    cpl_msg_info(__func__, "Correcting data for %scentric radial velocity of %.2f"
                 " km/s", muse_rvcorrect_typestring[aType], rvoffset);

    /* apply the velocity offset to all pixel table rows */
    const double cinv = 1000. / CPL_PHYS_C; /* inverse speed of light in km/s */
    float *lbda = cpl_table_get_data_float(aPixtable->table, MUSE_PIXTABLE_LAMBDA);
    cpl_size irow, nrow = muse_pixtable_get_nrow(aPixtable);
    #pragma omp parallel for default(none)                                       \
        shared(lbda, nrow, rvoffset)                                             \
        firstprivate(cinv)
    for (irow = 0; irow < nrow; irow++) {
      /* use the relativistic velocity correction formula, see e.g. IRAF dopcor */
      lbda[irow] *= sqrt((1. + rvoffset * cinv) / (1. - rvoffset * cinv));
    } /* for irow (all pixel table rows) */

    /* add the header to the pixel table to show that the correction was done */
    cpl_propertylist_update_double(aPixtable->header, MUSE_HDR_PT_RVCORR,
                                   rvoffset);
    char *comment = cpl_sprintf(MUSE_HDR_PT_RVCORR_C,
                                muse_rvcorrect_typestring[aType]);
    cpl_propertylist_set_comment(aPixtable->header, MUSE_HDR_PT_RVCORR, comment);
    cpl_free(comment);
  }
  /* Set the spectral reference frame in the pixeltable header. This is also *
   * needed if no correction was actually applied                            */
  cpl_propertylist_update_string(aPixtable->header, "SPECSYS", specsys);
  cpl_propertylist_set_comment(aPixtable->header, "SPECSYS",
                               "Spectral reference frame");
  return CPL_ERROR_NONE;
} /* muse_rvcorrect() */

/**@}*/
