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

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

/*----------------------------------------------------------------------------*
 *                             Functions code                                 *
 *----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------*/
/**
  @brief    Do the processing of each science exposure.
  @param    aProcessing   the processing structure
  @param    aParams       the parameters list
  @param    aTrace        trace table
  @param    aWave         wavelength calibration table
  @param    aGeo          geometry table
  @param    aImage        actual pre-reduced CCD image of the science data
  @param    aSkyLines     array of sky line wavelengths
  @param    aFlat         image of the relevant master-flat field
  @param    aAttached     table of per-slice corrections from an attached flat
  @param    aTwilights    NULL-terminated list of twilight cubes
  @return   0 if everything is ok, -1 something went wrong

  This function expects that no attached flats / illum flats are passed into
  this function. Otherwise they are handled like science exposures.
 */
/*----------------------------------------------------------------------------*/
static int
muse_scibasic_per_exposure(muse_processing *aProcessing,
                           muse_scibasic_params_t *aParams,
                           cpl_table *aTrace, cpl_table *aWave, cpl_table *aGeo,
                           muse_image *aImage, cpl_array *aSkyLines,
                           muse_image *aFlat, cpl_table *aAttached,
                           muse_datacube **aTwilights)
{
  cpl_ensure(aImage && aTrace && aWave, CPL_ERROR_NULL_INPUT, -1);

  /* add saturated number of pixels as QC early to save it in all outputs */
  muse_basicproc_qc_saturated(aImage, QC_SCIBASIC_PREFIX);

  /* get the input tag of this file from the header *
   * (before saving deletes it)                     */
  char *intag = cpl_strdup(cpl_propertylist_get_string(aImage->header,
                                                       MUSE_HDR_TMP_INTAG));
  if (aParams->saveimage) {
    char *tag = cpl_sprintf("%s_RED", intag);
    muse_processing_save_image(aProcessing, aParams->nifu, aImage, tag);
    cpl_free(tag);
  }

  /* create and save the output pixel table in any case */
  muse_pixtable *pixtable = muse_pixtable_create(aImage, aTrace, aWave, aGeo);
  if (!pixtable) {
    cpl_msg_error(__func__, "Pixel table was not created for IFU %d: %s",
                  aParams->nifu, cpl_error_get_message());
    cpl_free(intag);
    return -1;
  }
  muse_pixtable_append_ff(pixtable, aFlat, aTrace, aWave, kMuseSpectralSamplingA);
  if (aSkyLines) {
    cpl_array *rejpars = muse_cplarray_new_from_delimited_string(aParams->skyreject, ",");
    int nrej = cpl_array_get_size(rejpars);
    float lsigma = 15.,
          hsigma = 15.;
    unsigned char niter = 1;
    if (nrej > 0 && cpl_array_get_string(rejpars, 0)) {
      /* first number: high sigma limit */
      hsigma = atof(cpl_array_get_string(rejpars, 0));
    }
    if (nrej > 1 && cpl_array_get_string(rejpars, 1)) {
      /* second number: low sigma limit */
      lsigma = atof(cpl_array_get_string(rejpars, 1));
    }
    if (nrej > 2 && cpl_array_get_string(rejpars, 2)) {
      /* second number: low sigma limit */
      niter = atoi(cpl_array_get_string(rejpars, 2));
    }
    cpl_array_delete(rejpars);
    muse_basicproc_shift_pixtable(pixtable, aSkyLines, aParams->skyhalfwidth,
                                  aParams->skybinsize, lsigma, hsigma, niter);
  }

  /* Trim the pixel table to a useful wavelength range before saving it. *
   * Use slightly larger ranges to not cut too much, and so leave the    *
   * first plane of the output datacube hopefully mostly intact.         */
  const double llimN = kMuseUsefulNLambdaMin - FLT_EPSILON,
               llimE = kMuseUsefulELambdaMin - FLT_EPSILON,
               llimA = kMuseUsefulAOLambdaMin - FLT_EPSILON,
               llimF = kMuseUsefulNFMLambdaMin - FLT_EPSILON,
               hlim = kMuseUsefulLambdaMax + FLT_EPSILON;
  muse_ins_mode mode = muse_pfits_get_mode(pixtable->header);
  if (aParams->crop) {
    if (mode == MUSE_MODE_WFM_AO_N) {
      cpl_msg_info(__func__, "Nominal AO mode: cropping the pixel table of IFU "
                   "%d to %.1f...%.1f Angstrom", aParams->nifu, llimA, hlim);
      muse_pixtable_restrict_wavelength(pixtable, llimA, hlim);
    } else if (mode == MUSE_MODE_WFM_NONAO_N) {
      cpl_msg_info(__func__, "Nominal mode: cropping the pixel table of IFU %d"
                   " to %.1f...%.1f Angstrom", aParams->nifu, llimN, hlim);
      muse_pixtable_restrict_wavelength(pixtable, llimN, hlim);
    } else if (mode == MUSE_MODE_NFM_AO_N) {
      cpl_msg_info(__func__, "NFM: cropping the pixel table of IFU %d to "
                   "%.1f...%.1f Angstrom", aParams->nifu, llimF, hlim);
      muse_pixtable_restrict_wavelength(pixtable, llimF, hlim);
    } else { /* extended mode */
      cpl_msg_info(__func__, "Extended mode: cropping the pixel table of IFU %d"
                   " to %.1f...%.1f Angstrom", aParams->nifu, llimE, hlim);
      muse_pixtable_restrict_wavelength(pixtable, llimE, hlim);
    }
  } /* if crop */
  if (mode > MUSE_MODE_WFM_NONAO_N) {
    muse_basicproc_mask_notch_filter(pixtable, aParams->nifu);
  } /* if AO mode */

  if (aAttached) { /* test before, to not throw an error */
    muse_basicproc_apply_illum(pixtable, aAttached);
  } /* if attached flat given */

  /* twilight correction using one or more twilight cubes */
  int icor;
  for (icor = 0; aTwilights && aTwilights[icor]; icor++) {
    cpl_msg_info(__func__, "Starting twilight correction %d in IFU %d",
                 icor + 1, aParams->nifu);
    muse_basicproc_apply_twilight(pixtable, aTwilights[icor]);
  } /* for twilight correction cube not NULL */

  if (aParams->resample) {
    /* to visualize the current state better than the pixtable *
     * can do, resample the data onto an image                 */
    muse_image *image = muse_resampling_image(pixtable,
                                              MUSE_RESAMPLE_WEIGHTED_RENKA,
                                              1., aParams->dlambda);
    char *tag = cpl_sprintf("%s_RESAMPLED", intag);
    /* don't want to save QC headers with the samples table: */
    cpl_propertylist_erase_regexp(image->header, QC_SCIBASIC_PREFIX, 0);
    muse_processing_save_image(aProcessing, aParams->nifu, image, tag);
    cpl_free(tag);
    muse_image_delete(image);
  } /* if resample */

  /* construct output tag and finally save */
  char *outtag = cpl_sprintf("PIXTABLE_%s", intag);
  muse_processing_save_table(aProcessing, aParams->nifu, pixtable, NULL, outtag,
                             MUSE_TABLE_TYPE_PIXTABLE);
  cpl_free(outtag);
  cpl_free(intag);
  muse_pixtable_delete(pixtable);

  return 0;
} /* muse_scibasic_per_exposure() */

/*----------------------------------------------------------------------------*/
/**
  @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_scibasic_compute(muse_processing *aProcessing,
                      muse_scibasic_params_t *aParams)
{
  muse_imagelist *images = NULL;
  muse_image *masterflat = NULL;
  if (muse_processing_check_intags(aProcessing, "REDUCED", 8)) {
    cpl_msg_warning(__func__, "Found REDUCED files on input, ignoring all "
                    "others inputs!");
    images = muse_basicproc_load_reduced(aProcessing, aParams->nifu);
    cpl_size i;
    cpl_size n = muse_imagelist_get_size(images);
    for (i = 0; i < n; i++) {
      muse_image *img = muse_imagelist_get(images, i);
      cpl_propertylist_update_string(img->header, MUSE_HDR_TMP_INTAG,
                                     cpl_array_get_string(aProcessing->intags, 0));
    }
  } else {
    muse_basicproc_params *bpars = muse_basicproc_params_new(aProcessing->parameters,
                                                             "muse.muse_scibasic");
    bpars->keepflat = CPL_TRUE;
    images = muse_basicproc_load(aProcessing, aParams->nifu, bpars);
    masterflat = bpars->flatimage;
    bpars->flatimage = NULL; /* so that it doesn't get deallocated */
    muse_basicproc_params_delete(bpars);
  }
  if (!images) {
    muse_image_delete(masterflat);
    cpl_error_set(__func__, cpl_error_get_code());
    return -1;
  }
  /* Preprocess the RTC data tables of NFM observations. For non-NFM *
   * observations nothing will be done here.                         */
  cpl_error_code ecode = muse_basicproc_process_rtcdata(images);
  if (ecode != CPL_ERROR_NONE) {
    muse_imagelist_delete(images);
    cpl_msg_error(__func__, "RTC data could not be processed for IFU %d",
                  aParams->nifu);
    return -1;
  }

  cpl_table *tracetable = muse_processing_load_ctable(aProcessing, MUSE_TAG_TRACE_TABLE,
                                                      aParams->nifu);
  cpl_table *wavecaltable = muse_processing_load_ctable(aProcessing, MUSE_TAG_WAVECAL_TABLE,
                                                        aParams->nifu);
  cpl_table *geotable = muse_processing_load_ctable(aProcessing, MUSE_TAG_GEOMETRY_TABLE, 0);
  if (!tracetable || !wavecaltable || !geotable) {
    cpl_msg_error(__func__, "Calibration could not be loaded for IFU %d:%s%s%s",
                  aParams->nifu, !tracetable ? " "MUSE_TAG_TRACE_TABLE : "",
                  !wavecaltable ? " "MUSE_TAG_WAVECAL_TABLE : "",
                  !geotable ? " "MUSE_TAG_GEOMETRY_TABLE : "");
    /* try to clean up in case some files were successfully loaded */
    muse_imagelist_delete(images);
    cpl_table_delete(tracetable);
    cpl_table_delete(wavecaltable);
    cpl_table_delete(geotable);
    return -1;
  }

  /* load twilight cube */
  cpl_frameset *fset = muse_frameset_find(aProcessing->inframes,
                                          MUSE_TAG_TWILIGHT_CUBE, 0, 0);
  int i, ntwilight = cpl_frameset_get_size(fset);
  muse_datacube **twilights = cpl_calloc(ntwilight + 1, sizeof(muse_datacube *));
  for (i = 0; i < ntwilight; i++) {
    cpl_frame *ftwilight = cpl_frameset_get_position(fset, i);
    const char *fn = cpl_frame_get_filename(ftwilight);
    twilights[i] = muse_datacube_load(fn);
    if (!twilights[i]) {
      cpl_msg_warning(__func__, "Could not load %s from \"%s\"",
                      MUSE_TAG_TWILIGHT_CUBE, fn);
      break; /* makes no sense to continue */
    }
    const char *catg = muse_pfits_get_pro_catg(twilights[i]->header);
    if (catg && strncmp(MUSE_TAG_TWILIGHT_CUBE, catg,
                        strlen(MUSE_TAG_TWILIGHT_CUBE) + 1)) {
      cpl_msg_warning(__func__, "Supposed %s (\"%s\") has wrong PRO.CATG: %s",
                      MUSE_TAG_TWILIGHT_CUBE, fn, catg);
    }
    muse_processing_append_used(aProcessing, ftwilight, CPL_FRAME_GROUP_CALIB, 1);
  } /* for i (all twilight cubes in frameset) */
  cpl_frameset_delete(fset);

  cpl_array *lines = muse_cplarray_new_from_delimited_string(aParams->skylines, ","),
            *skylines = muse_cplarray_string_to_double(lines);
  cpl_array_delete(lines);
  int rc = 0;
  muse_combinepar *cpars = muse_combinepar_new(aProcessing->parameters,
                                               "muse.muse_scibasic");
  if (cpars->combine == MUSE_COMBINE_NONE) { /* no combination, the default */
    /* search for attached/illumination flat-field in input list, *
     * prepare them and remove all from the list                  */
    cpl_table *tattached = muse_basicproc_get_illum(images, tracetable,
                                                    wavecaltable, geotable);
    unsigned int k, nimages = muse_imagelist_get_size(images);
    for (k = 0; k < nimages && !rc; k++) {
      muse_image *image = muse_imagelist_get(images, k);
      rc = muse_scibasic_per_exposure(aProcessing, aParams, tracetable,
                                      wavecaltable, geotable, image, skylines,
                                      masterflat, tattached, twilights);
    } /* for k (all science images) */
    cpl_table_delete(tattached);
  } else { /* some combination is supposed to happen */
    int ntags = cpl_array_get_size(aProcessing->intags);
    if (ntags > 1) {
      cpl_msg_warning(__func__, "Combining images of %d different tags, but "
                      "will use %s for output!", ntags,
                      cpl_array_get_string(aProcessing->intags, 0));
    } else {
      cpl_msg_debug(__func__, "Combining images with %d tag", ntags);
    }
    muse_image *image = muse_combine_images(cpars, images);
    /* re-add the INTAG header that was removed by muse_combine_images() */
    cpl_propertylist_update_string(image->header, MUSE_HDR_TMP_INTAG,
                                   cpl_array_get_string(aProcessing->intags, 0));
    rc = muse_scibasic_per_exposure(aProcessing, aParams, tracetable,
                                    wavecaltable, geotable, image, skylines,
                                    masterflat, NULL, twilights);
    muse_image_delete(image);
  } /* else: combined processing */
  muse_image_delete(masterflat);
  cpl_array_delete(skylines);
  muse_combinepar_delete(cpars);

  /* clean up */
  muse_imagelist_delete(images);
  cpl_table_delete(tracetable);
  cpl_table_delete(wavecaltable);
  cpl_table_delete(geotable);
  for (i = 0; twilights[i]; i++) {
    muse_datacube_delete(twilights[i]);
  }
  cpl_free(twilights);

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