/* -*- 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
 *
 * 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 <string.h>
#include <math.h>

#include <muse.h>
#include "muse_create_sky_z.h"

/*---------------------------------------------------------------------------*
 *                             Functions code                                *
 *---------------------------------------------------------------------------*/
static muse_pixtable *
muse_create_sky_load_pixtable(muse_processing *aProcessing,
                              muse_create_sky_params_t *aParams)
{
   muse_pixtable *pixtable = NULL;

  /* sort input pixel tables into different exposures */
  cpl_table *exposures = muse_processing_sort_exposures(aProcessing);
  if (!exposures) {
    cpl_msg_error(__func__, "no science exposures found in input");
    return NULL;
  }
  int nexposures = cpl_table_get_nrow(exposures);
  if (nexposures != 1) {
    cpl_msg_error(__func__, "More than one exposure (%i) in sky creation",
                  nexposures);
  }

  /* now process all the pixel tables, do it separately for each exposure */
  int i;
  for (i = 0; i < nexposures; i++) {
    cpl_table *thisexp = cpl_table_extract(exposures, i, 1);
    muse_pixtable *p = muse_pixtable_load_merge_channels(thisexp,
                                                         aParams->lambdamin,
                                                         aParams->lambdamax);
    cpl_table_delete(thisexp);
    if (p == NULL) {
      muse_pixtable_delete(pixtable);
      pixtable = NULL;
      break;
    }
    /* erase pre-existing QC parameters */
    cpl_propertylist_erase_regexp(p->header, "ESO QC ", 0);
    if (pixtable == NULL) {
      pixtable = p;
    } else {
      cpl_table_insert(pixtable->table, p->table,
                       cpl_table_get_nrow(pixtable->table));
      muse_pixtable_delete(p);
    }
  }
  cpl_table_delete(exposures);

  if ((pixtable != NULL) && (muse_pixtable_is_skysub(pixtable) == CPL_TRUE)) {
    cpl_msg_error(__func__, "Pixel table already sky subtracted");
    muse_pixtable_delete(pixtable);
    pixtable = NULL;
  }

  muse_table *response = muse_processing_load_table(aProcessing,
                                                    MUSE_TAG_STD_RESPONSE, 0),
             *telluric = muse_processing_load_table(aProcessing,
                                                    MUSE_TAG_STD_TELLURIC, 0);
  cpl_table *extinction = muse_processing_load_ctable(aProcessing,
                                                      MUSE_TAG_EXTINCT_TABLE, 0);

  if ((pixtable != NULL) && (response != NULL)) {
    if (muse_pixtable_is_fluxcal(pixtable) == CPL_TRUE) {
      cpl_msg_error(__func__,
                    "Pixel table already flux calibrated. Dont specify %s, %s, %s",
                    MUSE_TAG_STD_RESPONSE, MUSE_TAG_EXTINCT_TABLE,
                    MUSE_TAG_STD_TELLURIC);
      muse_pixtable_delete(pixtable);
      pixtable = NULL;
    } else {
      /* See if we need to revert the flat-field spectrum correction, *
       * to match pixel table and response curve, before applying it. */
      muse_postproc_revert_ffspec_maybe(pixtable, response);
      cpl_error_code rc = muse_flux_calibrate(pixtable, response, extinction,
                                              telluric);
      if (rc != CPL_ERROR_NONE) {
        cpl_msg_error(__func__, "while muse_flux_calibrate");
        muse_pixtable_delete(pixtable);
        pixtable = NULL;
      }
    }
  }

  muse_table_delete(response);
  muse_table_delete(telluric);
  cpl_table_delete(extinction);

  if ((pixtable != NULL) && (muse_pixtable_is_fluxcal(pixtable) != CPL_TRUE)) {
    cpl_msg_error(__func__, "Pixel table not flux calibrated");
    muse_pixtable_delete(pixtable);
    pixtable = NULL;
  }

  if (pixtable != NULL) {
    cpl_table_and_selected_int(pixtable->table, MUSE_PIXTABLE_DQ,
                               CPL_NOT_EQUAL_TO, EURO3D_GOODPIXEL);
    cpl_table_erase_selected(pixtable->table);

    /* do DAR correction for WFM data */
    if (muse_pfits_get_mode(pixtable->header) < MUSE_MODE_NFM_AO_N) {
      cpl_msg_debug(__func__, "WFM detected: starting DAR correction");
      cpl_error_code rc = muse_dar_correct(pixtable, aParams->lambdaref);
      cpl_msg_debug(__func__, "DAR correction returned rc=%d: %s", rc,
                    rc != CPL_ERROR_NONE ? cpl_error_get_message() : "");
    } /* if WFM */
  }

  return pixtable;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Interpret the command line options and execute the data processing
  @param    aProcessing   the processing structure
  @param    aParams       the parameters list
  @return   0 if everything is ok, -1 something went wrong
 */
/*----------------------------------------------------------------------------*/
int
muse_create_sky_compute(muse_processing *aProcessing,
                        muse_create_sky_params_t *aParams)
{
  cpl_array *crarray = muse_cplarray_new_from_delimited_string(aParams->crsigma, ",");
  double crsigmac = 15., /* for cube-based CR rejection */
         crsigmas = 0.; /* spectrum-based CR rejection */
  if (cpl_array_get_size(crarray) < 2) {
    cpl_msg_warning(__func__, "Less than two values given by crsigma "
                    "parameter, using defaults (%.3f.,%.3f)!", crsigmac,
                    crsigmas);
  } else {
    crsigmac = cpl_array_get_string(crarray, 0)
             ? atof(cpl_array_get_string(crarray, 0)) : 15.;
    crsigmas = cpl_array_get_string(crarray, 1)
             ? atof(cpl_array_get_string(crarray, 1)) : 0.;
  }
  cpl_array_delete(crarray);

  muse_pixtable *pixtable = muse_create_sky_load_pixtable(aProcessing, aParams);
  if (pixtable == NULL) {
    cpl_msg_error(__func__, "Could not load pixel table");
    return -1;
  }

  /*
     First step: find the regions containing sky:
     - create a whitelight image
     - create the mask from it
     - select pixtable rows containing sky
     - create spectrum from selected rows
  */
  muse_mask *smask = muse_processing_load_mask(aProcessing, MUSE_TAG_SKY_MASK);
  if (smask) { /* apply existing mask on input pixel table */
    cpl_table_select_all(pixtable->table);
    muse_pixtable_and_selected_mask(pixtable, smask, NULL, NULL);
    muse_mask_delete(smask);
  }

  muse_image *whitelight = muse_postproc_whitelight(pixtable, crsigmac);
  if (whitelight == NULL) {
    cpl_msg_error(__func__, "Could not create whitelight image");
    return -1;
  }
  muse_processing_save_image(aProcessing, -1, whitelight, MUSE_TAG_SKY_IMAGE);

  muse_mask *sky_mask = muse_sky_create_skymask(whitelight, aParams->ignore,
                                                aParams->fraction,
                                                "ESO QC SKY");
  muse_processing_save_mask(aProcessing, -1, sky_mask, MUSE_TAG_SKY_MASK);

  cpl_table_select_all(pixtable->table);
  muse_pixtable_and_selected_mask(pixtable, sky_mask, NULL, NULL);
  cpl_table_not_selected(pixtable->table);
  cpl_table_erase_selected(pixtable->table);
  muse_image_delete(whitelight);
  muse_mask_delete(sky_mask);

  /* Apply mask and create spectrum, possibly with one CR-reject iteration */
  cpl_table *spectrum = muse_resampling_spectrum_iterate(pixtable,
                                                         aParams->sampling,
                                                         0., crsigmas, 1);
  if (spectrum == NULL) {
    muse_pixtable_delete(pixtable);
    return -1;
  }
  muse_processing_save_table(aProcessing, -1, spectrum, pixtable->header,
                             MUSE_TAG_SKY_SPECTRUM, MUSE_TABLE_TYPE_CPL);

  cpl_table *lines = muse_sky_lines_load(aProcessing);
  if (lines != NULL) {
    double lambda_low = cpl_table_get_column_min(spectrum, "lambda");
    double lambda_high = cpl_table_get_column_max(spectrum, "lambda");
    muse_sky_lines_set_range(lines, lambda_low-5, lambda_high+5);
  }

  cpl_table *continuum = muse_sky_continuum_load(aProcessing);

  /*
    Second step: create master sky, containing line list and
    continuum.  If a continuum was given as calibration frame, we
    ignore the computed one and replace it by the external one.
   */

  cpl_errorstate prestate = cpl_errorstate_get();
  /*
    Create the average LSF image for the spectrum of the pixels
    selected as sky. This is done in the following steps:

    1. Average all cubes with the pixel count as weights into a single
       image.

    2. Fold an additional rectangle to model the sky spectrum binning
   */
  muse_lsf_cube **lsfCube = muse_lsf_cube_load_all(aProcessing);
  if (lsfCube != NULL) {
    cpl_image *lsfImage = muse_lsf_average_cube_all(lsfCube, pixtable);
    muse_wcs *lsfWCS = muse_lsf_cube_get_wcs_all(lsfCube);
    muse_lsf_fold_rectangle(lsfImage, lsfWCS, aParams->sampling);

    cpl_msg_info(__func__, "Creating master sky spectrum using fits to lines "
                 "(fluxes) and residual continuum");

    // do the fit, ignoring possible errors
    muse_sky_lines_fit(spectrum, lines, lsfImage, lsfWCS);
    if (!cpl_errorstate_is_equal(prestate)) {
      cpl_errorstate_dump(prestate, CPL_FALSE, NULL);
      cpl_errorstate_set(prestate);
    }

    if (continuum == NULL) {
      continuum = muse_sky_continuum_create(spectrum, lines, lsfImage, lsfWCS,
                                            aParams->csampling);
    }

    muse_lsf_cube_delete_all(lsfCube);
    cpl_image_delete(lsfImage);
#ifdef USE_LSF_PARAMS
  } else {
    cpl_errorstate_set(prestate);
    muse_lsf_params **lsfParams = muse_processing_lsf_params_load(aProcessing, 0);
    if (lsfParams != NULL) {  // Old LSF params code

      // do the fit, ignoring possible errors
      muse_sky_lines_fit_old(spectrum, lines);
      if (!cpl_errorstate_is_equal(prestate)) {
        cpl_errorstate_dump(prestate, CPL_FALSE, NULL);
        cpl_errorstate_set(prestate);
      }

      if (continuum == NULL) {
        muse_sky_subtract_lines_old(pixtable, lines, lsfParams);
        continuum = muse_resampling_spectrum(pixtable, aParams->csampling);
        cpl_table_erase_column(continuum, "stat");
        cpl_table_erase_column(continuum, "dq");
        cpl_table_name_column(continuum, "data", "flux");
      }

      muse_lsf_params_delete_all(lsfParams);
    } else {
      cpl_msg_error(__func__, "Could not load LSF. Continuum is not created.");
    }
#endif
  }
  /* add QC parameters for both emission lines and continuum and save them */
  cpl_propertylist *header = cpl_propertylist_new();
  muse_sky_qc_lines(header, lines, "ESO QC SKY");
  muse_sky_lines_save(aProcessing, lines, header);
  cpl_propertylist_delete(header);

  header = cpl_propertylist_new();
  muse_sky_qc_continuum(header, continuum, "ESO QC SKY");
  muse_sky_save_continuum(aProcessing, continuum, header);
  cpl_propertylist_delete(header);

  /* Clean up the local objects. */
  cpl_table_delete(spectrum);
  cpl_table_delete(lines);
  cpl_table_delete(continuum);
  muse_pixtable_delete(pixtable);
  return cpl_errorstate_is_equal(prestate) ? 0 : -1;
}

