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

#define _BSD_SOURCE /* get mkdtemp() from stdlib.h */
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>

#include <muse.h>

#define DIR_TEMPLATE "/tmp/muse_image_test_XXXXXX"
#define OBJECT_STRING "Test muse_image"

static const char *chipdates[] = { "2014-02-06T11:16:57.850", "2014-02-06T11:16:57.850",
                                   "2014-02-06T11:16:57.871", "2014-02-06T11:16:57.757",
                                   "2014-02-06T11:16:57.870" },
                  *chipids[] = { "psyche", "victoria", "flora", "juno", "vesta" };

static void
image_merged_create(const char *aFilename)
{
  cpl_propertylist *header = cpl_propertylist_new();
  /* create and save a primary FITS header, containing some *
   * typical properties present in merged MUSE output files */
  cpl_propertylist_append_string(header, "DATE-OBS", "2014-02-06T11:16:57.408");
  cpl_propertylist_append_float(header, "EXPTIME", 0.);
  cpl_propertylist_append_bool(header, "INHERIT", CPL_TRUE);
  cpl_propertylist_append_string(header, "INSTRUME", "MUSE");
  cpl_propertylist_append_float(header, "MJD-OBS", 56694.4701089);
  cpl_propertylist_append_string(header, "OBJECT", "BIAS");
  cpl_propertylist_append_string(header, "OBSERVER", "UNKNOWN");
  cpl_propertylist_append_string(header, "ORIGIN", "TEST");
  cpl_propertylist_append_string(header, "PI-COI", "UNKNOWN");
  cpl_propertylist_append_string(header, "PIPEFILE", "MASTER_BIAS_merged.fits");
  cpl_propertylist_append_string(header, "TELESCOP", "ESO-VLT-U4");
  cpl_propertylist_append_string(header, "ESO OBS DID", "ESO-VLT-DIC.OBS-1.12");
  cpl_propertylist_append_int(header, "ESO OBS ID", 200302797);
  cpl_propertylist_append_string(header, "ESO OBS NAME", "REF_CALIB_SET_WFM");
  cpl_propertylist_append_int(header, "ESO OBS PI-COI ID", 79816);
  cpl_propertylist_append_string(header, "ESO OBS PROG ID", "60.A-9100(A)");
  cpl_propertylist_append_string(header, "ESO TPL DID", "ESO-VLT-DIC.TPL-1.9");
  cpl_propertylist_append_string(header, "ESO TPL ID", "MUSE_cal_bias");
  cpl_propertylist_append_string(header, "ESO TPL NAME", "Perform bias exposures");
  cpl_propertylist_append_float(header, "ESO GEN MOON PHASE", 0.22);
  cpl_propertylist_append_string(header, "ESO INS AMPL2 FILTER", "Kron_V");
  cpl_propertylist_append_string(header, "ESO INS AMPL2 ID", "ampl2");
  cpl_propertylist_append_bool(header, "ESO INS AMPL2 SWSIM", CPL_FALSE);
  cpl_propertylist_append_string(header, "ESO INS DATE", "2000-06-16");
  cpl_propertylist_append_int(header, "ESO DET BINX", 1);
  cpl_propertylist_append_int(header, "ESO DET BINY", 1);
  cpl_propertylist_append_int(header, "ESO DET CHIPS", 24);
  cpl_propertylist_append_float(header, "ESO DET DIT1", 0.);
  cpl_propertylist_append_float(header, "ESO DET DKTM", 0.);
  cpl_propertylist_append_int(header, "ESO DET EXP NO", 305);
  cpl_propertylist_append_string(header, "ESO DET EXP TYPE", "Bias");
  cpl_propertylist_append_int(header, "ESO DET NDIT", 1);
  cpl_propertylist_append_int(header, "ESO DET OUTPUTS", 96);
  cpl_propertylist_append_int(header, "ESO DET READ CURID", 1);
  cpl_propertylist_append_string(header, "ESO DET READ CURNAME", "SCI1.0");
  cpl_propertylist_append_float(header, "ESO DET UIT1", 0.);
  cpl_propertylist_append_int(header, "ESO DET2 CHIP1 NX", 1024);
  cpl_propertylist_append_int(header, "ESO DET2 CHIP1 NY", 1024);
  cpl_propertylist_append_string(header, "ESO PRO CATG", "MASTER_BIAS");
  cpl_propertylist_append_int(header, "ESO PRO DATANCOM", 11);
  cpl_propertylist_append_string(header, "ESO PRO DID", MUSE_PRO_DID);
  cpl_propertylist_append_string(header, "ESO PRO REC1 DRS ID", "cpl-6.5");
  cpl_propertylist_append_string(header, "ESO PRO REC1 ID", "muse_bias");
  cpl_propertylist_append_bool(header, "ESO PRO SCIENCE", CPL_FALSE);
  cpl_propertylist_append_string(header, "ESO PRO TECH", "IFU");
  cpl_propertylist_append_string(header, "ESO PRO TYPE", "REDUCED");
  cpl_propertylist_append_string(header, "ESO OCS DET IMGNAME", "MUSE_CAL_BIAS");
  cpl_propertylist_append_float(header, "ESO OCS DROT POSANG", 720.);
  cpl_propertylist_append_float(header, "ESO OCS IPS PIXSCALE", 0.2);
  cpl_propertylist_append_string(header, "ESO OCS SGS ALGO NAME", "CORRELATOR");
  cpl_propertylist_append_string(header, "ESO OCS TEL ACCESS", "IGNORE");
  cpl_propertylist_save(header, aFilename, CPL_IO_CREATE);

  /* create new header for each IFU */
  unsigned char ifu;
  for (ifu = 1; ifu <= 5; ifu++) {
    char kw[KEYWORD_LENGTH];

    cpl_propertylist_empty(header);
    snprintf(kw, KEYWORD_LENGTH, "CHAN%02hhu.DATA", ifu);
    cpl_propertylist_append_string(header, "EXTNAME", kw);
    cpl_propertylist_append_string(header, "BUNIT", "adu");
    snprintf(kw, KEYWORD_LENGTH, "CHAN%02hhu.STAT", ifu);
    cpl_propertylist_append_string(header, "ERRDATA", kw);
    cpl_propertylist_append_string(header, "HDUCLAS1", "IMAGE");
    cpl_propertylist_append_string(header, "HDUCLAS2", "DATA");
    cpl_propertylist_append_string(header, "HDUCLASS", "ESO");
    cpl_propertylist_append_string(header, "HDUDOC", "DICD");
    cpl_propertylist_append_string(header, "HDUVERS", "DICD version 6");
    snprintf(kw, KEYWORD_LENGTH, "CHAN%02hhu.DQ", ifu);
    cpl_propertylist_append_string(header, "QUALDATA", kw);
    cpl_propertylist_append_string(header, "ESO DET CHIP DATE", chipdates[ifu - 1]);
    cpl_propertylist_append_string(header, "ESO DET CHIP ID", chipids[ifu - 1]);
    cpl_propertylist_append_int(header, "ESO DET CHIP INDEX", 6);
    cpl_propertylist_append_bool(header, "ESO DET CHIP LIVE", CPL_TRUE);
    snprintf(kw, KEYWORD_LENGTH, "CHAN%02hhu", ifu);
    cpl_propertylist_append_string(header, "ESO DET CHIP NAME", kw);
    cpl_propertylist_append_int(header, "ESO DET CHIP NX", 100);
    cpl_propertylist_append_int(header, "ESO DET CHIP NY", 50);
    cpl_propertylist_append_float(header, "ESO DET CHIP PSZX", 15.);
    cpl_propertylist_append_float(header, "ESO DET CHIP PSZY", 15.);
    cpl_propertylist_append_float(header, "ESO DET CHIP RGAP", 0.);
    cpl_propertylist_append_int(header, "ESO DET CHIP X", 6);
    cpl_propertylist_append_float(header, "ESO DET CHIP XGAP", 0.);
    cpl_propertylist_append_int(header, "ESO DET CHIP Y", 6);
    cpl_propertylist_append_float(header, "ESO DET CHIP YGAP", 0.);
    cpl_propertylist_append_float(header, "ESO DET OUT1 CONAD", 0.94);
    cpl_propertylist_append_float(header, "ESO DET OUT1 GAIN", 1.06);
    cpl_propertylist_append_int(header, "ESO DET OUT4 X", 1);
    cpl_propertylist_append_int(header, "ESO DET OUT4 Y", 4112);
    cpl_propertylist_append_int(header, "ESO QC BIAS INPUT1 NSATURATED", 0);
    cpl_propertylist_append_int(header, "ESO QC BIAS MASTER NBADPIX", 0);
    cpl_propertylist_append_int(header, "ESO QC BIAS MASTER NSATURATED", 0);
    cpl_propertylist_append_float(header, "ESO QC BIAS MASTER1 MAX", 1228.578);
    cpl_propertylist_append_float(header, "ESO QC BIAS MASTER1 MEAN", 1202.896);
    cpl_propertylist_append_float(header, "ESO QC BIAS MASTER1 MEDIAN", 1202.922);
    cpl_propertylist_append_float(header, "ESO QC BIAS MASTER1 MIN", 1199.23);
    cpl_propertylist_append_float(header, "ESO QC BIAS MASTER1 RON", 2.107114);
    cpl_propertylist_append_float(header, "ESO QC BIAS MASTER1 RONERR", 0.07949691);
    cpl_propertylist_append_float(header, "ESO DRS MUSE OVSC1 MEAN", 1202.9);
    cpl_propertylist_append_float(header, "ESO DRS MUSE OVSC1 STDEV", 0.597856);
    cpl_image *imdata = cpl_image_new(100, 50, CPL_TYPE_FLOAT);
    cpl_image_add_scalar(imdata, ifu);
    cpl_image_save(imdata, aFilename, CPL_TYPE_UNSPECIFIED, header, CPL_IO_EXTEND);
    cpl_image_delete(imdata);

    cpl_propertylist_empty(header);
    snprintf(kw, KEYWORD_LENGTH, "CHAN%02hhu.DQ", ifu);
    cpl_propertylist_append_string(header, "EXTNAME", kw);
    snprintf(kw, KEYWORD_LENGTH, "CHAN%02hhu.STAT", ifu);
    cpl_propertylist_append_string(header, "ERRDATA", kw);
    cpl_propertylist_append_string(header, "HDUCLAS1", "IMAGE");
    cpl_propertylist_append_string(header, "HDUCLAS2", "QUALITY");
    cpl_propertylist_append_string(header, "HDUCLAS3", "FLAG32BIT");
    cpl_propertylist_append_string(header, "HDUCLASS", "ESO");
    cpl_propertylist_append_string(header, "HDUDOC", "DICD");
    cpl_propertylist_append_string(header, "HDUVERS", "DICD version 6");
    cpl_propertylist_append_long(header, "QUALMASK", 4294967295);
    snprintf(kw, KEYWORD_LENGTH, "CHAN%02hhu.DATA", ifu);
    cpl_propertylist_append_string(header, "SCIDATA", kw);
    cpl_image *imdq = cpl_image_new(100, 50, CPL_TYPE_INT);
    cpl_image_set(imdq, ifu, ifu, ifu);
    cpl_image_save(imdq, aFilename, CPL_TYPE_UNSPECIFIED, header, CPL_IO_EXTEND);
    cpl_image_delete(imdq);

    cpl_propertylist_empty(header);
    snprintf(kw, KEYWORD_LENGTH, "CHAN%02hhu.STAT", ifu);
    cpl_propertylist_append_string(header, "EXTNAME", kw);
    cpl_propertylist_append_string(header, "BUNIT", "adu**2");
    cpl_propertylist_append_string(header, "HDUCLAS1", "IMAGE");
    cpl_propertylist_append_string(header, "HDUCLAS2", "ERROR");
    cpl_propertylist_append_string(header, "HDUCLAS3", "MSE");
    cpl_propertylist_append_string(header, "HDUCLASS", "ESO");
    cpl_propertylist_append_string(header, "HDUDOC", "DICD");
    cpl_propertylist_append_string(header, "HDUVERS", "DICD version 6");
    snprintf(kw, KEYWORD_LENGTH, "CHAN%02hhu.DQ", ifu);
    cpl_propertylist_append_string(header, "QUALDATA", kw);
    snprintf(kw, KEYWORD_LENGTH, "CHAN%02hhu.DATA", ifu);
    cpl_propertylist_append_string(header, "SCIDATA", kw);
    cpl_image *imstat = cpl_image_new(100, 50, CPL_TYPE_FLOAT);
    cpl_image_add_scalar(imstat, ifu*ifu);
    cpl_image_save(imstat, aFilename, CPL_TYPE_UNSPECIFIED, header, CPL_IO_EXTEND);
    cpl_image_delete(imstat);
  } /* for ifu */
  cpl_propertylist_delete(header);
} /* image_merged_create() */

static void
image_merged_check(muse_image *aImage, unsigned char aIFU)
{
  char kw[KEYWORD_LENGTH];
  snprintf(kw, KEYWORD_LENGTH, "CHAN%02hhu", aIFU);
  cpl_test_eq_string(cpl_propertylist_get_string(aImage->header, "ESO DET CHIP NAME"),
                     kw);
  cpl_test_eq_string(cpl_propertylist_get_string(aImage->header, "ESO DET CHIP DATE"),
                     chipdates[aIFU - 1]);
  cpl_test_eq_string(cpl_propertylist_get_string(aImage->header, "ESO DET CHIP ID"),
                     chipids[aIFU - 1]);
  cpl_test_abs(cpl_image_get_mean(aImage->data), aIFU, FLT_EPSILON);
  int err;
  cpl_test_eq(cpl_image_get(aImage->dq, aIFU, aIFU, &err), aIFU);
  cpl_test_abs(cpl_image_get_mean(aImage->stat), aIFU*aIFU, FLT_EPSILON);
} /* image_merged_check() */

/*----------------------------------------------------------------------------*/
/**
  @brief    Test program to check that all the functions working on muse_images
            work correctly.

  This program explicitely tests:
    muse_image_new
    muse_image_delete
    muse_image_load
    muse_image_load_from_extensions
    muse_image_load_from_raw
    muse_image_save
    muse_image_duplicate
    muse_image_subtract
    muse_image_divide
    muse_image_scale
    muse_image_adu_to_count
    muse_image_reject_from_dq
    muse_image_dq_to_nan
 */
/*----------------------------------------------------------------------------*/
int main(int argc, char **argv)
{
  UNUSED_ARGUMENTS(argc, argv);
  cpl_test_init(PACKAGE_BUGREPORT, CPL_MSG_DEBUG);

  /* create a muse_image */
  muse_image *image = muse_image_new();
  /* now the muse_image should be non-NULL and the components should be NULL */
  cpl_test_nonnull(image);
  cpl_test(image->data == NULL && image->dq == NULL && image->stat == NULL);
  cpl_test_null(image->header);

  /* test freeing when the muse_image doesn't actually contain an image */
  cpl_errorstate state = cpl_errorstate_get();
  muse_image_delete(image);
  image = NULL;
  cpl_test(cpl_errorstate_is_equal(state));


  /* now create a muse_image with content */
  int nx = 10, ny = 5;
  image = muse_image_new();
  /* add noice in the data extension */
  image->data = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
  cpl_test_nonnull(image->data);
  cpl_image_fill_noise_uniform(image->data, 100, 200);
  /* create the variance to go with this noise: assume GAIN=2, RON=4, BIAS=50 */
  image->stat = cpl_image_duplicate(image->data);
  cpl_test_nonnull(image->stat);
  cpl_image_subtract_scalar(image->stat, 50);
  cpl_image_divide_scalar(image->stat, 2);
  cpl_image_add_scalar(image->stat, 4);
  /* let's set pixel (5,2) as hot */
  image->dq = cpl_image_new(nx, ny, CPL_TYPE_INT);
  cpl_test_nonnull(image->dq);
  cpl_image_set(image->dq, 5, 2, 256);
  /* add the headers, and set at least the OBJECT header */
  image->header = cpl_propertylist_new();
  cpl_test_nonnull(image->header);
  cpl_propertylist_append_string(image->header, "OBJECT", OBJECT_STRING);

  /* see if CPL bpm rejection works to mark that one pixel as bad */
  cpl_test(muse_image_reject_from_dq(image) == CPL_ERROR_NONE);
  cpl_test(cpl_image_is_rejected(image->data, 5, 2) &&
           cpl_image_is_rejected(image->stat, 5, 2));
  cpl_test(cpl_image_count_rejected(image->data) == 1 &&
           cpl_image_count_rejected(image->stat) == 1);
  /* test failure cases for this function */
  state = cpl_errorstate_get();
  cpl_test(muse_image_reject_from_dq(NULL) == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(state);
  cpl_image *cplimage = image->dq;
  image->dq = NULL;
  cpl_test(muse_image_reject_from_dq(image) == CPL_ERROR_NULL_INPUT);
  image->dq = cplimage;
  cpl_errorstate_set(state);
  cplimage = image->data;
  image->data = NULL;
  cpl_test(muse_image_reject_from_dq(image) == CPL_ERROR_NULL_INPUT);
  image->data = cplimage;
  cpl_errorstate_set(state);
  /* should still work with the stat component NULLed */
  cplimage = image->stat;
  image->stat = NULL;
  cpl_test(muse_image_reject_from_dq(image) == CPL_ERROR_NONE);
  image->stat = cplimage;

  /* save the image to a (temporary) file */
  char dirtemplate[FILENAME_MAX] = DIR_TEMPLATE;
  char *dir = mkdtemp(dirtemplate);
  char *file = cpl_sprintf("%s/%s", dir, "muse_image.fits");
  /* add EXTNAME header that should be recovered below when loading again */
  cpl_propertylist_update_string(image->header, "EXTNAME", "CHAN22");
  cpl_propertylist_append_string(image->header, "BUNIT", "count");
  state = cpl_errorstate_get();
  cpl_error_code err = muse_image_save(image, (const char *)file);
  cpl_test(cpl_errorstate_is_equal(state));
  cpl_test(err == CPL_ERROR_NONE);
  cpl_msg_info(__func__, "Written to \"%s\"", file);
  struct stat sb;
  cpl_test_zero(stat(file, &sb));
  /* the file written should be a normal file with size greater than zero... */
  cpl_test(S_ISREG(sb.st_mode) && sb.st_size > 0);
  /* ... and contain three image extensions */
  cpl_test(cpl_fits_count_extensions(file) == 3);
  /* test for EXTNAME headers */
  cpl_propertylist *hdata = cpl_propertylist_load(file, 1),
                   *hdq = cpl_propertylist_load(file, 2),
                   *hstat = cpl_propertylist_load(file, 3);
  cpl_test(!strcmp("DATA", cpl_propertylist_get_string(hdata, "EXTNAME")) &&
           !strcmp("DQ", cpl_propertylist_get_string(hdq, "EXTNAME")) &&
           !strcmp("STAT", cpl_propertylist_get_string(hstat, "EXTNAME")));
  /* test for ESO format headers produced by muse_image_save() */
  cpl_test(!strcmp("ESO", cpl_propertylist_get_string(hdata, "HDUCLASS")) &&
           !strcmp("ESO", cpl_propertylist_get_string(hdq, "HDUCLASS")) &&
           !strcmp("ESO", cpl_propertylist_get_string(hstat, "HDUCLASS")));
  cpl_test(!strcmp("DICD", cpl_propertylist_get_string(hdata, "HDUDOC")) &&
           !strcmp("DICD", cpl_propertylist_get_string(hdq, "HDUDOC")) &&
           !strcmp("DICD", cpl_propertylist_get_string(hstat, "HDUDOC")));
  cpl_test(!strncmp("DICD version ", cpl_propertylist_get_string(hdata, "HDUVERS"), 13) &&
           !strncmp("DICD version ", cpl_propertylist_get_string(hdq, "HDUVERS"), 13) &&
           !strncmp("DICD version ", cpl_propertylist_get_string(hstat, "HDUVERS"), 13));
  cpl_test(!strcmp("IMAGE", cpl_propertylist_get_string(hdata, "HDUCLAS1")) &&
           !strcmp("IMAGE", cpl_propertylist_get_string(hdq, "HDUCLAS1")) &&
           !strcmp("IMAGE", cpl_propertylist_get_string(hstat, "HDUCLAS1")));
  cpl_test(!strcmp("DATA", cpl_propertylist_get_string(hdata, "HDUCLAS2")) &&
           !strcmp("QUALITY", cpl_propertylist_get_string(hdq, "HDUCLAS2")) &&
           !strcmp("ERROR", cpl_propertylist_get_string(hstat, "HDUCLAS2")));
  cpl_test(!cpl_propertylist_has(hdata, "HDUCLAS3") && /* shouldn't exist */
           !strcmp("FLAG32BIT", cpl_propertylist_get_string(hdq, "HDUCLAS3")) &&
           !strcmp("MSE", cpl_propertylist_get_string(hstat, "HDUCLAS3")));
  cpl_test(!strcmp("STAT", cpl_propertylist_get_string(hdata, "ERRDATA")) &&
           !strcmp("DQ", cpl_propertylist_get_string(hdata, "QUALDATA")) &&
           !cpl_propertylist_has(hdata, "SCIDATA"));
  cpl_test(!strcmp("DATA", cpl_propertylist_get_string(hstat, "SCIDATA")) &&
           !strcmp("DQ", cpl_propertylist_get_string(hstat, "QUALDATA")) &&
           !cpl_propertylist_has(hstat, "ERRDATA"));
  cpl_test(!strcmp("DATA", cpl_propertylist_get_string(hdq, "SCIDATA")) &&
           !strcmp("STAT", cpl_propertylist_get_string(hdq, "ERRDATA")) &&
           !cpl_propertylist_has(hdq, "QUALDATA"));
  cpl_test(cpl_propertylist_has(hdq, "QUALMASK"));
  cpl_propertylist_delete(hdata);
  cpl_propertylist_delete(hdq);
  cpl_propertylist_delete(hstat);
  /* add two additional extensions to the file that contain dead CCDs, *
   * once with the proper header, once without                         */
  cpl_propertylist *header = cpl_propertylist_new();
  cpl_propertylist_append_bool(header, "ESO DET CHIP LIVE", CPL_FALSE);
  cpl_propertylist_save(header, file, CPL_IO_EXTEND);
  cpl_propertylist_set_bool(header, "ESO DET CHIP LIVE", CPL_TRUE);
  cpl_propertylist_save(header, file, CPL_IO_EXTEND);
  cpl_propertylist_delete(header);

  /* try to load the file again that we just saved */
  muse_image *imagel = muse_image_load(file);
  cpl_test_nonnull(imagel);
  cpl_test_nonnull(imagel->header);
  const char *object = cpl_propertylist_get_string(imagel->header, "OBJECT"),
             *extname = cpl_propertylist_get_string(imagel->header, "EXTNAME"),
             *unit = muse_pfits_get_bunit(imagel->header);
  cpl_test_zero(strncmp(object, OBJECT_STRING, strlen(OBJECT_STRING) + 1));
  cpl_test_zero(strncmp(extname, "CHAN22", 7));
  cpl_test_zero(strncmp(unit, "count", 6));
  cpl_test_image_abs(image->data, imagel->data, FLT_EPSILON);
  cpl_test_image_abs(image->dq, imagel->dq, 1);
  cpl_test_image_abs(image->stat, imagel->stat, FLT_EPSILON);
  /* test failure of loading a file without BUNIT */
  /* copy the file to another name, and overwrite the first *
   * "BUNIT" with something else to trigger that case       */
  char *file2 = cpl_sprintf("%s/%s", dir, "muse_image_no_bunit.fits");
  stat(file, &sb);
  char *fcontents = cpl_calloc(sb.st_size + 1, 1); /* file-sized buffer */
  FILE *fin = fopen(file, "r"), /* read-only */
       *fout = fopen(file2, "w"); /* wrote to this filename */
  size_t nitems = fread(fcontents, sb.st_size, 1, fin);
  cpl_test(nitems == 1);
  fclose(fin);
  int i = 0;
  do {
    i += 80; /* no data section to where we want to go, just step 80 chars */
  } while (i < sb.st_size - 80 && strncmp(fcontents + i, "BUNIT", 5));
  fcontents[i] = 'X'; /* make BUNIT into XUNIT */
  nitems = fwrite(fcontents, sb.st_size, 1, fout);
  cpl_test(nitems == 1);
  cpl_free(fcontents);
  fclose(fout);
  /* now do that actual test */
  state = cpl_errorstate_get();
  muse_image *imagef = muse_image_load(file2); /* check that a warning is printed */
  cpl_test(cpl_errorstate_is_equal(state));
  cpl_test_nonnull(imagef); /* should still be loaded */
  muse_image_delete(imagef);
  remove(file2);
  cpl_free(file2);
  /* test failure cases */
  state = cpl_errorstate_get();
  imagef = muse_image_load(NULL); /* NULL filename */
  cpl_test(!cpl_errorstate_is_equal(state) &&
           cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(state);
  cpl_test_null(imagef);
  imagef = muse_image_load("someimage.fits"); /* non-existing file */
  cpl_test(!cpl_errorstate_is_equal(state) &&
           cpl_error_get_code() == CPL_ERROR_FILE_IO);
  cpl_errorstate_set(state);
  cpl_test_null(imagef);
  /* save a file with bad extension names */
  char *filef = cpl_sprintf("%s/%s", dir, "muse_image_broken.fits");
  cpl_propertylist_save(image->header, filef, CPL_IO_CREATE);
  cpl_propertylist *headf = cpl_propertylist_new();
  cpl_propertylist_append_string(headf, "EXTNAME", "DATA");
  cpl_propertylist_append_string(headf, "BUNIT", "count");
  cpl_image_save(image->data, filef, CPL_TYPE_UNSPECIFIED, headf, CPL_IO_EXTEND);
  cpl_propertylist_update_string(headf, "EXTNAME", "DQ");
  cpl_image_save(image->dq, filef, CPL_TYPE_UNSPECIFIED, headf, CPL_IO_EXTEND);
  cpl_propertylist_erase_regexp(headf, "EXTNAME", 0);
  cpl_image_save(image->stat, filef, CPL_TYPE_UNSPECIFIED, headf, CPL_IO_EXTEND);
  cpl_propertylist_delete(headf);
  state = cpl_errorstate_get();
  imagef = muse_image_load(filef);
  cpl_test(!cpl_errorstate_is_equal(state) &&
           cpl_error_get_code() == MUSE_ERROR_READ_STAT);
  cpl_errorstate_set(state);
  cpl_test_null(imagef);
  remove(filef);
  /* save the file again, with missing extensions */
  cpl_propertylist_save(image->header, filef, CPL_IO_CREATE);
  headf = cpl_propertylist_new();
  cpl_propertylist_append_string(headf, "EXTNAME", "DATA");
  cpl_image_save(image->data, filef, CPL_TYPE_UNSPECIFIED, headf, CPL_IO_EXTEND);
  cpl_propertylist_update_string(headf, "EXTNAME", "DQ");
  cpl_image_save(image->dq, filef, CPL_TYPE_UNSPECIFIED, headf, CPL_IO_EXTEND);
  state = cpl_errorstate_get();
  imagef = muse_image_load(filef); /* missing stat extension */
  cpl_test(!cpl_errorstate_is_equal(state) &&
           cpl_error_get_code() == MUSE_ERROR_READ_STAT);
  cpl_errorstate_set(state);
  cpl_test_null(imagef);
  remove(filef);
  cpl_propertylist_save(image->header, filef, CPL_IO_CREATE);
  cpl_propertylist_update_string(headf, "EXTNAME", "DATA");
  cpl_image_save(image->data, filef, CPL_TYPE_UNSPECIFIED, headf, CPL_IO_EXTEND);
  state = cpl_errorstate_get();
  imagef = muse_image_load(filef); /* missing dq and stat extensions */
  cpl_test(!cpl_errorstate_is_equal(state) &&
           cpl_error_get_code() == MUSE_ERROR_READ_DQ);
  cpl_errorstate_set(state);
  cpl_test_null(imagef);
  remove(filef);
  cpl_propertylist_save(image->header, filef, CPL_IO_CREATE);
  cpl_propertylist_update_string(headf, "EXTNAME", "DATA_"); /* 1 extra char */
  cpl_image_save(image->data, filef, CPL_TYPE_UNSPECIFIED, headf, CPL_IO_EXTEND);
  state = cpl_errorstate_get();
  imagef = muse_image_load(filef); /* missing dq and stat extensions */
  cpl_test(!cpl_errorstate_is_equal(state) &&
           cpl_error_get_code() == MUSE_ERROR_READ_DATA);
  cpl_errorstate_set(state);
  cpl_test_null(imagef);
  remove(filef);
  cpl_propertylist_save(image->header, filef, CPL_IO_CREATE);
  state = cpl_errorstate_get();
  imagef = muse_image_load(filef); /* all extensions missing */
  cpl_test(!cpl_errorstate_is_equal(state) &&
           cpl_error_get_code() == MUSE_ERROR_READ_DATA);
  cpl_errorstate_set(state);
  cpl_test_null(imagef);
  remove(filef);
  cpl_propertylist_delete(headf);

  state = cpl_errorstate_get();
  cpl_propertylist_erase(image->header, "BUNIT");
  err = muse_image_save(image, "some_valid_name.fits");
  cpl_test(!cpl_errorstate_is_equal(state) && err == CPL_ERROR_INCOMPATIBLE_INPUT);
  cpl_errorstate_set(state);
  cpl_propertylist_append_string(image->header, "BUNIT", "count");
  state = cpl_errorstate_get();
  /* test other failure cases of muse_image_save(), too */
  state = cpl_errorstate_get();
  err = muse_image_save(NULL, filef);
  cpl_test(!cpl_errorstate_is_equal(state) && err == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(state);
  muse_image *imgbrk = muse_image_duplicate(image);
  cpl_image_delete(imgbrk->data);
  imgbrk->data = NULL;
  state = cpl_errorstate_get();
  err = muse_image_save(imgbrk, "some_nice_name.fits"); /* missing data component */
  cpl_test(!cpl_errorstate_is_equal(state) && err == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(state);
  muse_image_delete(imgbrk);
  err = muse_image_save(image, NULL);
  cpl_test(!cpl_errorstate_is_equal(state) && err == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(state);
  err = muse_image_save(image, "/bla_fail/bla.fits"); /* inaccessibly write location */
  cpl_test(!cpl_errorstate_is_equal(state) && err == CPL_ERROR_FILE_IO);
  cpl_errorstate_set(state);
  /* in turn cast the image extensions to complex types, to make saving of    *
   * those components fail (apparently other "normal" types still don't cause *
   * an error in cpl_image_save() when other types are given in the argument) */
  imgbrk = muse_image_duplicate(image);
  cpl_image_delete(imgbrk->data);
  imgbrk->data = cpl_image_cast(image->data, CPL_TYPE_FLOAT_COMPLEX);
  state = cpl_errorstate_get();
  err = muse_image_save(imgbrk, filef); /* wrongly typed data component */
  cpl_test(!cpl_errorstate_is_equal(state) && err == CPL_ERROR_INVALID_TYPE);
  cpl_errorstate_set(state);
  muse_image_delete(imgbrk);
  imgbrk = muse_image_duplicate(image);
  cpl_image_delete(imgbrk->dq);
  imgbrk->dq = cpl_image_cast(image->dq, CPL_TYPE_FLOAT_COMPLEX);
  state = cpl_errorstate_get();
  err = muse_image_save(imgbrk, filef); /* wrongly typed dq component */
  cpl_test(!cpl_errorstate_is_equal(state) && err == CPL_ERROR_INVALID_TYPE);
  cpl_errorstate_set(state);
  muse_image_delete(imgbrk);
  imgbrk = muse_image_duplicate(image);
  cpl_image_delete(imgbrk->stat);
  imgbrk->stat = cpl_image_cast(image->stat, CPL_TYPE_DOUBLE_COMPLEX);
  state = cpl_errorstate_get();
  err = muse_image_save(imgbrk, filef); /* wrongly typed stat component */
  cpl_test(!cpl_errorstate_is_equal(state) && err == CPL_ERROR_INVALID_TYPE);
  cpl_errorstate_set(state);
  muse_image_delete(imgbrk);
  remove(filef);
  cpl_free(filef);

  /* test freeing when the muse_image is filled */
  state = cpl_errorstate_get();
  muse_image_delete(imagel);
  imagel = NULL;
  cpl_test(cpl_errorstate_is_equal(state));
  state = cpl_errorstate_get();
  muse_image_delete(NULL); /* should work with NULL input */
  cpl_test(cpl_errorstate_is_equal(state));

  /* To test muse_image_load_from_extensions() generate an image plus headers *
   * similar to the ones from a MASTER_BIAS created from Comm1 data.          */
  char *filem = cpl_sprintf("%s/%s", dir, "muse_image_merged.fits");
  image_merged_create(filem);
  state = cpl_errorstate_get();
  muse_image *imagem = muse_image_load_from_extensions(filem, 1);
  cpl_test(cpl_errorstate_is_equal(state));
  image_merged_check(imagem, 1);
  muse_image_delete(imagem);
  state = cpl_errorstate_get();
  imagem = muse_image_load_from_extensions(filem, 3);
  cpl_test(cpl_errorstate_is_equal(state));
  image_merged_check(imagem, 3);
  muse_image_delete(imagem);
  state = cpl_errorstate_get();
  imagem = muse_image_load_from_extensions(filem, 5);
  cpl_test(cpl_errorstate_is_equal(state));
  image_merged_check(imagem, 5);
  muse_image_delete(imagem);
  /* failure cases follow */
  state = cpl_errorstate_get();
  imagem = muse_image_load_from_extensions(filem, 6); /* wrong IFU number (for this file) */
  cpl_test(!cpl_errorstate_is_equal(state) &&
           cpl_error_get_code() == MUSE_ERROR_READ_DATA);
  cpl_errorstate_set(state);
  cpl_test_null(imagem);
  state = cpl_errorstate_get();
  imagem = muse_image_load_from_extensions("/kkkdj_bla.fits", 2); /* non-existing file */
  cpl_test(!cpl_errorstate_is_equal(state) &&
           cpl_error_get_code() == CPL_ERROR_FILE_IO);
  cpl_errorstate_set(state);
  cpl_test_null(imagem);
  state = cpl_errorstate_get();
  imagem = muse_image_load_from_extensions(NULL, 3); /* NULL-filename passed */
  cpl_test(!cpl_errorstate_is_equal(state) &&
           cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(state);
  cpl_test_null(imagem);
  remove(filem);
  cpl_free(filem);

  /* To test muse_image_load_from_raw() */
  muse_image *image_test = muse_image_load_from_raw(file, 1);
  cpl_test(cpl_image_get_absflux(image_test->stat) == 0.0);
  cpl_test(cpl_image_get_absflux(image_test->dq) == 0.0);
  cpl_test_zero(muse_image_subtract(image_test, image));
  cpl_test(cpl_image_get_absflux(image_test->data) == 0.0);
  cpl_test(cpl_image_get_absflux(image_test->stat)
           == cpl_image_get_absflux(image->stat));
  cpl_test_zero(strncmp(muse_pfits_get_bunit(image_test->header), "adu", 4));
  muse_image_delete(image_test);
  cpl_errorstate prev_state = cpl_errorstate_get();
  /* load non-existing extension from existing file */
  image_test = muse_image_load_from_raw(file, 25);
  cpl_test(image_test == NULL && !cpl_errorstate_is_equal(prev_state));
  cpl_errorstate_set(prev_state);
  /* try to load from non-existing file */
  image_test = muse_image_load_from_raw("blabla", 1);
  cpl_test(image_test == NULL && !cpl_errorstate_is_equal(prev_state));
  cpl_errorstate_set(prev_state);
  /* try to load from extension with dead CCD */
  image_test = muse_image_load_from_raw(file, 4);
  cpl_test(image_test == NULL && !cpl_errorstate_is_equal(prev_state) &&
           cpl_error_get_code() == MUSE_ERROR_CHIP_NOT_LIVE);
  cpl_errorstate_set(prev_state);
  /* dito, but this header does not have the correct keyword */
  image_test = muse_image_load_from_raw(file, 5);
  cpl_test(image_test == NULL && !cpl_errorstate_is_equal(prev_state) &&
           cpl_error_get_code() != MUSE_ERROR_CHIP_NOT_LIVE);
  cpl_errorstate_set(prev_state);

  /* now we can remove the file again, use the return values to check that *
   * nothing else went wrong with the file in the meantime                 */
  cpl_test_zero(remove(file));
  cpl_test_zero(rmdir(dir));
  cpl_free(file);


  /* test failure case of muse_image_duplicate() */
  state = cpl_errorstate_get();
  imagef = muse_image_duplicate(NULL);
  cpl_test(!cpl_errorstate_is_equal(state) &&
           cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_test_null(imagef);
  cpl_errorstate_set(state);
  imagef = muse_image_new();
  muse_image *imagef2 = muse_image_duplicate(imagef);
  cpl_test(!cpl_errorstate_is_equal(state) &&
           cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_test_null(imagef2);
  muse_image_delete(imagef);
  cpl_errorstate_set(state);

  /* now do the same trick with the duplicated image */
  muse_image *image2 = muse_image_duplicate(image);
  cpl_test_nonnull(image2);
  cpl_test_nonnull(image2->header);
  object = cpl_propertylist_get_string(image->header, "OBJECT");
  cpl_test_zero(strncmp(object, OBJECT_STRING, strlen(OBJECT_STRING) + 1));
  cpl_test_image_abs(image->data, image2->data, FLT_EPSILON);
  cpl_test_image_abs(image->dq, image2->dq, 1);
  cpl_test_image_abs(image->stat, image2->stat, FLT_EPSILON);

  /* To test muse_image_subtract */
  muse_image *image3 = muse_image_duplicate(image);
  cpl_test_zero(muse_image_subtract(image, image2));

  cpl_test(cpl_image_get_absflux(image->data) == 0.0);
  cpl_image_multiply_scalar(image3->stat,2) ;
  cpl_test_image_abs(image->stat, image3->stat, FLT_EPSILON);

  prev_state = cpl_errorstate_get();
  cpl_test(muse_image_subtract(NULL, image2) == -1);
  cpl_test(muse_image_subtract(image,  NULL) == -2);
  cpl_errorstate_set(prev_state);

  /* To test muse_image_divide */
  muse_image *image4 = muse_image_duplicate(image3);
  muse_image *image5 = muse_image_duplicate(image3);

  cpl_test_zero(muse_image_divide(image4, image3));
  cpl_test_zero(muse_image_divide(image5, image3));

  cpl_test(cpl_image_get_absflux(image4->data)
           == cpl_image_get_absflux(image5->data));
  cpl_test_zero(muse_image_subtract(image4, image5));

  cpl_image_multiply_scalar(image5->stat, 2) ;
  cpl_test_image_abs(image4->stat, image5->stat, FLT_EPSILON);

  prev_state = cpl_errorstate_get();
  cpl_test(muse_image_divide(NULL, image3) == -1);
  cpl_test(muse_image_divide(image4, NULL) == -2);
  cpl_errorstate_set(prev_state);
  cpl_image *data = image4->data;
  image4->data = NULL;
  cpl_test(muse_image_divide(image4, image3) == CPL_ERROR_NULL_INPUT); /* missing data */
  cpl_errorstate_set(prev_state);
  cpl_test(muse_image_divide(image3, image4) == CPL_ERROR_NULL_INPUT); /* missing data */
  cpl_errorstate_set(prev_state);
  image4->data = data;
  cpl_image_delete(image4->stat);
  image4->stat = NULL;
  cpl_test(muse_image_divide(image4, image3) == CPL_ERROR_NULL_INPUT); /* missing stat */
  cpl_errorstate_set(prev_state);
  cpl_test(muse_image_divide(image3, image4) == CPL_ERROR_DIVISION_BY_ZERO); /* missing stat */
  cpl_errorstate_set(prev_state);
  muse_image_delete(image4);

  /* To test muse_image_scale */
  image4 = muse_image_duplicate(image3);
  cpl_test_zero(muse_image_scale(image3, 2));

  cpl_image_multiply_scalar(image4->data, 2) ;
  cpl_image_multiply_scalar(image4->stat, 4) ;
  cpl_test_zero(muse_image_subtract(image3, image4));

  prev_state = cpl_errorstate_get();
  cpl_test(muse_image_scale(NULL, 2) == -1);
  cpl_errorstate_set(prev_state);
  cpl_image_delete(image3->data);
  image3->data = NULL;
  prev_state = cpl_errorstate_get();
  cpl_test(muse_image_scale(image3, 2) == CPL_ERROR_NULL_INPUT); /* missing data */
  cpl_errorstate_set(prev_state);
  cpl_image_delete(image4->stat);
  image4->stat = NULL;
  prev_state = cpl_errorstate_get();
  cpl_test(muse_image_scale(image4, 2) == CPL_ERROR_NULL_INPUT); /* missing stat */
  cpl_errorstate_set(prev_state);

  /* clean up most muse_images */
  muse_image_delete(image2);
  muse_image_delete(image3);
  muse_image_delete(image4);
  muse_image_delete(image5);

  /* test muse_image_dq_to_nan() */
  cpl_image_accept_all(image->data); /* reset bad pixel masks, so that...*/
  cpl_image_accept_all(image->stat); /* ...cpl_image_get() below works */
  prev_state = cpl_errorstate_get();
  image2 = muse_image_duplicate(image);
  cpl_test(muse_image_dq_to_nan(image2) == CPL_ERROR_NONE);
  cpl_test(cpl_errorstate_is_equal(prev_state));
  int rej;
  cpl_test(isnan(cpl_image_get(image2->data, 5, 2, &rej))); /* pixel 5,2 is still... */
  cpl_test(isnan(cpl_image_get(image2->stat, 5, 2, &rej))); /* ... marked bad */
  cpl_test_zero(cpl_image_get(image2->data, 1, 1, &rej)); /* others untouched */
  cpl_test(isfinite(cpl_image_get(image2->stat, 1, 1, &rej)));
  cpl_test_null(image2->dq);
  /* failure cases */
  prev_state = cpl_errorstate_get();
  cpl_test(muse_image_dq_to_nan(NULL) == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(prev_state);
  prev_state = cpl_errorstate_get();
  cpl_test(muse_image_dq_to_nan(image2) == CPL_ERROR_NULL_INPUT); /* dq missing */
  cpl_errorstate_set(prev_state);
  muse_image_delete(image2);
  cpl_image_delete(image->data);
  image->data = NULL;
  prev_state = cpl_errorstate_get();
  cpl_test(muse_image_dq_to_nan(image) == CPL_ERROR_NULL_INPUT); /* data missing */
  cpl_errorstate_set(prev_state);
  /* clean up the last muse_image */
  muse_image_delete(image);

  /* To test muse_image_variance_create XXX still missing */

  /* To test muse_image_adu_to_count */
  /* create a new muse_image, with minimal size, 2x2 pixels to be able *
   * to create the four quadrants, and set the headers accordingly     */
  image = muse_image_new();
  image->header = cpl_propertylist_new();
  cpl_propertylist_append_string(image->header, "BUNIT", "adu");
  cpl_propertylist_append_int(image->header, "ESO DET BINX", 1);
  cpl_propertylist_append_int(image->header, "ESO DET BINY", 1);
  cpl_propertylist_append_int(image->header, "ESO DET OUT1 NX", 1);
  cpl_propertylist_append_int(image->header, "ESO DET OUT1 NY", 1);
  cpl_propertylist_append_int(image->header, "ESO DET OUT2 NX", 1);
  cpl_propertylist_append_int(image->header, "ESO DET OUT2 NY", 1);
  cpl_propertylist_append_int(image->header, "ESO DET OUT3 NX", 1);
  cpl_propertylist_append_int(image->header, "ESO DET OUT3 NY", 1);
  cpl_propertylist_append_int(image->header, "ESO DET OUT4 NX", 1);
  cpl_propertylist_append_int(image->header, "ESO DET OUT4 NY", 1);
  cpl_propertylist_append_double(image->header, "ESO DET OUT1 GAIN", 0.5);
  cpl_propertylist_append_double(image->header, "ESO DET OUT2 GAIN", 1.1);
  cpl_propertylist_append_double(image->header, "ESO DET OUT3 GAIN", 1.5);
  cpl_propertylist_append_double(image->header, "ESO DET OUT4 GAIN", 2.0);
  image->data = cpl_image_new(2, 2, CPL_TYPE_FLOAT);
  cpl_image_fill_window(image->data, 1, 1, 2, 2, 1.);
  image->stat = cpl_image_duplicate(image->data);
  /* image->dq is not required for this test */
  cpl_test(muse_image_adu_to_count(image) == CPL_ERROR_NONE);
  cpl_test_zero(strncmp(muse_pfits_get_bunit(image->header), "count", 6));
  cpl_test_rel(cpl_image_get(image->data, 1, 1, &rej), 0.5, FLT_EPSILON);
  cpl_test_rel(cpl_image_get(image->data, 1, 2, &rej), 2.0, FLT_EPSILON);
  cpl_test_rel(cpl_image_get(image->data, 2, 1, &rej), 1.1, FLT_EPSILON);
  cpl_test_rel(cpl_image_get(image->data, 2, 2, &rej), 1.5, FLT_EPSILON);
  cpl_test_rel(cpl_image_get(image->stat, 1, 1, &rej), 0.25, FLT_EPSILON);
  cpl_test_rel(cpl_image_get(image->stat, 1, 2, &rej), 4.00, FLT_EPSILON);
  cpl_test_rel(cpl_image_get(image->stat, 2, 1, &rej), 1.21, FLT_EPSILON);
  cpl_test_rel(cpl_image_get(image->stat, 2, 2, &rej), 2.25, FLT_EPSILON);
  /* now with broken components, BUNIT is already "count" so will fail */
  prev_state = cpl_errorstate_get();
  cpl_test(muse_image_adu_to_count(image) == CPL_ERROR_INCOMPATIBLE_INPUT);
  cpl_errorstate_set(prev_state);
  cpl_propertylist_erase(image->header, "BUNIT"); /* try without BUNIT */
  prev_state = cpl_errorstate_get();
  cpl_test(muse_image_adu_to_count(image) == CPL_ERROR_INCOMPATIBLE_INPUT);
  cpl_errorstate_set(prev_state);
  cpl_propertylist_append_string(image->header, "BUNIT", "adu");
  /* now with broken components, missing header first */
  cpl_propertylist *tmp = image->header;
  image->header = NULL;
  prev_state = cpl_errorstate_get();
  cpl_test(muse_image_adu_to_count(image) == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(prev_state);
  prev_state = cpl_errorstate_get();
  image->header = tmp;
  cpl_image *tmpimage = image->data;
  image->data = NULL; /* now with missing data */
  cpl_test(muse_image_adu_to_count(image) == CPL_ERROR_ILLEGAL_INPUT);
  image->data = tmpimage;
  cpl_errorstate_set(prev_state);
  prev_state = cpl_errorstate_get();
  tmpimage = image->stat;
  image->stat = NULL; /* now with missing stat */
  cpl_test(muse_image_adu_to_count(image) == CPL_ERROR_ILLEGAL_INPUT);
  image->stat = tmpimage;
  cpl_errorstate_set(prev_state);
  muse_image_delete(image);
  prev_state = cpl_errorstate_get();
  cpl_test(muse_image_adu_to_count(NULL) == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(prev_state);

  return cpl_test_end(0);
}
