/* -*- 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 _BSD_SOURCE /* get setenv() from stdlib.h */
#include <stdlib.h> /* setenv() */
#include <string.h>

#include <muse.h>

#define MUSE_TEST_RESAMPLING_DELETE_FILES 1 /* should be 1 when not debugging */

/* symbolic property names we compare to */
enum {                       TMEAN = 0, TSTD, TMED, TMAD, TFLUX, TCENX, TCENY };
/* comparison values for minimum and maximum values of above properties */
const double kComparisonMin[] = { 2.62, 5.75, 1.50, 1.40,  6550., 10.6, 6.75 },
             kComparisonMax[] = { 4.60, 7.85, 3.58, 3.50, 12000., 11.1, 6.95 },
             /* XXX Lanczos results in pretty deviant values, and    *
              *     especially high stdev in the collapsed images... */
             kComparisonMinLanczos[] = { 2.97, 5.79, 2.09, 1.44,  7450., 10.5, 6.79 },
             kComparisonMaxLanczos[] = { 4.86, 18.7, 3.59, 3.66, 12200., 11.0, 7.05 };

void
cube_resampling_test_image_stats(cpl_image *aImage, const char *aName)
{
  /* same tests as on central plane, collapsed image; use *
   * the full image and a window near the brightest start */
  cpl_stats *stats = cpl_stats_new_from_image(aImage, CPL_STATS_ALL),
            *statw = cpl_stats_new_from_image_window(aImage, CPL_STATS_CENTROID,
                                                     1, 1, 20, 13);
  cpl_msg_debug(__func__, "%s: average: %f +/- %f, median: %f +/- %f, flux: %f, "
                "centroid: %f %f", aName,
                cpl_stats_get_mean(stats), cpl_stats_get_stdev(stats),
                cpl_stats_get_median(stats), cpl_stats_get_median_dev(stats),
                cpl_stats_get_flux(stats),
                cpl_stats_get_centroid_x(statw), cpl_stats_get_centroid_y(statw));
  if (strstr(aName, " 5")) {
    /* special-case Lanczos resampled data because that amplifies extremes */
    cpl_test(cpl_stats_get_mean(stats) > kComparisonMinLanczos[TMEAN] &&
             cpl_stats_get_mean(stats) < kComparisonMaxLanczos[TMEAN]);
    cpl_test(cpl_stats_get_stdev(stats) > kComparisonMinLanczos[TSTD] &&
             cpl_stats_get_stdev(stats) < kComparisonMaxLanczos[TSTD]);
    cpl_test(cpl_stats_get_median(stats) > kComparisonMinLanczos[TMED] &&
             cpl_stats_get_median(stats) < kComparisonMaxLanczos[TMED]);
    cpl_test(cpl_stats_get_median_dev(stats) > kComparisonMinLanczos[TMAD] &&
             cpl_stats_get_median_dev(stats) < kComparisonMaxLanczos[TMAD]);
    cpl_test(cpl_stats_get_flux(stats) > kComparisonMinLanczos[TFLUX] &&
             cpl_stats_get_flux(stats) < kComparisonMaxLanczos[TFLUX]);
    cpl_test(cpl_stats_get_centroid_x(statw) > kComparisonMinLanczos[TCENX] &&
             cpl_stats_get_centroid_x(statw) < kComparisonMaxLanczos[TCENX]);
    cpl_test(cpl_stats_get_centroid_y(statw) > kComparisonMinLanczos[TCENY] &&
             cpl_stats_get_centroid_y(statw) < kComparisonMaxLanczos[TCENY]);
  } else {
    cpl_test(cpl_stats_get_mean(stats) > kComparisonMin[TMEAN] &&
             cpl_stats_get_mean(stats) < kComparisonMax[TMEAN]);
    cpl_test(cpl_stats_get_stdev(stats) > kComparisonMin[TSTD] &&
             cpl_stats_get_stdev(stats) < kComparisonMax[TSTD]);
    cpl_test(cpl_stats_get_median(stats) > kComparisonMin[TMED] &&
             cpl_stats_get_median(stats) < kComparisonMax[TMED]);
    cpl_test(cpl_stats_get_median_dev(stats) > kComparisonMin[TMAD] &&
             cpl_stats_get_median_dev(stats) < kComparisonMax[TMAD]);
    cpl_test(cpl_stats_get_flux(stats) > kComparisonMin[TFLUX] &&
             cpl_stats_get_flux(stats) < kComparisonMax[TFLUX]);
    cpl_test(cpl_stats_get_centroid_x(statw) > kComparisonMin[TCENX] &&
             cpl_stats_get_centroid_x(statw) < kComparisonMax[TCENX]);
    cpl_test(cpl_stats_get_centroid_y(statw) > kComparisonMin[TCENY] &&
             cpl_stats_get_centroid_y(statw) < kComparisonMax[TCENY]);
  }
  cpl_stats_delete(stats);
  cpl_stats_delete(statw);
} /* cube_resampling_test_image_stats() */

/*----------------------------------------------------------------------------*/
/**
  @brief   Test program to check that muse_resampling_cube() and related
           functions works when called with the necessary data for resampling.

  This program explicitely tests
    muse_resampling_params_new
    muse_resampling_params_set_wcs
    muse_resampling_params_delete
    muse_resampling_cube
    muse_resampling_collapse_pixgrid
    muse_datacube_collapse
    muse_datacube_convert_dq
    muse_datacube_save
    muse_datacube_save_recimages
    muse_resampling_euro3d
    muse_euro3dcube_collapse
    muse_euro3dcube_save
    muse_datacube_delete
    muse_euro3dcube_delete
 */
/*----------------------------------------------------------------------------*/
int main(int argc, char **argv)
{
  cpl_test_init(PACKAGE_BUGREPORT, CPL_MSG_DEBUG);

  /* load pixel table, it has spatial units in pixels */
  muse_pixtable *ptpix = NULL;
  if (argc > 1) {
    /* assume that the parameter is the filename of a pixel table */
    ptpix = muse_pixtable_load(argv[1]);
  } else {
    ptpix = muse_pixtable_load(BASEFILENAME"_pixtable.fits");
  }
  if (!ptpix) {
    return cpl_test_end(1);
  }
  cpl_msg_info(__func__, "using pixel table with %"CPL_SIZE_FORMAT" rows",
               muse_pixtable_get_nrow(ptpix));
  /* set WFM in header, so that resampling with WCS works */
  cpl_propertylist_append_string(ptpix->header, "ESO INS MODE", "WFM-NONAO-N");

  /* create built-in white-light filter, luckily it doesn't *
   * need the processing argument to "load" this one        */
  cpl_table *fwhite = muse_table_load_filter(NULL, "white");

  /* duplicate the pixel table, project to sky, to also get resampling with WCS */
  muse_pixtable *ptdeg = muse_pixtable_duplicate(ptpix);
  cpl_propertylist *wcs = muse_wcs_create_default();
  cpl_test(muse_wcs_project_tan(ptdeg, wcs) == CPL_ERROR_NONE);
  cpl_propertylist_delete(wcs);
  cpl_test(muse_wcs_position_celestial(ptdeg, muse_pfits_get_ra(ptdeg->header),
                                       muse_pfits_get_dec(ptdeg->header))
           == CPL_ERROR_NONE);
  muse_pixtable *pts[] = { ptpix, ptdeg, NULL };

  muse_resampling_dispersion_type tl;
  for (tl = MUSE_RESAMPLING_DISP_AWAV; tl <= MUSE_RESAMPLING_DISP_AWAV_LOG; tl++) {
    muse_resampling_type t;
    for (t = MUSE_RESAMPLE_NEAREST; t < MUSE_RESAMPLE_NONE; t++) {
      muse_pixtable *pt;
      unsigned ipt;
      for (ipt = 0, pt = pts[ipt]; pt; pt = pts[++ipt]) {
        const char *unit = cpl_table_get_column_unit(pt->table, MUSE_PIXTABLE_XPOS);

        /* ====================================================================== *
         * First test is for the FITS NAXIS=3 format                              *
         * ====================================================================== */
        cpl_msg_info(__func__, "Resampling pixel table [%s] to cube, method %d",
                     unit, t);
        muse_resampling_params *params = muse_resampling_params_new(t);
        params->tlambda = tl;
        if (t == MUSE_RESAMPLE_WEIGHTED_LANCZOS) {
          params->ld = 3; /* want to test lanczos3 */
        } else if (t == MUSE_RESAMPLE_WEIGHTED_RENKA) { /* want to test how... */
          params->ld = -5; /* the resampling recovers from negative loop distance */
        }
        muse_pixgrid *grid, **gridptr = NULL;
        char *fnw = cpl_sprintf("cube_resampled_%s_l%d_%d_weight.fits", unit, tl, t);
        if (t == MUSE_RESAMPLE_WEIGHTED_DRIZZLE) { /* test weight cube creation... */
          cpl_test(setenv("MUSE_DEBUG_WEIGHT_CUBE", fnw, 1) == 0); /* for drizzle */
        }
        if (tl == MUSE_RESAMPLING_DISP_AWAV && t != MUSE_RESAMPLE_NEAREST) {
          /* test grid creation, used for direct collapsing below */
          gridptr = &grid;
        }
        muse_datacube *cube = muse_resampling_cube(pt, params, gridptr);
        if (t == MUSE_RESAMPLE_WEIGHTED_DRIZZLE) {
          /* try to re-load a plane from the weight cube back */
          cpl_image *plane = cpl_image_load(fnw, CPL_TYPE_UNSPECIFIED, 5, 0);
          cpl_test_nonnull(plane);
          double mean = cpl_image_get_mean(plane),
                 min = cpl_image_get_min(plane);
          cpl_msg_debug(__func__, "weight image mean = %f", mean);
          cpl_test(mean > 0.3 && mean < 1.0); /* some rough guess */
          cpl_test(min >= 0.); /* zero or positive weights */
          cpl_image_delete(plane);
          cpl_test(unsetenv("MUSE_DEBUG_WEIGHT_CUBE") == 0);
#if MUSE_TEST_RESAMPLING_DELETE_FILES
          cpl_test(remove(fnw) == 0); /* can delete the file again */
#endif
        }
        cpl_free(fnw);
        /* primitive tests */
        cpl_test_nonnull(cube);
        cpl_test_nonnull(cube->data);
        cpl_test_nonnull(cube->header);
        int nplanes = cpl_imagelist_get_size(cube->data);
        if (tl == MUSE_RESAMPLING_DISP_AWAV) {
          cpl_test(nplanes == 9); /* (6505 - 6494) / 1.25 = 8.8 planes */
        } else {
          cpl_test(nplanes == 14); /* (6505. - 6494.) / 1.25 * 1.6 = 14.08 planes */
        }
        /* if the extra extensions exist, they should have the same size */
        if (cube->dq) {
          cpl_test_eq(cpl_imagelist_get_size(cube->dq), nplanes);
        }
        if (cube->stat) {
          cpl_test_eq(cpl_imagelist_get_size(cube->stat), nplanes);
        }
        /* test direct grid collapsing */
        if (tl == MUSE_RESAMPLING_DISP_AWAV && t != MUSE_RESAMPLE_NEAREST) {
          cpl_test_nonnull(grid);
          cpl_errorstate state = cpl_errorstate_get();
          muse_resampling_params *p2 = muse_resampling_params_new(t);
          p2->tlambda = tl;
          muse_image *collapsed = muse_resampling_collapse_pixgrid(pt, grid, cube,
                                                                   fwhite, p2);
          cpl_test(cpl_errorstate_is_equal(state));
          cpl_test_nonnull(collapsed);
          cpl_test_nonnull(collapsed->data);
          cpl_test_nonnull(collapsed->stat);
          char *name2 = cpl_sprintf("grid-collapsed FOV (%s l%d %d)", unit, tl, t);
          cube_resampling_test_image_stats(collapsed->data, name2);
          cpl_free(name2);
          char *fnc = cpl_sprintf("cube_resampled_%s_l%d_%d_collapsed.fits", unit, tl, t);
          muse_image_save(collapsed, fnc);
#if MUSE_TEST_RESAMPLING_DELETE_FILES
          cpl_test(remove(fnc) == 0); /* delete the file again */
#endif
          cpl_free(fnc);
          muse_image_delete(collapsed);
          if (t == MUSE_RESAMPLE_WEIGHTED_DRIZZLE) {
            /* various failure cases of muse_resampling_collapse_pixgrid() */
            state = cpl_errorstate_get();
            collapsed = muse_resampling_collapse_pixgrid(NULL, grid, cube, fwhite, p2);
            cpl_test_null(collapsed);
            cpl_test(cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
            cpl_errorstate_set(state);
            collapsed = muse_resampling_collapse_pixgrid(pt, NULL, cube, fwhite, p2);
            cpl_test_null(collapsed);
            cpl_test(cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
            cpl_errorstate_set(state);
            collapsed = muse_resampling_collapse_pixgrid(pt, grid, NULL, fwhite, p2);
            cpl_test_null(collapsed);
            cpl_test(cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
            cpl_errorstate_set(state);
            collapsed = muse_resampling_collapse_pixgrid(pt, grid, cube, fwhite, NULL);
            cpl_test_null(collapsed);
            cpl_test(cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
            cpl_errorstate_set(state);
            /* this should succeed (same as white filter) */
            collapsed = muse_resampling_collapse_pixgrid(pt, grid, cube, NULL, p2);
            cpl_test_nonnull(collapsed);
            cpl_test(cpl_errorstate_is_equal(state));
            name2 = cpl_sprintf("grid-collapsed FOV nofilter (%s l%d %d)", unit, tl, t);
            cube_resampling_test_image_stats(collapsed->data, name2);
            cpl_free(name2);
            muse_image_delete(collapsed);
            /* this should fail again */
            p2->method = MUSE_RESAMPLE_NONE + 1;
            collapsed = muse_resampling_collapse_pixgrid(pt, grid, cube, fwhite, p2);
            cpl_test_null(collapsed);
            cpl_test(cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
            cpl_errorstate_set(state);
            p2->method = MUSE_RESAMPLE_NEAREST;
            collapsed = muse_resampling_collapse_pixgrid(pt, grid, cube, fwhite, p2);
            cpl_test_null(collapsed);
            cpl_test(cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
            cpl_errorstate_set(state);
          } /* if drizzle */
          muse_resampling_params_delete(p2);
          muse_pixgrid_delete(grid);
        } /* if AWAV (with output grid) */

        /* statistics on the central plane */
        unsigned int iplane = 5; /* at 6501.25 Angstrom */
        if (tl == MUSE_RESAMPLING_DISP_AWAV_LOG) {
          iplane = 8; /* also at 6501.25 Angstrom */
        }
        cpl_image *image = cpl_imagelist_get(cube->data, iplane);
        char *name = cpl_sprintf("FITS NAXIS=3 plane %d (%s l%d %d)",
                                 iplane + 1, unit, tl, t);
        if (tl == MUSE_RESAMPLING_DISP_AWAV_LOG && t == MUSE_RESAMPLE_NEAREST) {
          /* this plane will have far too many *
           * empty pixels, skip the test       */
        } else {
          cube_resampling_test_image_stats(image, name);
        }
        cpl_free(name);

        /* try to collapse the data using the white-light filter */
        muse_image *fov = muse_datacube_collapse(cube, fwhite);
        cpl_test_nonnull(fov);
        cpl_test_nonnull(fov->data);
        name = cpl_sprintf("FITS NAXIS=3 FOV (%s l%d %d)", unit, tl, t);
        cube_resampling_test_image_stats(fov->data, name);
        cpl_free(name);

        /* try to save the cube, first without collapsed image */
        char *fn = cpl_sprintf("cube_resampled_%s_l%d_%d.fits", unit, tl, t);
        cpl_test(muse_datacube_save(cube, fn) == CPL_ERROR_NONE);
        /* test for EXTNAME headers */
        cpl_propertylist *hdata = cpl_propertylist_load(fn, 1),
                         *hdq = cpl_propertylist_load(fn, 2),
                         *hstat = cpl_propertylist_load(fn, 3);
        cpl_test(!strcmp("DATA", cpl_propertylist_get_string(hdata, "EXTNAME")) &&
                 !strcmp("DQ", cpl_propertylist_get_string(hdq, "EXTNAME")) &&
                 !strcmp("STAT", cpl_propertylist_get_string(hstat, "EXTNAME")));
        /* test for ESO format headers produced by muse_image_save() */
        cpl_test(!strcmp("ESO", cpl_propertylist_get_string(hdata, "HDUCLASS")) &&
                 !strcmp("ESO", cpl_propertylist_get_string(hdq, "HDUCLASS")) &&
                 !strcmp("ESO", cpl_propertylist_get_string(hstat, "HDUCLASS")));
        cpl_test(!strcmp("DICD", cpl_propertylist_get_string(hdata, "HDUDOC")) &&
                 !strcmp("DICD", cpl_propertylist_get_string(hdq, "HDUDOC")) &&
                 !strcmp("DICD", cpl_propertylist_get_string(hstat, "HDUDOC")));
        cpl_test(!strncmp("DICD version ", cpl_propertylist_get_string(hdata, "HDUVERS"), 13) &&
                 !strncmp("DICD version ", cpl_propertylist_get_string(hdq, "HDUVERS"), 13) &&
                 !strncmp("DICD version ", cpl_propertylist_get_string(hstat, "HDUVERS"), 13));
        cpl_test(!strcmp("IMAGE", cpl_propertylist_get_string(hdata, "HDUCLAS1")) &&
                 !strcmp("IMAGE", cpl_propertylist_get_string(hdq, "HDUCLAS1")) &&
                 !strcmp("IMAGE", cpl_propertylist_get_string(hstat, "HDUCLAS1")));
        cpl_test(!strcmp("DATA", cpl_propertylist_get_string(hdata, "HDUCLAS2")) &&
                 !strcmp("QUALITY", cpl_propertylist_get_string(hdq, "HDUCLAS2")) &&
                 !strcmp("ERROR", cpl_propertylist_get_string(hstat, "HDUCLAS2")));
        cpl_test(!cpl_propertylist_has(hdata, "HDUCLAS3") && /* shouldn't exist */
                 !strcmp("FLAG32BIT", cpl_propertylist_get_string(hdq, "HDUCLAS3")) &&
                 !strcmp("MSE", cpl_propertylist_get_string(hstat, "HDUCLAS3")));
        cpl_test(!strcmp("STAT", cpl_propertylist_get_string(hdata, "ERRDATA")) &&
                 !strcmp("DQ", cpl_propertylist_get_string(hdata, "QUALDATA")) &&
                 !cpl_propertylist_has(hdata, "SCIDATA"));
        cpl_test(!strcmp("DATA", cpl_propertylist_get_string(hstat, "SCIDATA")) &&
                 !strcmp("DQ", cpl_propertylist_get_string(hstat, "QUALDATA")) &&
                 !cpl_propertylist_has(hstat, "ERRDATA"));
        cpl_test(!strcmp("DATA", cpl_propertylist_get_string(hdq, "SCIDATA")) &&
                 !strcmp("STAT", cpl_propertylist_get_string(hdq, "ERRDATA")) &&
                 !cpl_propertylist_has(hdq, "QUALDATA"));
        cpl_test(cpl_propertylist_has(hdq, "QUALMASK"));
        cpl_propertylist_delete(hdata);
        cpl_propertylist_delete(hdq);
        cpl_propertylist_delete(hstat);
#if MUSE_TEST_RESAMPLING_DELETE_FILES
        cpl_test(remove(fn) == 0); /* delete the file again */
#endif
        cpl_free(fn);
        /* now add reconstructed image to cube and try to save again */
        cube->recimages = muse_imagelist_new();
        muse_imagelist_set(cube->recimages, fov, 0);
        cube->recnames = cpl_array_new(1, CPL_TYPE_STRING);
        cpl_array_set_string(cube->recnames, 0, "white");
        fn = cpl_sprintf("cube_resampled_%s_l%d_%d_with_recimage.fits", unit, tl, t);
        cpl_test(muse_datacube_save(cube, fn) == CPL_ERROR_NONE);
        /* test the the headers now also worked for the reconstructed image */
        cpl_propertylist *him = cpl_propertylist_load(fn, 4),
                         *himdq = cpl_propertylist_load(fn, 5);
        cpl_test(!strcmp("white", cpl_propertylist_get_string(him, "EXTNAME")) &&
                 !strcmp("white_DQ", cpl_propertylist_get_string(himdq, "EXTNAME")));
        /* test for ESO format headers produced by muse_image_save() */
        cpl_test(!strcmp("ESO", cpl_propertylist_get_string(him, "HDUCLASS")) &&
                 !strcmp("ESO", cpl_propertylist_get_string(himdq, "HDUCLASS")));
        cpl_test(!strcmp("DICD", cpl_propertylist_get_string(him, "HDUDOC")) &&
                 !strcmp("DICD", cpl_propertylist_get_string(himdq, "HDUDOC")));
        cpl_test(!strncmp("DICD version ", cpl_propertylist_get_string(him, "HDUVERS"), 13) &&
                 !strncmp("DICD version ", cpl_propertylist_get_string(himdq, "HDUVERS"), 13));
        cpl_test(!strcmp("IMAGE", cpl_propertylist_get_string(him, "HDUCLAS1")) &&
                 !strcmp("IMAGE", cpl_propertylist_get_string(himdq, "HDUCLAS1")));
        cpl_test(!strcmp("DATA", cpl_propertylist_get_string(him, "HDUCLAS2")) &&
                 !strcmp("QUALITY", cpl_propertylist_get_string(himdq, "HDUCLAS2")));
        cpl_test(!cpl_propertylist_has(him, "HDUCLAS3") && /* shouldn't exist */
                 !strcmp("FLAG32BIT", cpl_propertylist_get_string(himdq, "HDUCLAS3")));
        cpl_test(!cpl_propertylist_has(him, "ERRDATA") &&
                 !strcmp("white_DQ", cpl_propertylist_get_string(him, "QUALDATA")) &&
                 !cpl_propertylist_has(him, "SCIDATA"));
        cpl_test(!cpl_propertylist_has(himdq, "QUALDATA") &&
                 !strcmp("white", cpl_propertylist_get_string(himdq, "SCIDATA")) &&
                 !cpl_propertylist_has(himdq, "ERRDATA") &&
                 cpl_propertylist_has(himdq, "QUALMASK"));
        cpl_propertylist_delete(him);
        cpl_propertylist_delete(himdq);
#if MUSE_TEST_RESAMPLING_DELETE_FILES
        cpl_test(remove(fn) == 0); /* delete the file again */
#endif
        cpl_free(fn);
        cpl_errorstate prestate = cpl_errorstate_get();
        muse_resampling_params_delete(params);
        cpl_test(cpl_errorstate_is_equal(prestate));

        /* ====================================================================== *
         * Second test is almost the same but for the Euro3D format               *
         * ====================================================================== */
        cpl_msg_info(__func__, "Resampling pixel table [%s] to Euro3D, method %d",
                     unit, t);
        params = muse_resampling_params_new(t);
        params->tlambda = tl;
        if (t == MUSE_RESAMPLE_WEIGHTED_LANCZOS) {
          params->ld = 3; /* want to test lanczos3 */
        }
        muse_euro3dcube *e3d = muse_resampling_euro3d(pt, params);
        /* primitive tests */
        cpl_test_nonnull(e3d);
        cpl_test_nonnull(e3d->dtable);
        cpl_test_nonnull(e3d->hdata);
        cpl_test_nonnull(e3d->gtable);
        cpl_test_nonnull(e3d->header);
        int nwl = cpl_table_get_column_depth(e3d->dtable, "DATA_SPE");
        cpl_test(nwl == nplanes); /* same number as planes in cube above */
        if (cpl_table_has_column(e3d->dtable, "QUAL_SPE")) {
          cpl_test_eq(cpl_table_get_column_depth(e3d->dtable, "DATA_SPE"), nwl);
        }
        if (cpl_table_has_column(e3d->dtable, "STAT_SPE")) {
          cpl_test_eq(cpl_table_get_column_depth(e3d->dtable, "STAT_SPE"), nwl);
        }

        /* try to collapse the data using the white-light filter */
        muse_image *fove3d = muse_euro3dcube_collapse(e3d, fwhite);
        cpl_test_nonnull(fove3d);
        cpl_test_nonnull(fove3d->data);
        name = cpl_sprintf("Euro3D FOV (%s l%d %d)", unit, tl, t);
        cube_resampling_test_image_stats(fove3d->data, name);
        cpl_free(name);

        /* try to save the Euro3D object, first without collapsed image */
        fn = cpl_sprintf("cube_resampled_%s_l%d_%d_e3d.fits", unit, tl, t);
        cpl_test(muse_euro3dcube_save(e3d, fn) == CPL_ERROR_NONE);
#if MUSE_TEST_RESAMPLING_DELETE_FILES
        cpl_test(remove(fn) == 0); /* delete the file again */
#endif
        cpl_free(fn);
        /* now add reconstructed image to Euro3D object and try to save again */
        e3d->recimages = muse_imagelist_new();
        muse_imagelist_set(e3d->recimages, fove3d, 0);
        e3d->recnames = cpl_array_new(1, CPL_TYPE_STRING);
        cpl_array_set_string(e3d->recnames, 0, "white");
        fn = cpl_sprintf("cube_resampled_%s_l%d_%d_e3d_with_recimage.fits", unit, tl, t);
        cpl_test(muse_euro3dcube_save(e3d, fn) == CPL_ERROR_NONE);
#if MUSE_TEST_RESAMPLING_DELETE_FILES
        cpl_test(remove(fn) == 0); /* delete the file again */
#endif
        cpl_free(fn);

        /* test that both collapsed images are idential */
        cpl_test_image_abs(fov->data, fove3d->data, 0.001 /* absolute tolerance! */);

        prestate = cpl_errorstate_get();
        if (t == 0 && ipt == 0) {
          /* for the first type, test failure cases of the collapsing functions */
          muse_image *fovnew = muse_datacube_collapse(NULL, fwhite);
          cpl_test_null(fovnew);
          cpl_errorstate_set(prestate);
          fovnew = muse_euro3dcube_collapse(NULL, fwhite);
          cpl_test_null(fovnew);
          cpl_errorstate_set(prestate);

          /* no filter means to collapse everything, this should be identical *
           * to the white-light filter for this wavelength-limited test data  */
          fovnew = muse_datacube_collapse(cube, NULL);
          cpl_test_image_abs(fov->data, fovnew->data, 0.001 /* absolute tolerance! */);
          muse_image_delete(fovnew);
          fovnew = muse_euro3dcube_collapse(e3d, NULL);
          cpl_test_image_abs(fov->data, fovnew->data, 0.001 /* absolute tolerance! */);
          muse_image_delete(fovnew);
        } /* if first loop */

        /* some further tests with cubes that only need to be run once or so... */
        if (t == MUSE_RESAMPLE_WEIGHTED_DRIZZLE) {
          /* use this cube's WCS to try the OUTPUT_WCS-like  *
           * method to position the resampled cube somewhere */
          cpl_propertylist *hwcs = cpl_propertylist_duplicate(cube->header);
          cpl_propertylist_update_int(hwcs, "NAXIS3", 4); /* force dispersion axis */
          cpl_wcs *cwcs = cpl_wcs_new_from_propertylist(hwcs);
          cpl_propertylist_delete(hwcs);
          muse_resampling_params *pwcs = muse_resampling_params_new(t);
          pwcs->tlambda = tl;
          pwcs->wcs = cwcs;
          cpl_errorstate state = cpl_errorstate_get();
          muse_datacube *cubepos = muse_resampling_cube(pt, pwcs, NULL);
          muse_resampling_params_delete(pwcs);
          cpl_test(cpl_errorstate_is_equal(state));
          cpl_test_nonnull(cubepos);
          /* check that the forced size got set right */
          cpl_test(cpl_imagelist_get_size(cubepos->data) == 4);
          /* the 2nd plane should be the same for this and the non-forced cube */
          cpl_test_image_abs(cpl_imagelist_get(cubepos->data, 1),
                             cpl_imagelist_get(cube->data, 1), FLT_EPSILON);
          cpl_test_image_abs(cpl_imagelist_get(cubepos->dq, 1),
                             cpl_imagelist_get(cube->dq, 1), 0);
          cpl_test_image_abs(cpl_imagelist_get(cubepos->stat, 1),
                             cpl_imagelist_get(cube->stat, 1), FLT_EPSILON);
          char *fn2 = cpl_sprintf("cube_resampled_%s_l%d_%d_naxis3_overridden.fits",
                            unit, tl, t);
          cpl_test(muse_datacube_save(cubepos, fn2) == CPL_ERROR_NONE);
          muse_datacube_delete(cubepos);
#if MUSE_TEST_RESAMPLING_DELETE_FILES
          cpl_test(remove(fn2) == 0); /* delete the file again */
#endif
          cpl_free(fn2);

          /* also try to convert the DQ to NANs and save again */
          fn2 = cpl_sprintf("cube_resampled_%s_l%d_%d_with_recimage_NANs.fits",
                            unit, tl, t);
          state = cpl_errorstate_get();
          cpl_test(muse_datacube_convert_dq(cube) == CPL_ERROR_NONE);
          cpl_test_null(cube->dq);
          /* CPL statistics should now derive NAN, at least where  *
           * NANs are present, i.e. in the first plane of the cube */
          cpl_test(isnan(cpl_image_get_mean(cpl_imagelist_get(cube->data, 0))));
          cpl_test(isnan(cpl_image_get_mean(cpl_imagelist_get(cube->stat, 0))));
          cpl_test(muse_datacube_save(cube, fn2) == CPL_ERROR_NONE);
          cpl_test(cpl_errorstate_is_equal(state));
          /* count extensions, then save the reconstructed images and count again */
          int next = cpl_fits_count_extensions(fn2);
#if 0
          char *sys = cpl_sprintf("dfits -x 0 %s | grep EXTNAME", fn2);
          system(sys);
#endif
          cpl_test(muse_datacube_save_recimages(fn2, cube->recimages, cube->recnames)
                   == CPL_ERROR_NONE);
          int next2 = cpl_fits_count_extensions(fn2);
#if 0
          cpl_msg_debug("blablabla", "%d -> %d", next, next2);
          system(sys);
          cpl_free(sys);
#endif
          cpl_test_eq(next2, next + 1); /* one more extension now */
          muse_datacube *cubel = muse_datacube_load(fn2);
          cpl_test_nonnull(cubel);
          cpl_test_nonnull(cubel->data);
          cpl_test_nonnull(cubel->stat);
          cpl_test_eq(cpl_array_get_size(cubel->recnames), 2);
          cpl_test_eq(muse_imagelist_get_size(cubel->recimages), 2);
          muse_datacube_delete(cubel);
#if MUSE_TEST_RESAMPLING_DELETE_FILES
          cpl_test(remove(fn2) == 0); /* delete the file again */
#endif
          cpl_free(fn2);

          /* test failure cases */
          state = cpl_errorstate_get();
          /* converting again should fail because the dq component is now missing */
          cpl_test(muse_datacube_convert_dq(cube) == CPL_ERROR_NULL_INPUT);
          cpl_errorstate_set(state);
          /* NULL input should fail, too */
          cpl_test(muse_datacube_convert_dq(NULL) == CPL_ERROR_NULL_INPUT);
          cpl_errorstate_set(state);
        } /* if drizzle */

        /* now we are done with both cubes and the parameters */
        prestate = cpl_errorstate_get();
        muse_datacube_delete(cube);
        cpl_test(cpl_errorstate_is_equal(prestate));
        prestate = cpl_errorstate_get();
        muse_euro3dcube_delete(e3d);
        cpl_test(cpl_errorstate_is_equal(prestate));

        prestate = cpl_errorstate_get();
        muse_resampling_params_delete(params);
        cpl_test(cpl_errorstate_is_equal(prestate));
      } /* for ipt (both pixel tables) */
    } /* for t (all cube resampling methods) */
  } /* for tl (all wavelength dispersion types) */
  muse_pixtable_delete(ptdeg);

  /* test failure and special cases */
  muse_resampling_params *params =
    muse_resampling_params_new(MUSE_RESAMPLE_NEAREST);
  /* for FITS NAXIS=3 */
  cpl_errorstate ps = cpl_errorstate_get();
  muse_datacube *cube = muse_resampling_cube(NULL, params, NULL);
  cpl_test_null(cube);
  cpl_errorstate_set(ps);
  cube = muse_resampling_cube(ptpix, NULL, NULL);
  cpl_test_null(cube);
  cpl_errorstate_set(ps);
  params->method = MUSE_RESAMPLE_NONE; /* special case of no resampling */
  muse_pixgrid *grid;
  cube = muse_resampling_cube(ptpix, params, &grid);
  cpl_test_nonnull(grid);
  muse_pixgrid_delete(grid);
  cpl_test_nonnull(cube);
  cpl_test_nonnull(cube->header);
  muse_datacube_delete(cube);
  params->method = MUSE_RESAMPLE_NONE + 1; /* invalid type */
  ps = cpl_errorstate_get();
  cube = muse_resampling_cube(ptpix, params, NULL);
  cpl_test_null(cube);
  cpl_errorstate_set(ps);
  /* for Euro3D */
  params->method = MUSE_RESAMPLE_NEAREST;
  muse_euro3dcube *e3d = muse_resampling_euro3d(NULL, params);
  cpl_test_null(e3d);
  cpl_errorstate_set(ps);
  e3d = muse_resampling_euro3d(ptpix, NULL);
  cpl_test_null(e3d);
  cpl_errorstate_set(ps);
  params->method = MUSE_RESAMPLE_NONE; /* no resampling invalid for Euro3D */
  e3d = muse_resampling_euro3d(ptpix, params);
  cpl_test_null(e3d);
  cpl_errorstate_set(ps);
  params->method = MUSE_RESAMPLE_NONE; /* no resampling invalid for Euro3D */
  muse_resampling_params_delete(params);
  /* test parameter structure creation */
  ps = cpl_errorstate_get();
  params = muse_resampling_params_new(MUSE_RESAMPLE_NONE + 1);
  cpl_test_null(params);
  cpl_errorstate_set(ps);
  params = muse_resampling_params_new(MUSE_RESAMPLE_NEAREST - 1);
  cpl_test_null(params);
  cpl_errorstate_set(ps);
  /* test parameter wcs setting */
  params = muse_resampling_params_new(MUSE_RESAMPLE_WEIGHTED_DRIZZLE);
  params->tlambda = MUSE_RESAMPLING_DISP_AWAV_LOG;
  ps = cpl_errorstate_get();
  muse_resampling_params_set_wcs(params, NULL);
  cpl_test_null(params->wcs);
  cpl_test(params->tlambda == MUSE_RESAMPLING_DISP_AWAV);
  cpl_test(cpl_errorstate_is_equal(ps));
  cpl_propertylist *header = cpl_propertylist_new();
  ps = cpl_errorstate_get();
  muse_resampling_params_set_wcs(params, header);
  cpl_test_null(params->wcs); /* empty header fails cpl_wcs_new_from_propertylist() */
  cpl_test(params->tlambda == MUSE_RESAMPLING_DISP_AWAV);
  cpl_test(!cpl_errorstate_is_equal(ps));
  cpl_errorstate_set(ps);
  /* cpl_wcs_new_from_propertylist() tests for WCS presence by checking CRVAL... */
  cpl_propertylist_append_double(header, "CRVAL1", 1.);
  cpl_propertylist_append_double(header, "CRVAL2", 2.);
  cpl_propertylist_append_double(header, "CRVAL3", 3.);
  cpl_propertylist_append_string(header, "CTYPE3", "AWAV-LOG");
  /* it also throws an error if both CDi_j and PCi_j are missing (for CPL 6.4) */
  cpl_propertylist_append_double(header, "PC1_1", -0.2 / 3600.);
  cpl_propertylist_append_double(header, "PC2_2",  0.2 / 3600.);
  muse_resampling_params_set_wcs(params, header);
  cpl_test_nonnull(params->wcs);
  cpl_test(params->tlambda == MUSE_RESAMPLING_DISP_AWAV_LOG);
  cpl_test(cpl_errorstate_is_equal(ps)); /* cpl_wcs_new_from_propertylist() outputs warning! */
  cpl_propertylist_delete(header);
  muse_resampling_params_delete(params);
  /* parameter deletion is also supposed to work with NULL argument */
  ps = cpl_errorstate_get();
  muse_resampling_params_delete(NULL);
  cpl_test(cpl_errorstate_is_equal(ps));
  /* test loading non-existing cube */
  ps = cpl_errorstate_get();
  cube = muse_datacube_load("blabla.fits");
  cpl_test_null(cube);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
  cpl_errorstate_set(ps);
  /* test loading cube from file that does not have a DATA extension */
  cpl_image *imbla = cpl_image_new(3, 3, CPL_TYPE_FLOAT);
  cpl_image_save(imbla, "blabla.fits", CPL_TYPE_UNSPECIFIED, NULL, CPL_IO_CREATE);
  cpl_image_delete(imbla);
  ps = cpl_errorstate_get();
  cube = muse_datacube_load("blabla.fits");
  cpl_test_null(cube);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_DATA_NOT_FOUND);
  cpl_errorstate_set(ps);
  remove("blabla.fits");

  cpl_table_delete(fwhite);
  muse_pixtable_delete(ptpix);

  return cpl_test_end(0);
}
