/* -*- 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-2013 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 <float.h>
#include <math.h>

#include <muse.h>

#define OBJECT_STRING "Test muse_combine"

/* create the image components and fill them */
void
muse_test_combine_fill_image(muse_image *aImage, int aNum)
{
  aImage->header = cpl_propertylist_new();
  cpl_propertylist_append_string(aImage->header, "OBJECT", OBJECT_STRING);
  char *kw = cpl_sprintf(MUSE_HDR_TMPi_NSAT, 2);
  cpl_propertylist_append_int(aImage->header, kw, 0);
  cpl_free(kw);
  cpl_propertylist_append_string(aImage->header, MUSE_HDR_TMP_FN, "bla.fits");
  cpl_propertylist_append_double(aImage->header, "MUSE TMPDDK", 0.555);
  cpl_propertylist_append_bool(aImage->header, "MUSE TMP BLA", CPL_TRUE);
  cpl_propertylist_append_long(aImage->header, "MUSE TMP DDK NSATURATED", 10);
  cpl_propertylist_append_int(aImage->header, MUSE_HDR_TMP_NSAT, 0);
  /* set up exposure times to that for the first two pixels *
   * exposure time scaling will compensate the effect       */
  cpl_propertylist_append_double(aImage->header, "EXPTIME", aNum);

  /* make up some fake data */
  aImage->data = cpl_image_new(2, 2, 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 0
  cpl_image_dump_window(aImage->data, 1, 1, 2, 2, stdout);
  fflush(stdout);
#endif
  /* create empty bad pixel map */
  aImage->dq = cpl_image_new(2, 2, CPL_TYPE_INT);
  /* Derive the variance 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.);
} /* muse_test_combine_fill_image() */

/*----------------------------------------------------------------------------*/
/**
  @brief    Test program to check that all the functions working on image
            combination work correctly.

  This program explicitely tests
    muse_combine_sum_create
    muse_combine_average_create
    muse_combine_median_create
    muse_combine_minmax_create
    muse_combine_sigclip_create
    muse_combinepar_new
    muse_combinepar_delete
    muse_combine_images
 */
/*----------------------------------------------------------------------------*/
int main(int argc, char **argv)
{
  UNUSED_ARGUMENTS(argc, argv);
  cpl_test_init(PACKAGE_BUGREPORT, CPL_MSG_DEBUG);

  /* create five images with fixed contents */
  muse_imagelist *list5 = muse_imagelist_new(),
                 *list6 = muse_imagelist_new();
  muse_image **im5 = (muse_image **)cpl_calloc(sizeof(muse_image *), 5),
             **im6 = (muse_image **)cpl_calloc(sizeof(muse_image *), 6);
  int i;
  for (i = 0; i < 6; i++) {
    im6[i] = muse_image_new();
    muse_test_combine_fill_image(im6[i], i+1);
    if (i < 5) {
      im5[i] = muse_image_duplicate(im6[i]);
      muse_imagelist_set(list5, im5[i], i);
    }
    muse_imagelist_set(list6, im6[i], i);
#if 0 /* write files to compare combination with IRAF */
    char *filename = cpl_sprintf("im%1d.fits", i+1);
    cpl_msg_debug(__func__, "_%s_", filename);
    cpl_image_save(im6[i]->data, filename, CPL_TYPE_SHORT, im6[i]->header,
                   CPL_IO_CREATE);
    cpl_free(filename);
#endif
  }

  cpl_test(muse_imagelist_get_size(list5) == 5);
  cpl_test(muse_imagelist_get_size(list6) == 6);
  cpl_msg_debug(__func__, "created all 5/6 images");

#define FUDGE_FACTOR 1.61 /* XXX factor to make some tests pass... */

  /* summing */
  int err;
  cpl_errorstate ps = cpl_errorstate_get();
  muse_image *sum1 = muse_combine_sum_create(list5);
  cpl_test(cpl_errorstate_is_equal(ps));
  cpl_test_nonnull(sum1);
  cpl_test(fabs(cpl_image_get(sum1->data, 1, 1, &err) - 15) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(sum1->data, 1, 2, &err) - 30) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(sum1->data, 2, 1, &err) - 55) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(sum1->data, 2, 2, &err) - 31) < FLT_EPSILON);
  cpl_test(sum1->header && cpl_propertylist_get_size(sum1->header) == 0);
  muse_image_delete(sum1);

  /* summing with only bad pixels */
  muse_imagelist *list5b = muse_imagelist_new();
  unsigned int ii;
  for (ii = 0; ii < muse_imagelist_get_size(list5); ii++) {
    muse_image *im = muse_image_duplicate(muse_imagelist_get(list5, ii));
    /* first image with least bad status */
    cpl_image_fill_noise_uniform(im->dq, 1ul << (ii+1), (1ul << (ii+1)) + 0.1);
    muse_imagelist_set(list5b, im, ii);
  } /* for ii */
  ps = cpl_errorstate_get();
  muse_image *sum1b = muse_combine_sum_create(list5b);
  cpl_test(cpl_errorstate_is_equal(ps));
  cpl_test_nonnull(sum1b);
  cpl_msg_info(__func__, "sum1b: %f %f %f %f (%d)",
               cpl_image_get(sum1b->data, 1, 1, &err),
               cpl_image_get(sum1b->data, 1, 2, &err),
               cpl_image_get(sum1b->data, 2, 1, &err),
               cpl_image_get(sum1b->data, 2, 2, &err), err);
  cpl_test(fabs(cpl_image_get(sum1b->data, 1, 1, &err) - 1.*5.) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(sum1b->data, 1, 2, &err) - 2.*5.) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(sum1b->data, 2, 1, &err) - 1.*5.) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(sum1b->data, 2, 2, &err) - 5.*5.) < FLT_EPSILON);
  cpl_test(cpl_image_get_mean(sum1b->dq) == 1ul << 1); /* status from 1st image */
  muse_image_delete(sum1b);
  muse_imagelist_delete(list5b);

  /* average combination */
  ps = cpl_errorstate_get();
  muse_image *av1 = muse_combine_average_create(list5);
  cpl_test(cpl_errorstate_is_equal(ps));
  cpl_test_nonnull(av1);
  cpl_test(fabs(cpl_image_get(av1->data, 1, 1, &err) - 3) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(av1->data, 1, 2, &err) - 6) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(av1->data, 2, 1, &err) - 11) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(av1->data, 2, 2, &err) - 6.2)
           < FUDGE_FACTOR * FLT_EPSILON);
  cpl_test(av1->header && cpl_propertylist_get_size(av1->header) == 0);
  muse_image_delete(av1);

  /* median combination */
  ps = cpl_errorstate_get();
  muse_image *med1 = muse_combine_median_create(list5);
  cpl_test(cpl_errorstate_is_equal(ps));
  cpl_test_nonnull(med1);
  cpl_test(fabs(cpl_image_get(med1->data, 1, 1, &err) - 3) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(med1->data, 1, 2, &err) - 6) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(med1->data, 2, 1, &err) - 9) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(med1->data, 2, 2, &err) - 6) < FLT_EPSILON);
  cpl_test(med1->header && cpl_propertylist_get_size(med1->header) == 0);
  muse_image_delete(med1);
  ps = cpl_errorstate_get();
  muse_image *med2 = muse_combine_median_create(list6);
  cpl_test(cpl_errorstate_is_equal(ps));
  cpl_test_nonnull(med2);
  cpl_test(fabs(cpl_image_get(med2->data, 1, 1, &err) - 3.5) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(med2->data, 1, 2, &err) - 7) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(med2->data, 2, 1, &err) - 12.5) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(med2->data, 2, 2, &err) - 6.5) < FLT_EPSILON);
  cpl_test(med2->header && cpl_propertylist_get_size(med2->header) == 0);
  muse_image_delete(med2);

  /* minmax combination */
  ps = cpl_errorstate_get();
  muse_image *mm1 = muse_combine_minmax_create(list5, 2, 1, 3);
  cpl_test_null(mm1);
  cpl_errorstate_set(ps);
  muse_image *mm2 = muse_combine_minmax_create(list5, 3, 3, 0);
  cpl_test_null(mm2);
  cpl_errorstate_set(ps);
  muse_image *mm3 = muse_combine_minmax_create(list5, 1, 2, 0);
  cpl_test(cpl_errorstate_is_equal(ps));
  cpl_test_nonnull(mm3);
  cpl_test(fabs(cpl_image_get(mm3->data, 1, 1, &err) - 2.5) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(mm3->data, 1, 2, &err) - 5) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(mm3->data, 2, 1, &err) - 6.5) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(mm3->data, 2, 2, &err) - 5.5) < FLT_EPSILON);
  cpl_test(mm3->header && cpl_propertylist_get_size(mm3->header) == 0);
  muse_image_delete(mm3);
  ps = cpl_errorstate_get();
  muse_image *mm4 = muse_combine_minmax_create(list6, 1, 3, 0);
  cpl_test(cpl_errorstate_is_equal(ps));
  cpl_test_nonnull(mm4);
  cpl_test(fabs(cpl_image_get(mm4->data, 1, 1, &err) - 2.5) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(mm4->data, 1, 2, &err) - 5) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(mm4->data, 2, 1, &err) - 6.5) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(mm4->data, 2, 2, &err) - 5.5) < FLT_EPSILON);
  cpl_test(mm4->header && cpl_propertylist_get_size(mm4->header) == 0);
  muse_image_delete(mm4);

  /* sigclip combination */
  ps = cpl_errorstate_get();
  muse_image *sig1 = muse_combine_sigclip_create(list5, 10.0, 10.0);
  cpl_test(cpl_errorstate_is_equal(ps));
  cpl_test_nonnull(sig1);
  cpl_test(fabs(cpl_image_get(sig1->data, 1, 1, &err) - 3) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(sig1->data, 1, 2, &err) - 6) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(sig1->data, 2, 1, &err) - 11) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(sig1->data, 2, 2, &err) - 6.2)
           < FUDGE_FACTOR * FLT_EPSILON);
  cpl_test(sig1->header && cpl_propertylist_get_size(sig1->header) == 0);
  muse_image_delete(sig1);
  ps = cpl_errorstate_get();
  muse_image *sig2 = muse_combine_sigclip_create(list6, 3., 1.);
  cpl_test(cpl_errorstate_is_equal(ps));
  cpl_test_nonnull(sig2);
  cpl_test(fabs(cpl_image_get(sig2->data, 1, 1, &err) - 3) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(sig2->data, 1, 2, &err) - 6) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(sig2->data, 2, 1, &err) - 7.5) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(sig2->data, 2, 2, &err) - 5.75) < FLT_EPSILON);
  cpl_test(sig2->header && cpl_propertylist_get_size(sig2->header) == 0);
  muse_image_delete(sig2);
  /* try again, this time with a really low value in pixel 2,2 */
  cpl_image_set(im6[5]->data, 2, 2, -100.);
  ps = cpl_errorstate_get();
  muse_image *sig2l = muse_combine_sigclip_create(list6, 3., 1.);
  cpl_test(cpl_errorstate_is_equal(ps));
  cpl_test_nonnull(sig2l);
  cpl_test(fabs(cpl_image_get(sig2l->data, 1, 1, &err) - 3) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(sig2l->data, 1, 2, &err) - 6) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(sig2l->data, 2, 1, &err) - 7.5) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(sig2l->data, 2, 2, &err) - 6.2)
           < FUDGE_FACTOR * FLT_EPSILON);
  muse_image_delete(sig2l);

    /* again, with the same really low value in pixel 2,2, but minmax */
  ps = cpl_errorstate_get();
  muse_image *mm4l = muse_combine_minmax_create(list6, 1, 0, 0);
  cpl_test(cpl_errorstate_is_equal(ps));
  cpl_test_nonnull(mm4l);
  cpl_test(fabs(cpl_image_get(mm4l->data, 1, 1, &err) - 4) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(mm4l->data, 1, 2, &err) - 8) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(mm4l->data, 2, 1, &err) - 18) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(mm4l->data, 2, 2, &err) - 6.2)
           < FUDGE_FACTOR * FLT_EPSILON);
  muse_image_delete(mm4l);

  /* all the same again using the high-level function; now make it harder and *
   * introduce bad pixels for the middle image of the list of 5 images        */
  cpl_image_set(im5[2]->dq, 1, 1, 256); /* hot pixel */
  cpl_image_set(im5[2]->dq, 1, 2, 512); /* dark pixel */
  cpl_image_set(im5[2]->dq, 2, 1, 8192); /* permanent camera defect */
  cpl_image_set(im5[2]->dq, 2, 2, 32); /* cosmic ray (uncorrected) */

  cpl_parameterlist *parlist = cpl_parameterlist_new();
  cpl_parameter *p = cpl_parameter_new_value("test.combine", CPL_TYPE_STRING,
                                             "sum", "test", "sum");
  cpl_parameterlist_append(parlist, p);
  muse_combinepar *cpars = muse_combinepar_new(parlist, "test");
  ps = cpl_errorstate_get();
  muse_image *sum2 = muse_combine_images(cpars, list5);
  cpl_test(cpl_errorstate_is_equal(ps));
  cpl_test_nonnull(sum2);
  muse_combinepar_delete(cpars);
  cpl_msg_info(__func__, "sum2: %f %f %f %f",
               cpl_image_get(sum2->data, 1, 1, &err),
               cpl_image_get(sum2->data, 1, 2, &err),
               cpl_image_get(sum2->data, 2, 1, &err),
               cpl_image_get(sum2->data, 2, 2, &err));
  cpl_test(fabs(cpl_image_get(sum2->data, 1, 1, &err) - 15) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(sum2->data, 1, 2, &err) - 30) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(sum2->data, 2, 1, &err) - 57.5) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(sum2->data, 2, 2, &err) - 31.25) < FLT_EPSILON);
  cpl_test(sum2->header && cpl_propertylist_get_size(sum2->header) == 5);
  /* explicitely test this once for keywords that should *
   * be propagated and those that should be missing      */
  char *kw = cpl_sprintf(MUSE_HDR_TMPi_NSAT, 2);
  cpl_test(!cpl_propertylist_has(sum2->header, kw));
  cpl_free(kw);
  cpl_test(!cpl_propertylist_has(sum2->header, MUSE_HDR_TMP_FN));
  cpl_test(!cpl_propertylist_has(sum2->header, MUSE_HDR_TMP_NSAT));
  cpl_test(cpl_propertylist_has(sum2->header, "MUSE TMPDDK"));
  cpl_test(cpl_propertylist_has(sum2->header, "MUSE TMP BLA"));
  cpl_test(cpl_propertylist_has(sum2->header, "MUSE TMP DDK NSATURATED"));
  muse_image_delete(sum2);

  cpl_parameter_set_string(p, "average");
  cpars = muse_combinepar_new(parlist, "test");
  ps = cpl_errorstate_get();
  muse_image *av2 = muse_combine_images(cpars, list5);
  cpl_test(cpl_errorstate_is_equal(ps));
  cpl_test_nonnull(av2);
  muse_combinepar_delete(cpars);
  cpl_test(fabs(cpl_image_get(av2->data, 1, 1, &err) - 3) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(av2->data, 1, 2, &err) - 6) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(av2->data, 2, 1, &err) - 11.5) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(av2->data, 2, 2, &err) - 6.25) < FLT_EPSILON);
  cpl_test(av2->header && cpl_propertylist_get_size(av2->header) == 5);
  muse_image_delete(av2);

  cpl_parameter *pscale = cpl_parameter_new_value("test.scale", CPL_TYPE_BOOL,
                                                  "scale", "test", CPL_TRUE);
  cpl_parameterlist_append(parlist, pscale);
  cpars = muse_combinepar_new(parlist, "test");
  muse_imagelist *list5s = muse_imagelist_new();
  for (ii = 0; ii < muse_imagelist_get_size(list5); ii++) {
    muse_image *im = muse_image_duplicate(muse_imagelist_get(list5, ii));
    muse_imagelist_set(list5s, im, ii);
  } /* for ii */
  ps = cpl_errorstate_get();
  muse_image *av2s = muse_combine_images(cpars, list5s);
  cpl_test(cpl_errorstate_is_equal(ps));
  cpl_test_nonnull(av2);
  muse_combinepar_delete(cpars);
  /* ignore the middle image for comparison, since it's marked bad! */
  cpl_test(fabs(cpl_image_get(av2s->data, 1, 1, &err) - 1) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(av2s->data, 1, 2, &err) - 2) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(av2s->data, 2, 1, &err) - 3) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(av2s->data, 2, 2, &err)
                - (5.*1.0+5.*0.5+7.*0.25+8*0.2)/4.) < FLT_EPSILON);
  cpl_test(av2s->header && cpl_propertylist_get_size(av2s->header) == 5);
  muse_image_delete(av2s);
  cpl_parameter_set_bool(pscale, CPL_FALSE); /* no scaling for the other tests */
  muse_imagelist_delete(list5s); /* this list was scaled! */

  cpl_parameter_set_string(p, "median");
  cpars = muse_combinepar_new(parlist, "test");
  ps = cpl_errorstate_get();
  muse_image *med3 = muse_combine_images(cpars, list5);
  cpl_test(cpl_errorstate_is_equal(ps));
  cpl_test_nonnull(med3);
  muse_combinepar_delete(cpars);
  cpl_test(fabs(cpl_image_get(med3->data, 1, 1, &err) - 3) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(med3->data, 1, 2, &err) - 6) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(med3->data, 2, 1, &err) - 10) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(med3->data, 2, 2, &err) - 6) < FLT_EPSILON);
  cpl_test(med3->header && cpl_propertylist_get_size(med3->header) == 5);
  muse_image_delete(med3);

  cpl_parameter_set_string(p, "minmax");
  cpl_parameter *p2 = cpl_parameter_new_value("test.nlow", CPL_TYPE_INT,
                                              "nlow=2", "test", 2),
                *p3 = cpl_parameter_new_value("test.nkeep", CPL_TYPE_INT,
                                              "nkeep=2", "test", 2);
  cpl_parameterlist_append(parlist, p2);
  cpl_parameterlist_append(parlist, p3);
  cpars = muse_combinepar_new(parlist, "test");
  ps = cpl_errorstate_get();
  muse_image *mm5 = muse_combine_images(cpars, list5);
  cpl_test(cpl_errorstate_is_equal(ps));
  cpl_test_nonnull(mm5);
  muse_combinepar_delete(cpars);
  cpl_test(fabs(cpl_image_get(mm5->data, 1, 1, &err) - 3.5) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(mm5->data, 1, 2, &err) - 7) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(mm5->data, 2, 1, &err) - 12.5) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(mm5->data, 2, 2, &err) - 6.5) < FLT_EPSILON);
  cpl_test(cpl_image_get(mm5->dq, 1, 1, &err) == 256);
  cpl_test(cpl_image_get(mm5->dq, 1, 2, &err) == 512);
  cpl_test(cpl_image_get(mm5->dq, 2, 1, &err) == 8192);
  cpl_test(cpl_image_get(mm5->dq, 2, 2, &err) == 32);
  cpl_test(mm5->header && cpl_propertylist_get_size(mm5->header) == 5);
  muse_image_delete(mm5);
  cpl_parameter_set_string(p, "sigclip");
  cpars = muse_combinepar_new(parlist, "test");
  ps = cpl_errorstate_get();
  muse_image *sig3 = muse_combine_images(cpars, list5);
  cpl_test(cpl_errorstate_is_equal(ps));
  cpl_test_nonnull(sig3);
  muse_combinepar_delete(cpars);
  cpl_test(fabs(cpl_image_get(sig3->data, 1, 1, &err) - 3) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(sig3->data, 1, 2, &err) - 6) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(sig3->data, 2, 1, &err) - 11.5) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(sig3->data, 2, 2, &err) - 6.25) < FLT_EPSILON);
  cpl_test(sig3->header && cpl_propertylist_get_size(sig3->header) == 5);
  muse_image_delete(sig3);

  /* test failure cases of muse_combine_images() */
  /* unknown combination method */
  cpl_parameter_set_string(p, "something");
  cpars = muse_combinepar_new(parlist, "test");
  ps = cpl_errorstate_get();
  cpl_test_null(muse_combine_images(cpars, list5));
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
  muse_combinepar_delete(cpars);
  cpl_errorstate_set(ps);
  /* NULL inputs */
  ps = cpl_errorstate_get();
  cpl_test_null(muse_combine_images(NULL, list5));
  cpl_test(cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(ps);
  cpars = muse_combinepar_new(parlist, "test");
  cpl_test_null(muse_combine_images(cpars, NULL));
  cpl_test(cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(ps);

  /* check failure cases of the combinepar functions */
  ps = cpl_errorstate_get();
  cpl_test_null(muse_combinepar_new(NULL, "bla"));
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(ps);
  cpl_test_null(muse_combinepar_new(parlist, NULL));
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(ps);
  muse_combinepar_delete(NULL); /* this should not fail */
  cpl_test(cpl_errorstate_is_equal(ps));

  /* test duplication of single-image list */
  muse_imagelist *list1 = muse_imagelist_new();
  muse_imagelist_set(list1, muse_image_duplicate(muse_imagelist_get(list5, 2)), 0);
  ps = cpl_errorstate_get();
  muse_image *dupe = muse_combine_images(cpars, list1);
  cpl_test(cpl_errorstate_is_equal(ps));
  /* duplicate the image with the bad pixels here */
  cpl_test_image_abs(dupe->data, muse_imagelist_get(list1, 0)->data, FLT_EPSILON);
  cpl_test_image_abs(dupe->stat, muse_imagelist_get(list1, 0)->stat, FLT_EPSILON);
  cpl_test_image_abs(dupe->dq, muse_imagelist_get(list1, 0)->dq, FLT_EPSILON);
  muse_image_delete(dupe);
  muse_imagelist_delete(list1);
  muse_combinepar_delete(cpars);
  cpl_parameterlist_delete(parlist);

  /* now for the really hard test: make all other pixels bad, too, so  *
   * that the pixels in the third image in list5 are now the best ones */
  for (i = 0; i < 5; i++) {
    if (i == 2) i++; /* skip this one, already has bad pixels */
    /* bad pixel not fitting any other category */
    cpl_image_set(im5[i]->dq, 1, 1, 16384);
    cpl_image_set(im5[i]->dq, 1, 2, 16384);
    cpl_image_set(im5[i]->dq, 2, 1, 16384);
    cpl_image_set(im5[i]->dq, 2, 2, 16384);
  }

  muse_image *av3 = muse_combine_average_create(list5);
  cpl_test_nonnull(av3);
  cpl_test(fabs(cpl_image_get(av3->data, 1, 1, &err) - 3) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(av3->data, 1, 2, &err) - 6) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(av3->data, 2, 1, &err) - 9) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(av3->data, 2, 2, &err) - 6) < FLT_EPSILON);
  muse_image_delete(av3);

  muse_image *med4 = muse_combine_median_create(list5);
  cpl_test_nonnull(med4);
  cpl_test(fabs(cpl_image_get(med4->data, 1, 1, &err) - 3) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(med4->data, 1, 2, &err) - 6) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(med4->data, 2, 1, &err) - 9) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(med4->data, 2, 2, &err) - 6) < FLT_EPSILON);
  muse_image_delete(med4);

  muse_image *mm6 = muse_combine_minmax_create(list5, 1, 2, 1);
  cpl_test_nonnull(mm6);
  cpl_test(fabs(cpl_image_get(mm6->data, 1, 1, &err) - 3) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(mm6->data, 1, 2, &err) - 6) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(mm6->data, 2, 1, &err) - 9) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(mm6->data, 2, 2, &err) - 6) < FLT_EPSILON);
  muse_image_delete(mm6);

  muse_image *sig4 = muse_combine_sigclip_create(list5, 2.0, 1.5);
  cpl_test_nonnull(sig4);
  cpl_test(fabs(cpl_image_get(sig4->data, 1, 1, &err) - 3) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(sig4->data, 1, 2, &err) - 6) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(sig4->data, 2, 1, &err) - 9) < FLT_EPSILON);
  cpl_test(fabs(cpl_image_get(sig4->data, 2, 2, &err) - 6) < FLT_EPSILON);
  muse_image_delete(sig4);

  /* test failure cases for NULL input lists */
  cpl_errorstate state = cpl_errorstate_get();
  muse_image *failed = muse_combine_sum_create(NULL);
  cpl_test(failed == NULL && cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(state);
  failed = muse_combine_average_create(NULL);
  cpl_test(failed == NULL && cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(state);
  failed = muse_combine_median_create(NULL);
  cpl_test(failed == NULL && cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(state);
  failed = muse_combine_minmax_create(NULL, 1, 2, 1);
  cpl_test(failed == NULL && cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(state);
  failed = muse_combine_sigclip_create(NULL, 2.0, 1.5);
  cpl_test(failed == NULL && cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(state);

  /* test failure cases for less than three images list (sigclip only) */
  muse_imagelist *list2 = muse_imagelist_new();
  muse_imagelist_set(list2, muse_image_duplicate(muse_imagelist_get(list5, 0)), 0);
  muse_imagelist_set(list2, muse_image_duplicate(muse_imagelist_get(list5, 1)), 1);
  failed = muse_combine_sigclip_create(list2, 3.0, 3.0);
  cpl_test(failed == NULL && cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
  muse_imagelist_delete(list2);
  cpl_errorstate_set(state);
  /* test failure cases for NULL image components in input lists */
  muse_image *im52 = muse_imagelist_get(list5, 2);
  cpl_image_delete(im52->dq); /* delete dq component in one list */
  im52->dq = NULL;
  muse_image *im63 = muse_imagelist_get(list6, 3);
  cpl_image_delete(im63->stat); /* delete stat component in the other */
  im63->stat = NULL;
  state = cpl_errorstate_get();
  failed = muse_combine_sum_create(list5);
  cpl_test(failed == NULL && cpl_error_get_code() == CPL_ERROR_INCOMPATIBLE_INPUT);
  cpl_errorstate_set(state);
  failed = muse_combine_sum_create(list6);
  cpl_test(failed == NULL && cpl_error_get_code() == CPL_ERROR_INCOMPATIBLE_INPUT);
  cpl_errorstate_set(state);
  failed = muse_combine_average_create(list5);
  cpl_test(failed == NULL && cpl_error_get_code() == CPL_ERROR_INCOMPATIBLE_INPUT);
  cpl_errorstate_set(state);
  failed = muse_combine_average_create(list6);
  cpl_test(failed == NULL && cpl_error_get_code() == CPL_ERROR_INCOMPATIBLE_INPUT);
  cpl_errorstate_set(state);
  failed = muse_combine_median_create(list5);
  cpl_test(failed == NULL && cpl_error_get_code() == CPL_ERROR_INCOMPATIBLE_INPUT);
  cpl_errorstate_set(state);
  failed = muse_combine_median_create(list6);
  cpl_test(failed == NULL && cpl_error_get_code() == CPL_ERROR_INCOMPATIBLE_INPUT);
  cpl_errorstate_set(state);
  failed = muse_combine_minmax_create(list5, 1, 2, 1);
  cpl_test(failed == NULL && cpl_error_get_code() == CPL_ERROR_INCOMPATIBLE_INPUT);
  cpl_errorstate_set(state);
  failed = muse_combine_minmax_create(list6, 1, 2, 1);
  cpl_test(failed == NULL && cpl_error_get_code() == CPL_ERROR_INCOMPATIBLE_INPUT);
  cpl_errorstate_set(state);
  failed = muse_combine_sigclip_create(list5, 2.0, 1.5);
  cpl_test(failed == NULL && cpl_error_get_code() == CPL_ERROR_INCOMPATIBLE_INPUT);
  cpl_errorstate_set(state);
  failed = muse_combine_sigclip_create(list6, 2.0, 1.5);
  cpl_test(failed == NULL && cpl_error_get_code() == CPL_ERROR_INCOMPATIBLE_INPUT);
  cpl_errorstate_set(state);

  /* clean up the lists */
  muse_imagelist_delete(list5);
  muse_imagelist_delete(list6);
  cpl_free(im5);
  cpl_free(im6);

  /* another special case for sigclip: all images are the same;          *
   * test with an image that contains just one pixel (which is non-null) */
  muse_image *im1 = muse_image_new();
  im1->header = cpl_propertylist_new();
  im1->data = cpl_image_new(1, 1, CPL_TYPE_FLOAT);
  cpl_image_set(im1->data, 1, 1, 1.);
  im1->dq = cpl_image_new(1, 1, CPL_TYPE_INT);
  im1->stat = cpl_image_new(1, 1, CPL_TYPE_FLOAT);
  cpl_image_set(im1->stat, 1, 1, 2.);
  muse_image *im2 = muse_image_duplicate(im1),
             *im3 = muse_image_duplicate(im1);
  muse_imagelist *list3 = muse_imagelist_new();
  muse_imagelist_set(list3, im1, 0);
  muse_imagelist_set(list3, im2, 1);
  muse_imagelist_set(list3, im3, 2);
  muse_image *sig5 = muse_combine_sigclip_create(list3, 2.0, 1.5);
  cpl_test_nonnull(sig5);
  cpl_test(fabs(cpl_image_get(sig5->data, 1, 1, &err) - 1) < FLT_EPSILON);
  muse_image_delete(sig5);
  muse_imagelist_delete(list3);

  return cpl_test_end(0);
}
