/* -*- 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.
 */

#include <muse.h>

#include <math.h>
#include <string.h>

void
muse_test_flux_do_calibrate(muse_pixtable *aPt, cpl_table *aResponse,
                            cpl_table *aExtinction, cpl_table *aTelluric)
{
  cpl_msg_debug(__func__, "Starting muse_flux_calibrate(%s extinction, "
                "%s telluric)...", aExtinction ? "with" : "without",
                aTelluric ? "with" : "without");
  double cputime1 = cpl_test_get_cputime(),
         time1 = cpl_test_get_walltime();
  cpl_error_code rc = muse_flux_calibrate(aPt, aResponse, aExtinction,
                                          aTelluric);
  cpl_test(rc == CPL_ERROR_NONE);
  double cputime2 = cpl_test_get_cputime(),
         time2 = cpl_test_get_walltime();
  cpl_msg_debug(__func__, "...done muse_flux_calibrate(), "
                "took %gs (CPU) %gs (wall-clock)", cputime2 - cputime1,
                time2 - time1);
  /* test success case of muse_pixtable_is_fluxcal() */
  cpl_errorstate ps = cpl_errorstate_get();
  cpl_boolean fluxcal = muse_pixtable_is_fluxcal(aPt);
  cpl_test(fluxcal);
  cpl_test(cpl_errorstate_is_equal(ps));
} /* muse_test_flux_do_calibrate() */

void
muse_test_flux_check_cubes(muse_datacube **aCubes, unsigned short aNCubes)
{
  /* construct image for statistics from the inner pixel, *
   * i.e. [2,2,*] (the central spectrum!), of the cube    */
  int nlambda = cpl_imagelist_get_size(aCubes[0]->data);
  cpl_image *image = cpl_image_new(nlambda, 4, CPL_TYPE_DOUBLE);
  double cdelt = cpl_propertylist_get_double(aCubes[0]->header, "CD3_3"),
         crval = cpl_propertylist_get_double(aCubes[0]->header, "CRVAL3");
  const int windows[][2] = {
    {    1, nlambda }, /* full spectrum */
    {  650, 1050 }, /* blue line-free region, ~5610...6110 Angstrom */
    { 1651, 1735 }, /* telluric B-band */
    { 2075, 2222 } /* red line-free region, ~7395...7575 Angstrom */
  };
  cpl_stats *s[aNCubes * 4]; /* three methods, four windows */
  int i, /* method number / image row index */
      idxstat = 0; /* stats index */
  for (i = 1; i <= aNCubes; i++) {
    int idxcube = i - 1,
        imagerow = i + 1,
        l, /* wavelength (plane) index */
        err;
    for (l = 0; l < nlambda; l++) {
      if (i == 1) { /* fill lambda row */
        cpl_image_set(image, l + 1, 1, l * cdelt + crval);
      }
      double value = cpl_image_get(cpl_imagelist_get(aCubes[idxcube]->data, l),
                                   2, 2, &err);
      cpl_image_set(image, l + 1, imagerow, value);
    } /* for l (wavelengths) */
    muse_datacube_delete(aCubes[idxcube]);

    int k;
    for (k = 0; k < 4; k++) { /* statistics windows */
      cpl_stats *stats = cpl_stats_new_from_image_window(image, CPL_STATS_ALL,
                                                         windows[k][0], imagerow,
                                                         windows[k][1], imagerow);
      cpl_msg_debug(__func__, "Stats, method %d, window %d (%d...%d, "
                    "%.2f...%.2f): %.3f (%.3f) +/- %.3f, %.3f ... %.3f", i, k,
                    windows[k][0], windows[k][1],
                    cpl_image_get(image, windows[k][0], 1, &err),
                    cpl_image_get(image, windows[k][1], 1, &err),
                    cpl_stats_get_mean(stats), cpl_stats_get_median(stats),
                    cpl_stats_get_stdev(stats),
                    cpl_stats_get_min(stats), cpl_stats_get_max(stats));
      s[idxstat++] = stats;
    } /* for k */
  } /* for i (all aNCubes) */
#if 0
  cpl_plot_image_row(NULL, "w l", NULL, image, 1, 4, 1);
#endif
  cpl_image_delete(image);

  /* now do the testing of the various statistics: *
   * idx  cube  window                             *
   *   0  noext full                               *
   *   1  noext blue                               *
   *   2  noext B-band                             *
   *   3  noext red                                *
   *   4  ext   full                               *
   *   5  ext   blue                               *
   *   6  ext   B-band                             *
   *   7  ext   red                                *
   *   8  tell  full                               *
   *   9  tell  blue                               *
   *  10  tell  B-band                             *
   *  11  tell  red                                */
  /* flux increase with more corrections */
  cpl_test(cpl_stats_get_mean(s[0]) < cpl_stats_get_mean(s[4]));
  cpl_test(cpl_stats_get_mean(s[4]) < cpl_stats_get_mean(s[8]));
  /* flux increases more in the blue than in the red */
  double diffbluered_noext = cpl_stats_get_mean(s[1]) - cpl_stats_get_mean(s[3]),
         diffbluered_ext = cpl_stats_get_mean(s[5]) - cpl_stats_get_mean(s[7]);
  cpl_msg_debug(__func__, "diffbluered: %e (noext) %e (ext)",
                diffbluered_noext, diffbluered_ext);
  cpl_test(diffbluered_noext < diffbluered_ext &&
           diffbluered_ext / diffbluered_noext > 1.79);
  /* no flux change in blue or red regions between ext and tell */
  cpl_test(fabs(cpl_stats_get_mean(s[5]) - cpl_stats_get_mean(s[9])) < FLT_EPSILON);
  cpl_test(fabs(cpl_stats_get_mean(s[7]) - cpl_stats_get_mean(s[11])) < FLT_EPSILON);
  cpl_test(fabs(cpl_stats_get_stdev(s[5]) - cpl_stats_get_stdev(s[9])) < FLT_EPSILON);
  cpl_test(fabs(cpl_stats_get_stdev(s[7]) - cpl_stats_get_stdev(s[11])) < FLT_EPSILON);
  /* (strong) changes within the B-band */
  cpl_test(cpl_stats_get_mean(s[10]) / cpl_stats_get_mean(s[6]) > 1.06);
  cpl_test(cpl_stats_get_min(s[10]) / cpl_stats_get_min(s[6]) > 1.45);
  /* not so strong for the max value */
  cpl_test(cpl_stats_get_max(s[10]) / cpl_stats_get_max(s[6]) > 1.00 &&
           cpl_stats_get_max(s[10]) / cpl_stats_get_max(s[6]) < 1.06);
  /* check against reality: the GD71 reference spectrum has mean values of *
   * full   1.6275186e-15                                                  *
   * blue   1.9847039e-15            ==> blue / red = 1.553501088474       *
   * B-band 1.4732238e-15                blue / B-band = 1.3471842499422   *
   * red    1.2775684e-15                blue / full = 1.2194661861315     */
  double divbluered = cpl_stats_get_mean(s[9]) / cpl_stats_get_mean(s[11]),
         divblueB = cpl_stats_get_mean(s[9]) / cpl_stats_get_mean(s[10]),
         divbluefull = cpl_stats_get_mean(s[9]) / cpl_stats_get_mean(s[8]);
  cpl_msg_debug(__func__, "divs (tell): blue/red = %e, blue/B-band = %e, "
                "blue/full = %e", divbluered, divblueB, divbluefull);
  /* XXX why are they so low (0.8106, 0.8409, 0.9518), *
   *     the slope of the spectrum is too low!         */
  cpl_test(divbluered / 1.553501088474 < 1.05 && divbluered / 1.553501088474 > 0.81);
  cpl_test(divblueB / 1.3471842499422 < 1.05 && divblueB / 1.3471842499422 > 0.84);
  cpl_test(divbluefull / 1.2194661861315 < 1.05 && divbluefull / 1.2194661861315 > 0.95);
  for (i = 0; i < idxstat; i++) {
    cpl_stats_delete(s[i]);
  }
} /* muse_test_flux_check_cubes() */

/*----------------------------------------------------------------------------*/
/**
  @brief    test program to check that the functions from the muse_flux module
            do what they should when called with the necessary data
 */
/*----------------------------------------------------------------------------*/
int main(int argc, char **argv)
{
  UNUSED_ARGUMENTS(argc, argv);
  cpl_test_init(PACKAGE_BUGREPORT, CPL_MSG_DEBUG);

  cpl_table *ref = cpl_table_load(BASEFILENAME"_reference_gd71sm.fits", 1, 0),
            *extinction = cpl_table_load(BASEFILENAME"_extinction.fits", 1, 0);

  /* test muse_flux_reference_table_check() */
  cpl_test(muse_flux_reference_table_check(ref) == CPL_ERROR_NONE);
  cpl_errorstate state = cpl_errorstate_get();
  cpl_test(muse_flux_reference_table_check(NULL) == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(state);
  /* test with HST table */
  cpl_table *refhst = cpl_table_load(BASEFILENAME"_gd153_stisnic_004.fits", 1, 0),
            *refhst2 = cpl_table_duplicate(refhst);
  cpl_test(muse_flux_reference_table_check(refhst) == CPL_ERROR_NONE);
  cpl_test(cpl_table_has_column(refhst, "lambda"));
  cpl_test(cpl_table_has_column(refhst, "flux"));
  cpl_table_delete(refhst);
  /* test again with broken HST table */
  cpl_table_name_column(refhst2, "WAVELENGTH", "wavel");
  state = cpl_errorstate_get();
  cpl_test(muse_flux_reference_table_check(refhst2) == CPL_ERROR_INCOMPATIBLE_INPUT);
  cpl_errorstate_set(state);
  cpl_table_name_column(refhst2, "wavel", "WAVELENGTH");
  cpl_table_set_column_unit(refhst2, "FLUX", "FLAMM");
  cpl_test(muse_flux_reference_table_check(refhst2) == CPL_ERROR_INCOMPATIBLE_INPUT);
  cpl_errorstate_set(state);
  cpl_table_set_column_unit(refhst2, "WAVELENGTH", NULL);
  cpl_table_set_column_unit(refhst2, "FLUX", NULL);
  cpl_test(muse_flux_reference_table_check(refhst2) == CPL_ERROR_INCOMPATIBLE_INPUT);
  cpl_errorstate_set(state);
  cpl_table_delete(refhst2);
  /* test with alternative unit string */
  cpl_table *ref2 = cpl_table_duplicate(ref);
  cpl_table_set_column_unit(ref2, "flux", "erg/s/cm^2/Angstrom");
  cpl_test(muse_flux_reference_table_check(ref2) == CPL_ERROR_NONE);
  /* test without unit strings */
  cpl_table_set_column_unit(ref2, "lambda", NULL);
  cpl_table_set_column_unit(ref2, "flux", NULL);
  state = cpl_errorstate_get();
  cpl_test(muse_flux_reference_table_check(ref2) == CPL_ERROR_INCOMPATIBLE_INPUT);
  cpl_errorstate_set(state);
  /* test with (made-up) fluxerr column, but no unit */
  cpl_table_set_column_unit(ref2, "lambda", "Angstrom"); /* reset correct units */
  cpl_table_set_column_unit(ref2, "flux", "erg/s/cm**2/Angstrom");
  cpl_table_new_column(ref2, "fluxerr", CPL_TYPE_DOUBLE);
  cpl_test(muse_flux_reference_table_check(ref2) == CPL_ERROR_NONE);
  cpl_test(!cpl_table_has_column(ref2, "fluxerr")); /* get erased */
  cpl_table_new_column(ref2, "fluxerr", CPL_TYPE_DOUBLE);
  cpl_table_fill_column_window(ref2, "fluxerr", 0, cpl_table_get_nrow(ref2), 1e-3);
  cpl_table_set_column_unit(ref2, "fluxerr", "erg/s/cm**2/Angstrom");
  cpl_test(muse_flux_reference_table_check(ref2) == CPL_ERROR_NONE);
  cpl_test(cpl_table_has_column(ref2, "fluxerr")); /* now still there */
  /* now test with table where all columns are floats */
  cpl_table *ref3 = cpl_table_duplicate(ref2);
  cpl_table_name_column(ref3, "lambda", "lambda_d");
  cpl_table_name_column(ref3, "flux", "flux_d");
  cpl_table_name_column(ref3, "fluxerr", "fluxerr_d");
  cpl_table_cast_column(ref3, "lambda_d", "lambda", CPL_TYPE_FLOAT);
  cpl_table_cast_column(ref3, "flux_d", "flux", CPL_TYPE_FLOAT);
  cpl_table_cast_column(ref3, "fluxerr_d", "fluxerr", CPL_TYPE_FLOAT);
  cpl_table_erase_column(ref3, "lambda_d");
  cpl_table_erase_column(ref3, "flux_d");
  cpl_table_erase_column(ref3, "fluxerr_d");
  cpl_test(muse_flux_reference_table_check(ref3) == CPL_ERROR_NONE);
  cpl_test_zero(cpl_table_compare_structure(ref2, ref3)); /* now all doubles */
  cpl_table_delete(ref2);
  cpl_table_delete(ref3);

  state = cpl_errorstate_get();
  muse_pixtable *pt = muse_pixtable_load(BASEFILENAME"_pixtable_gd71sm.fits");
  cpl_test_nonnull(pt);
  muse_pixtable *ptdupe = muse_pixtable_duplicate(pt);
  cpl_errorstate_set(state); /* hide error from missing DQ and Origin columns */
  /* add (clean) DQ and (empty) origin columns back *
   * which were removed to save space               */
  cpl_table_new_column(pt->table, MUSE_PIXTABLE_DQ, CPL_TYPE_INT);
  cpl_table_fill_column_window_int(pt->table, MUSE_PIXTABLE_DQ,
                                   0, muse_pixtable_get_nrow(pt), 0);
  cpl_table_new_column(pt->table, MUSE_PIXTABLE_ORIGIN, CPL_TYPE_INT);
  cpl_table_fill_column_window_int(pt->table, MUSE_PIXTABLE_ORIGIN,
                                   0, muse_pixtable_get_nrow(pt), 0);
  double airmass = muse_astro_airmass(pt->header);

  muse_flux_object *fluxobj = muse_flux_object_new();
  cpl_test_nonnull(fluxobj);
  /* flux integration using Moffat profile fit */
  state = cpl_errorstate_get();
  cpl_error_code rc = muse_flux_integrate_std(pt, MUSE_FLUX_PROFILE_MOFFAT, fluxobj);
  cpl_test(rc == CPL_ERROR_NONE && cpl_errorstate_is_equal(state));
  cpl_test_nonnull(fluxobj->cube);
  cpl_test_nonnull(fluxobj->intimage);
  cpl_test(cpl_imagelist_get_size(fluxobj->cube->data)
           == cpl_image_get_size_x(fluxobj->intimage->data));
  /* compute the total flux recovered over the wavelength range ~4800 to 9300 */
  cpl_stats_mode smode = CPL_STATS_FLUX | CPL_STATS_MEDIAN;
  cpl_stats *s = cpl_stats_new_from_image_window(fluxobj->intimage->data, smode,
                                                 201, 1, 3801, 1);
  double ftot = cpl_stats_get_flux(s),
         fmedian = cpl_stats_get_median(s);
  cpl_stats_delete(s);
  /* before starting to mess with the pixel table, try       *
   * integrating the flux using the other profile types, too */
  unsigned int i;
  for (i = MUSE_FLUX_PROFILE_GAUSSIAN; i <= MUSE_FLUX_PROFILE_EQUAL_SQUARE; i++) {
    muse_flux_object *fobj2 = muse_flux_object_new();
    state = cpl_errorstate_get();
    rc = muse_flux_integrate_std(pt, i, fobj2);
    cpl_test(rc == CPL_ERROR_NONE && cpl_errorstate_is_equal(state));
    cpl_test_nonnull(fobj2->cube);
    cpl_test_nonnull(fobj2->intimage);
    cpl_test(cpl_imagelist_get_size(fobj2->cube->data)
             == cpl_image_get_size_x(fobj2->intimage->data));
    s = cpl_stats_new_from_image_window(fobj2->intimage->data, smode,
                                        201, 1, 3801, 1);
    /* should be at least comparable to Moffat integration */
    cpl_msg_debug(__func__, "flux %g / %g\n\tmedian %g / %g",
                  ftot, cpl_stats_get_flux(s), fmedian, cpl_stats_get_median(s));
    /* the Gaussian integrates less flux than the other methods... */
    double limit1 = i == MUSE_FLUX_PROFILE_GAUSSIAN ? 0.18 : 0.04,
           limit2 = i == MUSE_FLUX_PROFILE_GAUSSIAN ? 0.15 : 0.02;
    cpl_test_rel(ftot, cpl_stats_get_flux(s), limit1);
    cpl_test_rel(fmedian, cpl_stats_get_median(s), limit2);
    cpl_stats_delete(s);
    muse_flux_object_delete(fobj2);
  } /* for i (all other integration profiles) */
  /* test failure cases of muse_flux_integrate_std(), too */
  muse_flux_object *fobj3 = muse_flux_object_new();
  state = cpl_errorstate_get();
  rc = muse_flux_integrate_std(pt, MUSE_FLUX_PROFILE_EQUAL_SQUARE + 1, fobj3);
  cpl_test(rc == CPL_ERROR_ILLEGAL_INPUT && !cpl_errorstate_is_equal(state));
  cpl_errorstate_set(state);
  rc = muse_flux_integrate_std(NULL, MUSE_FLUX_PROFILE_MOFFAT, fobj3);
  cpl_test(rc == CPL_ERROR_NULL_INPUT && !cpl_errorstate_is_equal(state));
  cpl_errorstate_set(state);
  muse_flux_object_delete(fobj3);
  rc = muse_flux_integrate_std(pt, MUSE_FLUX_PROFILE_MOFFAT, NULL);
  cpl_test(rc == CPL_ERROR_NULL_INPUT && !cpl_errorstate_is_equal(state));
  cpl_errorstate_set(state);

  /* test success of muse_flux_response_compute() for reasonable data */
  state = cpl_errorstate_get();
  rc = muse_flux_response_compute(fluxobj, MUSE_FLUX_SELECT_BRIGHTEST, airmass,
                                  ref, NULL, extinction);
  cpl_test(rc == CPL_ERROR_NONE && cpl_errorstate_is_equal(state));
  cpl_test_nonnull(fluxobj->sensitivity);
  rc = muse_flux_get_response_table(fluxobj, MUSE_FLUX_SMOOTH_MEDIAN);
  cpl_test_nonnull(fluxobj->response);
  cpl_test(cpl_table_has_column(fluxobj->response, "lambda") &&
           cpl_table_has_column(fluxobj->response, "response") &&
           cpl_table_has_column(fluxobj->response, "resperr"));
  cpl_test(cpl_table_get_nrow(fluxobj->response)
           == cpl_table_get_nrow(fluxobj->sensitivity));
  /* check resulting responses at three wavelengths, values were read *
   * off by eye from the sensitivity plots around these wavelengths   */
  double r1 = muse_flux_response_interpolate(fluxobj->response, 5000., NULL,
                                             MUSE_FLUX_RESP_FLUX),
         r2 = muse_flux_response_interpolate(fluxobj->response, 7000., NULL,
                                             MUSE_FLUX_RESP_FLUX),
         r3 = muse_flux_response_interpolate(fluxobj->response, 9000., NULL,
                                             MUSE_FLUX_RESP_FLUX);
  cpl_msg_debug(__func__, "responses computed: %f ... %f ... %f", r1, r2, r3);
  cpl_test(fabs(r1 - 41.676) < 0.02 &&
           fabs(r2 - 41.581) < 0.02 &&
           fabs(r3 - 41.422) < 0.02);
  rc = muse_flux_get_telluric_table(fluxobj);
  cpl_test_nonnull(fluxobj->telluric);
  cpl_test(cpl_table_has_column(fluxobj->telluric, "lambda") &&
           cpl_table_has_column(fluxobj->telluric, "ftelluric") &&
           cpl_table_has_column(fluxobj->telluric, "ftellerr"));
  cpl_test(cpl_table_get_nrow(fluxobj->telluric)
           < cpl_table_get_nrow(fluxobj->sensitivity));
  double mean = cpl_table_get_column_mean(fluxobj->telluric, "ftelluric"),
         median = cpl_table_get_column_median(fluxobj->telluric, "ftelluric"),
         stdev = cpl_table_get_column_stdev(fluxobj->telluric, "ftelluric");
  cpl_msg_debug(__func__, "telluric correction statistics: %f (%f) +/- %f",
                mean, median, stdev);
  cpl_test(fabs(mean - 1.) < 0.092 && fabs(median - 1.) < 0.032 &&
           stdev < 0.16);

  /* try again, but selecting the nearest star, should give  *
   * exactly the same result, since we have only 1 star here */
  cpl_table *sens1 = fluxobj->sensitivity;
  cpl_table_delete(fluxobj->tellbands);
  fluxobj->sensitivity = NULL;
  fluxobj->tellbands = NULL;
  state = cpl_errorstate_get();
  fluxobj->raref = 15.8871528; /* RA of GD71 */
  fluxobj->decref = 88.1150583; /* DEC of GD71 */
  rc = muse_flux_response_compute(fluxobj, MUSE_FLUX_SELECT_NEAREST, airmass,
                                  ref, NULL, extinction);
  cpl_test(rc == CPL_ERROR_NONE && cpl_errorstate_is_equal(state));
  cpl_test_nonnull(fluxobj->sensitivity);
  cpl_test_zero(cpl_table_compare_structure(sens1, fluxobj->sensitivity));
  /* compare both tables row by row */
  int nrows1 = cpl_table_get_nrow(sens1),
      nrows2 = cpl_table_get_nrow(fluxobj->sensitivity);
  cpl_test_eq(nrows1, nrows2);
  cpl_array *acol = cpl_table_get_column_names(sens1);
  for (i = 0; i < 4; i++) { /* check cols lambda, sens, serr, and dq only */
    const char *colname = cpl_array_get_string(acol, i);
    cpl_type t = cpl_table_get_column_type(sens1, colname);
    cpl_array *a1, *a2;
    if (t == CPL_TYPE_INT) {
      a1 = cpl_array_wrap_int(cpl_table_get_data_int(sens1, colname), nrows1);
      a2 = cpl_array_wrap_int(cpl_table_get_data_int(fluxobj->sensitivity, colname),
                              nrows2);
    } else {
      a1 = cpl_array_wrap_double(cpl_table_get_data_double(sens1, colname), nrows1);
      a2 = cpl_array_wrap_double(cpl_table_get_data_double(fluxobj->sensitivity,
                                                           colname), nrows2);
    }
    cpl_test_array_abs(a1, a2, DBL_EPSILON);
    cpl_array_unwrap(a1);
    cpl_array_unwrap(a2);
  } /* for i (all column names) */
  cpl_array_delete(acol);
  cpl_table_delete(sens1);

  /* duplicate the telluric regions from above,      *
   * they are the defaults, so nothing should change */
  cpl_table *tellreg = cpl_table_duplicate(fluxobj->tellbands);
  muse_flux_object *fo2 = muse_flux_object_new();
  fo2->cube = fluxobj->cube;
  fo2->intimage = fluxobj->intimage;
  state = cpl_errorstate_get();
  rc = muse_flux_response_compute(fo2, MUSE_FLUX_SELECT_BRIGHTEST, airmass, ref,
                                  tellreg, extinction);
  cpl_test(rc == CPL_ERROR_NONE && cpl_errorstate_is_equal(state));
  cpl_table_delete(tellreg);
  /* check that some entries in the sensitivity table are identical */
  int nrow2 = cpl_table_get_nrow(fo2->sensitivity);
  cpl_msg_debug(__func__, "test with tellbands gave %d rows", nrow2);
  cpl_test(cpl_table_get_nrow(fluxobj->sensitivity) == nrow2);
  int err, err2;
  cpl_test(cpl_table_get(fluxobj->sensitivity, "sens", nrow2/100, &err)
           == cpl_table_get(fo2->sensitivity, "sens", nrow2/100, &err2)
           && err == err2);
  cpl_test(cpl_table_get(fluxobj->sensitivity, "sens", nrow2/10, &err)
           == cpl_table_get(fo2->sensitivity, "sens", nrow2/10, &err2)
           && err == err2);
  cpl_test(cpl_table_get(fluxobj->sensitivity, "sens", nrow2/5, &err)
           == cpl_table_get(fo2->sensitivity, "sens", nrow2/5, &err2)
           && err == err2);
  cpl_test(cpl_table_get(fluxobj->sensitivity, "sens", nrow2 - 10, &err)
           == cpl_table_get(fo2->sensitivity, "sens", nrow2 - 10, &err2)
           && err == err2);
  cpl_test(cpl_table_get(fluxobj->sensitivity, "serr", nrow2/10, &err)
           == cpl_table_get(fo2->sensitivity, "serr", nrow2/10, &err2)
           && err == err2);
  fo2->cube = NULL; /* reset the pointers again to not delete them yet */
  fo2->intimage = NULL;
  muse_flux_object_delete(fo2);

  cpl_msg_info(__func__, "using pixel table with %"CPL_SIZE_FORMAT" rows, "
               "response table with %"CPL_SIZE_FORMAT" entries",
               muse_pixtable_get_nrow(pt), cpl_table_get_nrow(fluxobj->response));

  /* duplicate the relevant columns to be able to compare before/after */
  cpl_table_duplicate_column(pt->table, "data_orig",
                             pt->table, MUSE_PIXTABLE_DATA);
  cpl_table_duplicate_column(pt->table, "stat_orig",
                             pt->table, MUSE_PIXTABLE_STAT);

  /* cut down to nominal wavelength range, that's where it counts! */
  muse_pixtable_restrict_wavelength(pt, 4800., 9300.); /* nominal wl-range */
  /* test-calibrate the table to investigate accuracy of the calibration */
  muse_test_flux_do_calibrate(pt, fluxobj->response, extinction,
                              fluxobj->telluric);
  muse_flux_object *fo = muse_flux_object_new();
  cpl_test(muse_flux_integrate_std(pt, MUSE_FLUX_PROFILE_MOFFAT, fo)
           == CPL_ERROR_NONE);
  /* rename the flux-calibrated columns to be able to run again */
  cpl_table_name_column(pt->table, MUSE_PIXTABLE_DATA, "data_cal");
  cpl_table_name_column(pt->table, MUSE_PIXTABLE_STAT, "stat_cal");
  cpl_table_duplicate_column(pt->table, MUSE_PIXTABLE_DATA,
                             pt->table, "data_orig");
  cpl_table_duplicate_column(pt->table, MUSE_PIXTABLE_STAT,
                             pt->table, "stat_orig");
  /* the resulting response curve should now be (almost) flat *
   * (but don't do the airmass correction again!)             */
  cpl_test(muse_flux_response_compute(fo, MUSE_FLUX_SELECT_BRIGHTEST, 1.0, ref,
                                      NULL, NULL) == CPL_ERROR_NONE);
  cpl_test(muse_flux_get_response_table(fo, MUSE_FLUX_SMOOTH_PPOLY) == CPL_ERROR_NONE);

#if 0
  cpl_error_code ec = cpl_plot_column(NULL, NULL, NULL, fo->response, "lambda",
                                      "response");
  printf("plotted: %d\n\n", ec); fflush(stdout);
#endif
  /* do not count the extrapolated regions */
  cpl_table_select_all(fo->response);
  cpl_table_and_selected_double(fo->response, "lambda", CPL_LESS_THAN, 4800.);
  cpl_table_or_selected_double(fo->response, "lambda", CPL_GREATER_THAN, 9300.);
  cpl_table_erase_selected(fo->response);
#if 0
  ec = cpl_plot_column(NULL, NULL, NULL, fo->response, "lambda", "response");
  printf("plotted: %d\n\n", ec); fflush(stdout);
#endif
  mean = cpl_table_get_column_mean(fo->response, "response");
  median = cpl_table_get_column_median(fo->response, "response");
  stdev = cpl_table_get_column_stdev(fo->response, "response");
  double min = cpl_table_get_column_min(fo->response, "response"),
         max = cpl_table_get_column_max(fo->response, "response");
  cpl_msg_debug(__func__, "test-calibration statistics: %f (%f) +/- %f, %f ... %f",
                mean, median, stdev, min, max);
  cpl_test(fabs(mean - 43.55) < 0.1); /* XXX why is it not ~50 mag == 1e-20 erg/s/cm**2/Angstrom? */
  cpl_test(fabs(mean - median) < 0.01 && stdev < 0.015);
  cpl_test(max - min < 0.12);
  muse_flux_object_delete(fo);
  cpl_table_delete(ref);

  /* cut down the pixel table to the innermost area of the standard star PSF, *
   * so that cube reconstruction creates a miniature 3x3xnlambda cube         */
  muse_pixtable_restrict_xpos(pt, -11.12, -9.12); /* center -10.12, 15.08 */
  muse_pixtable_restrict_ypos(pt, 14.08, 16.08);

  /* generate three cubes, with different levels of flux calibration */
  muse_datacube *cubes[3];
  muse_resampling_params *p
    = muse_resampling_params_new(MUSE_RESAMPLE_WEIGHTED_DRIZZLE);
  p->pfx = 1.;
  p->pfy = 1.;
  p->pfl = 1.;

  /* run once without extinction or telluric correction */
  muse_test_flux_do_calibrate(pt, fluxobj->response, NULL, NULL);
  cubes[0] = muse_resampling_cube(pt, p, NULL);
#if 0
  muse_datacube_convert_dq(cubes[0]);
  muse_datacube_save(cubes[0], "cube_0_noext.fits");
#endif
  cpl_table_name_column(pt->table, MUSE_PIXTABLE_DATA, "data_noext");
  cpl_table_name_column(pt->table, MUSE_PIXTABLE_STAT, "stat_noext");

  /* run again with extinction but without telluric correction */
  cpl_table_duplicate_column(pt->table, MUSE_PIXTABLE_DATA,
                             pt->table, "data_orig");
  cpl_table_duplicate_column(pt->table, MUSE_PIXTABLE_STAT,
                             pt->table, "stat_orig");
  muse_test_flux_do_calibrate(pt, fluxobj->response, extinction, NULL);
  cubes[1] = muse_resampling_cube(pt, p, NULL);
#if 0
  muse_datacube_convert_dq(cubes[1]);
  muse_datacube_save(cubes[1], "cube_1_ext.fits");
#endif

  /* test complete failure cases */
  cpl_errorstate ps = cpl_errorstate_get();
  rc = muse_flux_calibrate(NULL, fluxobj->response, extinction, fluxobj->telluric);
  cpl_test(rc == CPL_ERROR_NULL_INPUT && !cpl_errorstate_is_equal(ps));
  cpl_errorstate_set(ps);
  rc = muse_flux_calibrate(ptdupe, NULL, extinction, fluxobj->telluric);
  cpl_test(rc == CPL_ERROR_NULL_INPUT && !cpl_errorstate_is_equal(ps));
  cpl_errorstate_set(ps);
  /* wrong data format */
  cpl_table_set_column_unit(ptdupe->table, MUSE_PIXTABLE_DATA, "adu");
  ps = cpl_errorstate_get();
  rc = muse_flux_calibrate(ptdupe, fluxobj->response, extinction, fluxobj->telluric);
  cpl_test(rc == CPL_ERROR_INCOMPATIBLE_INPUT && !cpl_errorstate_is_equal(ps));
  cpl_errorstate_set(ps);
  /* the flux calibrated one should fail, too */
  rc = muse_flux_calibrate(pt, fluxobj->response, extinction, fluxobj->telluric);
  cpl_test(rc == CPL_ERROR_INCOMPATIBLE_INPUT && !cpl_errorstate_is_equal(ps));
  cpl_errorstate_set(ps);
  /* bad EXPTIME */
  cpl_table_set_column_unit(ptdupe->table, MUSE_PIXTABLE_DATA, "count");
  cpl_propertylist_update_double(ptdupe->header, "EXPTIME", 0.); /* bias-like */
  rc = muse_flux_calibrate(ptdupe, fluxobj->response, extinction, fluxobj->telluric);
  cpl_test(rc == CPL_ERROR_ILLEGAL_INPUT);
  cpl_propertylist_update_double(ptdupe->header, "EXPTIME", -1.); /* total garbage */
  rc = muse_flux_calibrate(ptdupe, fluxobj->response, extinction, fluxobj->telluric);
  cpl_test(rc == CPL_ERROR_ILLEGAL_INPUT);
  cpl_errorstate_set(ps);
  /* illegal airmass */
  cpl_propertylist_update_double(ptdupe->header, "EXPTIME", 1.); /* reset again */
  cpl_propertylist_update_double(ptdupe->header, "RA", 180.); /* 12h */
  cpl_propertylist_update_double(ptdupe->header, "LST", 10800.); /* 3h */
  cpl_propertylist_erase_regexp(ptdupe->header, "AIRM", 0.); /* remove fallback */
  ps = cpl_errorstate_get();
  cpl_msg_debug("", "_________________ warning below?! ______________________________");
  rc = muse_flux_calibrate(ptdupe, fluxobj->response, extinction, fluxobj->telluric);
  cpl_msg_debug("", "^^^^^^^^^^^^^^^^^ warning above?! ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^");
  /* visually check the warning, the airmass function will have given illegal output */
  cpl_test(rc == CPL_ERROR_NONE && !cpl_errorstate_is_equal(ps));
  cpl_errorstate_set(ps);
  cpl_test(muse_pixtable_is_fluxcal(ptdupe));
  muse_pixtable_delete(ptdupe);

  cpl_table_name_column(pt->table, MUSE_PIXTABLE_DATA, "data_ext");
  cpl_table_name_column(pt->table, MUSE_PIXTABLE_STAT, "stat_ext");

  /* run again with extinction and telluric correction */
  cpl_table_duplicate_column(pt->table, MUSE_PIXTABLE_DATA,
                             pt->table, "data_orig");
  cpl_table_duplicate_column(pt->table, MUSE_PIXTABLE_STAT,
                             pt->table, "stat_orig");
  muse_test_flux_do_calibrate(pt, fluxobj->response, extinction,
                              fluxobj->telluric);
  cubes[2] = muse_resampling_cube(pt, p, NULL);
#if 0
  muse_datacube_convert_dq(cubes[2]);
  muse_datacube_save(cubes[2], "cube_2_tell.fits");
#endif
  cpl_table_name_column(pt->table, MUSE_PIXTABLE_DATA, "data_tell");
  cpl_table_name_column(pt->table, MUSE_PIXTABLE_STAT, "stat_tell");

  muse_resampling_params_delete(p);
  muse_test_flux_check_cubes(cubes, 3);

  /* now we have eight relevant columns:                    *
   * data_{orig,noext,ext,tell}, stat_{orig,noext,ext,tell} */
#if 0
  cpl_table_erase_column(pt->table, MUSE_PIXTABLE_XPOS);
  cpl_table_erase_column(pt->table, MUSE_PIXTABLE_YPOS);
  cpl_table_erase_column(pt->table, MUSE_PIXTABLE_DQ);
  cpl_table_erase_column(pt->table, MUSE_PIXTABLE_ORIGIN);
  cpl_table_dump(pt->table, 0, 10, stdout); fflush(stdout);
  cpl_table_dump(pt->table, cpl_table_get_nrow(pt->table)-5, 10, stdout);
  fflush(stdout);
#endif

  /* cleanup */
  muse_pixtable_delete(pt);
  cpl_table_delete(extinction);
  /* test flux object deletion */
  state = cpl_errorstate_get();
  muse_flux_object_delete(fluxobj);
  /* should work without argument */
  muse_flux_object_delete(NULL);
  cpl_test(cpl_errorstate_is_equal(state));

  return cpl_test_end(0);
}
