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

/*----------------------------------------------------------------------------*/
/**
  @brief    Test program to check that muse_wave_calib() does what it should
            when called on (part of) a (simulated) MUSE arc exposure.

  This program also tests
    muse_wave_plot_residuals (failure cases only)
    muse_wave_plot_column (failure cases only)
 */
/*----------------------------------------------------------------------------*/
int main(int argc, char **argv)
{
  cpl_test_init(PACKAGE_BUGREPORT, CPL_MSG_DEBUG);

  muse_table *linelist = muse_table_load(BASEFILENAME"_linelist.fits", 0);
  if (!linelist) {
    cpl_msg_error(__func__, "Could not load line list: %s", cpl_error_get_message());
    cpl_error_reset();
  }
  int lineversion = cpl_propertylist_get_int(linelist->header, "VERSION");
  cpl_msg_info(__func__, "line list VERSION = %d", lineversion);
  cpl_boolean linelistcheck = muse_wave_lines_check(linelist);
  cpl_test(linelistcheck);
  if (!linelistcheck) {
    muse_table_delete(linelist);
    return cpl_test_end(1);
  }

  muse_image *arc = NULL;
  if (argc == 2) {
    /* assume that the file passed on the command line is an input image */
    cpl_msg_info(__func__, "command line argument detected, will try to load %s",
                 argv[1]);
    arc = muse_image_load(argv[1]);
  } else {
    arc = muse_image_load(BASEFILENAME"_in.fits");
  }
  /* make sure that the IFU number can be determined and a mode */
  if (!cpl_propertylist_has(arc->header, "EXTNAME")) {
    cpl_propertylist_update_string(arc->header, "EXTNAME", "CHAN10");
    cpl_propertylist_update_string(arc->header, "ESO INS MODE", "WFM-NOAO-N");
  }
  cpl_error_reset();
  cpl_table *tracetable = cpl_table_load(BASEFILENAME"_trace_in.fits", 1, 1);
  if (!tracetable) {
    cpl_msg_error(__func__, "Could not load trace table: %s", cpl_error_get_message());
    cpl_error_reset();
  }


  if (!arc->data || !arc->stat) {
    cpl_msg_error(__func__, "Could not load test image: %s", cpl_error_get_message());
    muse_image_delete(arc);
    return cpl_test_end(2);
  }

  /* some variables upfront */
  cpl_errorstate state = cpl_errorstate_get();
  cpl_table *wavecaltable = NULL;
  /* test with all instrument modes */
  int imode,
      nlines[] = { 50, 49, 44, 45, 49 }; /* number of detected lines per mode */
  for (imode = 0; imode <= MUSE_MODE_NFM_AO_N; imode++) {
    /* use default values from muse_wavecal */
    muse_wave_params *p = muse_wave_params_new(NULL); /* test without header */
    p->rflag = CPL_TRUE; /* want residuals table */
    p->mode = imode; /* pretend that the test data is of some other mode */
    /* we loop over the weighting type in the muse_test_wavecal_arc_lampwise *
     * program, so here just use the non-weighted part for quick runtime     */
    p->fitweighting = MUSE_WAVE_WEIGHTING_UNIFORM;
    double cputime1 = cpl_test_get_cputime(),
           time1 = cpl_test_get_walltime();
    state = cpl_errorstate_get();
    wavecaltable = muse_wave_calib(arc, tracetable, linelist->table, p);
    double cputime2 = cpl_test_get_cputime(),
           time2 = cpl_test_get_walltime();
    cpl_msg_debug(__func__, "finished muse_wave_calib(), took %gs (CPU) %gs "
                  "(wall-clock)", cputime2 - cputime1, time2 - time1);
    cpl_test(cpl_errorstate_is_equal(state));
    cpl_test_nonnull(wavecaltable);
    cpl_test_nonnull(p->residuals);
    if (!wavecaltable) {
      continue;
    }
    cpl_msg_info(__func__, "mode %d wavecaltable created", imode);
    cpl_test(cpl_table_get_nrow(wavecaltable) == kMuseSlicesPerCCD);
    /* 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(wavecaltable, "wlc00"));
    cpl_test_zero(cpl_table_has_invalid(wavecaltable, "wlc01"));
    cpl_test_zero(cpl_table_has_invalid(wavecaltable, "wlc10"));
    cpl_test_zero(cpl_table_has_invalid(wavecaltable, "MSE"));

    /* Instead of testing the zero order coeffs in the table, use the QC  *
     * parameters from the FITS headers returned in the original          *
     * arc-muse_image: the WLEN parameter should be between 6810 and 7190 *
     * Angstrom (at pixel 2056.5)                                         */
    int i;
    for (i = 1; i <= kMuseSlicesPerCCD; i++) {
      char *keyword = cpl_sprintf("ESO QC WAVECAL SLICE%d WLEN", i);
      float wlen = cpl_propertylist_get_float(arc->header, keyword);
      cpl_free(keyword);
      keyword = cpl_sprintf("ESO QC WAVECAL SLICE%d FIT RMS", i);
      float rms = cpl_propertylist_get_float(arc->header, keyword);
      cpl_free(keyword);
      cpl_msg_debug(__func__, "slice %d: lambda = %f rms = %f", i, wlen, rms);
      cpl_test(wlen > 6810. && wlen < 7190.);
      cpl_test(rms < 0.125); /* loose check, following PR27 */
    }
    char *keyword = cpl_sprintf(QC_WAVECAL_SLICEj_FIT_NLINES, 10);
    int nfit = cpl_propertylist_get_int(arc->header, keyword);
    cpl_free(keyword);
#if 0
    cpl_msg_debug(__func__, "Detected %d lines in slice 10 (should be %d)",
                  nfit, nlines[imode]);
#endif
    cpl_test_eq(nfit, nlines[imode]); /* that's what I see after a first test */

    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);
    muse_wave_params_delete(p);

    /* check muse_wave_table_get_poly_for_slice() */
    cpl_polynomial *pwave = NULL;
    if (imode == 0) {
      /* start with failure cases                   */
      state = cpl_errorstate_get();
      pwave = muse_wave_table_get_poly_for_slice(NULL, 1);
      cpl_test_null(pwave);
      cpl_test(!cpl_errorstate_is_equal(state) &&
               cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
      cpl_errorstate_set(state);
      pwave = muse_wave_table_get_poly_for_slice(wavecaltable, 0);
      cpl_test_null(pwave);
      cpl_test(!cpl_errorstate_is_equal(state) &&
               cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
      cpl_errorstate_set(state);
      pwave = muse_wave_table_get_poly_for_slice(wavecaltable,
                                                 kMuseSlicesPerCCD + 1);
      cpl_test_null(pwave);
      cpl_test(!cpl_errorstate_is_equal(state) &&
               cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
      cpl_errorstate_set(state);
      cpl_table *wvbroken = cpl_table_duplicate(wavecaltable);
      /* delete last three slices: */
      cpl_table_set_size(wvbroken, kMuseSlicesPerCCD - 3);
      /* test next to last slice; */
      pwave = muse_wave_table_get_poly_for_slice(wvbroken, kMuseSlicesPerCCD - 1);
      cpl_test_null(pwave);
      cpl_test(!cpl_errorstate_is_equal(state) &&
               cpl_error_get_code() == CPL_ERROR_DATA_NOT_FOUND);
      cpl_errorstate_set(state);
      /* break double quadratic coeff in slice 1 */
      cpl_table_set_invalid(wvbroken, "wlc22", 0);
      pwave = muse_wave_table_get_poly_for_slice(wvbroken, 1);
      cpl_test_null(pwave);
      cpl_test(!cpl_errorstate_is_equal(state) &&
               cpl_error_get_code() == CPL_ERROR_ILLEGAL_OUTPUT);
      cpl_errorstate_set(state);
      cpl_table_delete(wvbroken);
    } /* imode==0 (failure cases) */
    /* check successes */
    pwave = muse_wave_table_get_poly_for_slice(wavecaltable, kMuseSlicesPerCCD);
    cpl_test_nonnull(pwave);
    cpl_test(cpl_errorstate_is_equal(state));
    cpl_polynomial_delete(pwave);
    pwave = muse_wave_table_get_poly_for_slice(wavecaltable, 1);
    cpl_test_nonnull(pwave);
    cpl_test(cpl_errorstate_is_equal(state));
    /* check three coefficients at least */
    cpl_size pows[2] = { 2, 2 };
    cpl_test(cpl_polynomial_get_coeff(pwave, pows)
             == cpl_table_get(wavecaltable, "wlc22", 0, NULL));
    pows[1] = 4;
    cpl_test(cpl_polynomial_get_coeff(pwave, pows)
             == cpl_table_get(wavecaltable, "wlc24", 0, NULL));
    pows[0] = 1;
    pows[1] = 6;
    cpl_test(cpl_polynomial_get_coeff(pwave, pows)
             == cpl_table_get(wavecaltable, "wlc16", 0, NULL));
    cpl_polynomial_delete(pwave);

    cpl_table_delete(wavecaltable);
  } /* for imode (all instrument modes) */

  /* test failure cases */
  state = cpl_errorstate_get();
  muse_wave_params *p = muse_wave_params_new(NULL);
  wavecaltable = muse_wave_calib(NULL, tracetable, linelist->table, p);
  cpl_test(!cpl_errorstate_is_equal(state)
           && cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(state);
  muse_image *arc2 = muse_image_duplicate(arc);
  cpl_image_delete(arc2->stat);
  arc2->stat = NULL;
  wavecaltable = muse_wave_calib(arc2, tracetable, linelist->table, p); /* no variance */
  cpl_test(!cpl_errorstate_is_equal(state)
           && cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
  cpl_errorstate_set(state);
  muse_image_delete(arc2);
  wavecaltable = muse_wave_calib(arc, NULL, linelist->table, p);
  cpl_test(!cpl_errorstate_is_equal(state)
           && cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(state);
  cpl_table *trace2 = cpl_table_extract(tracetable, 0, 10);
  wavecaltable = muse_wave_calib(arc, 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_table_delete(trace2);
  wavecaltable = muse_wave_calib(arc, tracetable, NULL, p);
  cpl_test(!cpl_errorstate_is_equal(state)
           && cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(state);
  wavecaltable = muse_wave_calib(arc, tracetable, linelist->table, NULL);
  cpl_test(!cpl_errorstate_is_equal(state)
           && cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(state);
  arc2 = muse_image_new();
  arc2->data = cpl_image_new(4096, 100, CPL_TYPE_FLOAT);
  arc2->stat = cpl_image_new(4096, 100, CPL_TYPE_FLOAT);
  wavecaltable = muse_wave_calib(arc2, tracetable, linelist->table,
                                 p); /* too small image */
  cpl_test(!cpl_errorstate_is_equal(state)
           && cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
  cpl_errorstate_set(state);
  muse_image_delete(arc2);
  cpl_table *fewlines = cpl_table_extract(linelist->table, 0, 10);
  wavecaltable = muse_wave_calib(arc, tracetable,
                                 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_table_delete(fewlines);
  /* now the final cleanup */
  muse_wave_params_delete(p);
  cpl_table_delete(tracetable);
  muse_table_delete(linelist);
  muse_image_delete(arc);

  /* Cheat and test failure modes of the muse_wave_plot_*() functions only, *
   * since plotting performance cannot easily be unit-tested automatically. */
  state = cpl_errorstate_get();
  cpl_error_code rc = muse_wave_plot_residuals(NULL, 0, 10, 1, CPL_TRUE, NULL);
  cpl_test(!cpl_errorstate_is_equal(state) && rc == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(state);
  rc = muse_wave_plot_column(NULL, NULL, 0, 10, 1000, 1, CPL_TRUE);
  cpl_test(!cpl_errorstate_is_equal(state) && rc == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(state);

  return cpl_test_end(0);
}
