/* -*- 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-2015 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 <math.h>

#include "muse_sky.h"

#include "muse_cplwrappers.h"
#include "muse_data_format_z.h"
#include "muse_quality.h"
#include "muse_mask.h"
#include "muse_pfits.h"

/*----------------------------------------------------------------------------*
 *                          Special variable types                            *
 *----------------------------------------------------------------------------*/
/** @addtogroup muse_skysub */
/**@{*/

/*----------------------------------------------------------------------------*/
/**
    @brief Definition of the flux spectrum table structure.

    - <tt>lambda</tt>: wavelength [Angstrom]
    - <tt>flux</tt>: Flux [erg/(s cm^2 arcsec^2)]

    The table can be expected to be sorted by the wavelength.
 */
/*----------------------------------------------------------------------------*/
const muse_cpltable_def muse_fluxspectrum_def[] = {
  {"lambda", CPL_TYPE_DOUBLE, "Angstrom", "%7.2f", "wavelength", CPL_TRUE},
  {"flux", CPL_TYPE_DOUBLE, "erg/(s cm^2 arcsec^2)", "%e", "Flux", CPL_TRUE},
  { NULL, 0, NULL, NULL, NULL, CPL_FALSE }
};

/*----------------------------------------------------------------------------*/
/**
   @brief  Create a spectrum out of a cube by applying a mask.
   @param aCube MUSE datacube.
   @param aMask Mask image of sky regions.
   @return Table with @ref muse_dataspectrum_def "data spectrum".
*/
/*----------------------------------------------------------------------------*/
cpl_table *
muse_sky_spectrum_from_cube(muse_datacube *aCube, const cpl_mask *aMask) {
  unsigned count = cpl_imagelist_get_size(aCube->data);
  cpl_table *spectrum = muse_cpltable_new(muse_dataspectrum_def, count);
  double crval3 = muse_pfits_get_crval(aCube->header, 3);
  double crpix3 = muse_pfits_get_crpix(aCube->header, 3);
  double cdelt3 = muse_pfits_get_cd(aCube->header, 3, 3);
  unsigned i;
  cpl_mask *nmask = cpl_mask_duplicate(aMask);
  cpl_mask_not(nmask);
  for (i = 0; i < count; i++) {
    cpl_table_set(spectrum, "lambda", i, crval3 + (i + 1 - crpix3) * cdelt3);

    cpl_image *img = cpl_imagelist_get(aCube->data, i);
    cpl_mask *bpm = cpl_image_get_bpm(img);
    cpl_mask_or(bpm, nmask);
    if (aCube->dq != NULL) {
      cpl_image *dq = cpl_imagelist_get(aCube->dq, i);
      cpl_mask *dq_mask = cpl_mask_threshold_image_create(dq, -0.5, 0.5);
      cpl_mask_not(dq_mask);
      cpl_mask_or(bpm, dq_mask);
      cpl_mask_delete(dq_mask);
    }
    double dev = cpl_image_get_stdev(img);
    double mean = cpl_image_get_mean(img);
    cpl_table_set(spectrum, "data", i, mean);
    cpl_table_set(spectrum, "stat", i, dev / sqrt(cpl_mask_count(bpm)));
    cpl_table_set(spectrum, "dq", i, (cpl_mask_count(bpm) < 3));
  }
  cpl_mask_delete(nmask);

  return spectrum;
}

/*----------------------------------------------------------------------------*/
/**
  @brief  Select spaxels to be considered as sky.
  @param  aImage      MUSE (white-light) image of the field of view.
  @param  aMinFraction Lowest fraction of spectra to ignore
  @param  aFraction   Lowest fraction of remaining spectra to select
  @param  aQCPrefix   prefix for the QC keywords
  @return A newly created bitmask for sky spaxels.

  Use thresholding to create a mask of sky regions.
  The output mask contains a QC parameter aQCPrefix" THRESHOLD" in its header
  component.

  @error{set CPL_ERROR_NULL_INPUT\, return NULL, aImage is NULL}
 */
/*----------------------------------------------------------------------------*/
muse_mask *
muse_sky_create_skymask(muse_image *aImage, double aMinFraction, double aFraction,
                        const char *aQCPrefix)
{
  cpl_ensure(aImage, CPL_ERROR_NULL_INPUT, NULL);

  /* Re-normalize fraction: as parameter, it means the fraction of the remaining image,
     while we need the absolute number here. Example: if
     minFraction = 0.2, Fraction = 0.8 means: throw away the lowest 20% and then take the
     lowest 80% of the remaining image. This converts to 84 % of the total image. */
  aFraction = aMinFraction + aFraction * (1-aMinFraction);

  /* do statistics */
  muse_image_reject_from_dq(aImage);
  double t0 = muse_cplimage_get_percentile(aImage->data, aMinFraction);
  double t1 = muse_cplimage_get_percentile(aImage->data, aFraction);
  cpl_msg_info(__func__, "Creating sky mask for pixels between minimum (%g) and"
               " threshold (%g)", t0, t1);

  /* create output mask with pixels between min and threshold */
  muse_mask *selectedRegions = muse_mask_new();
  selectedRegions->mask = cpl_mask_threshold_image_create(aImage->data, t0, t1);
  cpl_mask_not(selectedRegions->mask);
  cpl_mask_or(selectedRegions->mask, cpl_image_get_bpm(aImage->data));
  cpl_mask_not(selectedRegions->mask);

  /* add threshold as QC parameter */
  selectedRegions->header = cpl_propertylist_duplicate(aImage->header);
  char keyword[KEYWORD_LENGTH];
  snprintf(keyword, KEYWORD_LENGTH, "%s THRESHOLD", aQCPrefix);
  cpl_propertylist_append_double(selectedRegions->header, keyword, t1);

  return selectedRegions;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Mark all pixel above a certain limit as COSMIC
  @param    aSpectrum   Reference spectrum
  @param    aPixtable   Pixel table
  @param    aSigma      sigma rejection level
  @return   CPL_ERROR_NONE on success, another CPL error code on failure

  Check each selected pixel of the pixel table against the
  reference spectrum. If the data value of the pixel is higher than the
  according value of the spectrum + aSigma * variance, it is considered as
  cosmic. This works well for sky since we assume a homogenious illumination,
  strong variations are not expected here.

  @error{set and return CPL_ERROR_NULL_INPUT,
         aSpectrum and/or aPixtable are NULL}
  @error{set and return CPL_ERROR_ILLEGAL_INPUT, aSigma is not positive}
 */
/*----------------------------------------------------------------------------*/
cpl_error_code
muse_sky_mark_cosmic(cpl_table *aSpectrum, muse_pixtable *aPixtable,
                     double aSigma)
{
  cpl_ensure_code(aSpectrum && aPixtable, CPL_ERROR_NULL_INPUT);
  cpl_ensure_code(aSigma > 0., CPL_ERROR_ILLEGAL_INPUT);

  float *lbda = cpl_table_get_data_float(aPixtable->table, MUSE_PIXTABLE_LAMBDA);
  float *data = cpl_table_get_data_float(aPixtable->table, MUSE_PIXTABLE_DATA);
  int *dq = cpl_table_get_data_int(aPixtable->table, MUSE_PIXTABLE_DQ);

  cpl_size spec_nrows = cpl_table_get_nrow(aSpectrum);
  double *spec_data = cpl_table_get_data_double(aSpectrum, "data");
  double *spec_stat = cpl_table_get_data_double(aSpectrum, "stat");
  double *spec_std = cpl_malloc(spec_nrows * sizeof(double));
  cpl_size i_spec;
  for (i_spec = 0; i_spec < spec_nrows; i_spec++) {
    spec_std[i_spec] = sqrt(spec_stat[i_spec]);
  }

  /* loop through all selected pixel table rows  */
  cpl_array *asel = cpl_table_where_selected(aPixtable->table);
  const cpl_size *sel = cpl_array_get_data_cplsize_const(asel);
  cpl_size isel, nsel = cpl_array_get_size(asel);
  cpl_size n_cosmic = 0;
  for (isel = 0; isel < nsel; isel++) {
    cpl_size i_row = sel[isel];
    if ((dq[i_row] != EURO3D_GOODPIXEL)) {
      continue;
    }
    double l = lbda[i_row];
    i_spec = muse_cpltable_find_sorted(aSpectrum, "lambda", l);
    if ((i_spec < spec_nrows - 1) && (spec_data[i_spec] < spec_data[i_spec+1])) {
      i_spec++;
    }
    if (spec_data[i_spec] + aSigma * spec_std[i_spec] < data[i_row]) {
      dq[i_row] = EURO3D_COSMICRAY;
      n_cosmic++;
    }
  }
  cpl_msg_info(__func__, "Marked %"CPL_SIZE_FORMAT" pixels of %"CPL_SIZE_FORMAT
               " as cosmic", n_cosmic, nsel);

  cpl_array_delete(asel);
  cpl_free(spec_std);
  return CPL_ERROR_NONE;
} /* muse_sky_mark_cosmic() */

/*----------------------------------------------------------------------------*/
/**
  @brief  Save sky continuum table to file.
  @param  aProcessing   the processing structure
  @param  aContinuum    the sky continuum table
  @param  aHeader       the FITS header to use for the primary HDU
  @return CPL_ERROR_NONE on success, another CPL error code on failure

  The table extension is marked with and EXTNAME of "CONTINUUM".

  @error{return CPL_ERROR_NULL_INPUT, one of the arguments is NULL}
  @error{return CPL_ERROR_ILLEGAL_INPUT, the output frame could not be created}
 */
/*----------------------------------------------------------------------------*/
cpl_error_code
muse_sky_save_continuum(muse_processing *aProcessing,
                        const cpl_table *aContinuum, cpl_propertylist *aHeader)
{
  cpl_ensure_code(aProcessing && aContinuum && aHeader, CPL_ERROR_NULL_INPUT);
  cpl_frame *frame = muse_processing_new_frame(aProcessing, -1, aHeader,
                                               MUSE_TAG_SKY_CONT,
                                               CPL_FRAME_TYPE_TABLE);
  cpl_ensure_code(frame, CPL_ERROR_ILLEGAL_INPUT);
  const char *filename = cpl_frame_get_filename(frame);
  cpl_error_code rc = cpl_propertylist_save(aHeader, filename, CPL_IO_CREATE);
  rc = muse_cpltable_append_file(aContinuum, filename,
                                 "CONTINUUM", muse_fluxspectrum_def);
  if (rc == CPL_ERROR_NONE) {
    cpl_frameset_insert(aProcessing->outframes, frame);
  } else {
    cpl_frame_delete(frame);
  }
  return rc;
} /* muse_sky_save_continuum() */

/**@}*/
