/* -*- 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) 2016 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 "muse_rtcdata.h"

/*----------------------------------------------------------------------------*/
/**
 * @defgroup muse_rtcdata        SPARTA RTC data container implementation
 *
 * This module implements a simple container object to store the SPARTA
 * real-time computer data tables, which are attached to the raw exposures if
 * the adaptive optics subsystem was used. It allows to easily propagate these
 * data tables as an entity.
 */
/*----------------------------------------------------------------------------*/
/**@{*/

/*----------------------------------------------------------------------------*/
/**
  @brief    Allocate memory for a new <tt><b>muse_rtcdata</b></tt> object.
  @return   a new <tt><b>muse_rtcdata *</b></tt> or @c NULL on error
  @remark   The returned object has to be deallocated using
            <tt><b>muse_rtcdata_delete()</b></tt>.
  @remark   This function does not allocate the contents of the elements,
            these have to be allocated with @c cpl_table_new() or an
            equivalent function.

  Simply allocate the memory for an empty <tt><b>muse_rtcdata</b></tt>
  container.
 */
/*----------------------------------------------------------------------------*/
muse_rtcdata *
muse_rtcdata_new(void)
{
  return (muse_rtcdata *)cpl_calloc(1, sizeof(muse_rtcdata));
} /* muse_rtcdata_new() */

/*----------------------------------------------------------------------------*/
/**
  @brief    Deallocate memory associated to a muse_rtcdata object.
  @param    aRtcdata   MUSE real-time data container to deallocate.

  Deallocates the memory used by the input @c aRtcdata object. If the
  input container is not empty, i.e. the data members are not @c NULL
  pointers, the data members are also deallocated.
 */
/*----------------------------------------------------------------------------*/
void
muse_rtcdata_delete(muse_rtcdata *aRtcdata)
{
  if (!aRtcdata) {
    return;
  }
  cpl_table_delete(aRtcdata->atm);
  cpl_table_delete(aRtcdata->cn2);
  cpl_free(aRtcdata);
  return;
} /* muse_rtcdata_delete() */

/*----------------------------------------------------------------------------*/
/**
  @brief    Load the real-time computer data tables from a file.
  @param    aFilename   The name of the file.
  @return   a new <tt><b>muse_rtcdata</b></tt> or @c NULL if no data tables
            were found, or an error occurred.

  Looks for the SPARTA RTC data in the file @em aFilename and reads
  them from the file if they are present. The RTC is considered to be present
  and complete, if both extenstions, @c SPARTA_ATM_DATA and @c SPARTA_CN2_DATA
  are found in @m aFilename.

  If no RTC data is found @c NULL is returned. If an error occurs @c NULL is
  returned and the appropriate error is set.

  @error{set CPL_ERROR_NULL_INPUT\, return NULL, aFilename is NULL}
  @error{set CPL_ERROR_DATA_NOT_FOUND\, return NULL, RTC data extensions
         are missing}
  @error{propagate CPL error code\, return NULL, cpl_find_extension() or
         cpl_table_load() failed}

 */
/*----------------------------------------------------------------------------*/
muse_rtcdata *
muse_rtcdata_load(const char *aFilename)
{
  cpl_ensure(aFilename, CPL_ERROR_NULL_INPUT, NULL);

  cpl_errorstate estate = cpl_errorstate_get();
  cpl_size iext_atm = cpl_fits_find_extension(aFilename, "SPARTA_ATM_DATA");
  cpl_size iext_cn2 = cpl_fits_find_extension(aFilename, "SPARTA_CN2_DATA");
  if (!cpl_errorstate_is_equal(estate)) {
    cpl_error_code ecode = cpl_error_get_code();
    cpl_errorstate_set(estate);
    cpl_error_set_message(__func__, ecode, "Searching for RTC data "
                          "in input file \"%s\" failed!", aFilename);
    return NULL;
  }
  if ((iext_atm == 0) || (iext_cn2 == 0)) {
    cpl_error_set_message(__func__, CPL_ERROR_DATA_NOT_FOUND,
                          "No or incomplete RTC data was found in \"%s\".",
                          aFilename);
    return NULL;
  }

  muse_rtcdata *rtcdata = muse_rtcdata_new();

  estate = cpl_errorstate_get();
  rtcdata->atm = cpl_table_load(aFilename, iext_atm, 1);
  rtcdata->cn2 = cpl_table_load(aFilename, iext_cn2, 1);
  if (!cpl_errorstate_is_equal(estate)) {
    cpl_error_code ecode = cpl_error_get_code();
    cpl_errorstate_set(estate);
    cpl_error_set_message(__func__, ecode, "Reading RTC data from input "
                          "file \"%s\" failed!", aFilename);
    muse_rtcdata_delete(rtcdata);
    return NULL;
  }
  return rtcdata;
} /* muse_rcdata_load() */

/*----------------------------------------------------------------------------*/
/**
  @brief    Compute the median Strehl ration from the measured RTC data.
  @param    aRtcdata  the RTC data structure to use for the computation
  @param    aStrehl   the computed median Strehl ratio
  @param    aStrehl   the uncertainty of the computed Strehl ratio
  @return   CPL_ERROR_NONE on success, or an appropriate CPL error code
            otherwise.

  Compute the median Strehl ratio from all measurements of all laser guide
  stars from the ATM data table of the given @em aRtcdata structure. The
  error of the computed Strehl ratio is estimated as its MAD. If there are
  not enough valid measurements of the Strehl ratio in the ATM data table,
  so that no Strehl ratio and error can be determined the function returns
  the error code @c CPL_ERROR_DATA_NOT_FOUND. In this case the output
  arguments @em aStrehl and @em aStrehlMad are not nodified!

  @error{set CPL_ERROR_NULL_INPUT\, return CPL_ERROR_NULL_INPUT,
         aRtcdata or its atm data table is NULL}
  @error{set CPL_ERROR_DATA_NOT_FOUND\, return CPL_ERROR_DATA_NOT_FOUND,
         aRtcdata or its atm data table is NULL}

 */
/*----------------------------------------------------------------------------*/
cpl_error_code
muse_rtcdata_median_strehl(const muse_rtcdata *aRtcdata, double *aStrehl,
                           double *aStrehlMad)
{
  cpl_ensure(aRtcdata && aRtcdata->atm, CPL_ERROR_NULL_INPUT,
             CPL_ERROR_NULL_INPUT);

  double strehl_median = 0.;
  double strehl_mad = 0.;

  /* The MUSE AO system uses 4 laser guide stars. Thus there are a maximum *
   * of 4 columns with measurements of the Strehl ratio available in the   *
   * data table                                                            */
  const cpl_size ncolumns = 4;
  const cpl_size nrows = cpl_table_get_nrow(aRtcdata->atm);

  cpl_size kdata = 0;
  cpl_size ndata = ncolumns * nrows;
  cpl_array *strehl_measurements = cpl_array_new(ndata, CPL_TYPE_DOUBLE);
  cpl_size icolumn;
  for (icolumn = 0; icolumn < ncolumns; ++icolumn) {
    char *column = cpl_sprintf("LGS%" CPL_SIZE_FORMAT "_STREHL", icolumn + 1);
    if (cpl_table_has_column(aRtcdata->atm, column)) {
      cpl_size irow;
      for (irow = 0; irow < nrows; ++irow) {
        int invalid = 0;
        float value = cpl_table_get_float(aRtcdata->atm, column, irow,
                                          &invalid);
        if (!invalid) {
          cpl_array_set_double(strehl_measurements, kdata++, value);
        }
      }
    }
    cpl_free(column);
  }

  if (kdata == 0) {
    cpl_error_set_message(__func__, CPL_ERROR_DATA_NOT_FOUND, "No valid RTC "
                          "Strehl measurements are available!");
    cpl_array_delete(strehl_measurements);
    return CPL_ERROR_DATA_NOT_FOUND;
  }

  /* Compute the median and the MAD of the measured Strehl ratios. When  *
   * calculating the MAD, the initial data buffer is reused and altered. */
  strehl_median = cpl_array_get_median(strehl_measurements);

  cpl_array_subtract_scalar(strehl_measurements, strehl_median);
  cpl_array_abs(strehl_measurements);
  strehl_mad = cpl_array_get_median(strehl_measurements);

  *aStrehl = strehl_median;
  *aStrehlMad = strehl_mad;

  return CPL_ERROR_NONE;
}

/**@}*/
