/* -*- 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-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 no
 * on the pointer returned by this function., write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include <float.h>
#include <math.h>

#include <muse.h>

/* create the image components and fill them (2x2 or 3x3 image) */
void
muse_test_imagelist_fill_image(muse_image *aImage, int aNum, int aSize)
{
  aImage->header = cpl_propertylist_new();
  cpl_propertylist_append_double(aImage->header, "EXPTIME", (double)aNum);

  aImage->data = cpl_image_new(aSize, aSize, CPL_TYPE_FLOAT);
  cpl_image_set(aImage->data, 1, 1, aNum);
  cpl_image_set(aImage->data, 1, 2, aNum * 2);
  cpl_image_set(aImage->data, 2, 1, aNum * aNum);
  cpl_image_set(aImage->data, 2, 2, (int)(sqrt(aNum * 10. + 15.)));

  if (aSize == 3) {
    cpl_image_set(aImage->data, 1, 3, aNum+3);
    cpl_image_set(aImage->data, 2, 3, aNum+3);

    cpl_image_set(aImage->data, 3, 1, aNum+3);
    cpl_image_set(aImage->data, 3, 2, aNum+3);
    cpl_image_set(aImage->data, 3, 3, aNum+3);
  }

  /* create empty bad pixel map */
  aImage->dq = cpl_image_new(aSize, aSize, CPL_TYPE_INT);

  /* derive variance map from the data, assume a bias *
   * level of 0 and so only divide by a GAIN of 2.    */
  aImage->stat = cpl_image_divide_scalar_create(aImage->data, 2.);
}

/*----------------------------------------------------------------------------*/
/**
  @brief    test program to check that all the functions working on image list
            work correctly
 */
/*----------------------------------------------------------------------------*/
int main(int argc, char **argv)
{
  UNUSED_ARGUMENTS(argc, argv);
  cpl_test_init(PACKAGE_BUGREPORT, CPL_MSG_DEBUG);

  /* ######------- Tests : MUSE imagelist Functions -------####### */

  /* create imagelist */
  muse_imagelist *list = muse_imagelist_new(),
                 *list1 = muse_imagelist_new();

  /* create images to fill in the imagelist */
  muse_image **img = (muse_image **)cpl_calloc(sizeof(muse_image *), 5),
             **img1 = (muse_image **)cpl_calloc(sizeof(muse_image *), 5);

  /* creating a uniform imagelist */
  unsigned int m;
  for (m = 0; m < 5; m++) {
    img[m] = muse_image_new();
    muse_test_imagelist_fill_image(img[m], m+1, 2);
  }

  /* creating a non-uniform imagelist */
  for (m = 0; m < 5; m++) {
    img1[m] = muse_image_new();
    if (m == 3) {
      muse_test_imagelist_fill_image(img1[m], m+1, 3); /* larger image */
    } else {
      muse_test_imagelist_fill_image(img1[m], m+1, 2);
    }
  }

  /* Set the muse imagelist */
  for (m = 0; m < 5; m++) {
    muse_imagelist_set(list, img[m], m);
    muse_imagelist_set(list1, img1[m], m);
  }
  /* test some bad inputs to muse_imagelist_set() */
  muse_image *dupe1 = muse_image_duplicate(img1[4]);
  cpl_errorstate prestate = cpl_errorstate_get();
  cpl_test(muse_imagelist_set(NULL, dupe1, 4) == CPL_ERROR_NULL_INPUT);
  cpl_test(muse_imagelist_set(list1, NULL, 4) == CPL_ERROR_NULL_INPUT);
  /* try to add the same image again */
  cpl_test(muse_imagelist_set(list1, img1[4], 5) == CPL_ERROR_ILLEGAL_INPUT);
  cpl_errorstate_set(prestate);

  /* test extending the list with some empty places */
  muse_imagelist_set(list1, dupe1, 10);

  /* get the imagelist size */
  cpl_test(muse_imagelist_get_size(list) == 5);
  prestate = cpl_errorstate_get();
  cpl_test(muse_imagelist_get_size(NULL) == 0);
  cpl_errorstate_set(prestate);

  /* To check if all the image are of same size */
  cpl_test(muse_imagelist_is_uniform(list) == 0);
  cpl_test(muse_imagelist_is_uniform(list1) == 4);
  prestate = cpl_errorstate_get();
  cpl_test(muse_imagelist_is_uniform(NULL) != 0);
  cpl_errorstate_set(prestate);
  muse_imagelist *listempty = muse_imagelist_new();
  cpl_test(muse_imagelist_is_uniform(listempty) == 1);
  muse_imagelist_delete(listempty);

  /*Get the museimage from list */
  muse_image *med1 = muse_imagelist_get(list, 1);
  /* check whether the returned image is same as img[1] */
  cpl_test_nonnull(med1);
  cpl_test(med1 == img[1]); /* just check that the same address got returned */
  /* check for failures on bad inputs */
  prestate = cpl_errorstate_get();
  med1 = muse_imagelist_get(NULL, 1);
  cpl_test_null(med1);
  med1 = muse_imagelist_get(list, 10); /* index outside range */
  cpl_test_null(med1);
  cpl_errorstate_set(prestate);

  /* imagelist statistics, should just run through without causing errors */
  muse_imagelist_dump_statistics(list);
  muse_imagelist_dump_statistics(NULL);
  /* should also work on list1 which has empty entries */
  muse_imagelist_dump_statistics(list1);

  /* muse image scale: exptime */
  cpl_test(muse_imagelist_scale_exptime(list) == CPL_ERROR_NONE);
  for (m = 0; m < muse_imagelist_get_size(list); m++) {
    muse_image *image = muse_imagelist_get(list, m);
    int err;
    double value = cpl_image_get(image->data, 1, 1, &err),
           exptime = muse_pfits_get_exptime(image->header);
    cpl_msg_debug(__func__, "pixel 1,1 in image %u = %g "
                  "(EXPTIME now %g)", m+1, value, exptime);
    /* first pixel (1,1) was originally m+1, the same *
     * as the exposure time, so both should be 1 now. */
    cpl_test(value == 1.);
    cpl_test(exptime == 1.);
  } /* for m (all images in list) */
  prestate = cpl_errorstate_get();
  cpl_test(muse_imagelist_scale_exptime(NULL) == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(prestate);

  /* test muse_imagelist_unset() */
  unsigned int nima = muse_imagelist_get_size(list);
  prestate = cpl_errorstate_get();
  muse_image *erased = muse_imagelist_unset(list, 1);
  cpl_test(cpl_errorstate_is_equal(prestate));
  cpl_test_eq(muse_imagelist_get_size(list), nima - 1);
  for (m = 0; m < muse_imagelist_get_size(list); m++) {
    /* the erased image should no longer be pointed to */
    cpl_test(muse_imagelist_get(list, m) != erased);
  } /* for m */
  muse_image_delete(erased);
  /* failure cases */
  prestate = cpl_errorstate_get();
  erased = muse_imagelist_unset(NULL, 1);
  cpl_test(!cpl_errorstate_is_equal(prestate) &&
           cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_test_null(erased);
  cpl_errorstate_set(prestate);
  erased = muse_imagelist_unset(list, 10);
  cpl_test(!cpl_errorstate_is_equal(prestate) &&
           cpl_error_get_code() == CPL_ERROR_ACCESS_OUT_OF_RANGE);
  cpl_test_null(erased);
  cpl_errorstate_set(prestate);

  /* Delete the imagelists */
  muse_imagelist_delete(list);
  muse_imagelist_delete(list1);
  muse_imagelist_delete(NULL); /* should work with NULL argument, too */
  /* free the image memory */
  cpl_free(img);
  cpl_free(img1);

  /* check read-out noise computation */
  /* create some noise-only images with known noise level */
  muse_imagelist *listron = muse_imagelist_new();
  for (m = 0; m < 3; m++) {
    muse_image *image = muse_image_new();
    /* muse_imagelist_compute_ron() uses the data component, so fill that */
    image->data = cpl_image_new(1000, 1000, CPL_TYPE_FLOAT);
    cpl_image_fill_noise_uniform(image->data, 95., 105.);
    cpl_msg_debug(__func__, "stdev: %f", cpl_image_get_stdev(image->data));
    /* it also uses the header to find the quadrants */
    image->header = cpl_propertylist_new();
    cpl_propertylist_update_int(image->header, "ESO DET BINX", 1);
    cpl_propertylist_update_int(image->header, "ESO DET BINY", 1);
    cpl_propertylist_update_int(image->header, "ESO DET OUT1 NX", 500);
    cpl_propertylist_update_int(image->header, "ESO DET OUT1 NY", 500);
    cpl_propertylist_update_int(image->header, "ESO DET OUT2 NX", 500);
    cpl_propertylist_update_int(image->header, "ESO DET OUT2 NY", 500);
    cpl_propertylist_update_int(image->header, "ESO DET OUT3 NX", 500);
    cpl_propertylist_update_int(image->header, "ESO DET OUT3 NY", 500);
    cpl_propertylist_update_int(image->header, "ESO DET OUT4 NX", 500);
    cpl_propertylist_update_int(image->header, "ESO DET OUT4 NY", 500);
    cpl_propertylist_update_double(image->header, "ESO DET OUT1 GAIN", 0.5);
    cpl_propertylist_update_double(image->header, "ESO DET OUT2 GAIN", 1.0);
    cpl_propertylist_update_double(image->header, "ESO DET OUT3 GAIN", 1.5);
    cpl_propertylist_update_double(image->header, "ESO DET OUT4 GAIN", 2.0);
    /* add RON values to quieten the WARNINGS that we would otherwise get */
    cpl_propertylist_update_double(image->header, "ESO DET OUT1 RON", 2.885 * 0.5);
    cpl_propertylist_update_double(image->header, "ESO DET OUT2 RON", 2.885 * 1.0);
    cpl_propertylist_update_double(image->header, "ESO DET OUT3 RON", 2.885 * 1.5);
    cpl_propertylist_update_double(image->header, "ESO DET OUT4 RON", 2.885 * 2.0);
    image->stat = cpl_image_new(1000, 1000, CPL_TYPE_FLOAT);
    muse_imagelist_set(listron, image, m);
  } /* for m (all images in list) */
  /* compute the read-out noise from bias images in an imagelist */
  prestate = cpl_errorstate_get();
  cpl_bivector *bron = muse_imagelist_compute_ron(listron, 9, 100);
  cpl_test(cpl_errorstate_is_equal(prestate));
  cpl_test_nonnull(bron);
  cpl_test(cpl_bivector_get_size(bron) == 4);
  const cpl_vector *vron = cpl_bivector_get_x_const(bron),
                   *verr = cpl_bivector_get_y_const(bron);
#if 0
  cpl_bivector_dump(bron, stdout);
  fflush(stdout);
#endif
  unsigned char n;
  for (n = 1; n <= 4; n++) {
    double ron = cpl_vector_get(vron, n - 1),
           rerr = cpl_vector_get(verr, n - 1),
           delta = fabs(ron - 10./sqrt(12.) * (n * 0.5));
    /* check that we get the RON out that we expect from the std.dev. *
     * that we input together with the GAIN that we also set above    */
    cpl_msg_debug(__func__, "ron: %f +/- %f expected %f delta %f",
                  ron, rerr, 10./sqrt(12.) * (n * 0.5), delta);
    cpl_test(delta < rerr); /* mild check, should always be below given error */
    cpl_test(rerr < 0.1 * ron);

    /* check that the variance images created are OK */
    for (m = 0; m < muse_imagelist_get_size(listron); m++) {
      muse_image *image = muse_imagelist_get(listron, m);
      cpl_size *w = muse_quadrants_get_window(image, n);
      cpl_stats_mode mode = CPL_STATS_MEAN | CPL_STATS_MIN | CPL_STATS_MAX;
      cpl_stats *s = cpl_stats_new_from_image_window(image->stat, mode,
                                                     w[0], w[2], w[1], w[3]);
      cpl_msg_debug(__func__, "variance: %hhu %u\t%f %f..%f, expected %f", n, m,
                    cpl_stats_get_mean(s), cpl_stats_get_min(s),
                    cpl_stats_get_max(s), 100./12.);
      /* should be the same for all quadrants, since the       *
       * variance gets added here in adu and not in electrons: */
      cpl_test_abs(100./12., cpl_stats_get_mean(s), 0.1);
      /* should be a constant: */
      cpl_test_abs(cpl_stats_get_min(s), cpl_stats_get_max(s), FLT_EPSILON);
      cpl_stats_delete(s);
      cpl_free(w);
    } /* for m */
  } /* for n (quadrants) */
  cpl_bivector_delete(bron);
  muse_imagelist_delete(listron);
  prestate = cpl_errorstate_get();
  bron = muse_imagelist_compute_ron(NULL, 5, 100);
  cpl_test(!cpl_errorstate_is_equal(prestate) &&
           cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(prestate);
  cpl_test_null(bron);
  listron = muse_imagelist_new();
  prestate = cpl_errorstate_get();
  bron = muse_imagelist_compute_ron(listron, 5, 100);
  cpl_test(!cpl_errorstate_is_equal(prestate) &&
           cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
  cpl_errorstate_set(prestate);
  cpl_test_null(bron);
  muse_imagelist_delete(listron);

  return cpl_test_end(0);
}
