/* -*- 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) 2007-2015 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.
 */

#include <muse.h>
#include <string.h>
#include <strings.h>

/*----------------------------------------------------------------------------*/
/**
 * @defgroup tools_museimagefwhm            Tool muse_image_fwhm
 *
 * <b>imuse_mage_fwhm</b>: Determine FWHM on objects in a (collapsed) image.
 *
 * This is mainly for AIT and PAE.
 * @note Most of the code is a copy of the muse_postproc_qc_fwhm() function.
 *
 * <b>Command line arguments:</b>
 *   - <tt>-s sigma</tt> (optional, default: from 50. down to 3.)\n
 *     sigma level used for object detection; by default this loops through
 *     several sigma values, in decreasing order, until at least one object is
 *     found
 *   - <tt>-x extindex</tt> (optional, default: load first image found)\n
 *     extension index to load image from, can be zero to load the image from
 *     the primary data unit
 *   - <tt>-n extname</tt> (optional, default: load first image found)\n
 *     extension name (EXTNAME) to load image from
 *   - <tt><b>IMAGE</b></tt>\n
 *     the filename of the image file to use
 *
 * <b>Return values:</b>
 *   - <tt> 0</tt>\n   Success
 *   - <tt> 1</tt>\n   no filename given
 *   - <tt> 2</tt>\n   argument <tt>-s</tt> without any number
 *   - <tt> 3</tt>\n   sigma value given after <tt>-s</tt> is not positive
 *   - <tt> 4</tt>\n   argument <tt>-x</tt> without any number
 *   - <tt> 5</tt>\n   extension index given after <tt>-x</tt> is negative
 *   - <tt> 6</tt>\n   no extension name given after <tt>-n</tt>
 *   - <tt> 9</tt>\n   unknown option given
 *   - <tt>10</tt>\n   image file does not exist
 *   - <tt>11</tt>\n   image could not be loaded from file
 *   - <tt>20</tt>\n   no sources detected
 *   - <tt>50</tt>\n   unknown error
 */
/*----------------------------------------------------------------------------*/

/**@{*/

/* a version of cpl_apertures_extract() without filtering */
static cpl_apertures *
image_fwhm_cplapertures_extract(cpl_image *aImage, cpl_vector *aSigmas,
                                cpl_size *aIndex)
{
  cpl_ensure(aImage && aSigmas, CPL_ERROR_NULL_INPUT, NULL);
  cpl_apertures *aperts = NULL;
  cpl_errorstate prestate = cpl_errorstate_get();
  cpl_size i, n = cpl_vector_get_size(aSigmas);
  for (i = 0; i < n; i++) {
    const double sigma = cpl_vector_get(aSigmas, i);
    if (sigma <= 0.0) {
      break;
    }

    /* Compute the threshold */
    double mdev, median = cpl_image_get_median_dev(aImage, &mdev),
           threshold = median + sigma * mdev;
    /* Binarise the image and divide into separate objects */
    cpl_mask *selection = cpl_mask_threshold_image_create(aImage, threshold,
                                                          DBL_MAX);
    cpl_size nlabels;
    cpl_image *labels = cpl_image_labelise_mask_create(selection, &nlabels);
    cpl_mask_delete(selection);
    /* Create the detected apertures list */
    aperts = cpl_apertures_new_from_image(aImage, labels);
    cpl_image_delete(labels);

    if (aperts) {
      break;
    }
  }
  cpl_ensure(aperts, CPL_ERROR_DATA_NOT_FOUND, NULL);
  /* Recover from any errors set in the extraction */
  cpl_errorstate_set(prestate);
  if (aIndex) {
    *aIndex = i;
  }
  return aperts;
} /* image_fwhm_cplapertures_extract() */

#define PRINT_USAGE(rc)                                                        \
  fprintf(stderr, "Usage: %s [ -s sigma ] [ -x extindex | -n extname] "        \
          "IMAGE\n", argv[0]);                                                 \
  cpl_end(); return (rc);

int main(int argc, char **argv)
{
  cpl_init(CPL_INIT_DEFAULT);
  muse_processing_recipeinfo(NULL);

  if (argc <= 1) {
    /* filename is needed at least */
    PRINT_USAGE(1);
  }

  char *iname = NULL, /* image name */
       *extname = NULL; /* half-optional extension name */
  double sigma = -1; /* detection sigma level */
  int iext = -1; /* half-optional extension number */

  /* argument processing */
  int i;
  for (i = 1; i < argc; i++) {
    if (strncmp(argv[i], "-s", 3) == 0) {
      /* skip to next arg to get sigma value */
      i++;
      if (i < argc) {
        sigma = atof(argv[i]);
        if (sigma <= 0.) {
          PRINT_USAGE(3);
        }
      } else {
        PRINT_USAGE(2);
      }
    } else if (strncmp(argv[i], "-x", 3) == 0) {
      /* skip to next arg to get sigma value */
      i++;
      if (i < argc) {
        iext = atoi(argv[i]);
        if (iext < 0) {
          PRINT_USAGE(5);
        }
      } else {
        PRINT_USAGE(4);
      }
    } else if (strncmp(argv[i], "-n", 3) == 0) {
      /* skip to next arg to get sigma value */
      i++;
      if (i < argc) {
        extname = argv[i];
      } else {
        PRINT_USAGE(6);
      }
    } else if (strncmp(argv[i], "-", 1) == 0) { /* unallowed options */
      PRINT_USAGE(9);
    } else {
      if (iname) {
        break; /* we have the possible names, skip the rest */
      }
      iname = argv[i] /* set the name for the image */;
    }
  } /* for i (all arguments) */
  if (extname && iext >= 0) {
    PRINT_USAGE(7);
  }

  int next = cpl_fits_count_extensions(iname);
  if (next < 0) {
    PRINT_USAGE(10);
  }
  if (extname) {
    iext = cpl_fits_find_extension(iname, extname);
  }
  cpl_image *image = NULL;
  if (iext >= 0) { /* load specified extension */
    image = cpl_image_load(iname, CPL_TYPE_UNSPECIFIED, 0, iext);
  } else { /* iterate through extensions, try to load the first image */
    cpl_errorstate ps = cpl_errorstate_get();
    do {
      image = cpl_image_load(iname, CPL_TYPE_UNSPECIFIED, 0, ++iext);
    } while (!image && iext <= next);
    cpl_errorstate_set(ps); /* recover from multiple trials to load the image */
  }
  if (!image) {
    PRINT_USAGE(11);
  }
  cpl_propertylist *header = cpl_propertylist_load(iname, iext);
  int nx = cpl_image_get_size_x(image),
      ny = cpl_image_get_size_y(image);
  char *extdisp = NULL; /* some extension property (name or index) to display */
  if (cpl_propertylist_has(header, "EXTNAME")) {
    extdisp = cpl_strdup(cpl_propertylist_get_string(header, "EXTNAME"));
  } else {
    extdisp = cpl_sprintf("%d", iext);
  }
  /* mark bad pixels (NAN) as bad in the CPL image, to ignore them for statistics */
  cpl_image_reject_value(image, CPL_VALUE_NAN);
  int nrej = cpl_image_count_rejected(image);
  printf("# Input image \"%s[%s]\" (size %dx%d, rejected %d)\n", iname, extdisp,
         nx, ny, nrej);
  cpl_free(extdisp);

  cpl_vector *vsigmas = NULL;
  if (sigma > 0) {
    vsigmas = cpl_vector_new(1);
    cpl_vector_set(vsigmas, 0, sigma);
  } else {
    vsigmas = cpl_vector_new(6);
    cpl_vector_set(vsigmas, 0, 50.);
    cpl_vector_set(vsigmas, 1, 30.);
    cpl_vector_set(vsigmas, 2, 20.);
    cpl_vector_set(vsigmas, 3, 10.);
    cpl_vector_set(vsigmas, 4, 8.);
    cpl_vector_set(vsigmas, 5, 5.);
  }
  cpl_size isigma = -1;
  cpl_errorstate prestate = cpl_errorstate_get();
  cpl_apertures *apertures = image_fwhm_cplapertures_extract(image, vsigmas, &isigma);
  if (!apertures || !cpl_errorstate_is_equal(prestate)) {
    cpl_image_delete(image);
    fprintf(stderr, "%s: no sources found for FWHM measurement down to %.1f "
            "sigma limit!\n", argv[0],
            cpl_vector_get(vsigmas, cpl_vector_get_size(vsigmas) - 1));
    cpl_vector_delete(vsigmas);
    return 20;
  }
  int ndet = cpl_apertures_get_size(apertures);
  printf("# %s: computing FWHM QC parameters for %d source%s found down to the "
         "%.1f sigma threshold\n", argv[0], ndet, ndet == 1 ? "" : "s",
         cpl_vector_get(vsigmas, isigma));
  cpl_vector_delete(vsigmas);

  /* get some kind of WCS for conversion of FWHM from pixels to arcsec, *
   * by default assume WFM and x=RA and y=DEC                           */
  double cd11 = kMuseSpaxelSizeX_WFM, cd12 = 0., cd21 = 0.,
         cd22 = kMuseSpaxelSizeY_WFM;
  cpl_wcs *wcs = cpl_wcs_new_from_propertylist(header);
  if (!wcs ||
      !strncasecmp(muse_pfits_get_ctype(header, 1), "PIXEL", 5)) {
    printf("# %s: FWHM parameter estimation (%d sources): simple conversion "
           "to arcsec (CTYPE=%s/%s)!\n", argv[0], ndet,
           muse_pfits_get_ctype(header, 1),
           muse_pfits_get_ctype(header, 2));
    if (muse_pfits_get_mode(header) == MUSE_MODE_NFM_AO_N) { /* NFM scaling */
      cd11 = kMuseSpaxelSizeX_NFM;
      cd22 = kMuseSpaxelSizeY_NFM;
    }
  } else {
    const cpl_matrix *cd = cpl_wcs_get_cd(wcs);
    /* take the absolute and scale by 3600 to get positive arcseconds */
    cd11 = fabs(cpl_matrix_get(cd, 0, 0)) * 3600.,
    cd12 = fabs(cpl_matrix_get(cd, 0, 1)) * 3600.,
    cd21 = fabs(cpl_matrix_get(cd, 1, 0)) * 3600.,
    cd22 = fabs(cpl_matrix_get(cd, 1, 1)) * 3600.;
    printf("# %s: FWHM parameter estimation (%d sources): full "
           "conversion to arcsec (CD=%.2f,%.2f,%.2f,%.2f)\n", argv[0], ndet,
           cd11, cd12, cd21, cd22);
  }
  cpl_wcs_delete(wcs); /* rely on internal NULL check */

  /* Compute FWHM of all apertures */
  cpl_vector *vfwhm = cpl_vector_new(ndet),
             *vgfwhm = cpl_vector_new(ndet);
  printf("#index   xPOS    yPOS      xFWHM yFWHM   xgFWHM ygFWHM\n");
  int n, idx = 0;
  for (n = 1; n <= ndet; n++) {
    double xcen = cpl_apertures_get_centroid_x(apertures, n),
           ycen = cpl_apertures_get_centroid_y(apertures, n);
    if (xcen < 2 || nx - xcen < 2 || ycen < 2 || ny - ycen < 2) {
      fprintf(stderr, "#bad %4d %7.3f %7.3f  too close to the edge\n",
              n, xcen, ycen);
      fflush(NULL);
      continue; /* skip, too close to image edge */
    }
    /* direct determination */
    double xfwhm, yfwhm;
    cpl_image_get_fwhm(image, lround(xcen), lround(ycen), &xfwhm, &yfwhm);

    /* Gaussian FWHM */
    cpl_array *params = cpl_array_new(7, CPL_TYPE_DOUBLE);
    cpl_array_set_double(params, 0, cpl_apertures_get_min(apertures, n));
    cpl_array_set_double(params, 1, cpl_apertures_get_flux(apertures, n));
    cpl_array_set_double(params, 2, 0);
    cpl_array_set_double(params, 3, xcen);
    cpl_array_set_double(params, 4, ycen);
    cpl_array_set_double(params, 5, xfwhm > 0 ? xfwhm / CPL_MATH_FWHM_SIG : 1.);
    cpl_array_set_double(params, 6, yfwhm > 0 ? yfwhm / CPL_MATH_FWHM_SIG : 1.);
    cpl_size xsize = cpl_apertures_get_right(apertures, n)
                   - cpl_apertures_get_left(apertures, n) + 1,
             ysize = cpl_apertures_get_top(apertures, n)
                   - cpl_apertures_get_bottom(apertures, n) + 1;
    xsize = xsize < 4 ? 4 : xsize;
    ysize = ysize < 4 ? 4 : ysize;
    cpl_error_code rc = cpl_fit_image_gaussian(image, NULL,
                                               cpl_apertures_get_pos_x(apertures, n),
                                               cpl_apertures_get_pos_y(apertures, n),
                                               xsize, ysize, params, NULL, NULL,
                                               NULL, NULL, NULL, NULL, NULL, NULL,
                                               NULL);
    double xgfwhm = cpl_array_get(params, 5, NULL) * CPL_MATH_FWHM_SIG,
           ygfwhm = cpl_array_get(params, 6, NULL) * CPL_MATH_FWHM_SIG;
    cpl_array_delete(params);
    /* check that the fit suceeded, and that it gave reasonable values */
#define MAX_AXIS_RATIO 5.
#define MAX_FWHM_RATIO 4.
    if (rc != CPL_ERROR_NONE || xgfwhm > nx || ygfwhm > ny ||
        fabs(xgfwhm/ygfwhm) > MAX_AXIS_RATIO || fabs(ygfwhm/xgfwhm) > MAX_AXIS_RATIO ||
        fabs(xgfwhm/xfwhm) > MAX_FWHM_RATIO || fabs(ygfwhm/yfwhm) > MAX_FWHM_RATIO ||
        fabs(xfwhm/xgfwhm) > MAX_FWHM_RATIO || fabs(yfwhm/ygfwhm) > MAX_FWHM_RATIO) {
      fprintf(stderr, "#bad %4d %7.3f %7.3f  %5.2f %5.2f    %5.2f  %5.2f   "
              "rc = %d: %s\n", n, xcen, ycen, xfwhm, yfwhm, xgfwhm, ygfwhm,
              rc, cpl_error_get_message());
      fflush(NULL);
      continue; /* skip this faulty one */
    }

    /* linear WCS scaling */
    xfwhm = cd11 * xfwhm + cd12 * yfwhm;
    yfwhm = cd22 * yfwhm + cd21 * xfwhm;
    xgfwhm = cd11 * xgfwhm + cd12 * ygfwhm;
    ygfwhm = cd22 * ygfwhm + cd21 * xgfwhm;
    printf("%6d   %7.3f %7.3f   %5.2f %5.2f    %5.2f  %5.2f\n",
           n, xcen, ycen, xfwhm, yfwhm, xgfwhm, ygfwhm);
    fflush(stdout);

    cpl_vector_set(vfwhm, idx, (xfwhm + yfwhm) / 2.);
    cpl_vector_set(vgfwhm, idx++, (xgfwhm + ygfwhm) / 2.);
  } /* for n (aperture number) */
  cpl_apertures_delete(apertures);
  cpl_vector_set_size(vfwhm, idx);
  cpl_vector_set_size(vgfwhm, idx);
  printf("#Summary:\n#\tdirect FWHM   %.3f +/- %.3f (%.3f) arcsec\n"
         "#\tGaussian FWHM %.3f +/- %.3f (%.3f) arcsec\n", cpl_vector_get_mean(vfwhm),
         cpl_vector_get_stdev(vfwhm), cpl_vector_get_median(vfwhm),
         cpl_vector_get_mean(vgfwhm), cpl_vector_get_stdev(vgfwhm),
         cpl_vector_get_median(vgfwhm));
  cpl_vector_delete(vfwhm);
  cpl_vector_delete(vgfwhm);

  cpl_image_delete(image);
  cpl_end();
  return 0;
}

/**@}*/
