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

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

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

#include <muse.h>

static muse_pixtable *
muse_test_xcombine_pixtable_create(double aValue)
{
  muse_pixtable *pt = cpl_calloc(1, sizeof(muse_pixtable));
  pt->header = cpl_propertylist_new();
  char *dateobs = cpl_sprintf("2014-08-%02dT10:00:%06.3f", (int)aValue, aValue);
  cpl_propertylist_append_string(pt->header, "DATE-OBS", dateobs);
  cpl_free(dateobs);
  cpl_propertylist_append_float(pt->header, "EXPTIME", aValue);
  cpl_propertylist_append_float(pt->header, "RA", 1e-5 * aValue);
  cpl_propertylist_append_float(pt->header, "DEC", -1e-4 * aValue);
  cpl_propertylist_append_string(pt->header, "CTYPE1", "RA---TAN");
  cpl_propertylist_append_string(pt->header, "CTYPE2", "DEC--TAN");
  cpl_propertylist_append_string(pt->header, "ESO INS MODE", "NFM-AO-N");
  pt->table = muse_cpltable_new(muse_pixtable_def, 10);
  cpl_table_fill_column_window(pt->table, MUSE_PIXTABLE_XPOS, 0, 10, 0.);
  cpl_table_fill_column_window(pt->table, MUSE_PIXTABLE_YPOS, 0, 10, 0.);
  cpl_table_set_float(pt->table, MUSE_PIXTABLE_LAMBDA, 0, 4650.);
  cpl_table_set_float(pt->table, MUSE_PIXTABLE_LAMBDA, 1, 4800.);
  cpl_table_set_float(pt->table, MUSE_PIXTABLE_LAMBDA, 2, 5000.);
  cpl_table_set_float(pt->table, MUSE_PIXTABLE_LAMBDA, 3, 5500.);
  cpl_table_set_float(pt->table, MUSE_PIXTABLE_LAMBDA, 4, 6000.);
  cpl_table_set_float(pt->table, MUSE_PIXTABLE_LAMBDA, 5, 6500.);
  cpl_table_set_float(pt->table, MUSE_PIXTABLE_LAMBDA, 6, 7000.);
  cpl_table_set_float(pt->table, MUSE_PIXTABLE_LAMBDA, 7, 8000.);
  cpl_table_set_float(pt->table, MUSE_PIXTABLE_LAMBDA, 8, 9000.);
  cpl_table_set_float(pt->table, MUSE_PIXTABLE_LAMBDA, 9, 9300.);
  cpl_table_fill_column_window(pt->table, MUSE_PIXTABLE_DATA, 0, 10, aValue);
  cpl_table_fill_column_window(pt->table, MUSE_PIXTABLE_ORIGIN, 0, 1, 0);
  return pt;
} /* muse_test_xcombine_pixtable_create() */

static muse_pixtable *
muse_test_xcombine_pixtable_create_pos(double aValue, double aRA, double aDEC)
{
  muse_pixtable *pt = cpl_calloc(1, sizeof(muse_pixtable));
  pt->header = cpl_propertylist_new();
  char *dateobs = cpl_sprintf("2014-08-%02dT10:00:%06.3f", (int)aValue, aValue);
  cpl_propertylist_append_string(pt->header, "DATE-OBS", dateobs);
  cpl_free(dateobs);
  cpl_propertylist_append_float(pt->header, "EXPTIME", aValue);
  cpl_propertylist_append_float(pt->header, "RA", aRA);
  cpl_propertylist_append_float(pt->header, "DEC", aDEC);
  cpl_propertylist_append_string(pt->header, "CTYPE1", "RA---TAN");
  cpl_propertylist_append_string(pt->header, "CTYPE2", "DEC--TAN");
  cpl_propertylist_append_double(pt->header, "ESO INS DROT POSANG", 0.);
  cpl_propertylist_append_string(pt->header, "ESO INS DROT MODE", "SKY");
  cpl_propertylist_append_string(pt->header, "ESO INS MODE", "NFM-AO-N");
  pt->table = muse_cpltable_new(muse_pixtable_def, 1);
  cpl_table_fill_column_window(pt->table, MUSE_PIXTABLE_XPOS, 0, 1, 0.);
  cpl_table_fill_column_window(pt->table, MUSE_PIXTABLE_YPOS, 0, 1, 0.);
  cpl_table_set_float(pt->table, MUSE_PIXTABLE_LAMBDA, 0, 7000.);
  cpl_table_fill_column_window(pt->table, MUSE_PIXTABLE_DATA, 0, 1, aValue);
  cpl_table_fill_column_window(pt->table, MUSE_PIXTABLE_STAT, 0, 1, 0.5*aValue);
  cpl_table_fill_column_window(pt->table, MUSE_PIXTABLE_ORIGIN, 0, 1, 0);
  muse_pixtable_compute_limits(pt);
  cpl_propertylist *wcs = muse_wcs_create_default(NULL);
  muse_wcs_project_tan(pt, wcs);
  cpl_propertylist_delete(wcs);
  return pt;
} /* muse_test_xcombine_pixtable_create() */

/* create fake pixel tables from exposures of the COSMOS-GR28 field *
 * with the headers as taken on 2015-04-15                          */
static muse_pixtable *
muse_test_xcombine_pixtable_create_cosmos(int aExp)
{
  muse_pixtable *pt = cpl_calloc(1, sizeof(muse_pixtable));
  pt->header = cpl_propertylist_new();
  switch (aExp) {
  case 1:
    cpl_propertylist_append_string(pt->header, "OBJECT", "COSMOS-GR28");   /* Original target. */
    cpl_propertylist_append_double(pt->header, "RA", 150.141922);          /* [deg] 10:00:34.0 RA (J2000) pointing */
    cpl_propertylist_append_double(pt->header, "DEC", 2.06683);            /* [deg] 02:04:00.5 DEC (J2000) pointing */
    cpl_propertylist_append_double(pt->header, "EXPTIME", 900.0000000);    /* Integration time */
    cpl_propertylist_append_double(pt->header, "MJD-OBS", 57127.00337376); /* Obs start */
    cpl_propertylist_append_string(pt->header, "DATE-OBS", "2015-04-15T00:04:51.493"); /* Observing date */
    cpl_propertylist_append_string(pt->header, "ESO INS DROT MODE", "SKY"); /* Instrument derotator mode */
    cpl_propertylist_append_double(pt->header, "ESO INS DROT POSANG", 0.5000); /* [deg] Derotator position angle */
    cpl_propertylist_append_string(pt->header, "ESO INS MODE", "NFM-AO-N");
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS AG FWHMX AVG", 0.774); /* [arcsec] AG FWHM X mean value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS AG FWHMX MAX", 0.993); /* [arcsec] AG FWHM X maximum value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS AG FWHMX MED", 0.764); /* [arcsec] AG FWHM X median value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS AG FWHMX MIN", 0.632); /* [arcsec] AG FWHM X minimum value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS AG FWHMX RMS", 0.079); /* [arcsec] AG FWHM X RMS value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS AG FWHMY AVG", 0.744); /* [arcsec] AG FWHM Y mean value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS AG FWHMY MAX", 0.963); /* [arcsec] AG FWHM Y maximum value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS AG FWHMY MED", 0.731); /* [arcsec] AG FWHM Y median value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS AG FWHMY MIN", 0.638); /* [arcsec] AG FWHM Y minimum value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS AG FWHMY RMS", 0.067); /* [arcsec] AG FWHM Y RMS value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS FWHM AVG", 0.578); /* [arcsec] SGS FWHM mean value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS FWHM MAX", 0.767); /* [arcsec] SGS FWHM maximum value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS FWHM MED", 0.566); /* [arcsec] SGS FWHM median value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS FWHM MIN", 0.462); /* [arcsec] SGS FWHM minimum value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS FWHM RMS", 0.073); /* [arcsec] SGS FWHM RMS value */
    cpl_propertylist_append_double(pt->header, "ESO TEL AMBI FWHM END", -1.00); /* [arcsec] Observatory Seeing queried fro */
    cpl_propertylist_append_double(pt->header, "ESO TEL AMBI FWHM START", -1.00); /* [arcsec] Observatory Seeing queried f */
    cpl_propertylist_append_double(pt->header, "ESO TEL IA FWHM", 0.80); /* [arcsec] Delivered seeing corrected by airmass */
    cpl_propertylist_append_double(pt->header, "ESO TEL IA FWHMLIN", 0.86); /* Delivered seeing on IA detector (linear fit */
    cpl_propertylist_append_double(pt->header, "ESO TEL IA FWHMLINOBS", 0.94); /* Delivered seeing on IA detector (linear */
    break;

  case 2:
    cpl_propertylist_append_string(pt->header, "OBJECT", "COSMOS-GR28");   /* Original target. */
    cpl_propertylist_append_double(pt->header, "RA", 150.142072);          /* [deg] 10:00:34.0 RA (J2000) pointing */
    cpl_propertylist_append_double(pt->header, "DEC", 2.06645);            /* [deg] 02:03:59.2 DEC (J2000) pointing */
    cpl_propertylist_append_double(pt->header, "EXPTIME", 900.0000000);    /* Integration time */
    cpl_propertylist_append_double(pt->header, "MJD-OBS", 57127.01507257); /* Obs start */
    cpl_propertylist_append_string(pt->header, "DATE-OBS", "2015-04-15T00:21:42.270"); /* Observing date */
    cpl_propertylist_append_string(pt->header, "ESO INS DROT MODE", "SKY"); /* Instrument derotator mode */
    cpl_propertylist_append_double(pt->header, "ESO INS DROT POSANG", 90.5000); /* [deg] Derotator position angle */
    cpl_propertylist_append_string(pt->header, "ESO INS MODE", "NFM-AO-N");
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS AG FWHMX AVG", 0.790); /* [arcsec] AG FWHM X mean value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS AG FWHMX MAX", 0.920); /* [arcsec] AG FWHM X maximum value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS AG FWHMX MED", 0.788); /* [arcsec] AG FWHM X median value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS AG FWHMX MIN", 0.689); /* [arcsec] AG FWHM X minimum value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS AG FWHMX RMS", 0.049); /* [arcsec] AG FWHM X RMS value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS AG FWHMY AVG", 0.799); /* [arcsec] AG FWHM Y mean value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS AG FWHMY MAX", 0.938); /* [arcsec] AG FWHM Y maximum value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS AG FWHMY MED", 0.791); /* [arcsec] AG FWHM Y median value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS AG FWHMY MIN", 0.707); /* [arcsec] AG FWHM Y minimum value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS AG FWHMY RMS", 0.044); /* [arcsec] AG FWHM Y RMS value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS FWHM AVG", 0.048); /* [arcsec] SGS FWHM mean value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS FWHM MAX", 0.702); /* [arcsec] SGS FWHM maximum value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS FWHM MED", 0.000); /* [arcsec] SGS FWHM median value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS FWHM MIN", 0.000); /* [arcsec] SGS FWHM minimum value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS FWHM RMS", 0.169); /* [arcsec] SGS FWHM RMS value */
    cpl_propertylist_append_double(pt->header, "ESO TEL AMBI FWHM END", 0.91); /* [arcsec] Observatory Seeing queried from */
    cpl_propertylist_append_double(pt->header, "ESO TEL AMBI FWHM START", -1.00); /* [arcsec] Observatory Seeing queried f */
    cpl_propertylist_append_double(pt->header, "ESO TEL IA FWHM", 0.75); /* [arcsec] Delivered seeing corrected by airmass */
    cpl_propertylist_append_double(pt->header, "ESO TEL IA FWHMLIN", 0.78); /* Delivered seeing on IA detector (linear fit */
    cpl_propertylist_append_double(pt->header, "ESO TEL IA FWHMLINOBS", 0.84); /* Delivered seeing on IA detector (linear */
    break;

  case 3:
    cpl_propertylist_append_string(pt->header, "OBJECT", "COSMOS-GR28");   /* Original target. */
    cpl_propertylist_append_double(pt->header, "RA", 150.141952);          /* [deg] 10:00:34.0 RA (J2000) pointing */
    cpl_propertylist_append_double(pt->header, "DEC", 2.06668);            /* [deg] 02:04:00.0 DEC (J2000) pointing */
    cpl_propertylist_append_double(pt->header, "EXPTIME", 900.0000000);    /* Integration time */
    cpl_propertylist_append_double(pt->header, "MJD-OBS", 57127.02677628); /* Obs start */
    cpl_propertylist_append_string(pt->header, "DATE-OBS", "2015-04-15T00:38:33.470"); /* Observing date */
    cpl_propertylist_append_string(pt->header, "ESO INS DROT MODE", "SKY"); /* Instrument derotator mode */
    cpl_propertylist_append_double(pt->header, "ESO INS DROT POSANG", 180.5000); /* [deg] Derotator position angle */
    cpl_propertylist_append_string(pt->header, "ESO INS MODE", "NFM-AO-N");
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS AG FWHMX AVG", 0.825); /* [arcsec] AG FWHM X mean value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS AG FWHMX MAX", 0.991); /* [arcsec] AG FWHM X maximum value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS AG FWHMX MED", 0.829); /* [arcsec] AG FWHM X median value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS AG FWHMX MIN", 0.700); /* [arcsec] AG FWHM X minimum value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS AG FWHMX RMS", 0.050); /* [arcsec] AG FWHM X RMS value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS AG FWHMY AVG", 0.834); /* [arcsec] AG FWHM Y mean value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS AG FWHMY MAX", 0.962); /* [arcsec] AG FWHM Y maximum value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS AG FWHMY MED", 0.833); /* [arcsec] AG FWHM Y median value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS AG FWHMY MIN", 0.700); /* [arcsec] AG FWHM Y minimum value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS AG FWHMY RMS", 0.049); /* [arcsec] AG FWHM Y RMS value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS FWHM AVG", 0.705); /* [arcsec] SGS FWHM mean value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS FWHM MAX", 1.033); /* [arcsec] SGS FWHM maximum value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS FWHM MED", 0.688); /* [arcsec] SGS FWHM median value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS FWHM MIN", 0.520); /* [arcsec] SGS FWHM minimum value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS FWHM RMS", 0.106); /* [arcsec] SGS FWHM RMS value */
    cpl_propertylist_append_double(pt->header, "ESO TEL AMBI FWHM END", 0.97); /* [arcsec] Observatory Seeing queried from */
    cpl_propertylist_append_double(pt->header, "ESO TEL AMBI FWHM START", 0.88); /* [arcsec] Observatory Seeing queried fr */
    cpl_propertylist_append_double(pt->header, "ESO TEL IA FWHM", 0.87); /* [arcsec] Delivered seeing corrected by airmass */
    cpl_propertylist_append_double(pt->header, "ESO TEL IA FWHMLIN", 0.92); /* Delivered seeing on IA detector (linear fit */
    cpl_propertylist_append_double(pt->header, "ESO TEL IA FWHMLINOBS", 0.98); /* Delivered seeing on IA detector (linear */
    break;

  case 4:
    cpl_propertylist_append_string(pt->header, "OBJECT", "COSMOS-GR28");   /* Original target. */
    cpl_propertylist_append_double(pt->header, "RA", 150.141873);          /* [deg] 10:00:34.0 RA (J2000) pointing */
    cpl_propertylist_append_double(pt->header, "DEC", 2.06665);            /* [deg] 02:03:59.9 DEC (J2000) pointing */
    cpl_propertylist_append_double(pt->header, "EXPTIME", 900.0000000);    /* Integration time */
    cpl_propertylist_append_double(pt->header, "MJD-OBS", 57127.03846395); /* Obs start */
    cpl_propertylist_append_string(pt->header, "DATE-OBS", "2015-04-15T00:55:23.285"); /* Observing date */
    cpl_propertylist_append_string(pt->header, "ESO INS DROT MODE", "SKY"); /* Instrument derotator mode */
    cpl_propertylist_append_double(pt->header, "ESO INS DROT POSANG", 270.5000); /* [deg] Derotator position angle */
    cpl_propertylist_append_string(pt->header, "ESO INS MODE", "NFM-AO-N");
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS AG FWHMX AVG", 0.831); /* [arcsec] AG FWHM X mean value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS AG FWHMX MAX", 1.045); /* [arcsec] AG FWHM X maximum value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS AG FWHMX MED", 0.821); /* [arcsec] AG FWHM X median value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS AG FWHMX MIN", 0.700); /* [arcsec] AG FWHM X minimum value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS AG FWHMX RMS", 0.064); /* [arcsec] AG FWHM X RMS value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS AG FWHMY AVG", 0.857); /* [arcsec] AG FWHM Y mean value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS AG FWHMY MAX", 1.096); /* [arcsec] AG FWHM Y maximum value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS AG FWHMY MED", 0.846); /* [arcsec] AG FWHM Y median value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS AG FWHMY MIN", 0.742); /* [arcsec] AG FWHM Y minimum value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS AG FWHMY RMS", 0.072); /* [arcsec] AG FWHM Y RMS value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS FWHM AVG", 0.743); /* [arcsec] SGS FWHM mean value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS FWHM MAX", 1.066); /* [arcsec] SGS FWHM maximum value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS FWHM MED", 0.739); /* [arcsec] SGS FWHM median value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS FWHM MIN", 0.548); /* [arcsec] SGS FWHM minimum value */
    cpl_propertylist_append_double(pt->header, "ESO OCS SGS FWHM RMS", 0.091); /* [arcsec] SGS FWHM RMS value */
    cpl_propertylist_append_double(pt->header, "ESO TEL AMBI FWHM END", 1.01); /* [arcsec] Observatory Seeing queried from */
    cpl_propertylist_append_double(pt->header, "ESO TEL AMBI FWHM START", 0.93); /* [arcsec] Observatory Seeing queried fr */
    cpl_propertylist_append_double(pt->header, "ESO TEL IA FWHM", 1.02); /* [arcsec] Delivered seeing corrected by airmass */
    cpl_propertylist_append_double(pt->header, "ESO TEL IA FWHMLIN", 1.09); /* Delivered seeing on IA detector (linear fit */
    cpl_propertylist_append_double(pt->header, "ESO TEL IA FWHMLINOBS", 1.16); /* Delivered seeing on IA detector (linear */
  } /* switch */
  pt->table = muse_cpltable_new(muse_pixtable_def, 1);
  muse_pixtable_compute_limits(pt);
  cpl_propertylist *wcs = muse_wcs_create_default(NULL);
  muse_wcs_project_tan(pt, wcs);
  cpl_propertylist_delete(wcs);
  return pt;
} /* muse_test_xcombine_pixtable_create_cosmos() */

/*----------------------------------------------------------------------------*/
/**
  @brief    Test program to check that all the functions about pixel table
            combination work correctly.

  This program explicitely tests
    muse_xcombine_weights
    muse_xcombine_tables
    muse_xcombine_find_offsets
 */
/*----------------------------------------------------------------------------*/
int main(int argc, char **argv)
{
  UNUSED_ARGUMENTS(argc, argv);
  cpl_test_init(PACKAGE_BUGREPORT, CPL_MSG_DEBUG);

  /* create pixel tables to work with */
  muse_pixtable *pt1 = muse_test_xcombine_pixtable_create(1),
                *pt2 = muse_test_xcombine_pixtable_create(2),
                *pt3 = muse_test_xcombine_pixtable_create(3);

  /* test failure cases of muse_xcombine_weights() */
  cpl_errorstate es = cpl_errorstate_get();
  cpl_test(muse_xcombine_weights(NULL, MUSE_XCOMBINE_EXPTIME)
           == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(es);
  muse_pixtable *pts[] = { pt1, NULL, NULL, NULL, NULL };
  cpl_test(muse_xcombine_weights(pts, MUSE_XCOMBINE_EXPTIME)
           == CPL_ERROR_ILLEGAL_INPUT);
  cpl_errorstate_set(es);
  /* check the warning outputs visually! */
  pts[1] = pt2;
  pts[2] = pt3;
  cpl_test(muse_xcombine_weights(pts, MUSE_XCOMBINE_EXPTIME - 1)
           == CPL_ERROR_UNSUPPORTED_MODE);
  cpl_errorstate_set(es);
  cpl_test(muse_xcombine_weights(pts, MUSE_XCOMBINE_NONE + 1)
           == CPL_ERROR_UNSUPPORTED_MODE);
  cpl_errorstate_set(es);
  /* missing or zero exposure time */
  cpl_propertylist_erase_regexp(pt1->header, "EXPTIME", 0);
  es = cpl_errorstate_get();
  cpl_test(muse_xcombine_weights(pts, MUSE_XCOMBINE_EXPTIME)
           == CPL_ERROR_INCOMPATIBLE_INPUT);
  cpl_errorstate_set(es);
  cpl_propertylist_append_float(pt1->header, "EXPTIME", 0.);
  es = cpl_errorstate_get();
  cpl_test(muse_xcombine_weights(pts, MUSE_XCOMBINE_FWHM)
           == CPL_ERROR_INCOMPATIBLE_INPUT); /* same error, even with FWHM */
  cpl_errorstate_set(es);
  cpl_propertylist_update_float(pt1->header, "EXPTIME", 1.);
  /* missing header or negative value */
  es = cpl_errorstate_get();
  cpl_error_code rc = muse_xcombine_weights(pts, MUSE_XCOMBINE_HEADER);
  cpl_test(!cpl_errorstate_is_equal(es) && rc == CPL_ERROR_NONE &&
           cpl_error_get_code() == CPL_ERROR_DATA_NOT_FOUND);
  cpl_errorstate_set(es);
  cpl_propertylist_update_float(pt1->header, "ESO DRS MUSE WEIGHT", -1.);
  cpl_propertylist_update_float(pt2->header, "ESO DRS MUSE WEIGHT", 1.);
  cpl_propertylist_update_float(pt3->header, "ESO DRS MUSE WEIGHT", -5.);
  es = cpl_errorstate_get();
  rc = muse_xcombine_weights(pts, MUSE_XCOMBINE_HEADER);
  cpl_test(cpl_errorstate_is_equal(es) && rc == CPL_ERROR_NONE); /* just 2 warnings */
  cpl_errorstate_set(es);
  cpl_propertylist_erase(pt1->header, "ESO DRS MUSE WEIGHT");
  cpl_propertylist_erase(pt2->header, "ESO DRS MUSE WEIGHT");
  cpl_propertylist_erase(pt3->header, "ESO DRS MUSE WEIGHT");
  cpl_table_erase_column(pt1->table, MUSE_PIXTABLE_WEIGHT); /* erase the ... */
  cpl_table_erase_column(pt2->table, MUSE_PIXTABLE_WEIGHT); /* ... weights ... */
  cpl_table_erase_column(pt3->table, MUSE_PIXTABLE_WEIGHT); /* ... again */

  /* test success cases of muse_xcombine_weights() */
  es = cpl_errorstate_get();
  cpl_test(muse_xcombine_weights(pts, MUSE_XCOMBINE_NONE) == CPL_ERROR_NONE);
  cpl_test(cpl_errorstate_is_equal(es)); /* nothing changed ... */
  cpl_test(!cpl_table_has_column(pt1->table, MUSE_PIXTABLE_WEIGHT));
  cpl_test(!cpl_table_has_column(pt2->table, MUSE_PIXTABLE_WEIGHT));
  cpl_test(!cpl_table_has_column(pt3->table, MUSE_PIXTABLE_WEIGHT));
  es = cpl_errorstate_get();
  cpl_propertylist_update_float(pt1->header, "ESO DRS MUSE WEIGHT", 1.);
  cpl_propertylist_update_float(pt2->header, "ESO DRS MUSE WEIGHT", 0.5);
  cpl_propertylist_update_float(pt3->header, "ESO DRS MUSE WEIGHT", 2.);
  cpl_test(muse_xcombine_weights(pts, MUSE_XCOMBINE_HEADER) == CPL_ERROR_NONE);
  cpl_test(cpl_errorstate_is_equal(es));
  cpl_test(cpl_table_has_column(pt1->table, MUSE_PIXTABLE_WEIGHT));
  cpl_test(cpl_table_has_column(pt2->table, MUSE_PIXTABLE_WEIGHT));
  cpl_test(cpl_table_has_column(pt3->table, MUSE_PIXTABLE_WEIGHT));
  cpl_test(cpl_table_get_float(pt1->table, MUSE_PIXTABLE_WEIGHT, 0, NULL) == 1.);
  cpl_test(cpl_table_get_float(pt2->table, MUSE_PIXTABLE_WEIGHT, 0, NULL) == 0.5 * 2.);
  cpl_test(cpl_table_get_float(pt3->table, MUSE_PIXTABLE_WEIGHT, 0, NULL) == 2. * 3.);
  cpl_table_erase_column(pt1->table, MUSE_PIXTABLE_WEIGHT); /* erase the ... */
  cpl_table_erase_column(pt2->table, MUSE_PIXTABLE_WEIGHT); /* ... weights ... */
  cpl_table_erase_column(pt3->table, MUSE_PIXTABLE_WEIGHT); /* ... again */
  cpl_propertylist_erase(pt1->header, "ESO DRS MUSE WEIGHT");
  cpl_propertylist_erase(pt2->header, "ESO DRS MUSE WEIGHT");
  cpl_propertylist_erase(pt3->header, "ESO DRS MUSE WEIGHT");
  es = cpl_errorstate_get();
  cpl_test(muse_xcombine_weights(pts, MUSE_XCOMBINE_EXPTIME) == CPL_ERROR_NONE);
  cpl_test(cpl_errorstate_is_equal(es));
  cpl_test(cpl_table_has_column(pt1->table, MUSE_PIXTABLE_WEIGHT));
  cpl_test(cpl_table_has_column(pt2->table, MUSE_PIXTABLE_WEIGHT));
  cpl_test(cpl_table_has_column(pt3->table, MUSE_PIXTABLE_WEIGHT));
  cpl_test(cpl_table_get_float(pt1->table, MUSE_PIXTABLE_WEIGHT, 0, NULL) == 1.);
  cpl_test(cpl_table_get_float(pt2->table, MUSE_PIXTABLE_WEIGHT, 0, NULL) == 2.);
  cpl_test(cpl_table_get_float(pt3->table, MUSE_PIXTABLE_WEIGHT, 0, NULL) == 3.);

  /* add FWHM keywords, all exposures with average seeing of 1.0'' */
  cpl_table_erase_column(pt1->table, MUSE_PIXTABLE_WEIGHT); /* erase the ... */
  cpl_table_erase_column(pt2->table, MUSE_PIXTABLE_WEIGHT); /* ... weights ... */
  cpl_table_erase_column(pt3->table, MUSE_PIXTABLE_WEIGHT); /* ... again */
  cpl_propertylist_append_float(pt1->header, "ESO TEL AMBI FWHM START", 1.1);
  cpl_propertylist_append_float(pt1->header, "ESO TEL AMBI FWHM END", 0.9);
  cpl_propertylist_append_float(pt2->header, "ESO TEL AMBI FWHM START", 1.2);
  cpl_propertylist_append_float(pt2->header, "ESO TEL AMBI FWHM END", 0.8);
  cpl_propertylist_append_float(pt3->header, "ESO TEL AMBI FWHM START", 1.05);
  cpl_propertylist_append_float(pt3->header, "ESO TEL AMBI FWHM END", 0.95);
  es = cpl_errorstate_get();
  cpl_test(muse_xcombine_weights(pts, MUSE_XCOMBINE_FWHM) == CPL_ERROR_NONE);
  cpl_test(cpl_errorstate_is_equal(es));
  cpl_test(cpl_table_get_float(pt1->table, MUSE_PIXTABLE_WEIGHT, 0, NULL) == 1.);
  cpl_test(cpl_table_get_float(pt2->table, MUSE_PIXTABLE_WEIGHT, 0, NULL) == 2.);
  cpl_test(cpl_table_get_float(pt3->table, MUSE_PIXTABLE_WEIGHT, 0, NULL) == 3.);

  /* try with same exposure times, but decreasing seeing; *
   * leave the weight columns in place                    */
  cpl_propertylist_update_float(pt1->header, "EXPTIME", 1.0);
  cpl_propertylist_update_float(pt1->header, "ESO TEL AMBI FWHM START", 1.0);
  cpl_propertylist_update_float(pt1->header, "ESO TEL AMBI FWHM END", 1.0);
  cpl_propertylist_update_float(pt2->header, "EXPTIME", 1.0);
  cpl_propertylist_update_float(pt2->header, "ESO TEL AMBI FWHM START", 0.8);
  cpl_propertylist_update_float(pt2->header, "ESO TEL AMBI FWHM END", 0.8);
  cpl_propertylist_update_float(pt3->header, "EXPTIME", 1.0);
  cpl_propertylist_update_float(pt3->header, "ESO TEL AMBI FWHM START", 0.5);
  cpl_propertylist_update_float(pt3->header, "ESO TEL AMBI FWHM END", 0.5);
  es = cpl_errorstate_get();
  cpl_test(muse_xcombine_weights(pts, MUSE_XCOMBINE_FWHM) == CPL_ERROR_NONE);
  cpl_test(cpl_errorstate_is_equal(es));
  cpl_test(cpl_table_get_float(pt1->table, MUSE_PIXTABLE_WEIGHT, 0, NULL) == 1.);
  cpl_test(cpl_table_get_float(pt2->table, MUSE_PIXTABLE_WEIGHT, 0, NULL) == 1./0.8);
  cpl_test(cpl_table_get_float(pt3->table, MUSE_PIXTABLE_WEIGHT, 0, NULL) == 1./0.5);

  /* test partial success, with 2nd table missing FWHM info */
  cpl_propertylist_erase_regexp(pt2->header, "ESO TEL AMBI FWHM ", 0);
  es = cpl_errorstate_get();
  cpl_test(muse_xcombine_weights(pts, MUSE_XCOMBINE_FWHM) == CPL_ERROR_NONE);
  /* verify the warning visually */
  cpl_test(!cpl_errorstate_is_equal(es) &&
           cpl_error_get_code() == CPL_ERROR_DATA_NOT_FOUND);
  cpl_errorstate_set(es);
  cpl_test(cpl_table_get_float(pt1->table, MUSE_PIXTABLE_WEIGHT, 0, NULL) == 1.);
  cpl_test(cpl_table_get_float(pt2->table, MUSE_PIXTABLE_WEIGHT, 0, NULL) == 1.);
  cpl_test(cpl_table_get_float(pt3->table, MUSE_PIXTABLE_WEIGHT, 0, NULL) == 1.);

  /* test failure cases of muse_xcombine_tables() */
  es = cpl_errorstate_get();
  muse_pixtable *ptcomb = muse_xcombine_tables(NULL, NULL);
  cpl_test(!cpl_errorstate_is_equal(es) &&
           cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(es);
  pts[1] = NULL;
  pts[2] = NULL;
  ptcomb = muse_xcombine_tables(pts, NULL); /* only one input table */
  cpl_test(!cpl_errorstate_is_equal(es) &&
           cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
  cpl_errorstate_set(es);
  pts[1] = pt2;
  pts[2] = pt3;
  /* not yet projected (needs "rad" unit) */
  es = cpl_errorstate_get();
  ptcomb = muse_xcombine_tables(pts, NULL);
  cpl_test(!cpl_errorstate_is_equal(es) &&
           cpl_error_get_code() == CPL_ERROR_INCOMPATIBLE_INPUT);
  cpl_errorstate_set(es);
  cpl_test_null(ptcomb);

  /* test partly successful combination:            *
   * pretend projected first pixel table; duplicate *
   * to keep un-combined first pixtable around      */
  cpl_table_set_column_unit(pt1->table, MUSE_PIXTABLE_XPOS, "rad");
  cpl_table_set_column_unit(pt1->table, MUSE_PIXTABLE_YPOS, "rad");
  muse_pixtable *pt1copy = muse_pixtable_duplicate(pt1);
  es = cpl_errorstate_get();
  ptcomb = muse_xcombine_tables(pts, NULL); /* produces warning, check visually! */
  cpl_test(cpl_errorstate_is_equal(es));
  cpl_test_nonnull(ptcomb);
  if (ptcomb) {
    cpl_test(muse_pixtable_get_nrow(ptcomb) == 10); /* just 1st one */
    cpl_test(cpl_propertylist_get_int(ptcomb->header, MUSE_HDR_PT_COMBINED) == 1);
    muse_pixtable_delete(ptcomb);
  }
  pts[0] = pt1copy; /* reset the one that was consumed */
  pt1 = pt1copy;

  /* test fully successful case of muse_xcombine_tables() */
  cpl_table_set_column_unit(pt2->table, MUSE_PIXTABLE_XPOS, "rad");
  cpl_table_set_column_unit(pt2->table, MUSE_PIXTABLE_YPOS, "rad");
  cpl_table_set_column_unit(pt3->table, MUSE_PIXTABLE_XPOS, "rad");
  cpl_table_set_column_unit(pt3->table, MUSE_PIXTABLE_YPOS, "rad");
  /* set weights to 1., 2., and 3. in the three tables */
  cpl_table_fill_column_window_float(pts[0]->table, MUSE_PIXTABLE_WEIGHT, 0, 10, 1.);
  cpl_table_fill_column_window_float(pts[1]->table, MUSE_PIXTABLE_WEIGHT, 0, 10, 2.);
  cpl_table_fill_column_window_float(pts[2]->table, MUSE_PIXTABLE_WEIGHT, 0, 10, 3.);
  muse_pixtable *pt2copy = muse_pixtable_duplicate(pt2),
                *pt3copy = muse_pixtable_duplicate(pt3);
  es = cpl_errorstate_get();
  ptcomb = muse_xcombine_tables(pts, NULL);
  cpl_test(cpl_errorstate_is_equal(es));
  cpl_test_nonnull(ptcomb);
  if (ptcomb) {
    /* should be as long as the three individual ones */
    cpl_test(muse_pixtable_get_nrow(ptcomb) == 30);
    /* all three weights should be there equally */
    cpl_test(cpl_table_get_column_mean(ptcomb->table, MUSE_PIXTABLE_WEIGHT) == 2.);
    cpl_test_abs(cpl_table_get_column_stdev(ptcomb->table, MUSE_PIXTABLE_WEIGHT),
                 0.830454799, FLT_EPSILON);
    /* don't need to test correct positioning here, as   *
     * muse_wcs_position_celestial() is tested elsewhere */
    cpl_test(cpl_propertylist_get_int(ptcomb->header, MUSE_HDR_PT_COMBINED) == 3);
    muse_pixtable_delete(ptcomb);
  }
  /* another failure case: erase the weight columns */
  cpl_table_erase_column(pt2copy->table, MUSE_PIXTABLE_WEIGHT);
  cpl_table_erase_column(pt3copy->table, MUSE_PIXTABLE_WEIGHT);
  pts[0] = pt2copy;
  pts[1] = pt3copy;
  pts[2] = NULL;
  es = cpl_errorstate_get();
  ptcomb = muse_xcombine_tables(pts, NULL);
  cpl_test(cpl_errorstate_is_equal(es));
  cpl_test_nonnull(ptcomb);
  if (ptcomb) {
    /* should be as long as the two individual ones */
    cpl_test(muse_pixtable_get_nrow(ptcomb) == 20);
    /* no weights were created */
    cpl_test(!cpl_table_has_column(ptcomb->table, MUSE_PIXTABLE_WEIGHT));
    /* should have no OFFSET keywords in the header */
    cpl_propertylist_erase_regexp(ptcomb->header, "DRS MUSE OFFSET[0-9]", 1);
    cpl_test_eq(cpl_propertylist_get_size(ptcomb->header), 0);
    muse_pixtable_delete(ptcomb);
  }

  /* testing with offsets, create small pixel tables to work with, all   *
   * at the same RA/DEC position (equinox for easier testing) on the sky */
  pts[0] = muse_test_xcombine_pixtable_create_pos(1, 0., 0.);
  pts[1] = muse_test_xcombine_pixtable_create_pos(2, 0., 0.);
  pts[2] = muse_test_xcombine_pixtable_create_pos(3, 0., 0.);
  pts[3] = NULL;
  cpl_table *offsets = muse_cpltable_new(muse_offset_list_def, 3);
  cpl_table_set_string(offsets, MUSE_OFFSETS_DATEOBS, 0, muse_pfits_get_dateobs(pts[0]->header));
  cpl_table_set_string(offsets, MUSE_OFFSETS_DATEOBS, 1, muse_pfits_get_dateobs(pts[1]->header));
  cpl_table_set_string(offsets, MUSE_OFFSETS_DATEOBS, 2, muse_pfits_get_dateobs(pts[2]->header));
  cpl_table_set_double(offsets, MUSE_OFFSETS_DRA, 0, 0.);
  cpl_table_set_double(offsets, MUSE_OFFSETS_DRA, 1, 1e-5);
  cpl_table_set_double(offsets, MUSE_OFFSETS_DRA, 2, -1e-5);
  cpl_table_set_double(offsets, MUSE_OFFSETS_DDEC, 0, 0.);
  cpl_table_set_double(offsets, MUSE_OFFSETS_DDEC, 1, 2e-5);
  cpl_table_set_double(offsets, MUSE_OFFSETS_DDEC, 2, -2e-5);
  cpl_table_set_double(offsets, MUSE_OFFSETS_FSCALE, 0, 1.5);
  cpl_table_set_double(offsets, MUSE_OFFSETS_FSCALE, 1, 2.);
  cpl_table_set_double(offsets, MUSE_OFFSETS_FSCALE, 2, -2.);
  ptcomb = muse_xcombine_tables(pts, offsets);
  cpl_table_delete(offsets);
  cpl_test_nonnull(ptcomb);
  /* check the output positions, a few orders of magnitude above double   *
   * precision is OK here, since we are dealing with intermediate floats! */
  cpl_test_abs(cpl_table_get_float(ptcomb->table, MUSE_PIXTABLE_XPOS, 0, NULL), 0., 20.* DBL_EPSILON);
  cpl_test_abs(cpl_table_get_float(ptcomb->table, MUSE_PIXTABLE_YPOS, 0, NULL), 0., 20.* DBL_EPSILON);
  cpl_test_abs(cpl_table_get_float(ptcomb->table, MUSE_PIXTABLE_XPOS, 1, NULL), -1e-5, FLT_EPSILON);
  cpl_test_abs(cpl_table_get_float(ptcomb->table, MUSE_PIXTABLE_YPOS, 1, NULL), -2e-5, FLT_EPSILON);
  cpl_test_abs(cpl_table_get_float(ptcomb->table, MUSE_PIXTABLE_XPOS, 2, NULL), 1e-5, FLT_EPSILON);
  cpl_test_abs(cpl_table_get_float(ptcomb->table, MUSE_PIXTABLE_YPOS, 2, NULL), 2e-5, FLT_EPSILON);
  cpl_test_rel(cpl_table_get_float(ptcomb->table, MUSE_PIXTABLE_DATA, 0, NULL), 1.5, DBL_EPSILON);
  cpl_test_rel(cpl_table_get_float(ptcomb->table, MUSE_PIXTABLE_STAT, 0, NULL), 1.125, DBL_EPSILON);
  cpl_test_rel(cpl_table_get_float(ptcomb->table, MUSE_PIXTABLE_DATA, 1, NULL), 4., DBL_EPSILON);
  cpl_test_rel(cpl_table_get_float(ptcomb->table, MUSE_PIXTABLE_STAT, 1, NULL), 4., DBL_EPSILON);
  cpl_test_rel(cpl_table_get_float(ptcomb->table, MUSE_PIXTABLE_DATA, 2, NULL), -6., DBL_EPSILON);
  cpl_test_rel(cpl_table_get_float(ptcomb->table, MUSE_PIXTABLE_STAT, 2, NULL), 6., DBL_EPSILON);
  /* should have 9 OFFSET keywords in the header */
  cpl_propertylist *head = cpl_propertylist_duplicate(ptcomb->header);
  cpl_propertylist_erase_regexp(ptcomb->header, "DRS MUSE OFFSET[0-9]", 1);
  cpl_test_eq(cpl_propertylist_get_size(ptcomb->header), 9); /* erased all others */
  /* and 3 FLUX SCALE keywords */
  cpl_propertylist_erase_regexp(head, "DRS MUSE FLUX SCALE[0-9]", 1);
  cpl_test_eq(cpl_propertylist_get_size(head), 3);
  cpl_propertylist_delete(head);
  muse_pixtable_delete(ptcomb);

  /* position the next set of pixel tables very southern to check for delta-cos */
  double crval1 = 180.,
         crval2 = -75.;
  pt1 = muse_test_xcombine_pixtable_create_pos(1, crval1, crval2),
  pt2 = muse_test_xcombine_pixtable_create_pos(2, crval1, crval2),
  pt3 = muse_test_xcombine_pixtable_create_pos(3, crval1, crval2);
  pts[0] = muse_pixtable_duplicate(pt1);
  pts[1] = muse_pixtable_duplicate(pt2);
  pts[2] = muse_pixtable_duplicate(pt3);
  offsets = muse_cpltable_new(muse_offset_list_def, 5); /* with two extra entries */
  cpl_table_set_string(offsets, MUSE_OFFSETS_DATEOBS, 0, "2015-08-10T17:42:12.999");
  cpl_table_set_string(offsets, MUSE_OFFSETS_DATEOBS, 1, muse_pfits_get_dateobs(pts[0]->header));
  cpl_table_set_string(offsets, MUSE_OFFSETS_DATEOBS, 2, muse_pfits_get_dateobs(pts[1]->header));
  cpl_table_set_string(offsets, MUSE_OFFSETS_DATEOBS, 3, "2015-10-10T27:42:24.999");
  cpl_table_set_string(offsets, MUSE_OFFSETS_DATEOBS, 4, muse_pfits_get_dateobs(pts[2]->header));
  cpl_table_set_double(offsets, MUSE_OFFSETS_DRA, 0, 5e4); /* wrong */
  cpl_table_set_double(offsets, MUSE_OFFSETS_DRA, 1, 0.);
  cpl_table_set_double(offsets, MUSE_OFFSETS_DRA, 2, 1e-4);
  cpl_table_set_double(offsets, MUSE_OFFSETS_DRA, 3, 1e-3); /* wrong */
  cpl_table_set_double(offsets, MUSE_OFFSETS_DRA, 4, -1e-4);
  cpl_table_set_double(offsets, MUSE_OFFSETS_DDEC, 0, -9e5); /* wrong */
  cpl_table_set_double(offsets, MUSE_OFFSETS_DDEC, 1, 0.);
  cpl_table_set_double(offsets, MUSE_OFFSETS_DDEC, 2, -1e-5);
  cpl_table_set_double(offsets, MUSE_OFFSETS_DDEC, 3, 0.9e-3); /* wrong */
  cpl_table_set_double(offsets, MUSE_OFFSETS_DDEC, 4, 1e-5);
  cpl_table_set_double(offsets, MUSE_OFFSETS_FSCALE, 0, 0.0000001); /* wrong */
  cpl_table_set_double(offsets, MUSE_OFFSETS_FSCALE, 1, 1.);
  cpl_table_set_double(offsets, MUSE_OFFSETS_FSCALE, 2, 0.995);
  cpl_table_set_double(offsets, MUSE_OFFSETS_FSCALE, 3, -99.9); /* wrong */
  cpl_table_set_double(offsets, MUSE_OFFSETS_FSCALE, 4, 1.15);
  ptcomb = muse_xcombine_tables(pts, offsets);
  cpl_test_nonnull(ptcomb);
  /* check the output positions, a few orders of magnitude above double   *
   * precision is OK here, since we are dealing with intermediate floats! *
   * take care to check relative to CRVALi of the table!                  */
  cpl_test_abs(cpl_table_get_float(ptcomb->table, MUSE_PIXTABLE_XPOS, 0, NULL), 180.-crval1, 100.*DBL_EPSILON);
  cpl_test_abs(cpl_table_get_float(ptcomb->table, MUSE_PIXTABLE_YPOS, 0, NULL), -75.-crval2, 100.*DBL_EPSILON);
  cpl_test_abs(cpl_table_get_float(ptcomb->table, MUSE_PIXTABLE_XPOS, 1, NULL), 179.9999-crval1, 2e4*DBL_EPSILON);
  cpl_test_abs(cpl_table_get_float(ptcomb->table, MUSE_PIXTABLE_YPOS, 1, NULL), -74.99999-crval2, 2e4*DBL_EPSILON);
  cpl_test_abs(cpl_table_get_float(ptcomb->table, MUSE_PIXTABLE_XPOS, 2, NULL), 180.0001-crval1, 2e4*DBL_EPSILON);
  cpl_test_abs(cpl_table_get_float(ptcomb->table, MUSE_PIXTABLE_YPOS, 2, NULL), -75.00001-crval2, 2e4*DBL_EPSILON);
  cpl_test_abs(cpl_table_get_float(ptcomb->table, MUSE_PIXTABLE_DATA, 0, NULL), 1., DBL_EPSILON);
  cpl_test_abs(cpl_table_get_float(ptcomb->table, MUSE_PIXTABLE_STAT, 0, NULL), 0.5, DBL_EPSILON);
  cpl_test_abs(cpl_table_get_float(ptcomb->table, MUSE_PIXTABLE_DATA, 1, NULL), 1.99, FLT_EPSILON);
  cpl_test_abs(cpl_table_get_float(ptcomb->table, MUSE_PIXTABLE_STAT, 1, NULL), 0.990025, FLT_EPSILON);
  cpl_test_abs(cpl_table_get_float(ptcomb->table, MUSE_PIXTABLE_DATA, 2, NULL), 3.45, FLT_EPSILON);
  cpl_test_abs(cpl_table_get_float(ptcomb->table, MUSE_PIXTABLE_STAT, 2, NULL), 1.98375, FLT_EPSILON);
  muse_pixtable_delete(ptcomb);

  /* Check again, with the same pixel tables; this time give wrong DATE-OBS *
   * in the offset table, thereby causing the function to not use them.     */
  pts[0] = muse_pixtable_duplicate(pt1);
  pts[1] = muse_pixtable_duplicate(pt2);
  pts[2] = muse_pixtable_duplicate(pt3);
  /* also pretend that these tables were RV corrected                       *
   * --> make sure that in the output, the warnings disappear for this call */
  cpl_propertylist_append_double(pt1->header, MUSE_HDR_PT_RVCORR, 1.);
  cpl_propertylist_append_double(pt2->header, MUSE_HDR_PT_RVCORR, 2.);
  cpl_propertylist_append_double(pt3->header, MUSE_HDR_PT_RVCORR, 3.);
  cpl_table *offsets2 = cpl_table_duplicate(offsets),
            *offsets3 = cpl_table_duplicate(offsets);
  cpl_table_set_string(offsets2, MUSE_OFFSETS_DATEOBS, 1, "2015-10-10T17:42:24.111");
  cpl_table_set_string(offsets2, MUSE_OFFSETS_DATEOBS, 2, "2015-10-10T17:42:24.222");
  cpl_table_set_string(offsets2, MUSE_OFFSETS_DATEOBS, 4, "2015-10-10T17:42:24.333");
  ptcomb = muse_xcombine_tables(pts, offsets2);
  cpl_table_delete(offsets2);
  /* the positions and fluxes are unchanged */
  cpl_test_abs(cpl_table_get_column_mean(ptcomb->table, MUSE_PIXTABLE_XPOS), 180.-crval1, 100.*DBL_EPSILON);
  cpl_test_abs(cpl_table_get_column_mean(ptcomb->table, MUSE_PIXTABLE_YPOS), -75.-crval2, 100.*DBL_EPSILON);
  cpl_test_abs(cpl_table_get_column_mean(ptcomb->table, MUSE_PIXTABLE_DATA), 2., 100.*DBL_EPSILON);
  cpl_test_abs(cpl_table_get_column_mean(ptcomb->table, MUSE_PIXTABLE_STAT), 1., 100.*DBL_EPSILON);
  cpl_test_abs(cpl_table_get_float(ptcomb->table, MUSE_PIXTABLE_DATA, 0, NULL), 1., DBL_EPSILON);
  cpl_test_abs(cpl_table_get_float(ptcomb->table, MUSE_PIXTABLE_STAT, 0, NULL), 0.5, DBL_EPSILON);
  cpl_test_abs(cpl_table_get_float(ptcomb->table, MUSE_PIXTABLE_DATA, 1, NULL), 2., DBL_EPSILON);
  cpl_test_abs(cpl_table_get_float(ptcomb->table, MUSE_PIXTABLE_STAT, 1, NULL), 1., DBL_EPSILON);
  cpl_test_abs(cpl_table_get_float(ptcomb->table, MUSE_PIXTABLE_DATA, 2, NULL), 3., DBL_EPSILON);
  cpl_test_abs(cpl_table_get_float(ptcomb->table, MUSE_PIXTABLE_STAT, 2, NULL), 1.5, DBL_EPSILON);
  cpl_test_nonnull(ptcomb);
  /* should have no OFFSET keywords in the header */
  head = cpl_propertylist_duplicate(ptcomb->header);
  cpl_propertylist_erase_regexp(ptcomb->header, "DRS MUSE OFFSET[0-9]", 1);
  cpl_test_zero(cpl_propertylist_get_size(ptcomb->header)); /* erased all others */
  /* and no FLUX SCALE keywords */
  cpl_propertylist_erase_regexp(head, "DRS MUSE FLUX SCALE[0-9]", 1);
  cpl_test_zero(cpl_propertylist_get_size(head));
  cpl_propertylist_delete(head);
  muse_pixtable_delete(ptcomb);

  /* Check again, with the same pixel tables; this time give invalid entries *
   * in the offset table, thereby causing the function to not use them.      */
  pts[0] = pt1;
  pts[1] = pt2;
  pts[2] = pt3;
  cpl_table_set_invalid(offsets, MUSE_OFFSETS_DRA, 1);
  cpl_table_set_invalid(offsets, MUSE_OFFSETS_DDEC, 2);
  cpl_table_set_invalid(offsets, MUSE_OFFSETS_DRA, 4);
  cpl_table_set_invalid(offsets, MUSE_OFFSETS_DDEC, 4);
  cpl_table_set_column_invalid(offsets, MUSE_OFFSETS_FSCALE, 0,
                               cpl_table_get_nrow(offsets));
  ptcomb = muse_xcombine_tables(pts, offsets);
  /* check by eye that 4 WARNINGs appeared in the output */
  cpl_table_delete(offsets);
  /* the positions and fluxes are unchanged */
  cpl_test_abs(cpl_table_get_column_mean(ptcomb->table, MUSE_PIXTABLE_XPOS), 180.-crval1, 100.*DBL_EPSILON);
  cpl_test_abs(cpl_table_get_column_mean(ptcomb->table, MUSE_PIXTABLE_YPOS), -75.-crval2, 100.*DBL_EPSILON);
  cpl_test_abs(cpl_table_get_column_mean(ptcomb->table, MUSE_PIXTABLE_DATA), 2., 100.*DBL_EPSILON);
  cpl_test_abs(cpl_table_get_column_mean(ptcomb->table, MUSE_PIXTABLE_STAT), 1., 100.*DBL_EPSILON);
  cpl_test_abs(cpl_table_get_float(ptcomb->table, MUSE_PIXTABLE_DATA, 0, NULL), 1., DBL_EPSILON);
  cpl_test_abs(cpl_table_get_float(ptcomb->table, MUSE_PIXTABLE_STAT, 0, NULL), 0.5, DBL_EPSILON);
  cpl_test_abs(cpl_table_get_float(ptcomb->table, MUSE_PIXTABLE_DATA, 1, NULL), 2., DBL_EPSILON);
  cpl_test_abs(cpl_table_get_float(ptcomb->table, MUSE_PIXTABLE_STAT, 1, NULL), 1., DBL_EPSILON);
  cpl_test_abs(cpl_table_get_float(ptcomb->table, MUSE_PIXTABLE_DATA, 2, NULL), 3., DBL_EPSILON);
  cpl_test_abs(cpl_table_get_float(ptcomb->table, MUSE_PIXTABLE_STAT, 2, NULL), 1.5, DBL_EPSILON);
  cpl_test_nonnull(ptcomb);
  muse_pixtable_delete(ptcomb);

  /* test FWHM-based weighting with headers taken from COSMOS-GR28 data */
  pt1 = muse_test_xcombine_pixtable_create_cosmos(1);
  pt2 = muse_test_xcombine_pixtable_create_cosmos(2);
  pt3 = muse_test_xcombine_pixtable_create_cosmos(3);
  muse_pixtable *pt4 = muse_test_xcombine_pixtable_create_cosmos(4);
  pts[0] = pt1;
  pts[1] = pt2;
  pts[2] = pt3;
  pts[3] = pt4;
  es = cpl_errorstate_get();
  cpl_test(muse_xcombine_weights(pts, MUSE_XCOMBINE_FWHM) == CPL_ERROR_NONE);
  cpl_test(cpl_errorstate_is_equal(es));
  /* fully usable AG seeing values */
  cpl_test_abs(cpl_table_get_float(pt1->table, MUSE_PIXTABLE_WEIGHT, 0, NULL), 1., 0.001);
  cpl_test_abs(cpl_table_get_float(pt2->table, MUSE_PIXTABLE_WEIGHT, 0, NULL), 0.9553, 0.001);
  cpl_test_abs(cpl_table_get_float(pt3->table, MUSE_PIXTABLE_WEIGHT, 0, NULL), 0.9150, 0.001);
  cpl_test_abs(cpl_table_get_float(pt4->table, MUSE_PIXTABLE_WEIGHT, 0, NULL), 0.8993, 0.001);
  /* one broken AG seeing entry causes IA values to be used */
  cpl_propertylist_erase(pt2->header, "ESO OCS SGS AG FWHMY AVG");
  es = cpl_errorstate_get();
  cpl_test(muse_xcombine_weights(pts, MUSE_XCOMBINE_FWHM) == CPL_ERROR_NONE);
  cpl_test(cpl_errorstate_is_equal(es));
  cpl_test_abs(cpl_table_get_float(pt1->table, MUSE_PIXTABLE_WEIGHT, 0, NULL), 1., 0.001);
  cpl_test_abs(cpl_table_get_float(pt2->table, MUSE_PIXTABLE_WEIGHT, 0, NULL), 1.0667, 0.001);
  cpl_test_abs(cpl_table_get_float(pt3->table, MUSE_PIXTABLE_WEIGHT, 0, NULL), 0.9195, 0.001);
  cpl_test_abs(cpl_table_get_float(pt4->table, MUSE_PIXTABLE_WEIGHT, 0, NULL), 0.7843, 0.001);
  /* One broken IA.FWHM entry should cause DIMM values to be used, but *
   * since they are also bad, this falls back to no FWHM-weighting,    *
   * printing a warning message and setting "Data not found".          */
  cpl_propertylist_erase(pt4->header, "ESO TEL IA FWHM");
  es = cpl_errorstate_get();
  cpl_test(muse_xcombine_weights(pts, MUSE_XCOMBINE_FWHM) == CPL_ERROR_NONE);
  cpl_test(!cpl_errorstate_is_equal(es) &&
           cpl_error_get_code() == CPL_ERROR_DATA_NOT_FOUND);
  cpl_errorstate_set(es);
  cpl_test_eq(cpl_table_get_float(pt1->table, MUSE_PIXTABLE_WEIGHT, 0, NULL), 1.);
  cpl_test_eq(cpl_table_get_float(pt2->table, MUSE_PIXTABLE_WEIGHT, 0, NULL), 1.);
  cpl_test_eq(cpl_table_get_float(pt3->table, MUSE_PIXTABLE_WEIGHT, 0, NULL), 1.);
  cpl_test_eq(cpl_table_get_float(pt4->table, MUSE_PIXTABLE_WEIGHT, 0, NULL), 1.);
  /* adding some valid DIMM values should fix it again */
  cpl_propertylist_update_double(pt1->header, "ESO TEL AMBI FWHM START", 0.91);
  cpl_propertylist_update_double(pt2->header, "ESO TEL AMBI FWHM START", 0.89);
  cpl_propertylist_update_double(pt1->header, "ESO TEL AMBI FWHM END", 0.89);
  es = cpl_errorstate_get();
  cpl_test(muse_xcombine_weights(pts, MUSE_XCOMBINE_FWHM) == CPL_ERROR_NONE);
  cpl_test(cpl_errorstate_is_equal(es));
  cpl_test_abs(cpl_table_get_float(pt1->table, MUSE_PIXTABLE_WEIGHT, 0, NULL), 1., 0.001);
  cpl_test_abs(cpl_table_get_float(pt2->table, MUSE_PIXTABLE_WEIGHT, 0, NULL), 1., 0.001);
  cpl_test_abs(cpl_table_get_float(pt3->table, MUSE_PIXTABLE_WEIGHT, 0, NULL), 0.9730, 0.001);
  cpl_test_abs(cpl_table_get_float(pt4->table, MUSE_PIXTABLE_WEIGHT, 0, NULL), 0.9278, 0.001);
  muse_pixtable_delete(pt1);
  muse_pixtable_delete(pt2);
  muse_pixtable_delete(pt3);
  muse_pixtable_delete(pt4);
  pts[0] = NULL;
  pts[1] = NULL;
  pts[2] = NULL;
  pts[3] = NULL;

  /* test computation of the RTC Strehl ratio for the combined pixel table */
  pts[0] = muse_test_xcombine_pixtable_create_cosmos(1);
  pts[1] = muse_test_xcombine_pixtable_create_cosmos(2);
  pts[2] = muse_test_xcombine_pixtable_create_cosmos(3);
  cpl_propertylist_update_double(pts[0]->header, MUSE_HDR_RTC_STREHL, 1.);
  cpl_propertylist_update_double(pts[0]->header, MUSE_HDR_RTC_STREHLERR, 0.25);
  cpl_propertylist_update_double(pts[1]->header, MUSE_HDR_RTC_STREHL, 2.);
  cpl_propertylist_update_double(pts[1]->header, MUSE_HDR_RTC_STREHLERR, 0.1);
  cpl_propertylist_update_double(pts[2]->header, MUSE_HDR_RTC_STREHL, 3.);
  cpl_propertylist_update_double(pts[2]->header, MUSE_HDR_RTC_STREHLERR, 0.5);
  ptcomb = muse_xcombine_tables(pts, NULL);
  cpl_test_nonnull(ptcomb);
  cpl_test(cpl_propertylist_has(ptcomb->header, MUSE_HDR_RTC_STREHL) &&
           cpl_propertylist_has(ptcomb->header, MUSE_HDR_RTC_STREHLERR));
  /* check that the RTC Strehl keywords of the combined pixel table has *
   * the correct values                                                */
  cpl_test_abs(cpl_propertylist_get_double(ptcomb->header, MUSE_HDR_RTC_STREHL),
               1.9, 100. * DBL_EPSILON);
  cpl_test_abs(cpl_propertylist_get_double(ptcomb->header, MUSE_HDR_RTC_STREHLERR),
               0.091287, 1.e-6);
  muse_pixtable_delete(ptcomb);
  ptcomb = NULL;
  muse_pixtable_delete(pts[0]);
  muse_pixtable_delete(pts[1]);
  muse_pixtable_delete(pts[2]);
  /* test behavior if not all pixel tables provide the RTC Strehl header keywords */
  pts[0] = muse_test_xcombine_pixtable_create_cosmos(1);
  pts[1] = muse_test_xcombine_pixtable_create_cosmos(2);
  pts[2] = muse_test_xcombine_pixtable_create_cosmos(3);
  cpl_propertylist_update_double(pts[0]->header, MUSE_HDR_RTC_STREHL, 1.);
  cpl_propertylist_update_double(pts[0]->header, MUSE_HDR_RTC_STREHLERR, 0.25);
  cpl_propertylist_update_double(pts[2]->header, MUSE_HDR_RTC_STREHL, 3.);
  cpl_propertylist_update_double(pts[2]->header, MUSE_HDR_RTC_STREHLERR, 0.5);
  ptcomb = muse_xcombine_tables(pts, NULL);
  cpl_test_nonnull(ptcomb);
  cpl_test(cpl_propertylist_has(ptcomb->header, MUSE_HDR_RTC_STREHL) &&
           cpl_propertylist_has(ptcomb->header, MUSE_HDR_RTC_STREHLERR));
  /* check that the RTC Strehl keywords are still correct */
  cpl_test_abs(cpl_propertylist_get_double(ptcomb->header, MUSE_HDR_RTC_STREHL),
               1.4, 100. * DBL_EPSILON);
  cpl_test_abs(cpl_propertylist_get_double(ptcomb->header, MUSE_HDR_RTC_STREHLERR),
               0.223607, 1.e-6);
  muse_pixtable_delete(ptcomb);
  ptcomb = NULL;
  muse_pixtable_delete(pts[0]);
  muse_pixtable_delete(pts[1]);
  muse_pixtable_delete(pts[2]);
  pts[0] = muse_test_xcombine_pixtable_create_cosmos(1);
  pts[1] = muse_test_xcombine_pixtable_create_cosmos(2);
  pts[2] = muse_test_xcombine_pixtable_create_cosmos(3);
  cpl_propertylist_update_double(pts[2]->header, MUSE_HDR_RTC_STREHL, 3.);
  cpl_propertylist_update_double(pts[2]->header, MUSE_HDR_RTC_STREHLERR, 0.5);
  ptcomb = muse_xcombine_tables(pts, NULL);
  cpl_test_nonnull(ptcomb);
  cpl_test(cpl_propertylist_has(ptcomb->header, MUSE_HDR_RTC_STREHL) &&
           cpl_propertylist_has(ptcomb->header, MUSE_HDR_RTC_STREHLERR));
  /* check that the RTC Strehl keywords are still correct */
  cpl_test_abs(cpl_propertylist_get_double(ptcomb->header, MUSE_HDR_RTC_STREHL),
               3., 100. * DBL_EPSILON);
  cpl_test_abs(cpl_propertylist_get_double(ptcomb->header, MUSE_HDR_RTC_STREHLERR),
               0.5, 1.e-6);
  muse_pixtable_delete(ptcomb);
  ptcomb = NULL;
  muse_pixtable_delete(pts[0]);
  muse_pixtable_delete(pts[1]);
  muse_pixtable_delete(pts[2]);
  pts[0] = NULL;
  pts[1] = NULL;
  pts[2] = NULL;

  /* check muse_xcombine_find_offsets() separately */
  es = cpl_errorstate_get();
  double *off = muse_xcombine_find_offsets(offsets3, "2015-10-10T27:42:24.999");
  cpl_test(cpl_errorstate_is_equal(es));
  cpl_test_nonnull(off); /* found this one */
  cpl_test_eq(off[0], 1e-3);
  cpl_test_eq(off[1], 0.9e-3);
  cpl_test_eq(off[2], -99.9);
  cpl_free(off);
  off = muse_xcombine_find_offsets(offsets3, "2111-11-11T22:22:22.888");
  cpl_test_null(off); /* did not find this one */
  cpl_test(cpl_errorstate_is_equal(es)); /* still no error */
  /* failure cases */
  es = cpl_errorstate_get();
  off = muse_xcombine_find_offsets(NULL, "2111-11-11T22:22:22.888");
  cpl_test(!cpl_errorstate_is_equal(es) &&
           cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(es);
  cpl_test_null(off);
  off = muse_xcombine_find_offsets(offsets3, NULL);
  cpl_test(!cpl_errorstate_is_equal(es) &&
           cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(es);
  cpl_test_null(off);
  off = muse_xcombine_find_offsets(offsets3,
                                   "2111-11-11T22:22:22.6969696969696969696969696969696969696969696969696");
  cpl_test(!cpl_errorstate_is_equal(es) &&
           cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
  cpl_errorstate_set(es);
  cpl_test_null(off);
  off = muse_xcombine_find_offsets(offsets3, "2111-11-11T18:18:1");
  cpl_test(!cpl_errorstate_is_equal(es) &&
           cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
  cpl_errorstate_set(es);
  cpl_test_null(off);
  /* table with invalid elements in target row */
  cpl_table_set_invalid(offsets3, MUSE_OFFSETS_DRA, 0);
  cpl_table_set_invalid(offsets3, MUSE_OFFSETS_DDEC, 0);
  cpl_table_set_invalid(offsets3, MUSE_OFFSETS_FSCALE, 0);
  es = cpl_errorstate_get();
  off = muse_xcombine_find_offsets(offsets3, "2015-08-10T17:42:12.999");
  cpl_test_nonnull(off);
  cpl_test(cpl_errorstate_is_equal(es));
  cpl_test(isnan(off[0]));
  cpl_test(isnan(off[1]));
  cpl_test(isnan(off[2]));
  cpl_free(off);
  cpl_table_delete(offsets3);

  return cpl_test_end(0);
}
