/* -*- 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 not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#define _DEFAULT_SOURCE
#define _BSD_SOURCE /* get setenv() from stdlib.h */
#include <stdlib.h> /* setenv() */
#include <string.h> /* strcmp() */

#include <muse.h>

/*----------------------------------------------------------------------------*/
/**
  @brief    Test program to check that all the functions in the muse_cplwrapper
            module work correctly.

  This program explicitely tests
    muse_cplimage_slope_window
    muse_cplmask_adapt_to_image
    muse_cplvector_get_adev_const
    muse_cplvector_get_median_dev
    muse_cplarray_erase_outliers
    muse_cplarray_histogram
    muse_cplarray_new_from_delimited_string
    muse_cplarray_string_to_double
    muse_cplparameterlist_from_propertylist
    muse_cplparameterlist_duplicate
    muse_cplpropertylist_update_long_long
    muse_cplerrorstate_dump_some
 */
/*----------------------------------------------------------------------------*/
int main(int argc, char **argv)
{
  UNUSED_ARGUMENTS(argc, argv);
  cpl_test_init(PACKAGE_BUGREPORT, CPL_MSG_DEBUG);

  /* test muse_cplimage_slope_window() */
  cpl_size w[] = { 1, 3, 1, 2 };
  /* construct minimal image with slope 1/2 in x and 1 in y */
  cpl_image *image = cpl_image_new(3, 2, CPL_TYPE_FLOAT);
  cpl_image_set(image, 1, 1, 1.0);
  cpl_image_set(image, 2, 1, 1.5);
  cpl_image_set(image, 3, 1, 2.0);
  cpl_image_set(image, 1, 2, 2.0);
  cpl_image_set(image, 2, 2, 2.5);
  cpl_image_set(image, 3, 2, 3.0);
  cpl_errorstate state = cpl_errorstate_get();
  cpl_vector *vslopes = muse_cplimage_slope_window(image, w);
  cpl_test(cpl_errorstate_is_equal(state));
  cpl_test_nonnull(vslopes);
  cpl_test(cpl_vector_get_size(vslopes) == 2);
  cpl_test_abs(cpl_vector_get(vslopes, 0), 0.5, FLT_EPSILON);
  cpl_test_abs(cpl_vector_get(vslopes, 1), 1.0, FLT_EPSILON);
  cpl_vector_delete(vslopes);
  /* failures */
  state = cpl_errorstate_get();
  cpl_test_null(muse_cplimage_slope_window(NULL, w));
  cpl_test(!cpl_errorstate_is_equal(state) &&
           cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(state);
  cpl_test_null(muse_cplimage_slope_window(image, NULL));
  cpl_test(!cpl_errorstate_is_equal(state) &&
           cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(state);
  /* test failure because of illegal window */
  w[3] = 3;
  cpl_test_null(muse_cplimage_slope_window(image, w));
  cpl_test(!cpl_errorstate_is_equal(state) &&
           cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
  cpl_errorstate_set(state);
  cpl_image_delete(image);
  /* test failure because of failing polynomial fit */
  image = cpl_image_new(1, 1, CPL_TYPE_FLOAT);
  w[1] = 1;
  w[3] = 1;
  cpl_test_null(muse_cplimage_slope_window(image, w));
  cpl_test(!cpl_errorstate_is_equal(state) &&
           cpl_error_get_code() == CPL_ERROR_DATA_NOT_FOUND);
  cpl_errorstate_set(state);
  cpl_image_delete(image);

  /* test muse_cplmask_adapt_to_image() */
  image = cpl_image_new(315, 305, CPL_TYPE_FLOAT);
  /* mask with bottom-left corner masked */
  cpl_mask *mask = cpl_mask_new(300, 300);
  cpl_mask_set(mask, 1, 1, CPL_BINARY_1);
  cpl_mask *outmask = muse_cplmask_adapt_to_image(mask, image);
  cpl_mask_delete(mask);
  cpl_test_nonnull(outmask);
  cpl_test(cpl_mask_get(outmask, 1, 1) == CPL_BINARY_1);
  cpl_test(cpl_mask_count(outmask) == 1);
  cpl_test(cpl_mask_get_size_x(outmask) == 315);
  cpl_test(cpl_mask_get_size_y(outmask) == 305);
#if 0
  cpl_mask_save(outmask, "mask_out1.fits", NULL, CPL_IO_CREATE);
#endif
  cpl_mask_delete(outmask);
  /* mask with bottom-right corner masked */
  mask = cpl_mask_new(300, 300);
  cpl_mask_set(mask, 300, 1, CPL_BINARY_1);
  outmask = muse_cplmask_adapt_to_image(mask, image);
  cpl_mask_delete(mask);
  cpl_test_nonnull(outmask);
  cpl_test(cpl_mask_get(outmask, 315, 1) == CPL_BINARY_1);
  cpl_test(cpl_mask_count(outmask) == 1);
  cpl_test(cpl_mask_get_size_x(outmask) == 315);
  cpl_test(cpl_mask_get_size_y(outmask) == 305);
#if 0
  cpl_mask_save(outmask, "mask_out2.fits", NULL, CPL_IO_CREATE);
#endif
  cpl_mask_delete(outmask);
  /* mask with pixel _near_ bottom-right corner masked */
  mask = cpl_mask_new(300, 300);
  cpl_mask_set(mask, 299, 2, CPL_BINARY_1);
  outmask = muse_cplmask_adapt_to_image(mask, image);
  cpl_mask_delete(mask);
  cpl_test_nonnull(outmask);
  cpl_test(cpl_mask_get(outmask, 314, 2) == CPL_BINARY_1);
  cpl_test(cpl_mask_count(outmask) == 1);
  cpl_test(cpl_mask_get_size_x(outmask) == 315);
  cpl_test(cpl_mask_get_size_y(outmask) == 305);
#if 0
  cpl_mask_save(outmask, "mask_out2b.fits", NULL, CPL_IO_CREATE);
#endif
  cpl_mask_delete(outmask);
  /* mask with top-right corner masked */
  mask = cpl_mask_new(300, 300);
  cpl_mask_set(mask, 300, 300, CPL_BINARY_1);
  outmask = muse_cplmask_adapt_to_image(mask, image);
  cpl_mask_delete(mask);
  cpl_test_nonnull(outmask);
  cpl_test(cpl_mask_get(outmask, 315, 305) == CPL_BINARY_1);
  cpl_test(cpl_mask_count(outmask) == 1);
  cpl_test(cpl_mask_get_size_x(outmask) == 315);
  cpl_test(cpl_mask_get_size_y(outmask) == 305);
#if 0
  cpl_mask_save(outmask, "mask_out3.fits", NULL, CPL_IO_CREATE);
#endif
  cpl_mask_delete(outmask);
  /* mask with top-left corner masked */
  mask = cpl_mask_new(300, 300);
  cpl_mask_set(mask, 1, 300, CPL_BINARY_1);
  outmask = muse_cplmask_adapt_to_image(mask, image);
  cpl_test_nonnull(outmask);
  cpl_test(cpl_mask_get(outmask, 1, 305) == CPL_BINARY_1);
  cpl_test(cpl_mask_count(outmask) == 1);
  cpl_test(cpl_mask_get_size_x(outmask) == 315);
  cpl_test(cpl_mask_get_size_y(outmask) == 305);
#if 0
  cpl_mask_save(outmask, "mask_out4.fits", NULL, CPL_IO_CREATE);
#endif
  cpl_mask_delete(outmask);
  /* failure cases */
  state = cpl_errorstate_get();
  cpl_test_null(muse_cplmask_adapt_to_image(NULL, image));
  cpl_test(cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(state);
  state = cpl_errorstate_get();
  cpl_test_null(muse_cplmask_adapt_to_image(mask, NULL));
  cpl_test(cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(state);
  cpl_mask_delete(mask);
  mask = cpl_mask_new(1000, 1000);
  state = cpl_errorstate_get();
  /* mask without masked pixels */
  cpl_test_null(muse_cplmask_adapt_to_image(mask, image));
  cpl_test(cpl_error_get_code() == CPL_ERROR_DATA_NOT_FOUND);
  cpl_errorstate_set(state);
  /* too large mask */
  cpl_mask_set(mask, 999, 999, CPL_BINARY_1);
  cpl_test_null(muse_cplmask_adapt_to_image(mask, image));
  cpl_test(cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
  cpl_errorstate_set(state);
  cpl_mask_delete(mask);
  cpl_image_delete(image);

  /* test muse_cplvector_get_adev_const() */
  double vdata1[] = { 1, 2, 3, 4, 5 }, /* median = 3., dev = 1.2 */
         vdata2[] = { -8, -2, 11, 1, 10, 4 }; /* median = 2.5, dev = 34./6. ~ 5.667 */
  cpl_vector *vd1 = cpl_vector_wrap(sizeof(vdata1) / sizeof(double), vdata1),
             *vd2 = cpl_vector_wrap(sizeof(vdata2) / sizeof(double), vdata2);
  state = cpl_errorstate_get();
  double adev = muse_cplvector_get_adev_const(vd1, cpl_vector_get_median_const(vd1));
  cpl_test_abs(adev, 1.2, DBL_EPSILON);
  adev = muse_cplvector_get_adev_const(vd2, cpl_vector_get_median_const(vd2));
  cpl_test_abs(adev, 34./6., DBL_EPSILON);
  /* try with wrong given median values */
  adev = muse_cplvector_get_adev_const(vd2, 6.);
  cpl_test_abs(adev, 38./6., DBL_EPSILON);
  adev = muse_cplvector_get_adev_const(vd2, 0.);
  cpl_test_abs(adev, 6., DBL_EPSILON);
  cpl_test(cpl_errorstate_is_equal(state));
  /* failure */
  state = cpl_errorstate_get();
  adev = muse_cplvector_get_adev_const(NULL, 0.);
  cpl_test(!cpl_errorstate_is_equal(state) && cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(state);
  cpl_test_abs(adev, 0., DBL_EPSILON);

  /* muse_cplvector_get_median_dev() */
  double median;
  state = cpl_errorstate_get();
  double mdev = muse_cplvector_get_median_dev(vd1, &median);
  cpl_test_abs(mdev, 1.2, DBL_EPSILON);
  cpl_test_abs(median, 3., DBL_EPSILON);
  mdev = muse_cplvector_get_median_dev(vd2, &median);
  cpl_test_abs(mdev, 34./6., DBL_EPSILON);
  cpl_test_abs(median, 2.5, DBL_EPSILON);
  mdev = muse_cplvector_get_median_dev(vd2, NULL); /* without median pointer... */
  cpl_test_abs(mdev, 34./6., DBL_EPSILON);         /* ... get the same result   */
  cpl_test(cpl_errorstate_is_equal(state));
  cpl_vector_unwrap(vd1);
  cpl_vector_unwrap(vd2);
  /* failure */
  state = cpl_errorstate_get();
  mdev = muse_cplvector_get_median_dev(NULL, NULL);
  cpl_test(!cpl_errorstate_is_equal(state) && cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(state);
  cpl_test_abs(mdev, 0., DBL_EPSILON);

  /* test muse_cplarray_erase_outliers() */
  double radata[] = { -2.41256e-01, -1.28041e-01, -1.33916e-01, -1.87903e-01, -7.42621e-02,
                       4.51980e-03, -1.84075e-01, -2.01826e-01, -9.56855e-02,  6.17117e-02,
                      -1.34188e-01, -2.23923e-01, -2.34347e-01, -2.08916e-01, -2.22872e-01,
                      -2.32847e-01, -6.22621e-02,  3.91740e-01, -4.11138e-02, -2.80019e-01,
                      -2.66560e-01, -2.22407e-01, -2.64309e-01, -2.33237e-01, -1.49058e-01,
                      -1.79277e-01, -2.23632e-01, -2.05031e-01, -1.76368e-01, -1.94881e-01,
                      -1.12916e+00, -9.19497e-01, -1.85502e-01, -2.44129e-01, -2.57406e-01,
                      -2.73002e-01, -2.79470e-01, -2.77538e-01, -2.92829e-01, -3.37820e-01,
                      -2.54105e-01, -2.38019e-01, -2.44714e-01, -2.90512e-01, -1.79494e-01,
                      -1.45528e-01, -1.98066e-01, -3.69797e-01, -3.32269e-01, -3.28991e-01,
                      -3.47502e-01, -2.30199e-01, -1.54252e-01, -1.96010e-01, -2.17825e-01,
                      -3.07621e-01, -1.64216e-01, -1.57507e-01, -1.07888e-01, -3.94059e-01,
                      -3.57458e-01, -2.90314e-01, -3.04480e-01, -2.30525e-01, -1.41682e-01,
                      -1.47027e-01, -2.86045e-01, -2.74269e-01, -1.33114e-01, -1.03509e-01,
                       5.55956e-02, -3.82503e-01, -3.14103e-01, -2.51042e-01, -2.29674e-01,
                      -2.16089e-01, -1.27406e-01,  1.09470e-01, -1.57101e-01, -2.53918e-01,
                      -1.39695e-01, -1.24918e-01, -1.36750e-01, -1.41054e-01, -1.80588e-01,
                      -3.70105e-01, -3.55442e-01, -3.04347e-01, -2.28093e-01, -2.21615e-01,
                      -2.84223e-01, -3.18564e-01, -2.19712e-01,  8.22053e-01, -1.57245e-01,
                      -1.54080e-01, -2.32280e-01, -2.36499e-01, -1.01396e-01, -1.02498e-01,
                      -9.04895e-02, -3.65203e-01, -2.81410e-01, -2.04030e-01, -2.29156e-01,
                      -2.02218e-01, -6.77268e-02, -1.24251e-01, -1.21786e-01 };
  cpl_array *outliers1 = cpl_array_wrap_double(radata, sizeof(radata) / sizeof(double)),
            *outliers2 = cpl_array_duplicate(outliers1);
  /* failures */
  state = cpl_errorstate_get();
  cpl_size nrej = muse_cplarray_erase_outliers(NULL, NULL, 1, 0.);
  cpl_test(!cpl_errorstate_is_equal(state) && cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(state);
  cpl_test(nrej < 0);
  nrej = muse_cplarray_erase_outliers(outliers2, NULL, 1, 0.);
  cpl_test(!cpl_errorstate_is_equal(state) && cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(state);
  cpl_test(nrej < 0);
  /* create a histogram for the data that we want to handle */
  cpl_bivector *rhist = muse_cplarray_histogram(outliers2, 0.1, -1, 1.);
  /* successes */
  state = cpl_errorstate_get();
  nrej = muse_cplarray_erase_outliers(outliers2, rhist, 1, 0.);
  cpl_test(cpl_errorstate_is_equal(state));
  cpl_msg_debug(__func__, "nrej(gap = 2) = %d", (int)nrej);
  cpl_test_eq(nrej, 4); /* with a gap of 2 it should find 4 outliers   *
                         * (including one outside the histogram range) */
  cpl_array_delete(outliers2);
  outliers2 = cpl_array_duplicate(outliers1);
  state = cpl_errorstate_get();
  nrej = muse_cplarray_erase_outliers(outliers2, rhist, 3, 0.5);
  cpl_test(cpl_errorstate_is_equal(state));
  cpl_msg_debug(__func__, "nrej(gap = 3) = %d", (int)nrej);
  cpl_test_eq(nrej, 3); /* with a gap of 3 it should find 3 outliers */
  cpl_array_delete(outliers2);
  outliers2 = cpl_array_duplicate(outliers1);
  state = cpl_errorstate_get();
  nrej = muse_cplarray_erase_outliers(outliers2, rhist, 4, 0.6);
  cpl_test(cpl_errorstate_is_equal(state));
  cpl_msg_debug(__func__, "nrej(gap = 4) = %d", (int)nrej);
  cpl_test_eq(nrej, 2); /* with a gap of 4 it should find 2 outliers */
  cpl_array_delete(outliers2);
  cpl_array_unwrap(outliers1);
  cpl_bivector_delete(rhist);

  /* test muse_cplarray_histogram() */
  double adata[] = { 0.50, 0.51, 0.54, 0.58, 0.60,
                     0.77, 0.95, 0.05, 0.20 };
  cpl_array *array = cpl_array_wrap_double(adata, sizeof(adata) / sizeof(double));
  /* failures */
  state = cpl_errorstate_get();
  cpl_bivector *hist = muse_cplarray_histogram(NULL, 0.1, NAN, NAN);
  cpl_test(!cpl_errorstate_is_equal(state) && cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(state);
  cpl_test_null(hist);
  cpl_array *stringarray = cpl_array_new(10, CPL_TYPE_STRING);
  hist = muse_cplarray_histogram(stringarray, 0.1, 0.0, 1.0);
  cpl_test(!cpl_errorstate_is_equal(state) && cpl_error_get_code() == CPL_ERROR_INVALID_TYPE);
  cpl_errorstate_set(state);
  cpl_test_null(hist);
  cpl_array_delete(stringarray);
  cpl_array *badarray = cpl_array_new(10, CPL_TYPE_FLOAT);
  hist = muse_cplarray_histogram(badarray, 0.1, 1.0, 0.9);
  cpl_test(!cpl_errorstate_is_equal(state) && cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
  cpl_errorstate_set(state);
  cpl_test_null(hist);
  cpl_array_delete(badarray);
  /* successes */
  /* output histograms to check against */
  double thist1[][2] = { { 0.05, 1 }, /* this one for "natural" sampling */
                         { 0.15, 0 },
                         { 0.25, 1 },
                         { 0.35, 0 },
                         { 0.45, 0 },
                         { 0.55, 5 },
                         { 0.65, 0 },
                         { 0.75, 1 },
                         { 0.85, 0 },
                         { 0.95, 1 } },
         thist2[][2] = { { 0.0, 0 }, /* this one for fixed 0.0 to 1.0 sampling */
                         { 0.1, 1 },
                         { 0.2, 1 },
                         { 0.3, 0 },
                         { 0.4, 0 },
                         { 0.5, 3 },
                         { 0.6, 2 },
                         { 0.7, 0 },
                         { 0.8, 1 },
                         { 0.9, 1 },
                         { 1.0, 0 } },
         thist3[][2] = { { 0.40, 2 }, /* for fixed 0.4..1.0 sampling with 0.25 bins */
                         { 0.65, 4 },
                         { 0.90, 1 } };
  state = cpl_errorstate_get();
  cpl_bivector *hist1 = muse_cplarray_histogram(array, 0.1, NAN, NAN),
               *hist2 = muse_cplarray_histogram(array, 0.1, 0.0, 1.0),
               *hist3 = muse_cplarray_histogram(array, 0.25, 0.4, 1.0);
  cpl_test(cpl_errorstate_is_equal(state));
  cpl_array_unwrap(array);
  const cpl_vector *hist1x = cpl_bivector_get_x_const(hist1),
                   *hist1y = cpl_bivector_get_y_const(hist1),
                   *hist2x = cpl_bivector_get_x_const(hist2),
                   *hist2y = cpl_bivector_get_y_const(hist2),
                   *hist3x = cpl_bivector_get_x_const(hist3),
                   *hist3y = cpl_bivector_get_y_const(hist3);
  int i, n = cpl_bivector_get_size(hist1);
  for (i = 0; i < n; i++) {
    cpl_test_abs(cpl_vector_get(hist1x, i), thist1[i][0], DBL_EPSILON);
    cpl_test_eq(cpl_vector_get(hist1y, i), thist1[i][1]);
  } /* for i */
  n = cpl_bivector_get_size(hist2);
  for (i = 0; i < n; i++) {
    cpl_test_abs(cpl_vector_get(hist2x, i), thist2[i][0], DBL_EPSILON);
    cpl_test_eq(cpl_vector_get(hist2y, i), thist2[i][1]);
  } /* for i */
  n = cpl_bivector_get_size(hist3);
  for (i = 0; i < n; i++) {
    cpl_test_abs(cpl_vector_get(hist3x, i), thist3[i][0], DBL_EPSILON);
    cpl_test_eq(cpl_vector_get(hist3y, i), thist3[i][1]);
  } /* for i */
  cpl_bivector_delete(hist1);
  cpl_bivector_delete(hist2);
  cpl_bivector_delete(hist3);

  /* test muse_cplarray_new_from_delimited_string() */
  /* failures */
  state = cpl_errorstate_get();
  cpl_test_null(muse_cplarray_new_from_delimited_string(NULL, ","));
  cpl_test(cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(state);
  cpl_test_null(muse_cplarray_new_from_delimited_string("bla,bla", NULL));
  cpl_test(cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(state);
  /* successes */
  cpl_array *a = muse_cplarray_new_from_delimited_string("bla1,bla2", ",");
  cpl_test_nonnull(a);
  cpl_test(cpl_errorstate_is_equal(state));
  cpl_test(cpl_array_get_size(a) == 2);
  cpl_test(!strcmp(cpl_array_get_string(a, 0), "bla1"));
  cpl_test(!strcmp(cpl_array_get_string(a, 1), "bla2"));
  cpl_array_delete(a);
  a = muse_cplarray_new_from_delimited_string("bla1blabla2,3", "bla");
  cpl_test_nonnull(a);
  cpl_test(cpl_errorstate_is_equal(state));
  cpl_test(cpl_array_get_size(a) == 2);
  cpl_test(!strcmp(cpl_array_get_string(a, 0), "1"));
  cpl_test(!strcmp(cpl_array_get_string(a, 1), "2,3"));
  cpl_array_delete(a);

  /* test muse_cplarray_string_to_double() */
  /* failures */
  state = cpl_errorstate_get();
  cpl_test_null(muse_cplarray_string_to_double(NULL));
  cpl_test(cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(state);
  a = cpl_array_new(4, CPL_TYPE_DOUBLE_COMPLEX);
  cpl_test_null(muse_cplarray_string_to_double(a));
  cpl_test(cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
  cpl_errorstate_set(state);
  cpl_array_delete(a);
  /* successes */
  a = cpl_array_new(5, CPL_TYPE_STRING);
  cpl_array_set_string(a, 0, "1");
  cpl_array_set_string(a, 1, "2.775");
  cpl_array_set_string(a, 2, "123bla");
  cpl_array_set_string(a, 3, "-12.");
  cpl_array_set_string(a, 4, NULL);
  state = cpl_errorstate_get();
  cpl_array *adouble = muse_cplarray_string_to_double(a);
  cpl_test_nonnull(adouble);
  cpl_test(cpl_errorstate_is_equal(state));
  cpl_test_eq(cpl_array_get_type(adouble), CPL_TYPE_DOUBLE);
  cpl_test_eq(cpl_array_get_size(adouble), cpl_array_get_size(a));
  cpl_test_rel(cpl_array_get_double(adouble, 0, NULL), 1, DBL_EPSILON);
  cpl_test_rel(cpl_array_get_double(adouble, 1, NULL), 2.775, DBL_EPSILON);
  cpl_test_rel(cpl_array_get_double(adouble, 2, NULL), 123., DBL_EPSILON);
  cpl_test_rel(cpl_array_get_double(adouble, 3, NULL), -12., DBL_EPSILON);
  int err;
  cpl_array_get_double(adouble, 4, &err);
  cpl_test_eq(err, 1); /* invalid element */
  cpl_array_delete(a);
  cpl_array_delete(adouble);

  /* test muse_cplparameterlist_from_propertylist() */
  cpl_propertylist *phead = cpl_propertylist_new();
  /* use info from a MASTER_FLAT file */
  cpl_propertylist_append_string(phead, "ESO PRO REC1 CAL1 CATG", "MASTER_BIAS"); // Category of calibration frame
  cpl_propertylist_append_string(phead, "ESO PRO REC1 CAL1 DATAMD5", "f9f413d219ec25a55b981e1561223659"); // MD5 si
  cpl_propertylist_append_string(phead, "ESO PRO REC1 CAL1 NAME", "MASTER_BIAS_merged.fits"); // File name of calib
  cpl_propertylist_append_string(phead, "ESO PRO REC1 DRS ID", "cpl-6.4.1"); // Data Reduction System identifier
  cpl_propertylist_append_string(phead, "ESO PRO REC1 ID", "muse_flat"); // Pipeline recipe (unique) identifier
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM1 NAME", "nifu"); // IFU to handle. If set to 0, all
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM1 VALUE", "-1"); // Default: 0
  cpl_propertylist_set_comment(phead, "ESO PRO REC1 PARAM1 VALUE", "Default: 0");
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM10 NAME", "lsigma"); // Low sigma for pixel rejection
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM10 VALUE", "3"); // Default: 3
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM11 NAME", "hsigma"); // High sigma for pixel rejection
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM11 VALUE", "3"); // Default: 3
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM12 NAME", "scale"); // Scale the individual images to
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM12 VALUE", "false"); // Default: true
  cpl_propertylist_set_comment(phead, "ESO PRO REC1 PARAM12 VALUE", "Default: true");
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM13 NAME", "normalize"); // Normalize the master flat to
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM13 VALUE", "true"); // Default: true
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM14 NAME", "trace"); // Trace the position of the slic
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM14 VALUE", "true"); // Default: true
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM15 NAME", "nsum"); // Number of lines over which to
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM15 VALUE", "16"); // Default: 32
  cpl_propertylist_set_comment(phead, "ESO PRO REC1 PARAM15 VALUE", "Default: 32");
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM16 NAME", "order"); // Order of polynomial fit to the
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM16 VALUE", "5"); // Default: 5
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM17 NAME", "edgefrac"); // Fractional change required to
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM17 VALUE", "0.5"); // Default: 0.5
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM18 NAME", "losigmabadpix"); // Low sigma to find dark pi
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM18 VALUE", "5"); // Default: 5
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM19 NAME", "hisigmabadpix"); // High sigma to find bright
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM19 VALUE", "5"); // Default: 5
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM2 NAME", "overscan"); // If this is "none", stop when de
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM2 VALUE", "vpoly"); // Default: "vpoly"
  cpl_propertylist_set_comment(phead, "ESO PRO REC1 PARAM2 VALUE", "Default: bla");
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM20 NAME", "samples"); // Create a table containing all
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM20 VALUE", "true"); // Default: false
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM3 NAME", "ovscreject"); // This influences how values ar
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM3 VALUE", "dcr"); // Default: "dcr"
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM4 NAME", "ovscsigma"); // If the deviation of mean overs
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM4 VALUE", "3"); // Default: 3
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM5 NAME", "ovscignore"); // The number of pixels of the o
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM5 VALUE", "3"); // Default: 3
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM6 NAME", "combine"); // Type of combination to use
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM6 VALUE", "sigclip"); // Default: "sigclip"
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM7 NAME", "nlow"); // Number of minimum pixels to rej
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM7 VALUE", "1"); // Default: 1
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM8 NAME", "nhigh"); // Number of maximum pixels to rej
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM8 VALUE", "1"); // Default: 1
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM9 NAME", "nkeep"); // Number of pixels to keep with m
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM9 VALUE", "1"); // Default: 1
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PIPE ID", "muse/0.19.2"); // Pipeline (unique) identifier
  cpl_propertylist_append_string(phead, "ESO PRO REC1 RAW1 CATG", "FLAT"); // Category of raw frame
  cpl_propertylist_append_string(phead, "ESO PRO REC1 RAW1 NAME", "MUSE.2014-09-12T11:03:30.700.fits.fz"); // File
  cpl_propertylist_append_string(phead, "ESO PRO REC1 RAW2 CATG", "FLAT"); // Category of raw frame
  cpl_propertylist_append_string(phead, "ESO PRO REC1 RAW2 NAME", "MUSE.2014-09-12T11:04:32.819.fits.fz"); // File
  cpl_propertylist_append_string(phead, "ESO PRO REC1 RAW3 CATG", "FLAT"); // Category of raw frame
  cpl_propertylist_append_string(phead, "ESO PRO REC1 RAW3 NAME", "MUSE.2014-09-12T11:05:35.523.fits.fz"); // File
  /* make up a few more special cases */
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM21 NAME", "commasep");
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM21 VALUE", "3.5,2");
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM22 NAME", "doubleexp");
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM22 VALUE", "3E+05");
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM23 NAME", "falseboolean");
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM23 VALUE", "false");
  /* not sure, if this can happen... */
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM24 NAME", "intwithplus");
  cpl_propertylist_append_string(phead, "ESO PRO REC1 PARAM24 VALUE", "+2");
  state = cpl_errorstate_get();
  cpl_parameterlist *parlist = muse_cplparameterlist_from_propertylist(phead, 1);
  cpl_test(cpl_errorstate_is_equal(state));
  cpl_test(cpl_parameterlist_get_size(parlist) == 24);
  /* test a few parameters, at least one of each type */
  state = cpl_errorstate_get();
  cpl_parameter *par = cpl_parameterlist_find(parlist, "muse.muse_flat.nifu");
  cpl_test_nonnull(par);
  cpl_test(cpl_parameter_get_int(par) == -1);
  cpl_test(cpl_parameter_get_default_int(par) == 0); /* was set in the header */
  par = cpl_parameterlist_find(parlist, "muse.muse_flat.order");
  cpl_test_nonnull(par);
  cpl_test(cpl_parameter_get_int(par) == 5);
  cpl_test(cpl_parameter_get_default_int(par) == 0); /* no default was set in the header */
  par = cpl_parameterlist_find(parlist, "muse.muse_flat.trace");
  cpl_test_nonnull(par);
  cpl_test(cpl_parameter_get_bool(par) == CPL_TRUE);
  par = cpl_parameterlist_find(parlist, "muse.muse_flat.falseboolean");
  cpl_test_nonnull(par);
  cpl_test(cpl_parameter_get_bool(par) == CPL_FALSE);
  par = cpl_parameterlist_find(parlist, "muse.muse_flat.edgefrac");
  cpl_test_nonnull(par);
  cpl_test(cpl_parameter_get_double(par) == 0.5);
  par = cpl_parameterlist_find(parlist, "muse.muse_flat.doubleexp");
  cpl_test_nonnull(par);
  cpl_test_abs(cpl_parameter_get_double(par), 3e5, DBL_EPSILON);
  par = cpl_parameterlist_find(parlist, "muse.muse_flat.overscan");
  cpl_test_nonnull(par);
  char *string = (char *)cpl_parameter_get_string(par);
  cpl_test_nonnull(string);
  if (string) {
    cpl_test_zero(strncmp(string, "vpoly", 6));
  }
  string = (char *)cpl_parameter_get_default_string(par);
  cpl_test_nonnull(string); /* we set this in the header */
  if (string) {
    cpl_test_zero(strncmp(string, "bla", 4));
  }
  par = cpl_parameterlist_find(parlist, "muse.muse_flat.commasep");
  cpl_test_nonnull(par);
  string = (char *)cpl_parameter_get_string(par);
  cpl_test_nonnull(string);
  if (string) {
    cpl_test_zero(strncmp(string, "3.5,2", 6));
  }
  cpl_test(cpl_errorstate_is_equal(state)); /* no errors (like wrong type */

  /* test muse_cplparameterlist_duplicate() before removing this one */
  state = cpl_errorstate_get();
  cpl_parameterlist *parlist2 = muse_cplparameterlist_duplicate(parlist);
#if 0
  printf("________________ original parameters ___________________\n");
  cpl_parameterlist_dump(parlist, stdout);
  fflush(stdout);
  printf("________________ duplicated parameters ___________________\n");
  cpl_parameterlist_dump(parlist2, stdout);
  fflush(stdout);
#endif
  cpl_test(cpl_errorstate_is_equal(state));
  cpl_test_eq(cpl_parameterlist_get_size(parlist), /* compare sizes */
              cpl_parameterlist_get_size(parlist2));
  /* compare first parameter */
  const cpl_parameter *p = cpl_parameterlist_get_first_const(parlist),
                      *p2 = cpl_parameterlist_get_first_const(parlist2);
  cpl_test(p != p2);
  cpl_test(!strcmp(cpl_parameter_get_name(p), cpl_parameter_get_name(p2)));
  cpl_test(!strcmp(cpl_parameter_get_context(p), cpl_parameter_get_context(p2)));
  cpl_test_eq(cpl_parameter_get_class(p), cpl_parameter_get_class(p2));
  cpl_test_eq(cpl_parameter_get_type(p), cpl_parameter_get_type(p2));
  /* test (integer) value with this first parameter */
  cpl_test_eq(cpl_parameter_get_int(p), cpl_parameter_get_int(p2));
  /* compare last parameter */
  p = cpl_parameterlist_get_last_const(parlist);
  p2 = cpl_parameterlist_get_last_const(parlist2);
  cpl_test(p != p2);
  cpl_test(!strcmp(cpl_parameter_get_name(p), cpl_parameter_get_name(p2)));
  cpl_test(!strcmp(cpl_parameter_get_context(p), cpl_parameter_get_context(p2)));
  cpl_test_eq(cpl_parameter_get_class(p), cpl_parameter_get_class(p2));
  cpl_test_eq(cpl_parameter_get_type(p), cpl_parameter_get_type(p2));
  cpl_parameterlist_delete(parlist2);
  cpl_parameterlist_delete(parlist);
  /* test failure of muse_cplparameterlist_duplicate() */
  state = cpl_errorstate_get();
  parlist2 = muse_cplparameterlist_duplicate(NULL);
  cpl_test(!cpl_errorstate_is_equal(state) &&
           cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(state);
  cpl_test_null(parlist2);

  /* failure cases of muse_cplparameterlist_from_propertylist() */
  state = cpl_errorstate_get();
  parlist = muse_cplparameterlist_from_propertylist(NULL, 1); /* NULL header */
  cpl_test(!cpl_errorstate_is_equal(state) &&
           cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(state);
  cpl_test_null(parlist);
  parlist = muse_cplparameterlist_from_propertylist(phead, 0); /* wrong RECi */
  cpl_test(!cpl_errorstate_is_equal(state) &&
           cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
  cpl_errorstate_set(state);
  cpl_test_null(parlist);
  /* set wrong pipeline ID */
  cpl_propertylist_update_string(phead, "ESO PRO REC1 PIPE ID", "kmo/0.0.0.1");
  state = cpl_errorstate_get();
  parlist = muse_cplparameterlist_from_propertylist(phead, 1);
  cpl_test(!cpl_errorstate_is_equal(state) &&
           cpl_error_get_code() == CPL_ERROR_INCOMPATIBLE_INPUT);
  cpl_errorstate_set(state);
  cpl_test_null(parlist);
  /* remove the top-level ID (the recipe name) */
  cpl_propertylist_erase(phead, "ESO PRO REC1 ID");
  state = cpl_errorstate_get();
  parlist = muse_cplparameterlist_from_propertylist(phead, 1);
  cpl_test(!cpl_errorstate_is_equal(state) &&
           cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
  cpl_errorstate_set(state);
  cpl_test_null(parlist);
  cpl_propertylist_delete(phead);

  /* test muse_cplpropertylist_update_long_long() */
  state = cpl_errorstate_get();
  cpl_propertylist *header = cpl_propertylist_new();
  cpl_test(muse_cplpropertylist_update_long_long(NULL, "BLA BLA", 0)
           == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(state);
  cpl_test(muse_cplpropertylist_update_long_long(header, NULL, 0)
           == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(state);
  cpl_test(muse_cplpropertylist_update_long_long(header, "BLA BLA", 0)
           == CPL_ERROR_DATA_NOT_FOUND);
  cpl_errorstate_set(state);
  /* set some integers */
  cpl_propertylist_append_int(header, "BLAI", 1);
  cpl_propertylist_append_long(header, "BLAL", 1);
  cpl_propertylist_append_long_long(header, "BLALL", 1);
  state = cpl_errorstate_get();
  /* reset the integer values */
  cpl_test(muse_cplpropertylist_update_long_long(header, "BLAI", 10)
           == CPL_ERROR_NONE);
  cpl_test(muse_cplpropertylist_update_long_long(header, "BLAL", 20)
           == CPL_ERROR_NONE);
  cpl_test(muse_cplpropertylist_update_long_long(header, "BLALL", 30)
           == CPL_ERROR_NONE);
  cpl_test(cpl_errorstate_is_equal(state));
  /* read the values back */
  cpl_test(cpl_propertylist_get_int(header, "BLAI") == 10);
  cpl_test(cpl_propertylist_get_long(header, "BLAL") == 20);
  cpl_test(cpl_propertylist_get_long_long(header, "BLALL") == 30);
  cpl_propertylist_delete(header);

  /* test muse_cplframeset_erase_all() */
  state = cpl_errorstate_get();
  cpl_error_code rc = muse_cplframeset_erase_all(NULL);
  cpl_test(!cpl_errorstate_is_equal(state) && rc == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(state);
  /* create some frame set */
  cpl_frameset *fset = cpl_frameset_new();
  cpl_frame *f = cpl_frame_new();
  cpl_frame_set_tag(f, "SOME_TAG");
  cpl_frameset_insert(fset, cpl_frame_duplicate(f));
  cpl_frame_set_filename(f, "/some/file");
  cpl_frameset_insert(fset, cpl_frame_duplicate(f));
  cpl_frame_set_filename(f, "/some/other/file");
  cpl_frame_set_group(f, CPL_FRAME_GROUP_RAW);
  cpl_frameset_insert(fset, cpl_frame_duplicate(f));
  cpl_frame_set_group(f, CPL_FRAME_GROUP_PRODUCT);
  cpl_frameset_insert(fset, f);
  cpl_test(cpl_frameset_get_size(fset) == 4);
  cpl_frameset *fset2 = cpl_frameset_duplicate(fset); /* duplicate for later */
  /* do the test */
  state = cpl_errorstate_get();
  rc = muse_cplframeset_erase_all(fset);
  cpl_test(cpl_errorstate_is_equal(state) && rc == CPL_ERROR_NONE);
  cpl_test(cpl_frameset_get_size(fset) == 0);
  cpl_frameset_delete(fset);

  /* test muse_cplframeset_erase_duplicate() */
  state = cpl_errorstate_get();
  rc = muse_cplframeset_erase_duplicate(NULL);
  cpl_test(!cpl_errorstate_is_equal(state) && rc == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(state);
  /* test with a frame set without duplicates */
  state = cpl_errorstate_get();
  rc = muse_cplframeset_erase_duplicate(fset2);
  cpl_test(cpl_errorstate_is_equal(state) && rc == CPL_ERROR_NONE);
  cpl_test(cpl_frameset_get_size(fset2) == 4);
  /* test with a frame set with duplicates */
  state = cpl_errorstate_get();
  f = cpl_frameset_get_position(fset2, 0);
  cpl_frameset_insert(fset2, cpl_frame_duplicate(f));
  f = cpl_frameset_get_position(fset2, 2);
  cpl_frameset_insert(fset2, cpl_frame_duplicate(f));
  f = cpl_frame_new(); /* also add a really new one */
  cpl_frame_set_tag(f, "SOME_OTHER_TAG");
  cpl_frame_set_level(f, CPL_FRAME_LEVEL_FINAL);
  cpl_frameset_insert(fset2, f);
  rc = muse_cplframeset_erase_duplicate(fset2);
  cpl_test(cpl_errorstate_is_equal(state) && rc == CPL_ERROR_NONE);
  cpl_test(cpl_frameset_get_size(fset2) == 5);
  cpl_frameset_delete(fset2);

  /* test muse_cplerrorstate_dump_some() as well as possible, i.e. *
   * the tester nees to visually check the output of this below    */
  state = cpl_errorstate_get();
  cpl_error_set_message("func1", CPL_ERROR_NULL_INPUT, "error1");
  cpl_error_set_message("func2", CPL_ERROR_ILLEGAL_INPUT, "error2");
  cpl_errorstate state2 = cpl_errorstate_get();
  cpl_msg_info(__func__, "\n\n_____ the following should output both errors: _____");
  cpl_errorstate_dump(state, CPL_FALSE, muse_cplerrorstate_dump_some);
  cpl_msg_info(__func__, "^^^^^ done! ^^^^^\n");
  cpl_test(setenv("MUSE_CPL_ERRORSTATE_NDUMP", "1", 1) == 0);
  cpl_msg_info(__func__, "\n\n_____ the following should output only the second error: _____");
  cpl_errorstate_dump(state, CPL_FALSE, muse_cplerrorstate_dump_some);
  cpl_msg_info(__func__, "^^^^^ done! ^^^^^\n");
  cpl_test(unsetenv("MUSE_CPL_ERRORSTATE_NDUMP")== 0);
  cpl_test(cpl_errorstate_is_equal(state2)); /* no new errors? */
  cpl_errorstate_set(state); /* throw away the two artificial errors */

  return cpl_test_end(0);
}
