/* -*- 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 or NULL on error.

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

  The function muse_autocalib_create_mask() is similar, but tries to select
  larger contiguous regions, instead of the lowest ones.

  @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.                           */
  double fraction = aMinFraction + aFraction * (1-aMinFraction);

  /* do statistics, ignoring pre-existing bad pixels */
  muse_image_reject_from_dq(aImage);
  double t0 = muse_cplimage_get_percentile(aImage->data, aMinFraction),
         t1 = muse_cplimage_get_percentile(aImage->data, fraction);
  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 *selected = muse_mask_new();
  selected->mask = cpl_mask_threshold_image_create(aImage->data, t0, t1);
  cpl_mask_not(selected->mask);
  cpl_mask_or(selected->mask, cpl_image_get_bpm(aImage->data));
  cpl_mask_not(selected->mask);

  /* add threshold as QC parameter */
  selected->header = cpl_propertylist_duplicate(aImage->header);
  cpl_propertylist_erase_regexp(selected->header, "BUNIT|ESO DRS MUSE", 0);
  char keyword[KEYWORD_LENGTH];
  snprintf(keyword, KEYWORD_LENGTH, "%s LOWLIMIT", aQCPrefix);
  cpl_propertylist_append_double(selected->header, keyword, t0);
  snprintf(keyword, KEYWORD_LENGTH, "%s THRESHOLD", aQCPrefix);
  cpl_propertylist_append_double(selected->header, keyword, t1);

  return selected;
} /* muse_sky_create_skymask() */

/*----------------------------------------------------------------------------*/
/**
  @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) {
#pragma omp critical(muse_processing_output_frames)
    cpl_frameset_insert(aProcessing->outframes, frame);
  } else {
    cpl_frame_delete(frame);
  }
  return rc;
} /* muse_sky_save_continuum() */

/**@}*/
