/* -*- 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) 2008-2014 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

#include "muse_sky.h"
#include "muse_cplwrappers.h"
#include "muse_data_format_z.h"

/*----------------------------------------------------------------------------*/
/**
   @defgroup muse_skysub       Sky subtraction

   The sky subtraction consists of two steps: the calculation of the common
   sky parameters (continuum spectrum and emission line fluxes),
   and the actual subtraction of this spectrum applied to LSF parameters
   from each slice. The communication between these two steps is realized with
   the <tt>muse_sky_master</tt> structure.

   As input for the master sky calculation a parameter file is used that
   will be read into a <tt>muse_sky_lines</tt> structure.

   @copydetails muse_sky_lines_ohtransition_def

   @copydetails muse_sky_lines_lines_def
 */
/*----------------------------------------------------------------------------*/

/**@{*/

/*----------------------------------------------------------------------------*/
/**
   @private
   @brief Create a sky spectrum from a parametrization.
   @param aPixtable Pixel table to take lambda values from.
   @param aMaster Master sky parameters.
   @param aLsf Slice specific LSF parameters.
   @return The sky spectrum, according to the "lambda" column of the
           pixel table.
 */
/*----------------------------------------------------------------------------*/
static cpl_array *
muse_slice_get_skyspectrum(muse_pixtable *aPixtable, muse_sky_master *aMaster,
                           muse_lsf_params *aLsf)
{
  cpl_array *lambda = NULL;
  if (cpl_table_get_column_type(aPixtable->table, MUSE_PIXTABLE_LAMBDA)
      == CPL_TYPE_DOUBLE) {
    lambda = muse_cpltable_extract_column(aPixtable->table,
                                          MUSE_PIXTABLE_LAMBDA);
  } else {
    cpl_table_cast_column(aPixtable->table, MUSE_PIXTABLE_LAMBDA,
                          "lambda_double", CPL_TYPE_DOUBLE);
    lambda = muse_cpltable_extract_column(aPixtable->table, "lambda_double");
  }


  cpl_array *spectrum = NULL;
  if ((aMaster->lines != NULL) && (aLsf != NULL)) {
    spectrum = muse_sky_apply_lsf(lambda, aMaster->lines, aLsf);
  } else {
    spectrum = cpl_array_new(cpl_array_get_size(lambda), CPL_TYPE_DOUBLE);
    cpl_array_fill_window(spectrum, 0, cpl_array_get_size(spectrum), 0.0);
  }

  if (aMaster->continuum != NULL) {
    cpl_array *continuum
      = muse_cplarray_interpolate_table_linear(lambda, aMaster->continuum,
                                               "lambda", "flux");
    cpl_array_add(spectrum, continuum);
    cpl_array_delete(continuum);
  }

  cpl_array_unwrap(lambda);
  if (cpl_table_has_column(aPixtable->table, "lambda_double")) {
    cpl_table_erase_column(aPixtable->table, "lambda_double");
  }

  return spectrum;
}

/*----------------------------------------------------------------------------*/
/**
   @brief Subtract the sky spectrum from the "data" column of a pixel table for
          one slice.
   @param aPixtable Pixel table to take lambda values from.
   @param aMaster Master sky parameters.
   @param aLsf Slice specific LSF parameters.
   @retval CPL_ERROR_NONE if everything was OK.

   The pixel table is sorted by wavelength in this function.
 */
/*----------------------------------------------------------------------------*/
cpl_error_code
muse_sky_subtract_slice(muse_pixtable *aPixtable, muse_sky_master *aMaster,
                        muse_lsf_params *aLsf)
{
  cpl_propertylist *order = cpl_propertylist_new();
  cpl_propertylist_append_bool(order, MUSE_PIXTABLE_LAMBDA, CPL_FALSE);
  cpl_table_sort(aPixtable->table, order);
  cpl_propertylist_delete(order);

  cpl_array *spectrum = muse_slice_get_skyspectrum(aPixtable, aMaster, aLsf);

  cpl_array *data = muse_cpltable_extract_column(aPixtable->table,
                                                 MUSE_PIXTABLE_DATA);
  cpl_array_subtract(data, spectrum);

  cpl_size ii;
  for (ii = 0; ii < cpl_array_get_size(data); ii++) {
    if (!cpl_array_is_valid(spectrum, ii)) {
      cpl_table_set_invalid(aPixtable->table, MUSE_PIXTABLE_DATA, ii);
    }
  }
  cpl_array_unwrap(data);
  cpl_array_delete(spectrum);


  return CPL_ERROR_NONE;
}

/*----------------------------------------------------------------------------*/
/**
   @brief Subtract the sky spectrum from the "data" column of a pixel table
   @param aPixtable Pixel table to take lambda values from.
   @param aMaster Master sky parameters.
   @param aLsf Array with slice specific LSF parameters.
   @retval CPL_ERROR_NONE if everything was OK.
   @cpl_ensure{aPixtable != NULL, CPL_ERROR_NULL_INPUT, CPL_ERROR_NULL_INPUT}
   @cpl_ensure{aPixtable->table != NULL, CPL_ERROR_NULL_INPUT, CPL_ERROR_NULL_INPUT}
   @cpl_ensure{aPixtable->table is a pixel table, CPL_ERROR_DATA_NOT_FOUND, CPL_ERROR_DATA_NOT_FOUND}
   @cpl_ensure{aMaster != NULL, CPL_ERROR_NULL_INPUT, CPL_ERROR_NULL_INPUT}
   @cpl_ensure{aLsfs != NULL, CPL_ERROR_NULL_INPUT, CPL_ERROR_NULL_INPUT}
   @remark This function adds a FITS header (@ref MUSE_HDR_PT_SKYSUB) with the
           boolean value 'T' to the pixel table, for information.
 */
/*----------------------------------------------------------------------------*/
cpl_error_code
muse_sky_subtract_pixtable(muse_pixtable *aPixtable, muse_sky_master *aMaster,
                           muse_lsf_params **aLsf)
{
  cpl_ensure_code(aPixtable != NULL, CPL_ERROR_NULL_INPUT);
  cpl_ensure_code(aPixtable->table != NULL, CPL_ERROR_NULL_INPUT);
  cpl_ensure_code(muse_cpltable_check(aPixtable->table, muse_pixtable_def) == CPL_ERROR_NONE,
                  CPL_ERROR_DATA_NOT_FOUND);
  cpl_ensure_code(aMaster != NULL, CPL_ERROR_NULL_INPUT);
  if ((aMaster->lines == NULL) && (aMaster->continuum == NULL)) {
    return CPL_ERROR_NONE;
  }
  if (aMaster->lines != NULL) {
    cpl_ensure_code(aLsf != NULL, CPL_ERROR_NULL_INPUT);
  }

  if (aMaster->continuum != NULL) {
    /* cut input pixel table to the length of the continuum in the sky master */
    double lmin = cpl_table_get_column_min(aMaster->continuum, "lambda"),
      lmax = cpl_table_get_column_max(aMaster->continuum, "lambda");
    cpl_msg_info(__func__, "Cutting data to %.3f...%.3f Angstrom for sky "
                 "subtraction (range of continuum)", lmin, lmax);
    muse_pixtable_restrict_wavelength(aPixtable, lmin, lmax);
  }

  muse_pixtable **slice_pixtable = muse_pixtable_extracted_get_slices(aPixtable);
  cpl_size n_slices = muse_pixtable_extracted_get_size(slice_pixtable);
  cpl_size i_slice;
  cpl_msg_info(__func__, "Starting sky subtraction of %"CPL_SIZE_FORMAT" slices",
               n_slices);
  #pragma omp parallel for default(none)                 /* as req. by Ralf */ \
          shared(aLsf, aMaster, n_slices, slice_pixtable)
  for (i_slice = 0; i_slice < n_slices; i_slice++) {
    uint32_t origin
      = (uint32_t)cpl_table_get_int(slice_pixtable[i_slice]->table,
                                    MUSE_PIXTABLE_ORIGIN, 0, NULL);
    int ifu = muse_pixtable_origin_get_ifu(origin);
    int slice = muse_pixtable_origin_get_slice(origin);
    muse_lsf_params *slice_params = muse_lsf_params_get(aLsf, ifu, slice);
    if ((slice_params == NULL) && (aMaster->lines != NULL)){
      cpl_msg_warning(__func__, "No LSF params for slice #%i.%i."
                      " Ignoring lines in sky subtraction for this slice.",
                      ifu, slice);
    }

    cpl_size nrows = muse_pixtable_get_nrow(slice_pixtable[i_slice]);
    cpl_msg_debug(__func__, "Sky subtraction of %li pixels for slice #%i.%i",
                  (long)nrows, ifu, slice);
    cpl_errorstate prestate = cpl_errorstate_get();
    muse_sky_subtract_slice(slice_pixtable[i_slice], aMaster, slice_params);
    if (!cpl_errorstate_is_equal(prestate)) {
      cpl_errorstate_dump(prestate, CPL_FALSE, NULL);
      cpl_errorstate_set(prestate);
    }
  }
  muse_pixtable_extracted_delete(slice_pixtable);

  if (aPixtable->header) {
    /* add the status header */
    cpl_propertylist_update_bool(aPixtable->header, MUSE_HDR_PT_SKYSUB,
                                 CPL_TRUE);
    cpl_propertylist_set_comment(aPixtable->header, MUSE_HDR_PT_SKYSUB,
                                 MUSE_HDR_PT_SKYSUB_COMMENT);
  }
  return CPL_ERROR_NONE;
}

/**@}*/
