/* -*- 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 <muse.h>
#include "muse_scipost_z.h"

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

/*----------------------------------------------------------------------------*/
/**
  @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

  @pseudocode
  muse_processing_sort_exposures()
  for i = 0 ... nexposures:
    muse_postproc_process_exposure()
  if nexposures > 1:
    muse_xcombine_weights()
    muse_xcombine_tables()
  muse_resampling_cube() or muse_resampling_euro3d()@endpseudocode
 */
/*----------------------------------------------------------------------------*/
int
muse_scipost_compute(muse_processing *aProcessing,
                     muse_scipost_params_t *aParams)
{
  const char *savevalid = "cube,skymodel,individual,positioned,combined,stacked";
  if (!muse_postproc_check_save_param(aParams->save, savevalid)) {
    return -1;
  }
  muse_postproc_properties *prop = muse_postproc_properties_new(MUSE_POSTPROC_SCIPOST);
  /* per-exposure parameters */
  prop->lambdamin = aParams->lambdamin;
  prop->lambdamax = aParams->lambdamax;
  prop->lambdaref = aParams->lambdaref;
  prop->darcheck = MUSE_POSTPROC_DARCHECK_NONE;
  if (aParams->darcheck == MUSE_SCIPOST_PARAM_DARCHECK_CHECK) {
    prop->darcheck = MUSE_POSTPROC_DARCHECK_CHECK;
  } else if (aParams->darcheck == MUSE_SCIPOST_PARAM_DARCHECK_CORRECT) {
    prop->darcheck = MUSE_POSTPROC_DARCHECK_CORRECT;
  }
  prop->rvtype = muse_rvcorrect_select_type(aParams->rvcorr_s);
  /* flux calibration */
  prop->response = muse_table_load(aProcessing, MUSE_TAG_STD_RESPONSE, 0);
  prop->extinction = muse_table_load(aProcessing, MUSE_TAG_EXTINCT_TABLE, 0);
  prop->telluric = muse_table_load(aProcessing, MUSE_TAG_STD_TELLURIC, 0);
  /* astrometric correction */
  if (aParams->astrometry) {
    prop->wcs = muse_propertylist_load(aProcessing, MUSE_TAG_ASTROMETRY_WCS);
    if (!prop->wcs) {
      /* if it was not in the input, create a default WFM astrometry */
      cpl_msg_warning(__func__, "Using default MUSE WFM astrometry, output "
                      "world coordinates will be inaccurate!");
      prop->wcs = muse_wcs_create_default();
    }
  }

  const struct {
    int par;
    muse_postproc_skymethod postproc;
  } skymethod[] = {
    { MUSE_SCIPOST_PARAM_SKYMETHOD_NONE, MUSE_POSTPROC_SKYMETHOD_NONE },
    { MUSE_SCIPOST_PARAM_SKYMETHOD_SUBTRACT_MODEL, MUSE_POSTPROC_SKYMETHOD_NONE }, /* sic! */
    { MUSE_SCIPOST_PARAM_SKYMETHOD_MODEL, MUSE_POSTPROC_SKYMETHOD_MODEL },
    /* { MUSE_SCIPOST_PARAM_SKYMETHOD_ROW_BY_ROW, MUSE_POSTPROC_SKYMETHOD_ROWBYROW }, */
    { MUSE_SCIPOST_PARAM_SKYMETHOD_INVALID_VALUE, MUSE_POSTPROC_SKYMETHOD_MODEL },
  };
  prop->skymethod = MUSE_POSTPROC_SKYMETHOD_MODEL;
  int i_method;
  for (i_method = 0;
       skymethod[i_method].par != MUSE_SCIPOST_PARAM_SKYMETHOD_INVALID_VALUE;
       i_method++) {
    if (skymethod[i_method].par == aParams->skymethod) {
      prop->skymethod = skymethod[i_method].postproc;
      break;
    }
  }
  prop->skymodel_params.fraction = aParams->skymodel_fraction;
  prop->skymodel_params.sampling = aParams->skymodel_sampling;
  prop->skymodel_params.csampling = aParams->skymodel_csampling;

  prop->sky = muse_sky_master_new();
  if (aParams->skymethod == MUSE_SCIPOST_PARAM_SKYMETHOD_SUBTRACT_MODEL) {
    prop->sky->lsf = muse_processing_lsf_params_load(aProcessing, 0);
    if (prop->sky->lsf != NULL) {
      prop->sky->lines = muse_sky_lines_load(aProcessing);
      if (prop->sky->lines == NULL) {
        cpl_msg_warning(__func__, "Sky lines missing for subtraction");
      }
    }
    prop->sky->continuum = muse_sky_continuum_load(aProcessing);
  } else if (aParams->skymethod == MUSE_SCIPOST_PARAM_SKYMETHOD_MODEL) {
    prop->sky->lsf = muse_processing_lsf_params_load(aProcessing, 0);
    if (prop->sky->lsf == NULL) {
      cpl_msg_error(__func__, "Missing required LSF parameter frames for sky model");
    }
    prop->sky->lines = muse_sky_lines_load(aProcessing);
    if (prop->sky->lines == NULL) {
      cpl_msg_error(__func__, "Missing required sky lines frame for sky model");
    }
    if ((prop->sky->lsf == NULL) || (prop->sky->lines == NULL)) {
      muse_postproc_properties_delete(prop);
      return -1;
    }
    prop->sky->continuum = muse_sky_continuum_load(aProcessing);
    prop->sky_mask = muse_processing_mask_load(aProcessing, MUSE_TAG_SKY_MASK);
  }

  /* sort input pixel tables into different exposures */
  prop->exposures = muse_processing_sort_exposures(aProcessing);
  if (!prop->exposures) {
    cpl_msg_error(__func__, "no science exposures found in input");
    muse_postproc_properties_delete(prop);
    return -1;
  }
  int nexposures = cpl_table_get_nrow(prop->exposures);

  /* now process all the pixel tables, do it separately for each exposure */
  /* allocate one additional element for NULL termination */
  muse_pixtable **pixtables = cpl_calloc(nexposures + 1, sizeof(muse_pixtable *));
  int i;
  for (i = 0; i < nexposures; i++) {
    muse_postproc_sky_outputs *skyout = cpl_calloc(1, sizeof(muse_postproc_sky_outputs));
    pixtables[i] = muse_postproc_process_exposure(prop, i, skyout);
    if (!pixtables[i]) {
      int i2;
      for (i2 = 0; i2 <= i; i2++) {
        muse_pixtable_delete(pixtables[i2]);
      } /* for i2 */
      cpl_free(pixtables);
      muse_postproc_properties_delete(prop);
      return -1; /* enough error messages, just return */
    }
    /* save the effectively used sky products, if requested */
    if (strstr(aParams->save, "skymodel")) {
      if (skyout->mask) {
        muse_processing_save_mask(aProcessing, -1, skyout->mask, MUSE_TAG_SKY_MASK);
      }
      if (skyout->spectrum) {
        cpl_propertylist *header = cpl_propertylist_duplicate(pixtables[i]->header);
        cpl_propertylist_erase_regexp(header, MUSE_HDR_PT_REGEXP, 0);
        muse_processing_save_table(aProcessing, -1, skyout->spectrum, header,
                                   MUSE_TAG_SKY_SPECTRUM, MUSE_TABLE_TYPE_CPL);
        cpl_propertylist_delete(header);
      }
      if (skyout->lines) {
        cpl_propertylist *header = cpl_propertylist_duplicate(pixtables[i]->header);
        cpl_propertylist_erase_regexp(header, MUSE_HDR_PT_REGEXP, 0);
        muse_sky_qc_lines(header, skyout->lines, "ESO QC SCIPOST");
        muse_sky_lines_save(aProcessing, skyout->lines, header);
        cpl_propertylist_delete(header);
      }
      if (skyout->continuum) {
        cpl_propertylist *header = cpl_propertylist_duplicate(pixtables[i]->header);
        cpl_propertylist_erase_regexp(header, MUSE_HDR_PT_REGEXP, 0);
        muse_sky_qc_continuum(header, skyout->continuum, "ESO QC SCIPOST");
        muse_sky_save_continuum(aProcessing, skyout->continuum, header);
        cpl_propertylist_delete(header);
      }
    }
    muse_mask_delete(skyout->mask);
    cpl_table_delete(skyout->spectrum);
    cpl_table_delete(skyout->lines);
    cpl_table_delete(skyout->continuum);
    cpl_free(skyout);

    if (strstr(aParams->save, "individual")) {
      muse_processing_save_table(aProcessing, -1, pixtables[i], NULL,
                                 MUSE_TAG_PIXTABLE_REDUCED,
                                 MUSE_TABLE_TYPE_PIXTABLE);
    }
    if (strstr(aParams->save, "positioned")) {
      /* duplicate table to not mess with the coordinates in the individual *
       * pixel tables, which are still needed when combining the exposures  */
      muse_pixtable *pt = muse_pixtable_duplicate(pixtables[i]);
      double ra = muse_pfits_get_ra(pt->header),
             dec = muse_pfits_get_dec(pt->header);
      muse_wcs_position_celestial(pt, ra, dec);
      muse_processing_save_table(aProcessing, -1, pt, NULL,
                                 MUSE_TAG_PIXTABLE_POSITIONED,
                                 MUSE_TABLE_TYPE_PIXTABLE);
      muse_pixtable_delete(pt);
    }
  } /* for i (exposures) */
  muse_postproc_properties_delete(prop);

  /* now combine the possibly more than one exposures */
  muse_pixtable *bigpixtable = NULL;
  if (nexposures > 1) {
    muse_xcombine_types weight = muse_postproc_get_weight_type(aParams->weight_s);
    cpl_error_code rc = muse_xcombine_weights(pixtables, weight);
    if (rc != CPL_ERROR_NONE) {
      cpl_msg_error(__func__, "weighting the pixel tables didn't work: %s",
                    cpl_error_get_message());
      for (i = 0; i < nexposures; i++) {
        muse_pixtable_delete(pixtables[i]);
      } /* for i (exposures) */
      cpl_free(pixtables);
      return -1;
    }
    /* combine individual pixel tables and delete them */
    bigpixtable = muse_xcombine_tables(pixtables);
    if (!bigpixtable) {
      cpl_msg_error(__func__, "combining the pixel tables didn't work: %s",
                    cpl_error_get_message());
      for (i = 0; i < nexposures; i++) {
        muse_pixtable_delete(pixtables[i]);
      } /* for i (exposures) */
      cpl_free(pixtables);
      return -1;
    }
  } else {
    bigpixtable = pixtables[0];
    muse_wcs_position_celestial(bigpixtable,
                                muse_pfits_get_ra(bigpixtable->header),
                                muse_pfits_get_dec(bigpixtable->header));
  }
  cpl_free(pixtables);

  cpl_error_code rc = CPL_ERROR_NONE;
  if (strstr(aParams->save, "cube")) {
    muse_resampling_type resample
      = muse_postproc_get_resampling_type(aParams->resample_s);
    muse_resampling_params *rp = muse_resampling_params_new(resample);
    rp->dx = aParams->dx;
    rp->dy = aParams->dy;
    rp->dlambda = aParams->dlambda;
    rp->crtype = muse_postproc_get_cr_type(aParams->crtype_s);
    rp->crsigma = aParams->crsigma;
    rp->ld = aParams->ld;
    rp->rc = aParams->rc;
    rp->pfx = aParams->pixfrac;
    rp->pfy = aParams->pixfrac;
    rp->pfl = aParams->pixfrac;
    cpl_propertylist *outwcs = muse_postproc_cube_load_output_wcs(aProcessing);
    muse_resampling_params_set_wcs(rp, outwcs);
    cpl_propertylist_delete(outwcs);
    muse_cube_type format = muse_postproc_get_cube_format(aParams->format_s);
    rc = muse_postproc_cube_resample_and_collapse(aProcessing, bigpixtable,
                                                  format, rp, aParams->filter);
    muse_resampling_params_delete(rp);
  } /* if save contains cube */
  if (strstr(aParams->save, "combined")) {
    muse_processing_save_table(aProcessing, -1, bigpixtable, NULL,
                               MUSE_TAG_PIXTABLE_COMBINED,
                               MUSE_TABLE_TYPE_PIXTABLE);
  }
  if (strstr(aParams->save, "stacked")) {
    cpl_msg_debug(__func__, "additional output as column-stacked image");
    muse_image *img = muse_resampling_image(bigpixtable,
                                            MUSE_RESAMPLE_WEIGHTED_RENKA,
                                            aParams->dx, aParams->dlambda);
    muse_processing_save_image(aProcessing, -1, img, MUSE_TAG_OBJECT_RESAMPLED);
    muse_image_delete(img);
  }

  muse_pixtable_delete(bigpixtable);

  /* the cube is normally the main output, check only its return code */
  return rc == CPL_ERROR_NONE ? 0 : -1;
} /* muse_scipost_compute() */
