/* -*- 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-2018 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_astrometry_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:
    wcs = muse_postproc_process_exposure()
    save wcs@endpseudocode
 */
/*----------------------------------------------------------------------------*/
int
muse_astrometry_compute(muse_processing *aProcessing,
                        muse_astrometry_params_t *aParams)
{
  cpl_errorstate state = cpl_errorstate_get();

  muse_postproc_properties *prop = muse_postproc_properties_new(MUSE_POSTPROC_ASTROMETRY);
  /* 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_ASTROMETRY_PARAM_DARCHECK_CHECK) {
    prop->darcheck = MUSE_POSTPROC_DARCHECK_CHECK;
  } else if (aParams->darcheck == MUSE_ASTROMETRY_PARAM_DARCHECK_CORRECT) {
    prop->darcheck = MUSE_POSTPROC_DARCHECK_CORRECT;
  }
  /* setup and check astrometric calibration parameters and centroiding method */
  prop->detsigma = aParams->detsigma;
  prop->radius = aParams->radius;
  prop->faccuracy = aParams->faccuracy;
  if (prop->faccuracy < 0) {
    cpl_msg_error(__func__, "Negative facurracy. Use positive values, or zero to switch "
                  "to the quadruple based method.");
    muse_postproc_properties_delete(prop);
    return -1;
  }
  prop->rejsigma = aParams->rejsigma;
  prop->niter = aParams->niter;
  prop->centroid = MUSE_WCS_CENTROID_GAUSSIAN;
  if (aParams->centroid == MUSE_ASTROMETRY_PARAM_CENTROID_MOFFAT) {
    prop->centroid = MUSE_WCS_CENTROID_MOFFAT;
  } else if (aParams->centroid == MUSE_ASTROMETRY_PARAM_CENTROID_BOX) {
    prop->centroid = MUSE_WCS_CENTROID_BOX;
  } else if (aParams->centroid != MUSE_ASTROMETRY_PARAM_CENTROID_GAUSSIAN) {
    cpl_msg_error(__func__, "unknown centroiding method \"%s\"", aParams->centroid_s);
    muse_postproc_properties_delete(prop);
    return -1;
  }
  /* set up rotation center from parameter string */
  cpl_array *rotcenter = muse_cplarray_new_from_delimited_string(aParams->rotcenter, ",");
  if (rotcenter && cpl_array_get_size(rotcenter) >= 2) {
    prop->crpix1 = atof(cpl_array_get_string(rotcenter, 0));
    prop->crpix2 = atof(cpl_array_get_string(rotcenter, 1));
  }
  cpl_array_delete(rotcenter);
  /* per-exposure parameters */
  /* flux calibration */
  prop->response = muse_processing_load_table(aProcessing,
                                              MUSE_TAG_STD_RESPONSE, 0);
  prop->telluric = muse_processing_load_table(aProcessing,
                                              MUSE_TAG_STD_TELLURIC, 0);
  prop->extinction = muse_processing_load_ctable(aProcessing,
                                                 MUSE_TAG_EXTINCT_TABLE, 0);
  /* astrometric reference */
  prop->refframe = muse_frameset_find_master(aProcessing->inframes,
                                             MUSE_TAG_ASTROMETRY_REFERENCE, 0);
  if (!prop->refframe) {
    cpl_msg_error(__func__, "Required input %s not found in input files",
                  MUSE_TAG_ASTROMETRY_REFERENCE);
    cpl_error_set_message(__func__, CPL_ERROR_NULL_INPUT,
                          MUSE_TAG_ASTROMETRY_REFERENCE" missing");
    muse_postproc_properties_delete(prop);
    return -1;
  }
  muse_processing_append_used(aProcessing, prop->refframe,
                              CPL_FRAME_GROUP_CALIB, 1);

  /* sort input pixel tables into different exposures */
  prop->exposures = muse_processing_sort_exposures(aProcessing);
  if (!prop->exposures) {
    cpl_msg_error(__func__, "no astrometric 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 */
  muse_wcs_object **wcsobjs = cpl_calloc(nexposures, sizeof(muse_wcs_object *));
  int i;
  for (i = 0; i < nexposures; i++) {
    wcsobjs[i] = muse_postproc_process_exposure(prop, i, NULL, NULL, NULL, NULL);
    if (!wcsobjs[i]) {
      int i2;
      for (i2 = 0; i2 <= i; i2++) {
        muse_wcs_object_delete(wcsobjs[i2]);
      } /* for i2 */
      cpl_free(wcsobjs);
      muse_postproc_properties_delete(prop);
      return -1; /* enough error messages, just return */
    }
  } /* for i (exposures) */
  muse_postproc_properties_delete(prop);

  for (i = 0; i < nexposures; i++) {
    /* convert DQ to NANs, do QC, then save cube; *
     * it already contains the images             */
    muse_postproc_qc_fwhm(aProcessing, wcsobjs[i]->cube); /* before NANs! */
    muse_datacube_convert_dq(wcsobjs[i]->cube);
    muse_processing_save_cube(aProcessing, -1, wcsobjs[i]->cube,
                              MUSE_TAG_CUBE_ASTROMETRY, MUSE_CUBE_TYPE_FITS);

    /* save the astrometric solution */
    char *object = cpl_sprintf("Astrometric calibration (%s)",
                               cpl_propertylist_get_string(wcsobjs[i]->cube->header,
                                                           "OBJECT"));
    cpl_propertylist_update_string(wcsobjs[i]->wcs, "OBJECT", object);
    cpl_free(object);
    /* try to save the bare header with the solution at least */
    cpl_error_code rc = muse_processing_save_header(aProcessing, -1,
                                                    wcsobjs[i]->wcs,
                                                    MUSE_TAG_ASTROMETRY_WCS);
    if (rc != CPL_ERROR_NONE) {
      cpl_msg_warning(__func__, "Failed to save %s for exposure %d (header): %s",
                      MUSE_TAG_ASTROMETRY_WCS, i+1, cpl_error_get_message());
    } else {
      /* Since saving the table directly with the WCS as primary header    *
       * erases the WCS keywords, manually append the table to the file.   *
       * For that, find the filename to use (from the last frame in the    *
       * output frameset) and then save the table to a new FITS extension. */
      int nf = cpl_frameset_get_size(aProcessing->outframes);
      cpl_frame *f = cpl_frameset_get_position(aProcessing->outframes, nf - 1);
      const char *fn = cpl_frame_get_filename(f);
      cpl_propertylist *header = cpl_propertylist_new();
      cpl_propertylist_append_string(header, "EXTNAME",
                                     MUSE_WCS_SOURCE_TABLE_NAME);
      rc = cpl_table_save(wcsobjs[i]->detected, NULL, header, fn, CPL_IO_EXTEND);
      cpl_propertylist_delete(header);
      if (rc != CPL_ERROR_NONE) {
        cpl_msg_warning(__func__, "Failed to save %s for exposure %d (table): %s",
                        MUSE_TAG_ASTROMETRY_WCS, i+1, cpl_error_get_message());
      } else {
        cpl_msg_info(__func__, "Appended %s to \"%s\".", MUSE_WCS_SOURCE_TABLE_NAME, fn);
      } /* else */
    } /* else */
    muse_wcs_object_delete(wcsobjs[i]);
  } /* for i (exposures) */
  cpl_free(wcsobjs);

  return cpl_errorstate_is_equal(state) ? 0 : -1;
} /* muse_astrometry_compute() */
