/* -*- 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_imagelist.h"

#include "muse_dfs.h"
#include "muse_pfits.h"
#include "muse_quadrants.h"
#include "muse_quality.h"
#include "muse_utils.h"

/*---------------------------------------------------------------------------*/
/**
 * @defgroup muse_imagelist    Imagelist processing for the four-extension image format
 *
 * Functions to handle lists of muse_images.
 */
/*---------------------------------------------------------------------------*/

/**@{*/

/*---------------------------------------------------------------------------*/
/**
  @brief  Create a new (empty) MUSE image list.
  @return the image list
 */
/*---------------------------------------------------------------------------*/
muse_imagelist *
muse_imagelist_new(void)
{
  muse_imagelist *images = cpl_calloc(sizeof(muse_imagelist), 1);
  /* the components are nulled automatically by using calloc */
  return images;
} /* muse_imagelist_new() */

/*---------------------------------------------------------------------------*/
/**
  @brief  Free the memory of the MUSE image list.
  @param  aList   the image list

  Frees the memory of all muse_images in the list and the pointers to the
  image list and the list itself.  As a safeguard, it checks if a valid pointer
  was passed, so that crashes cannot occur.
 */
/*---------------------------------------------------------------------------*/
void
muse_imagelist_delete(muse_imagelist *aList)
{
  if (!aList) {
    return;
  }
  unsigned int k;
  for (k = 0; k < aList->size; k++) {
    muse_image_delete(aList->list[k]);
  }
  cpl_free(aList->list);
  aList->list = NULL;
  aList->size = 0;
  cpl_free(aList);
} /* muse_imagelist_delete() */

/*---------------------------------------------------------------------------*/
/**
  @brief  Return the number of stored images.
  @param  aList   the image list
  @return the size of this list or 0 on error or if the list is empty

  @error{set CPL_ERROR_NULL_INPUT\, return 0, aList is NULL}
 */
/*---------------------------------------------------------------------------*/
unsigned int
muse_imagelist_get_size(muse_imagelist *aList)
{
  cpl_ensure(aList, CPL_ERROR_NULL_INPUT, 0);
  return aList->size;
} /* muse_imagelist_get_size() */

/*---------------------------------------------------------------------------*/
/**
  @brief  Get the muse_image of given list index.
  @param  aList   the image list
  @param  aIdx    index of the image to return, starting at 0
  @return the corresponding muse_image or NULL on error

  This function only returns a pointer to the actual image which remains in the
  image list and will be deallocated with it. So do not use muse_image_delete()
  on the pointer returned by this function.

  @error{set CPL_ERROR_NULL_INPUT\, return NULL, aList is NULL}
  @error{set CPL_ERROR_ACCESS_OUT_OF_RANGE\, return NULL,
         aIdx does not exist in the list}
 */
/*---------------------------------------------------------------------------*/
muse_image *
muse_imagelist_get(muse_imagelist *aList, unsigned int aIdx)
{
  cpl_ensure(aList, CPL_ERROR_NULL_INPUT, NULL);
  cpl_ensure(aIdx < aList->size, CPL_ERROR_ACCESS_OUT_OF_RANGE, NULL);

  return aList->list[aIdx];
} /* muse_imagelist_get() */

/*---------------------------------------------------------------------------*/
/**
  @brief  Set the muse_image of given list index.
  @param  aList    the image list
  @param  aImage   the muse_image to set
  @param  aIdx     index of the image to return, starting at 0
  @return CPL_ERROR_NONE on success of a CPL error on failure

  If the index points to an existing muse_image in the list, that will be
  deallocated and replaced by the given image. Otherwise the list will be
  extended to store a new image at aIdx, possibly even with empty entries
  in between.

  You should not deallocate the image after it has been inserted into an
  imagelist, since the muse_imagelist_delete() will deallocate its images.

  @error{return CPL_ERROR_NULL_INPUT, aList or aImage are NULL}
  @error{return CPL_ERROR_ILLEGAL_INPUT,
         the pointer to aImage was already added to aList}
 */
/*---------------------------------------------------------------------------*/
cpl_error_code
muse_imagelist_set(muse_imagelist *aList, muse_image *aImage, unsigned int aIdx)
{
  cpl_ensure_code(aList && aImage, CPL_ERROR_NULL_INPUT);
  /* check that this muse_image * is not already present! */
  unsigned int k;
  for (k = 0; k < aList->size; k++) {
    /* just check that the address is not the same */
    cpl_ensure_code(aList->list[k] != aImage, CPL_ERROR_ILLEGAL_INPUT);
  } /* for k (all images) */

  if (aIdx >= aList->size || aList->list == NULL) {
    aList->list = (muse_image **)cpl_realloc(aList->list,
                                             sizeof(muse_image *) * (aIdx+1));
    /* null out all entries from the old size to the new size */
    for (k = aList->size; k <= aIdx; k++) {
      aList->list[k] = NULL;
    } /* for k (all new list positions) */
    aList->size = aIdx + 1;
  } /* if list needs to be expanded */
  /* make sure this location in the list is empty */
  muse_image_delete(aList->list[aIdx]);
  aList->list[aIdx] = aImage;
  return CPL_ERROR_NONE;
} /* muse_imagelist_set() */

/*---------------------------------------------------------------------------*/
/**
  @brief  Check that all images in the muse_imagelist have the same size.
  @param  aList   the image list
  @return 0 when the list is uniform, a positive number if the list contains
          deviant images, a negative value on error.

  Unlike cpl_imagelists, the types of the images are automatically equal, so
  this is not tested.

  @note Normally, the positive return value matches the number (starting at 1!)
        of the first deviant image in the list. If the list is empty, it returns
        1. If the number of images in the list should exceed INT_MAX, a negative
        number will be returned (due to integer overflow).

  @error{set CPL_ERROR_NULL_INPUT\, return -1, aList is NULL}
 */
/*---------------------------------------------------------------------------*/
int
muse_imagelist_is_uniform(muse_imagelist *aList)
{
  cpl_ensure(aList, CPL_ERROR_NULL_INPUT, -1);
  if (!aList->size) {
   return 1;
  }

  /* Check the images */
  int nx = cpl_image_get_size_x(muse_imagelist_get(aList, 0)->data),
      ny = cpl_image_get_size_y(muse_imagelist_get(aList, 0)->data);
  unsigned int k;
  for (k = 1; k < aList->size; k++) {
    if (cpl_image_get_size_x(muse_imagelist_get(aList, k)->data) != nx ||
        cpl_image_get_size_y(muse_imagelist_get(aList, k)->data) != ny) {
       return k+1;
    }
  } /* for k (all images except first) */
  return 0;
} /* muse_imagelist_is_uniform() */

/*---------------------------------------------------------------------------*/
/**
  @brief  Show statistics of a muse_image list.
  @param  aList   the image list

  @error{return without doing anything, aList is NULL}
 */
/*---------------------------------------------------------------------------*/
void
muse_imagelist_dump_statistics(muse_imagelist *aList)
{
  if (!aList) {
    return;
  }
  double t0 = muse_pfits_get_exptime(muse_imagelist_get(aList, 0)->header);
  cpl_msg_info(__func__, " index    median       mean       stdev     scale");
  unsigned int k;
  for (k = 0; k < aList->size; k++) {
    muse_image *image = muse_imagelist_get(aList, k);
    if (!image) {
      const char *empty = "----------";
      cpl_msg_info(__func__, "%5d %10s %10s %10s %10s" , k,
                   empty, empty, empty, empty);
      continue;
    }
    double t = muse_pfits_get_exptime(image->header),
           scale = t0 / t;
    cpl_msg_info(__func__, "%5d %10.2f %10.2f %10.2f %10.2f", k,
                 cpl_image_get_median(image->data),
                 cpl_image_get_mean(image->data),
                 cpl_image_get_stdev(image->data), scale);
  } /* for k (all images) */
} /* muse_imagelist_dump_statistics() */

/*---------------------------------------------------------------------------*/
/**
  @brief  Scale muse_images to a common exposure time.
  @param  aList   the image list
  @return CPL_ERROR_NONE on success, another CPL error code on failure.

  Scale all muse_images and their statistics by their exposure time (FITS
  keyword EXPTIME) relative to the first exposure in the input list. Reset
  their EXPTIME keyword to the value of the first exposure, too.

  @error{set and return CPL_ERROR_NULL_INPUT, aList is NULL}
 */
/*---------------------------------------------------------------------------*/
cpl_error_code
muse_imagelist_scale_exptime(muse_imagelist *aList)
{
  cpl_ensure_code(aList, CPL_ERROR_NULL_INPUT);
  double t0 = muse_pfits_get_exptime(muse_imagelist_get(aList, 0)->header);
  cpl_msg_info(__func__, "Scale all images to %7.2fs exposure time",
               t0);
  cpl_msg_debug(__func__, "Image  EXPTIME   scale");
  cpl_msg_debug(__func__, "   1   %7.2fs   1.000", t0);
  unsigned int k;
  for (k = 1; k < aList->size; k++) {
    muse_image *image = muse_imagelist_get(aList, k);
    double t = muse_pfits_get_exptime(image->header),
           scale = t0 / t;
    cpl_msg_debug(__func__, "%4d   %7.2fs  %6.3f", k+1, t, scale);
    muse_image_scale(image, scale);
    cpl_propertylist_update_double(image->header, "EXPTIME", t0);
  } /* for k (images except first) */
  return CPL_ERROR_NONE;
} /* muse_imagelist_scale_exptime() */

/*---------------------------------------------------------------------------*/
/**
  @brief  Compute the read-out noise from bias images in an imagelist.
  @param  aList       the image list
  @param  aHalfsize   half size of sample squares
  @param  aNSamples   number of sample squares
  @return cpl_bivector * for read-out noise values of the quadrants
          or NULL on error

  The output bivector has a length of 4, with one entry for RON (in the
  x-component) and RONERR (in y) for each quadrant.

  This loops through all images in the input list and subtracts one from the
  next. From the difference image the RON is measured. So several RON values
  are derived (one less than the size of the input list) which are then
  averaged to create the final value. RONERR should be at most 10% of RON, so
  this function reruns cpl_flux_get_noise_window() until that is reached.

  This function assumes that the input images are all bias images, that
  the GAIN value in the header is accurate, and that the images are still in the
  original adu.
  @note This function overwrites the stat component (the variance extension) of
        all images with the variance computed using the read-out noise derived
        here.

  Uses the formula from "Handbook of CCD Astronomy" by Steve B. Howell
  (2000, Sect. 4.3, p. 53) to scale the measured values to the correct ones:
     RON = GAIN * sigma(B1 - B2) / sqrt(2)
  where sigma() is the read-out noise measured on the difference bias image
  and GAIN is in count/adu.

  @error{set CPL_ERROR_NULL_INPUT\, return NULL, aList is NULL}
  @error{set CPL_ERROR_ILLEGAL_INPUT\, return NULL, aList has no elementes}
 */
/*---------------------------------------------------------------------------*/
cpl_bivector *
muse_imagelist_compute_ron(muse_imagelist *aList, int aHalfsize, int aNSamples)
{
  cpl_ensure(aList, CPL_ERROR_NULL_INPUT, NULL);
  unsigned int nvalues = aList->size;
  cpl_ensure(nvalues > 0, CPL_ERROR_ILLEGAL_INPUT, NULL);
  nvalues -= 1; /* we'll get one value less than images */
  unsigned char ifu = muse_utils_get_ifu(aList->list[0]->header); /* IFU for output */

  /* Use an image to store the read-out noise values and the errors for the *
   * four quadrants of the CCD. Store RON in columns 1-4, RONERR in 5-8.    */
  cpl_image *ronimage = cpl_image_new(4 * 2, nvalues, CPL_TYPE_DOUBLE);

  /* loop through all images and compute difference image with the next one */
  unsigned int k; /* image index */
  unsigned char n; /* quadrant number */
  for (k = 0; k < nvalues; k++) {
    /* loop through the quadrants of all images */
    cpl_image *diff = cpl_image_subtract_create(aList->list[k]->data,
                                                aList->list[k+1]->data);
    for (n = 1; n <= 4; n++) {
      /* here we want gain in count/adu */
      double gain = muse_pfits_get_gain(aList->list[k]->header, n);
      cpl_size *window = muse_quadrants_get_window(aList->list[k], n);
#if 0
      cpl_stats *s = cpl_stats_new_from_image_window(diff, CPL_STATS_ALL,
                                                     window[0], window[2],
                                                     window[1], window[3]);
      cpl_msg_debug(__func__, "Quadrant %hhu stats: %f+/-%f %f %f..%f\n", n,
                    cpl_stats_get_mean(s), cpl_stats_get_stdev(s),
                    cpl_stats_get_median(s),
                    cpl_stats_get_min(s), cpl_stats_get_max(s));
      cpl_stats_delete(s);
#endif
      double ron = 100., ronerr = 1000.;
      unsigned int niter = 0;
      #pragma omp critical (cpl_flux_get_noise)
      do {
        srand(niter++ * 100 + 1); /* as if running without seed the first time */
        cpl_flux_get_noise_window(diff, window, aHalfsize, aNSamples,
                                  &ron, &ronerr);
      } while (ronerr > 0.1 * ron && niter < 5);
#if 0
      cpl_msg_debug(__func__, "--> intermediate RON=%f+/-%f (GAIN=%f)",
                    ron, ronerr, gain);
#endif
      /* use the formula from Howell "CCD Astronomy" */
      ron *= gain / sqrt(2);
      ronerr *= gain / sqrt(2);
      cpl_image_set(ronimage, n,   k+1, ron);
      cpl_image_set(ronimage, n+4, k+1, ronerr);
      cpl_free(window);
    } /* for n (quadrants) */
    cpl_image_delete(diff);
  } /* for k (all but one bias files) */

  /* compute and store the final values */
  cpl_vector *ron = cpl_vector_new(4),
             *ronerr = cpl_vector_new(4);
  for (n = 1; n <= 4; n++) {
#if 0 /* for debugging generate and use some more values */
    cpl_stats *ronstats, *ronerrstats;
    ronstats = cpl_stats_new_from_image_window(ronimage, CPL_STATS_ALL,
                                               n, 1, n, nvalues);
    ronerrstats = cpl_stats_new_from_image_window(ronimage, CPL_STATS_ALL,
                                                  n+4, 1, n+4, nvalues);
    cpl_msg_debug(__func__, "IFU %hhu, quadrant %hhu RON: %f+/-%f %f %f..%f\n",
                  ifu, n, cpl_stats_get_mean(ronstats),
                  cpl_stats_get_stdev(ronstats),
                  cpl_stats_get_median(ronstats),
                  cpl_stats_get_min(ronstats),
                  cpl_stats_get_max(ronstats));
    cpl_msg_debug(__func__, "IFU %hhu, quadrant %hhu RONERR: %f+/-%f %f %f..%f\n",
                  ifu, n, cpl_stats_get_mean(ronerrstats),
                  cpl_stats_get_stdev(ronerrstats),
                  cpl_stats_get_median(ronerrstats),
                  cpl_stats_get_min(ronerrstats),
                  cpl_stats_get_max(ronerrstats));
    cpl_stats_delete(ronstats);
    cpl_stats_delete(ronerrstats);
#endif

    double ronmean = cpl_image_get_mean_window(ronimage, n, 1, n, nvalues),
           ronmeanerr = cpl_image_get_mean_window(ronimage, n+4, 1, n+4, nvalues);
    cpl_vector_set(ron, n - 1, ronmean);
    cpl_vector_set(ronerr, n - 1, ronmeanerr);

    /* take RON value in first input header as reference */
    double ronheader = muse_pfits_get_ron(aList->list[0]->header, n);
    if (ronmean < 1. || ronmean > 5.) {
      cpl_msg_warning(__func__, "The RON value computed for quadrant %hhu in "
                      "IFU %hhu is likely wrong (outside the range 1..5 count: "
                      "%.2f +/- %.2f count; the raw header says %.2f count)", n,
                      ifu, ronmean, ronmeanerr, ronheader);
    } /* if bad RON */
  } /* for n (quadrants) */
  cpl_image_delete(ronimage);

  /* now apply the read-out noise by filling the variance accordingly */
  nvalues = muse_imagelist_get_size(aList); /* now use all images */
  for (k = 0; k < nvalues; k++) {
    for (n = 1; n <= 4; n++) {
      double gain = muse_pfits_get_gain(aList->list[k]->header, n);
      /* read-out noise + error of the read-out noise is            *
       *    (1 + 1/n_bias) * sigma_bias^2                           *
       * where n_bias is the number of pixels used to determine the *
       * RON and sigma_bias is the RON itself (here still in adu)   */
      double variance = (1. + 1. / (pow(2 * aHalfsize + 1, 2) * aNSamples))
                      * pow(cpl_vector_get(ron, n-1)/gain, 2);
      if (k == 0) { /* output message for the first image in the list */
        cpl_msg_info(__func__, "IFU %hhu, quadrant %hhu: RON = %.3f +/- %.3f "
                     "count ==> variance = %.4f adu**2 (1st value of image "
                     "series)", ifu, n, cpl_vector_get(ron, n-1),
                     cpl_vector_get(ronerr, n-1), variance);
      } /* if */
      /* get the image window for this quadrant */
      cpl_size *window = muse_quadrants_get_window(aList->list[k], n);
      /* fill the variance into the stat extension of the input image */
      cpl_image_fill_window(aList->list[k]->stat, window[0], window[2],
                            window[1], window[3], variance);
      cpl_free(window);
    } /* for n (quadrants) */
  } /* for k (all images in list) */

  return cpl_bivector_wrap_vectors(ron, ronerr);
} /* muse_imagelist_compute_ron() */

/**@}*/
