/* -*- 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) 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 <muse.h>

#include "muse_lsf_z.h"

/*---------------------------------------------------------------------------*
 *                             Functions code                                *
 *---------------------------------------------------------------------------*/
static cpl_error_code
muse_lsf_save_lsf_params(cpl_propertylist *aHeader,
                         const muse_lsf_params **aSlicePars,
                         muse_processing *aProcessing, int aIFU)
{
  cpl_error_code r = CPL_ERROR_NONE;
  if (aSlicePars != NULL) {
    cpl_frame *frame = muse_processing_new_frame(aProcessing, aIFU, aHeader,
                                                 MUSE_TAG_LSF_PROFILE,
                                                 CPL_FRAME_TYPE_TABLE);
    if (frame != NULL) {
      const char *filename = cpl_frame_get_filename(frame);
      cpl_msg_info(__func__, "Saving LSF profile as %s", filename);
      char *channel = cpl_sprintf("CHAN%02d", aIFU);
      cpl_propertylist_update_string(aHeader, "EXTNAME", channel);
      cpl_free(channel);
      r = cpl_propertylist_save(aHeader, filename, CPL_IO_CREATE);
      r = muse_lsf_params_save(aSlicePars, filename);
      if (r == CPL_ERROR_NONE) {
        cpl_frameset_insert(aProcessing->outframes, frame);
      } else {
        cpl_frame_delete(frame);
      }
    }
  }
  return r;
} /* muse_lsf_save_lsf_params() */

static muse_lsf_params *
muse_lsf_compute_slice_lsf(cpl_table *aLines, muse_pixtable *aPixtable,
                           muse_lsf_params *aFirstguess, int aMaxIter,
                           const muse_sky_fit_params *aSliceParams)
{
  uint32_t origin = (uint32_t)cpl_table_get_int(aPixtable->table,
                                                MUSE_PIXTABLE_ORIGIN, 0, NULL);
  unsigned short i_ifu = muse_pixtable_origin_get_ifu(origin),
                 i_slice = muse_pixtable_origin_get_slice(origin);
  cpl_size nrows = muse_pixtable_get_nrow(aPixtable);

  cpl_msg_info(__func__, "processing slice %hu.%hu with %"CPL_SIZE_FORMAT" rows",
               i_ifu, i_slice, nrows);

  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 *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 *data = muse_cpltable_extract_column(aPixtable->table,
                                                 MUSE_PIXTABLE_DATA);
  cpl_array *stat = muse_cpltable_extract_column(aPixtable->table,
                                                 MUSE_PIXTABLE_STAT);

  muse_lsf_params *lsf = muse_lsf_params_fit(lambda, data, stat, aLines,
                                             aFirstguess, aMaxIter,
                                             aSliceParams);
  lsf->slice = i_slice;
  lsf->ifu = i_ifu;

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

  cpl_array_unwrap(lambda);
  cpl_array_unwrap(data);
  cpl_array_unwrap(stat);

  return lsf;
} /* muse_lsf_compute_slice_lsf() */

static muse_lsf_params **
muse_lsf_compute_lsf(cpl_table *aLines, muse_pixtable *aPixtable,
                     muse_lsf_params **aFirstguess, int aMaxIter,
                     const muse_sky_fit_params *aSliceParams)
{
  muse_pixtable **slice_pixtable = muse_pixtable_extracted_get_slices(aPixtable);
  int n_slices = muse_pixtable_extracted_get_size(slice_pixtable);
  int i_slice;

  muse_lsf_params **lsfParams = cpl_calloc(n_slices+1,
                                           sizeof(muse_lsf_params *));
  #pragma omp parallel for default(none) num_threads(2)  /* as req. by Ralf */ \
          shared(aFirstguess, aLines, aMaxIter, aSliceParams, lsfParams,       \
                 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);
    unsigned short ifu = muse_pixtable_origin_get_ifu(origin),
                   slice = muse_pixtable_origin_get_slice(origin);
    muse_lsf_params *firstguess_slice = muse_lsf_params_get(aFirstguess, ifu,
                                                            slice);
    cpl_errorstate prestate = cpl_errorstate_get();
    lsfParams[i_slice] = muse_lsf_compute_slice_lsf(aLines,
                                                    slice_pixtable[i_slice],
                                                    firstguess_slice, aMaxIter,
                                                    aSliceParams);
    if (!cpl_errorstate_is_equal(prestate)) {
      cpl_msg_error(__func__, "While processing slice %hu.%hu:", ifu, slice);
      cpl_errorstate_dump(prestate, CPL_FALSE, NULL);
      cpl_errorstate_set(prestate);
    }
  }
  muse_pixtable_extracted_delete(slice_pixtable);
  return lsfParams;
} /* muse_lsf_compute_lsf() */

/*----------------------------------------------------------------------------*/
/**
  @brief    Create a header with the QC parameters for the LSF.
  @param    aHeader   Header to which to add the QC parameters.
  @param    aMaster   Computed LSF parameters
  @return   CPL_ERROR_NONE on success, another CPL error code on failure.

  @error{return CPL_ERROR_NULL_INPUT, aHeader and/or aSlicePars are NULL}
 */
/*----------------------------------------------------------------------------*/
static cpl_error_code
muse_lsf_qc(cpl_propertylist *aHeader, const muse_lsf_params **aSlicePars)
{
  cpl_ensure_code(aHeader && aSlicePars, CPL_ERROR_NULL_INPUT);

#define LSF_QC_DLBDA 150. /* use steps of 150 Angstrom in wavelength, i.e. *
                           * 30 steps for nominal and 31 for extended mode */
  /* determine range (and step size) of LSF to compute FWHM for */
  double lbda1 = muse_pfits_get_mode(aHeader) == MUSE_MODE_WFM_NONAO_X
               ? 4650. : 4800.,
         lbda2 = kMuseNominalLambdaMax;
  int nsteps = (lbda2 - lbda1) / LSF_QC_DLBDA;

  unsigned char ifu = muse_utils_get_ifu(aHeader);
  unsigned short nslice = 1;
  const muse_lsf_params **slice;
  for (slice = aSlicePars; *slice; slice++, nslice++) {
    cpl_array *afwhm = cpl_array_new(nsteps + 1, CPL_TYPE_DOUBLE);
    int idx = 0;
    double lambda;
    for (lambda = lbda1; lambda <= lbda2; lambda += LSF_QC_DLBDA, idx++) {
      double fwhm = muse_lsf_fwhm_lambda(*slice, lambda,
                                         1000., 0.01, 1000., NULL);
      cpl_array_set(afwhm, idx, fwhm);
    } /* for lambda */
    char keyword[KEYWORD_LENGTH];
    snprintf(keyword, KEYWORD_LENGTH, "ESO QC LSF IFU%hhu SLICE%hu FWHM MEAN",
             ifu, nslice);
    cpl_propertylist_append_float(aHeader, keyword, cpl_array_get_mean(afwhm));
    snprintf(keyword, KEYWORD_LENGTH, "ESO QC LSF IFU%hhu SLICE%hu FWHM STDEV",
             ifu, nslice);
    cpl_propertylist_append_float(aHeader, keyword, cpl_array_get_stdev(afwhm));
    snprintf(keyword, KEYWORD_LENGTH, "ESO QC LSF IFU%hhu SLICE%hu FWHM MIN",
             ifu, nslice);
    cpl_propertylist_append_float(aHeader, keyword, cpl_array_get_min(afwhm));
    snprintf(keyword, KEYWORD_LENGTH, "ESO QC LSF IFU%hhu SLICE%hu FWHM MAX",
             ifu, nslice);
    cpl_propertylist_append_float(aHeader, keyword, cpl_array_get_max(afwhm));
    cpl_array_delete(afwhm);
  } /* for slice */
  return CPL_ERROR_NONE;
} /* muse_lsf_qc_lsf() */

/*----------------------------------------------------------------------------*/
/**
  @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_lsf_compute(muse_processing *aProcessing,
                 muse_lsf_params_t *aParams)
{
  /* load necessary calibrations first */
  cpl_table *tracetable = muse_table_load(aProcessing, MUSE_TAG_TRACE_TABLE,
                                          aParams->nifu);
  cpl_table *wavecaltable = muse_table_load(aProcessing, MUSE_TAG_WAVECAL_TABLE,
                                            aParams->nifu);
  /* line catalog is needed in any case */
  cpl_table *linelist = muse_table_load(aProcessing, MUSE_TAG_LINE_CATALOG, 0);
  cpl_propertylist *linehead = muse_propertylist_load(aProcessing,
                                                      MUSE_TAG_LINE_CATALOG);
  int rc = muse_wave_lines_check(linelist, linehead) ? 0 : -1;
  cpl_propertylist_delete(linehead);
  if (!tracetable || !wavecaltable || !linelist || rc) {
    cpl_table_delete(tracetable);
    cpl_table_delete(wavecaltable);
    cpl_table_delete(linelist);
    return rc;
  }

  muse_basicproc_params *bpars = muse_basicproc_params_new(aProcessing->parameters,
                                                           "muse.muse_lsf");
  muse_imagelist *images = muse_basicproc_combine_images_lampwise(aProcessing,
                                                                  aParams->nifu,
                                                                  bpars, NULL);
  muse_basicproc_params_delete(bpars);
  cpl_ensure(images, cpl_error_get_code(), -1);

  /* do the final (sum!) image combination */
  char *pname = cpl_sprintf("muse.%s.combine", aProcessing->name);
  cpl_parameter *param = cpl_parameterlist_find(aProcessing->parameters, pname);
  char *porig = cpl_strdup(cpl_parameter_get_string(param));
  cpl_parameter_set_string(param, "sum");
  cpl_free(pname);
  muse_combinepar *cpars = muse_combinepar_new(aProcessing->parameters,
                                               "muse.muse_lsf");
  cpl_parameter_set_string(param, porig);
  cpl_free(porig);
  muse_image *masterimage = muse_combine_images(cpars, images);
  muse_combinepar_delete(cpars);


  /* for the LSF determination, we need a pixel table */
  muse_pixtable *pixtable = muse_pixtable_create(masterimage, tracetable,
                                                 wavecaltable, NULL);
  if (!pixtable) {
    cpl_error_set_message(__func__, CPL_ERROR_ILLEGAL_INPUT, "pixel table "
                          "creation failed!");
    muse_image_delete(masterimage);
    muse_imagelist_delete(images);
    cpl_table_delete(wavecaltable);
    cpl_table_delete(tracetable);
    cpl_table_delete(linelist);
    return -1;
  } /* if no pixel table */

  if (aParams->save_subtracted) {
   /* Duplicate to have access to original data after fit for residual calc */
    cpl_table_duplicate_column(pixtable->table, "orig_data", pixtable->table,
                               "data");
  }

  /* erase unused (flux == 0.) and non-isolated *
   * (quality < line_quality, default 3) lines  */
  cpl_table_unselect_all(linelist);
  cpl_table_or_selected_int(linelist, MUSE_LINE_CATALOG_QUALITY,
                            CPL_LESS_THAN, aParams->line_quality);
  cpl_table_or_selected_float(linelist, "flux", CPL_NOT_GREATER_THAN, 0.);
  cpl_table_erase_selected(linelist);
  /* sort list by decreasing flux and cut it to the 40 brightest lines */
  cpl_propertylist *flux_sort = cpl_propertylist_new();
  cpl_propertylist_append_bool(flux_sort, "flux", CPL_TRUE);
  cpl_table_sort(linelist, flux_sort);
  cpl_propertylist_delete(flux_sort);
  cpl_size nrows = 40;
  if (nrows < cpl_table_get_nrow(linelist)) {
    cpl_table_erase_window(linelist, nrows, cpl_table_get_nrow(linelist));
  }

  muse_sky_fit_params *slice_fit_params = muse_sky_fit_params_new
    (
     0, // aParams->slice_fit_offset,
     0, // aParams->slice_fit_refraction,
     0, // sensitivity is not used here
     1, // aParams->slice_fit_slit_width,
     1, // aParams->slice_fit_bin_width,
     3, // aParams->slice_fit_lsf_width + 1,
     1, // aParams->slice_fit_h3 + 1,
     2, // aParams->slice_fit_h4 + 1,
     1, // aParams->slice_fit_h5 + 1,
     2  // aParams->slice_fit_h6 + 1
     );
  muse_lsf_params **firstguess = muse_processing_lsf_params_load(aProcessing,
                                                                 aParams->nifu);
  muse_lsf_params **lsf = muse_lsf_compute_lsf(linelist, pixtable, firstguess,
                                               40, slice_fit_params);
  muse_lsf_params_delete(firstguess);
  muse_sky_fit_params_delete(slice_fit_params);

  cpl_propertylist *header = cpl_propertylist_duplicate(pixtable->header);
  cpl_propertylist_erase_regexp(header, MUSE_HDR_PT_REGEXP"|ESO QC|ESO DRS MUSE",
                                0);
  muse_lsf_qc(header, (const muse_lsf_params **)lsf);
  muse_lsf_save_lsf_params(header, (const muse_lsf_params **)lsf,
                           aProcessing, aParams->nifu);
  cpl_propertylist_delete(header);
  muse_lsf_params_delete(lsf);

  if (aParams->save_subtracted) {
    muse_processing_save_table(aProcessing, aParams->nifu, pixtable, NULL,
                               "PIXTABLE_SUBTRACTED", MUSE_TABLE_TYPE_PIXTABLE);
  }

  /* clean up */
  muse_pixtable_delete(pixtable);
  muse_image_delete(masterimage);
  muse_imagelist_delete(images);
  cpl_table_delete(tracetable);
  cpl_table_delete(wavecaltable);
  cpl_table_delete(linelist);

  return rc; /* can only be 0 or -1 */
} /* muse_lsf_compute() */
