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

#include <muse.h>

/* test limits for all four weighting types */
/* stringent check, this should ideally be 0.04 Angstrom *
 * to be accurate to 2 km/s at 7000 Angstrom wavelength  */
double kRmsLimit[4] = { 0.050, 0.030, 0.035, 0.036 };

/*----------------------------------------------------------------------------*/
/**
  @brief    Test program to check that wavelength calibration works when called
            on three (simulated) MUSE arc exposures (with lamps HgCd, Ne, Xe).
 */
/*----------------------------------------------------------------------------*/
int main(int argc, char **argv)
{
  UNUSED_ARGUMENTS(argc, argv);
  cpl_test_init(PACKAGE_BUGREPORT, CPL_MSG_DEBUG);

  muse_imagelist *images = muse_imagelist_new();
  muse_image *image = muse_image_load(BASEFILENAME"_lamp_HgCd.fits");
  /* make sure that the IFU number can be determined */
  cpl_propertylist_update_string(image->header, "EXTNAME", "CHAN10");
  muse_imagelist_set(images, image, 0);
  image = muse_image_load(BASEFILENAME"_lamp_Ne.fits");
  cpl_propertylist_update_string(image->header, "EXTNAME", "CHAN10");
  muse_imagelist_set(images, image, 1);
  image = muse_image_load(BASEFILENAME"_lamp_Xe.fits");
  cpl_propertylist_update_string(image->header, "EXTNAME", "CHAN10");
  muse_imagelist_set(images, image, 2);

  /* trace table and line catalog are needed in any case */
  cpl_table *trace = cpl_table_load(BASEFILENAME"_trace.fits", 1, 1);
  muse_table *linelist = muse_table_load(BASEFILENAME"_linelist.fits", 0);
  int lineversion = cpl_propertylist_get_int(linelist->header, "VERSION");
  cpl_msg_debug(__func__, "line list VERSION = %d", lineversion);
  cpl_boolean linelistcheck = muse_wave_lines_check(linelist);
  cpl_test(linelistcheck);
  if (!linelistcheck) {
    cpl_msg_warning(__func__, "line list check failed, bad things will happen!");
  }

  /* loop over weighting types */
  muse_wave_weighting_type wtype;
  for (wtype = MUSE_WAVE_WEIGHTING_UNIFORM;
       wtype <= MUSE_WAVE_WEIGHTING_CERRSCATTER;
       wtype++) {
    /* here test with header */
    muse_wave_params *p = muse_wave_params_new(muse_imagelist_get(images, 0)->header);
    p->rflag = CPL_TRUE; /* want residuals table */
    p->fitweighting = wtype;
    double cputime1 = cpl_test_get_cputime(),
           time1 = cpl_test_get_walltime();
    cpl_table *wavecal = muse_wave_calib_lampwise(images, trace,
                                                  linelist->table, p);
    double cputime2 = cpl_test_get_cputime(),
           time2 = cpl_test_get_walltime();
    cpl_msg_debug(__func__, "finished muse_wave_calib_lampwise() with wtype=%u, "
                  "took %gs (CPU) %gs (wall-clock)", wtype, cputime2 - cputime1,
                  time2 - time1);
    cpl_test_nonnull(wavecal);
    cpl_test_nonnull(p->residuals);
    if (wavecal) {
      cpl_msg_info(__func__, "wavecal created");
      cpl_test(cpl_table_get_nrow(wavecal) == 48);
      /* there should be no invalid elements in the low-order coefficients *
       * nor in the mean squared error column                              */
      cpl_test_zero(cpl_table_has_invalid(wavecal, "wlc00"));
      cpl_test_zero(cpl_table_has_invalid(wavecal, "wlc01"));
      cpl_test_zero(cpl_table_has_invalid(wavecal, "wlc10"));
      cpl_test_zero(cpl_table_has_invalid(wavecal, "MSE"));

      /* Instead of testing the zero order coeffs in the table, use the *
       * QC parameters from the FITS headers returned in image list:    *
       * the WLEN parameter should have a certain range, DWLEN should   *
       * point to a certain tilt, and RMS should be low                 */
      cpl_propertylist *head = muse_imagelist_get(images, 0)->header;
      int i;
      for (i = 1; i <= 48; i++) {
        char *kw = cpl_sprintf("ESO QC WAVECAL SLICE%d WLEN", i),
             *kwt = cpl_sprintf("ESO QC WAVECAL SLICE%d DWLEN TOP", i),
             *kwb = cpl_sprintf("ESO QC WAVECAL SLICE%d DWLEN BOTTOM", i),
             *kwr = cpl_sprintf("ESO QC WAVECAL SLICE%d FIT RMS", i);
        float dlamt = cpl_propertylist_get_float(head, kwt),
              dlamb = cpl_propertylist_get_float(head, kwb),
              wlen = cpl_propertylist_get_float(head, kw),
              rms = cpl_propertylist_get_float(head, kwr);
        cpl_free(kw);
        cpl_free(kwt);
        cpl_free(kwb);
        cpl_free(kwr);
        cpl_msg_debug(__func__, "slice %d: lambda = %f, dlambda = %f / %f, "
                      "rms = %f (wtype = %u)", i, wlen, dlamt, dlamb, rms, wtype);
        cpl_test(wlen > 6822.4 && wlen < 7198.5);
        /* the outer ones have stronger tilt, and bottom is usually    *
         * worse, because the last line to constrain it is futher away */
        if (i <= 12 || i >= 37) {
          cpl_test(fabs(dlamt) > 2.3 && fabs(dlamb) > 2.5);
          cpl_test(fabs(dlamt) < 3.65 && fabs(dlamb) < 4.9);
        } else {
          cpl_test(fabs(dlamt) < 1.65 && fabs(dlamb) < 1.90);
        }
        cpl_test(rms < 0.125); /* loose check, following PR27 */
        cpl_test(rms < kRmsLimit[wtype]); /* stringent check, see above */
      } /* for i (all slices) */

      cpl_test(cpl_table_get_nrow(p->residuals) > 0);
      double meanlambda = cpl_table_get_column_mean(p->residuals, "lambda");
      cpl_test(meanlambda > 4650. && meanlambda < 9300.);
      double meanresiduals = cpl_table_get_column_mean(p->residuals, "residual");
      /* mean of all residuals should be much smaller than 0.1 Angstrom */
      cpl_test(meanresiduals > -0.1 && meanresiduals < 0.1);
    } /* if wavecal */
#if 0 /* for debugging, save the results */
    char fn[FILENAME_MAX];
    sprintf(fn, "muse_test_wavecal_arc_lampwise_WAVECAL_TABLE_%1u.fits", wtype);
    cpl_table_save(wavecal, muse_imagelist_get(images, 0)->header, NULL, fn, CPL_IO_CREATE);
    sprintf(fn, "muse_test_wavecal_arc_lampwise_WAVECAL_RESIDUALS_%1u.fits", wtype);
    cpl_table_save(p->residuals, muse_imagelist_get(images, 0)->header, NULL, fn, CPL_IO_CREATE);
#endif
    cpl_table_delete(wavecal);
    muse_wave_params_delete(p);
  } /* for wtype */

  /* test failure cases */
  cpl_errorstate state = cpl_errorstate_get();
  muse_wave_params *p = muse_wave_params_new(NULL);
  cpl_table *wavecal = muse_wave_calib_lampwise(NULL, trace, linelist->table,
                                                p);
  cpl_test(!cpl_errorstate_is_equal(state)
           && cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(state);
  cpl_test_null(wavecal);
  muse_imagelist *images2 = muse_imagelist_new();
  wavecal = muse_wave_calib_lampwise(images2, trace, linelist->table,
                                     p); /* empty image list */
  cpl_test(!cpl_errorstate_is_equal(state)
           && cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(state);
  cpl_test_null(wavecal);
  wavecal = muse_wave_calib_lampwise(images, NULL, linelist->table, p);
  cpl_test(!cpl_errorstate_is_equal(state)
           && cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(state);
  cpl_test_null(wavecal);
  cpl_table *trace2 = cpl_table_extract(trace, 0, 10);
  wavecal = muse_wave_calib_lampwise(images, trace2, linelist->table,
                                     p); /* bad trace table */
  cpl_test(!cpl_errorstate_is_equal(state)
           && cpl_error_get_code() == CPL_ERROR_INCOMPATIBLE_INPUT);
  cpl_errorstate_set(state);
  cpl_test_null(wavecal);
  cpl_table_delete(trace2);
  wavecal = muse_wave_calib_lampwise(images, trace, NULL, p);
  cpl_test(!cpl_errorstate_is_equal(state)
           && cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(state);
  cpl_test_null(wavecal);
  wavecal = muse_wave_calib_lampwise(images, trace, linelist->table, NULL);
  cpl_test(!cpl_errorstate_is_equal(state)
           && cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(state);
  cpl_test_null(wavecal);
  muse_image *arc2 = muse_image_new();
  arc2->data = cpl_image_new(4096, 100, CPL_TYPE_FLOAT);
  arc2->stat = cpl_image_new(4096, 100, CPL_TYPE_FLOAT);
  muse_imagelist_set(images2, arc2, 0);
  wavecal = muse_wave_calib_lampwise(images2, trace, linelist->table,
                                     p); /* too small data */
  cpl_test(!cpl_errorstate_is_equal(state)
           && cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
  cpl_errorstate_set(state);
  cpl_test_null(wavecal);
  /* create imagelist with two images of different sized (non-uniform) */
  arc2 = muse_image_new();
  arc2->data = cpl_image_new(4096, 4112, CPL_TYPE_FLOAT);
  arc2->stat = cpl_image_new(4096, 4112, CPL_TYPE_FLOAT);
  muse_imagelist_set(images2, arc2, 0);
  arc2 = muse_image_new();
  arc2->data = cpl_image_new(4096, 4100, CPL_TYPE_FLOAT);
  arc2->stat = cpl_image_new(4096, 4100, CPL_TYPE_FLOAT);
  muse_imagelist_set(images2, arc2, 1);
  wavecal = muse_wave_calib_lampwise(images2, trace, linelist->table,
                                     p); /* non-uniform list */
  cpl_test(!cpl_errorstate_is_equal(state)
           && cpl_error_get_code() == CPL_ERROR_INCOMPATIBLE_INPUT);
  cpl_errorstate_set(state);
  cpl_test_null(wavecal);
  muse_imagelist_delete(images2);
  cpl_table *fewlines = cpl_table_extract(linelist->table, 0, 10);
  wavecal = muse_wave_calib_lampwise(images, trace,
                                     fewlines, p); /* no FWHM ref lines */
  cpl_test(!cpl_errorstate_is_equal(state)
           && cpl_error_get_code() == CPL_ERROR_BAD_FILE_FORMAT);
  cpl_errorstate_set(state);
  cpl_test_null(wavecal);
  cpl_table_delete(fewlines);
  /* create imagelist with a single image whose data portion contains nothing */
  images2 = muse_imagelist_new();
  arc2 = muse_image_duplicate(muse_imagelist_get(images, 0));
  cpl_image_fill_noise_uniform(arc2->data, -FLT_MIN, FLT_MIN);
  muse_imagelist_set(images2, arc2, 0);
  wavecal = muse_wave_calib_lampwise(images2, trace, linelist->table,
                                     p); /* no arc lines found */
  /* it might succeed and give a different final error code  *
   * in reality but this test was constructed to give a NULL *
   * result and a final code of CPL_ERROR_DATA_NOT_FOUND     */
  cpl_test(!cpl_errorstate_is_equal(state)
           && cpl_error_get_code() == CPL_ERROR_DATA_NOT_FOUND);
  cpl_errorstate_set(state);
  cpl_test_null(wavecal);
  muse_imagelist_delete(images2);
  /* XXX case of failing poly solutions is not covered yet */
  /* test special case of muse_wave_params_delete(), too */
  muse_wave_params_delete(NULL);
  cpl_test(cpl_errorstate_is_equal(state));

  /* clean up */
  muse_wave_params_delete(p);
  muse_imagelist_delete(images);
  cpl_table_delete(trace);
  muse_table_delete(linelist);

  return cpl_test_end(0);
}
