/* -*- 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) 2015-16 European Southern Observatory
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#define _DEFAULT_SOURCE
#define _BSD_SOURCE /* get mkdtemp() from stdlib.h */
#include <stdlib.h>
#include <unistd.h> /* for chdir() */
#include <sys/stat.h> /* for mkdir() */
#include <string.h>

#include <muse.h>

#define DIR_TEMPLATE "/tmp/muse_utils_test_XXXXXX"

/* Compute mean of the data and standard-deviation of the slope over three *
 * small wavelength ranges. Using the slope instead of the direct stddev   *
 * should make this robust against the overall slope of the response or    *
 * flat-field curve.                                                       */
double *
muse_test_utils_table_stats(muse_table *aSpec)
{
  cpl_table *tin = aSpec->table;
  const char *colname = cpl_table_has_column(tin, "response") ? "response"
                                                              : "data";
  /* create column with slope, by shifting the data over 10 bins */
  cpl_table_duplicate_column(tin, "slope", tin, colname);
  cpl_table_shift_column(tin, "slope", 10);
  cpl_table_subtract_columns(tin, "slope", colname);
  double *stats = cpl_calloc(6, sizeof(double));
  cpl_table_select_all(tin);
  cpl_table_and_selected_double(tin, "lambda", CPL_GREATER_THAN, 4900.);
  cpl_table_and_selected_double(tin, "lambda", CPL_LESS_THAN, 5100.);
  cpl_table *tx = cpl_table_extract_selected(tin);
  stats[0] = cpl_table_get_column_mean(tx, colname),
  stats[1] = cpl_table_get_column_stdev(tx, "slope");
  cpl_table_delete(tx);
  cpl_table_select_all(tin);
  cpl_table_and_selected_double(tin, "lambda", CPL_GREATER_THAN, 6400.);
  cpl_table_and_selected_double(tin, "lambda", CPL_LESS_THAN, 6600.);
  tx = cpl_table_extract_selected(tin);
  stats[2] = cpl_table_get_column_mean(tx, colname),
  stats[3] = cpl_table_get_column_stdev(tx, "slope");
  cpl_table_delete(tx);
  cpl_table_select_all(tin);
  cpl_table_and_selected_double(tin, "lambda", CPL_GREATER_THAN, 7900.);
  cpl_table_and_selected_double(tin, "lambda", CPL_LESS_THAN, 8100.);
  tx = cpl_table_extract_selected(tin);
  stats[4] = cpl_table_get_column_mean(tx, colname),
  stats[5] = cpl_table_get_column_stdev(tx, "slope");
  cpl_table_delete(tx);
  cpl_table_erase_column(tin, "slope");
  return stats;
} /* muse_test_utils_table_stats() */

typedef struct {
  const char *fn;
  const char *tag;
  cpl_frame_group group;
} musefset;


static cpl_frameset *
muse_test_utils_new_frameset(musefset aSet[])
{
  cpl_frameset *frames = cpl_frameset_new();
  int i = 0, n = 0;
  while (aSet[i].fn) {
    cpl_frame *f = cpl_frame_new();
    cpl_frame_set_filename(f, aSet[i].fn);
    cpl_frame_set_tag(f, aSet[i].tag);
    cpl_frame_set_group(f, aSet[i].group);
    cpl_frameset_insert(frames, f);
    i++, n++;
  } /* while */
#if 0
  cpl_msg_debug(__func__, "%d frames", n);
#endif
  return frames;
} /* muse_test_utils_new_frameset() */

static char *
muse_test_utils_create_files(musefset aSet[])
{
  char dirtemplate[FILENAME_MAX] = DIR_TEMPLATE;
  char *dir = mkdtemp(dirtemplate);
  cpl_test_zero(chdir(dir));
  mkdir("RAW", S_IRWXU); /* only to be used by the creator */
  cpl_msg_debug(__func__, "Created dir \"%s/%s\"...", dir, "RAW");

  int i = 0;
  while (aSet[i].fn) {
    if (strstr(aSet[i].fn, "2016")) {
      /* get the 23chars starting with 2016 from the filename */
      char *dateobs = cpl_sprintf("%.23s", strstr(aSet[i].fn, "2016"));
      cpl_msg_debug(__func__, "%d %s", i, dateobs);
      cpl_propertylist *p = cpl_propertylist_new();
      cpl_propertylist_append_string(p, "FILENAME", aSet[i].fn);
      cpl_propertylist_append_string(p, "DATE-OBS", dateobs);
      cpl_free(dateobs);
      cpl_propertylist_save(p, aSet[i].fn, CPL_IO_CREATE);
      cpl_propertylist_delete(p);
    } /* filename with DATE-OBS */
    i++;
  } /* while */
  return cpl_strdup(dir);
} /* muse_test_utils_create_files() */

static cpl_error_code
muse_test_utils_delete_files(musefset aSet[], char *aDir)
{
  int i = 0;
  while (aSet[i].fn) {
    if (strstr(aSet[i].fn, "2016")) {
      remove(aSet[i].fn);
    } /* filename with DATE-OBS */
    i++;
  } /* while */
  cpl_test_zero(rmdir("RAW"));
  cpl_test_zero(rmdir(aDir));
  cpl_msg_debug(__func__, "... removed dir \"RAW\" and \"%s\".", aDir);
  return CPL_ERROR_NONE;
} /* muse_test_utils_delete_files() */

static cpl_error_code
muse_test_utils_compare_framesets(const cpl_frameset *aF1,
                                  const cpl_frameset *aF2)
{
  int i, nframes = cpl_frameset_get_size(aF1);
  if (cpl_frameset_get_size(aF2) != nframes) {
    cpl_msg_debug(__func__, "Inequal number of frames: %d %d", nframes,
                  (int)cpl_frameset_get_size(aF2));
    return CPL_ERROR_ILLEGAL_INPUT;
  }
#if 0
  cpl_frameset_dump(aF1, stdout);
  cpl_frameset_dump(aF2, stdout);
  fflush(stdout);
#endif
  for (i = 0; i < nframes; i++) {
    const cpl_frame *f1 = cpl_frameset_get_position_const(aF1, i),
                    *f2 = cpl_frameset_get_position_const(aF2, i);
    if (strcmp(cpl_frame_get_filename(f1), cpl_frame_get_filename(f2))) {
      cpl_msg_debug(__func__, "bad fn: %s %s", cpl_frame_get_filename(f1),
                    cpl_frame_get_filename(f2));
      return CPL_ERROR_INCOMPATIBLE_INPUT;
    }
    if (strcmp(cpl_frame_get_tag(f1), cpl_frame_get_tag(f2))) {
      cpl_msg_debug(__func__, "bad tag: %s %s", cpl_frame_get_tag(f1),
                    cpl_frame_get_tag(f2));
      return CPL_ERROR_INCOMPATIBLE_INPUT;
    }
#if 0
    cpl_msg_debug(__func__, "OK: %s, %s", cpl_frame_get_filename(f1),
                    cpl_frame_get_tag(f1));
#endif
  } /* for i (all frames) */
  return CPL_ERROR_NONE;
} /* muse_test_utils_compare_framesets() */

/*----------------------------------------------------------------------------*/
/**
  @brief    Test program to check that functions from muse_utils work when
            called with the necessary data.

  This program explicitely tests
    muse_utils_spectrum_smooth()
    muse_utils_frameset_merge_frames()   (but only for trivial cases!)
 */
/*----------------------------------------------------------------------------*/
int main(int argc, char **argv)
{
  UNUSED_ARGUMENTS(argc, argv);
  cpl_test_init(PACKAGE_BUGREPORT, CPL_MSG_DEBUG);

  /* Test muse_utils_spectrum_smooth() */
  cpl_errorstate ps = cpl_errorstate_get();
  cpl_error_code rc;
  /* try both response curves and flat-field spectra, *
   * for WFM-NOAO-N and WFM-AO-E                      */
  const char *tables[] = { "response_WFM-NOAO-N.fits",
                           "response_WFM-AO-E.fits",
                           "ffspec_WFM-AO-E.fits",
                           "ffspec_WFM-NOAO-N.fits",
                           NULL };
  int i;
  for (i = 0; tables[i] != NULL; i++) {
    char *fn = cpl_sprintf("%s_%s", BASEFILENAME, tables[i]);
    muse_table *spec = muse_table_load(fn, 0);
    cpl_boolean hasresp = cpl_table_has_column(spec->table, "response");
    cpl_msg_debug(__func__, "===== testing file \"%s\" === %s ===============",
                  fn, hasresp ? "response curve" : "flat-field spectrum");
    /* test effect of smoothing, but extracting a sub-table *
     * and measuring mean and standard deviation            */
    double *stats1 = muse_test_utils_table_stats(spec);
    cpl_msg_debug("stats1", "%f +/- %f, %f +/- %f, %f +/- %f",
                  stats1[0], stats1[1], stats1[2], stats1[3], stats1[4],
                  stats1[5]);
    rc = muse_utils_spectrum_smooth(spec, MUSE_SPECTRUM_SMOOTH_NONE);
    cpl_test(rc == CPL_ERROR_NONE && cpl_errorstate_is_equal(ps));
    double *stats2 = muse_test_utils_table_stats(spec);
    cpl_msg_debug("stats2", "%f +/- %f, %f +/- %f, %f +/- %f",
                  stats2[0], stats2[1], stats2[2], stats2[3], stats2[4],
                  stats2[5]);
    /* should all be equal to high precision */
    cpl_test_eq(stats1[0], stats2[0]);
    cpl_test_eq(stats1[1], stats2[1]);
    cpl_test_eq(stats1[2], stats2[2]);
    cpl_test_eq(stats1[3], stats2[3]);
    cpl_test_eq(stats1[4], stats2[4]);
    cpl_test_eq(stats1[5], stats2[5]);
    cpl_free(stats2);

    rc = muse_utils_spectrum_smooth(spec, MUSE_SPECTRUM_SMOOTH_MEDIAN);
    cpl_test(rc == CPL_ERROR_NONE && cpl_errorstate_is_equal(ps));
    double *stats3 = muse_test_utils_table_stats(spec);
    cpl_msg_debug("stats3", "%f +/- %f, %f +/- %f, %f +/- %f",
                  stats3[0], stats3[1], stats3[2], stats3[3], stats3[4],
                  stats3[5]);
    cpl_msg_debug("stats3", "means: %f %f %f, stdevs: %f %f %f",
                  stats1[0] - stats3[0], stats1[2] - stats3[2],
                  stats1[4] - stats3[4], stats1[1] - stats3[1],
                  stats1[3] - stats3[3], stats1[5] - stats3[5]);
    /* means should be almost the same, but stdev should now be smaller */
    cpl_test(fabs(stats1[0] - stats3[0]) < 0.0016);
    cpl_test(fabs(stats1[2] - stats3[2]) < 0.0016);
    cpl_test(fabs(stats1[4] - stats3[4]) < 0.001);
    if (hasresp) {
      cpl_test(stats1[1] > stats3[1]);
      cpl_test(stats1[3] > stats3[3]);
      cpl_test(stats1[5] > stats3[5]);
    } else { /* for flat-field spectra, the standard deviations *
              * don't change through median smoothing           */
      cpl_test_eq(stats1[1], stats3[1]);
      cpl_test_eq(stats1[3], stats3[3]);
      cpl_test_eq(stats1[5], stats3[5]);
    }
    cpl_free(stats3);
    muse_table_delete(spec);

    spec = muse_table_load(fn, 0);
    ps = cpl_errorstate_get();
    rc = muse_utils_spectrum_smooth(spec, MUSE_SPECTRUM_SMOOTH_PPOLY);
#if 0
    muse_table_save(spec, tables[i]);
#endif
    cpl_test(rc == CPL_ERROR_NONE && cpl_errorstate_is_equal(ps));
    double *stats4 = muse_test_utils_table_stats(spec);
    cpl_msg_debug("stats4", "%f +/- %f, %f +/- %f, %f +/- %f",
                  stats4[0], stats4[1], stats4[2], stats4[3], stats4[4],
                  stats4[5]);
    cpl_msg_debug("stats4", "means: %f %f %f, stdevs: %f %f %f",
                  stats1[0] - stats4[0], stats1[2] - stats4[2],
                  stats1[4] - stats4[4], stats1[1] - stats4[1],
                  stats1[3] - stats4[3], stats1[5] - stats4[5]);
    cpl_test(fabs(stats1[0] - stats4[0]) < 0.0016);
    cpl_test(stats1[1] > stats4[1]);
    cpl_test(fabs(stats1[2] - stats4[2]) < 0.0026); /* a bit bigger to pass */
    cpl_test(stats1[3] > stats4[3]);
    cpl_test(fabs(stats1[4] - stats4[4]) < 0.001);
    cpl_test(stats1[5] > stats4[5]);
    cpl_free(stats1);
    cpl_free(stats4);
    if (i == 0) {
      /* try with a broken table -> unsupported type */
      cpl_table_erase_column(spec->table, "lambda");
      ps = cpl_errorstate_get();
      rc = muse_utils_spectrum_smooth(spec, MUSE_SPECTRUM_SMOOTH_PPOLY);
      cpl_test(rc == CPL_ERROR_UNSUPPORTED_MODE && cpl_errorstate_is_equal(ps));
      cpl_errorstate_set(ps);
      /* also try with a response function without error column */
      muse_table_delete(spec);
      spec = muse_table_load(fn, 0);
      cpl_table_erase_column(spec->table, "resperr");
      ps = cpl_errorstate_get();
      rc = muse_utils_spectrum_smooth(spec, MUSE_SPECTRUM_SMOOTH_PPOLY);
      cpl_test(rc == CPL_ERROR_NONE && cpl_errorstate_is_equal(ps));
      muse_table_delete(spec);
      /* and the same but for median smoothing */
      spec = muse_table_load(fn, 0);
      cpl_table_erase_column(spec->table, "resperr");
      ps = cpl_errorstate_get();
      rc = muse_utils_spectrum_smooth(spec, MUSE_SPECTRUM_SMOOTH_MEDIAN);
      cpl_test(rc == CPL_ERROR_NONE && cpl_errorstate_is_equal(ps));
    } /* if i==0 */
    muse_table_delete(spec);
    cpl_free(fn);
  } /* for i (all tables) */

  /* Test trivial cases of muse_utils_frameset_merge_frames() */
  ps = cpl_errorstate_get();
  rc = muse_utils_frameset_merge_frames(NULL, CPL_TRUE);
  cpl_test(!cpl_errorstate_is_equal(ps) && rc == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(ps);
  /* check with existing but empty frameset */
  cpl_frameset *frameset = cpl_frameset_new();
  rc = muse_utils_frameset_merge_frames(frameset, CPL_TRUE);
  cpl_test(!cpl_errorstate_is_equal(ps) && rc == CPL_ERROR_ILLEGAL_INPUT);
  cpl_errorstate_set(ps);
  /* since testing on real files is too costly in development, *
   * make up a minimal frameset, first with one, then with two *
   * frames for very simple testing                            */
  cpl_frame *frame = cpl_frame_new();
  cpl_frame_set_filename(frame, "WAVECAL_TABLE-10.fits");
  cpl_frame_set_tag(frame, MUSE_TAG_WAVECAL_TABLE);
  cpl_frameset_insert(frameset, frame);
  rc = muse_utils_frameset_merge_frames(frameset, CPL_TRUE);
  cpl_test(rc == CPL_ERROR_NONE); /* nothing done (WARNING output) but OK */
  cpl_test_zero(strcmp(cpl_frame_get_filename(cpl_frameset_get_position(frameset, 0)),
                       "WAVECAL_TABLE-10.fits")); /* filename unchanged */
  /* now with 2nd frame */
  frame = cpl_frame_new();
  cpl_frame_set_filename(frame, "WAVECAL_TABLE-11.fits");
  cpl_frame_set_tag(frame, MUSE_TAG_WAVECAL_TABLE);
  cpl_frameset_insert(frameset, frame);
  int errstate = errno;
  rc = muse_utils_frameset_merge_frames(frameset, CPL_TRUE);
  cpl_test_noneq(errstate, errno); /* the C error state has changed! */
  cpl_test(rc == CPL_ERROR_NONE); /* nothing done (WARNING output) but OK */
  cpl_test_zero(strcmp(cpl_frame_get_filename(cpl_frameset_get_position(frameset, 0)),
                       "WAVECAL_TABLE-10.fits")); /* filename unchanged */
  cpl_test_zero(strcmp(cpl_frame_get_filename(cpl_frameset_get_position(frameset, 1)),
                       "WAVECAL_TABLE-11.fits")); /* filename unchanged */
  cpl_frameset_dump(frameset, stdout);
  fflush(stdout);
  cpl_frameset_delete(frameset);

  /* now test the same with pixel table frames */
  frameset = cpl_frameset_new();
  frame = cpl_frame_new();
  cpl_frame_set_filename(frame, "PIXTABLE_OBJECT-10.fits");
  cpl_frame_set_tag(frame, "PIXTABLE_OBJECT");
  cpl_frameset_insert(frameset, frame);
  rc = muse_utils_frameset_merge_frames(frameset, CPL_TRUE);
  cpl_test(rc == CPL_ERROR_NONE); /* nothing done (WARNING output) but OK */
  cpl_test_zero(strcmp(cpl_frame_get_filename(cpl_frameset_get_position(frameset, 0)),
                       "PIXTABLE_OBJECT-10.fits")); /* filename unchanged */

  frame = cpl_frame_new();
  cpl_frame_set_filename(frame, "PIXTABLE_OBJECT-11.fits");
  cpl_frame_set_tag(frame, "PIXTABLE_OBJECT");
  cpl_frameset_insert(frameset, frame);
  errstate = errno;
  rc = muse_utils_frameset_merge_frames(frameset, CPL_TRUE);
  cpl_test_eq(errstate, errno); /* no change this time, since merging not done */
  cpl_test(rc == CPL_ERROR_NONE); /* nothing done (WARNING output) but OK */
  cpl_test_zero(strcmp(cpl_frame_get_filename(cpl_frameset_get_position(frameset, 0)),
                       "PIXTABLE_OBJECT-10.fits")); /* filename unchanged */
  cpl_test_zero(strcmp(cpl_frame_get_filename(cpl_frameset_get_position(frameset, 1)),
                       "PIXTABLE_OBJECT-11.fits")); /* filename unchanged */
  cpl_frameset_dump(frameset, stdout);
  fflush(stdout);
  cpl_frameset_delete(frameset);

  /* test muse_frameset_sort_raw_other() with a few semi-real framesets */
  musefset fset1[] = { { "RAW/MUSE.2016-02-01T23:24:58.069.fits.fz", "ILLUM",           CPL_FRAME_GROUP_RAW },
                       { "RAW/MUSE.2016-02-01T23:34:54.532.fits.fz", "SKYFLAT",         CPL_FRAME_GROUP_RAW },
                       { "RAW/MUSE.2016-02-01T23:36:14.186.fits.fz", "SKYFLAT",         CPL_FRAME_GROUP_RAW },
                       { "RAW/MUSE.2016-02-01T23:37:34.014.fits.fz", "SKYFLAT",         CPL_FRAME_GROUP_RAW },
                       { "CALIB/MASTER_BIAS_02.fits",                "MASTER_BIAS",     CPL_FRAME_GROUP_CALIB },
                       { "CALIB/MASTER_FLAT_02e.fits",               "MASTER_FLAT",     CPL_FRAME_GROUP_CALIB },
                       { "CALIB/TRACE_TABLE_02e.fits",               "TRACE_TABLE",     CPL_FRAME_GROUP_CALIB },
                       { "CALIB/WAVECAL_TABLE_02e.fits",             "WAVECAL_TABLE",   CPL_FRAME_GROUP_CALIB },
                       { "muse-calib/vignetting_mask.fits",          "VIGNETTING_MASK", CPL_FRAME_GROUP_CALIB },
                       { "muse-calib/badpix_table.fits",             "BADPIX_TABLE",    CPL_FRAME_GROUP_CALIB },
                       { "muse-calib/geometry_table_wfm_gto11.fits", "GEOMETRY_TABLE",  CPL_FRAME_GROUP_CALIB },
                       { NULL,                                       NULL,              CPL_FRAME_GROUP_NONE }
                     },
           fset2[] = { { "RAW/MUSE.2016-02-01T23:34:54.532.fits.fz", "SKYFLAT",         CPL_FRAME_GROUP_NONE },
                       { "RAW/MUSE.2016-02-01T23:36:14.186.fits.fz", "SKYFLAT",         CPL_FRAME_GROUP_NONE },
                       { "RAW/MUSE.2016-02-01T23:37:34.014.fits.fz", "SKYFLAT",         CPL_FRAME_GROUP_NONE },
                       { "RAW/MUSE.2016-02-01T23:24:58.069.fits.fz", "ILLUM",           CPL_FRAME_GROUP_NONE },
                       { "muse-calib/badpix_table.fits",             "BADPIX_TABLE",    CPL_FRAME_GROUP_NONE },
                       { "muse-calib/geometry_table_wfm_gto11.fits", "GEOMETRY_TABLE",  CPL_FRAME_GROUP_NONE },
                       { "CALIB/MASTER_BIAS_02.fits",                "MASTER_BIAS",     CPL_FRAME_GROUP_NONE },
                       { "CALIB/MASTER_FLAT_02e.fits",               "MASTER_FLAT",     CPL_FRAME_GROUP_NONE },
                       { "CALIB/TRACE_TABLE_02e.fits",               "TRACE_TABLE",     CPL_FRAME_GROUP_NONE },
                       { "muse-calib/vignetting_mask.fits",          "VIGNETTING_MASK", CPL_FRAME_GROUP_NONE },
                       { "CALIB/WAVECAL_TABLE_02e.fits",             "WAVECAL_TABLE",   CPL_FRAME_GROUP_NONE },
                       { NULL,                                       NULL,              CPL_FRAME_GROUP_NONE }
                     };
  cpl_frameset *fin = muse_test_utils_new_frameset(fset1),
               *fout = muse_test_utils_new_frameset(fset2),
               *fsorted = muse_frameset_sort_raw_other(fin, 0, NULL, CPL_TRUE);
  cpl_frameset_delete(fin);
  cpl_test_eq(muse_test_utils_compare_framesets(fout, fsorted), CPL_ERROR_NONE);
  cpl_frameset_delete(fout);
  cpl_frameset_delete(fsorted);

  /* for the following we need a temporary directory */
  musefset fset3[] = { { "RAW/MUSE.2016-02-02T06:11:47.516.fits.fz", "ILLUM",          CPL_FRAME_GROUP_RAW },
                       { "RAW/MUSE.2016-02-02T08:42:35.875.fits.fz", "ILLUM",          CPL_FRAME_GROUP_RAW },
                       { "RAW/MUSE.2016-02-02T09:08:12.890.fits.fz", "STD",            CPL_FRAME_GROUP_RAW },
                       { "RAW/MUSE.2016-02-02T06:16:26.342.fits.fz", "OBJECT",         CPL_FRAME_GROUP_RAW },
                       { "RAW/MUSE.2016-02-02T06:40:32.217.fits.fz", "OBJECT",         CPL_FRAME_GROUP_RAW },
                       { "muse-calib/badpix_table.fits",             "BADPIX_TABLE",   CPL_FRAME_GROUP_CALIB },
                       { "CALIB/MASTER_BIAS_02.fits",                "MASTER_BIAS",    CPL_FRAME_GROUP_CALIB },
                       { "CALIB/MASTER_FLAT_02e.fits",               "MASTER_FLAT",    CPL_FRAME_GROUP_CALIB },
                       { "CALIB/TRACE_TABLE_02e.fits",               "TRACE_TABLE",    CPL_FRAME_GROUP_CALIB },
                       { "CALIB/WAVECAL_TABLE_02e.fits",             "WAVECAL_TABLE",  CPL_FRAME_GROUP_CALIB },
                       { "muse-calib/geometry_table_wfm_gto11.fits", "GEOMETRY_TABLE", CPL_FRAME_GROUP_CALIB },
                       { "CALIB/TWILIGHT_CUBE_02e.fits",             "TWILIGHT_CUBE",  CPL_FRAME_GROUP_CALIB },
                       { NULL,                                       NULL,             CPL_FRAME_GROUP_NONE }
                     },
           fset4[] = { { "RAW/MUSE.2016-02-02T06:40:32.217.fits.fz", "OBJECT",         CPL_FRAME_GROUP_NONE },
                       { "RAW/MUSE.2016-02-02T06:11:47.516.fits.fz", "ILLUM",          CPL_FRAME_GROUP_NONE },
                       { "muse-calib/badpix_table.fits",             "BADPIX_TABLE",   CPL_FRAME_GROUP_NONE },
                       { "muse-calib/geometry_table_wfm_gto11.fits", "GEOMETRY_TABLE", CPL_FRAME_GROUP_NONE },
                       { "CALIB/MASTER_BIAS_02.fits",                "MASTER_BIAS",    CPL_FRAME_GROUP_NONE },
                       { "CALIB/MASTER_FLAT_02e.fits",               "MASTER_FLAT",    CPL_FRAME_GROUP_NONE },
                       { "CALIB/TRACE_TABLE_02e.fits",               "TRACE_TABLE",    CPL_FRAME_GROUP_NONE },
                       { "CALIB/TWILIGHT_CUBE_02e.fits",             "TWILIGHT_CUBE",  CPL_FRAME_GROUP_NONE },
                       { "CALIB/WAVECAL_TABLE_02e.fits",             "WAVECAL_TABLE",  CPL_FRAME_GROUP_NONE },
                       { NULL,                                       NULL,             CPL_FRAME_GROUP_NONE }
                     };
  char *dir = muse_test_utils_create_files(fset3);
  fin = muse_test_utils_new_frameset(fset3);
  fout = muse_test_utils_new_frameset(fset4);
  ps = cpl_errorstate_get();
  fsorted = muse_frameset_sort_raw_other(fin, 2, "2016-02-02T06:40:32.217", CPL_FALSE);
  cpl_test_eq(muse_test_utils_compare_framesets(fout, fsorted), CPL_ERROR_NONE);
  cpl_test(cpl_errorstate_is_equal(ps));
  cpl_frameset_delete(fsorted);
  /* failure case: wrongly assumed exposure sequence */
  fsorted = muse_frameset_sort_raw_other(fin, 2, "2016-02-02T06:40:32.217", CPL_TRUE);
  ps = cpl_errorstate_get();
  rc = muse_test_utils_compare_framesets(fout, fsorted);
  cpl_test(cpl_errorstate_is_equal(ps) && rc == CPL_ERROR_ILLEGAL_INPUT); /* wrong number found */
  cpl_frameset_delete(fin);
  cpl_frameset_delete(fout);
  cpl_frameset_delete(fsorted);
  muse_test_utils_delete_files(fset3, dir);
  cpl_free(dir);

  return cpl_test_end(0);
}
