/* -*- 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_trace() does what it should
            when called on (part of) a (simulated) MUSE flat field exposure.

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

  muse_image *flat = 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]);
    flat = muse_image_load(argv[1]);
    if (!flat->data) {
      cpl_msg_error(__func__, "could not load image from first extension");
      muse_image_delete(flat);
      return cpl_test_end(1);
    }
  } else {
    flat = muse_image_new();
    flat->data = cpl_image_load(BASEFILENAME"_in.fits", CPL_TYPE_FLOAT, 0, 1);
    flat->header = cpl_propertylist_load(BASEFILENAME"_in.fits", 0);
    /* the other extensions shouldn't be needed for this test */
    flat->dq = NULL;
    flat->stat = NULL;
  }
  double cputime = cpl_test_get_cputime();
  /* use default values from muse_masterflat */
  cpl_errorstate state = cpl_errorstate_get();
  cpl_table *tracetable = muse_trace(flat, 32, 0.5, 5, NULL);
  cpl_test_nonnull(tracetable);
  cpl_test(cpl_errorstate_is_equal(state));
  double diff = cpl_test_get_cputime() - cputime;
  if (tracetable) {
    cpl_msg_info(__func__, "tracetable created using muse_trace(), took %gs",
                 diff);
    cpl_test(cpl_table_get_nrow(tracetable) == kMuseSlicesPerCCD);

    /* now some real tests on the values in the table */
    char colname[80];
    double zeroorder = 0.;
    int islice;
    for (islice = 0; islice < cpl_table_get_nrow(tracetable); islice++) {
      cpl_msg_info_overwritable(__func__, "testing slice %d", islice + 1);

      /* the slice number should match */
      cpl_test(cpl_table_get_int(tracetable, MUSE_TRACE_TABLE_COL_SLICE_NO,
                                 islice, NULL)
               == islice + 1);

      /* 0th order of central polynomial should increase monotonically */
      sprintf(colname, MUSE_TRACE_TABLE_COL_COEFF, MUSE_TRACE_CENTER, 0);
      double zorder = cpl_table_get_double(tracetable, colname, islice, NULL);
      cpl_test(zorder > zeroorder);
      zeroorder = zorder;

      /* 0th order of central polynomial should be between left and right one */
      sprintf(colname, MUSE_TRACE_TABLE_COL_COEFF, MUSE_TRACE_LEFT, 0);
      double zleft = cpl_table_get_double(tracetable, colname, islice, NULL);
      sprintf(colname, MUSE_TRACE_TABLE_COL_COEFF, MUSE_TRACE_RIGHT, 0);
      double zright = cpl_table_get_double(tracetable, colname, islice, NULL);
      cpl_test(zleft < zorder && zright > zorder);

      /* the MSEs should be small */
      sprintf(colname, MUSE_TRACE_TABLE_COL_MSE, MUSE_TRACE_CENTER);
      double msec = cpl_table_get_double(tracetable, colname, islice, NULL);
      sprintf(colname, MUSE_TRACE_TABLE_COL_MSE, MUSE_TRACE_LEFT);
      double msel = cpl_table_get_double(tracetable, colname, islice, NULL);
      sprintf(colname, MUSE_TRACE_TABLE_COL_MSE, MUSE_TRACE_RIGHT);
      double mser = cpl_table_get_double(tracetable, colname, islice, NULL);
      cpl_test(msec < 0.01 && msel < 0.01 && mser < 0.01);
    } /* for islice */

#if 0 /* there are probably outliers, so this won't work, either */
    /* Likely due to the error bars on the 1st-order coefficients (?), direct *
     * tests of all coefficients, won't work, so only test each 5th one       */
    double firstorder = -1.; /* start with a low value */
    for (islice = 0; islice < cpl_table_get_nrow(tracetable); islice += 5) {
      sprintf(colname, MUSE_TRACE_TABLE_COL_COEFF, MUSE_TRACE_CENTER, 1);
      double forder = cpl_table_get_double(tracetable, colname, islice, NULL);
      cpl_msg_info_overwritable(__func__, "curvature slice %d (old %f, new %f)",
                                islice + 1, firstorder, forder);
      cpl_test(forder > firstorder);
      firstorder = forder;
    } /* for islice */
    cpl_msg_info_overwritable(__func__, "done testing curvatures");
#endif
#if 0
    cpl_table_save(tracetable, NULL, NULL, "trace_flat.fits", CPL_IO_CREATE);
#endif

    /* check muse_trace_table_get_polys_for_slice() *
     * start with failure cases                     */
    state = cpl_errorstate_get();
    cpl_polynomial **ptrace = muse_trace_table_get_polys_for_slice(NULL, 1);
    cpl_test_null(ptrace);
    cpl_test(!cpl_errorstate_is_equal(state) &&
             cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
    cpl_errorstate_set(state);
    ptrace = muse_trace_table_get_polys_for_slice(tracetable, 0);
    cpl_test_null(ptrace);
    cpl_test(!cpl_errorstate_is_equal(state) &&
             cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
    cpl_errorstate_set(state);
    ptrace = muse_trace_table_get_polys_for_slice(tracetable,
                                                  kMuseSlicesPerCCD + 1);
    cpl_test_null(ptrace);
    cpl_test(!cpl_errorstate_is_equal(state) &&
             cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
    cpl_errorstate_set(state);
    cpl_table *trbroken = cpl_table_duplicate(tracetable);
    /* delete last three slices: */
    cpl_table_set_size(trbroken, kMuseSlicesPerCCD - 3);
    /* test next to last slice; */
    ptrace = muse_trace_table_get_polys_for_slice(trbroken, kMuseSlicesPerCCD - 1);
    cpl_test_null(ptrace);
    cpl_test(!cpl_errorstate_is_equal(state) &&
             cpl_error_get_code() == CPL_ERROR_DATA_NOT_FOUND);
    cpl_errorstate_set(state);
    /* break quadratic coeff of MUSE_TRACE_RIGHT in slice 1 */
    cpl_table_set_invalid(trbroken, "tc2_02", 0);
    ptrace = muse_trace_table_get_polys_for_slice(trbroken, 1);
    cpl_test_null(ptrace);
    cpl_test(!cpl_errorstate_is_equal(state) &&
             cpl_error_get_code() == CPL_ERROR_ILLEGAL_OUTPUT);
    cpl_errorstate_set(state);
    cpl_table_delete(trbroken);
    /* check successes */
    ptrace = muse_trace_table_get_polys_for_slice(tracetable, 1);
    cpl_test_nonnull(ptrace);
    cpl_test_nonnull(ptrace[0]);
    cpl_test_nonnull(ptrace[1]);
    cpl_test_nonnull(ptrace[2]);
    /* check one coefficient at least in each polynomial */
    cpl_size pows = 2;
    cpl_test(cpl_polynomial_get_coeff(ptrace[MUSE_TRACE_CENTER], &pows)
             == cpl_table_get(tracetable, "tc0_02", 0, NULL));
    pows = 4;
    cpl_test(cpl_polynomial_get_coeff(ptrace[MUSE_TRACE_LEFT], &pows)
             == cpl_table_get(tracetable, "tc1_04", 0, NULL));
    pows = 3;
    cpl_test(cpl_polynomial_get_coeff(ptrace[MUSE_TRACE_RIGHT], &pows)
             == cpl_table_get(tracetable, "tc2_03", 0, NULL));
    muse_trace_polys_delete(ptrace);
    state = cpl_errorstate_get();
    ptrace = muse_trace_table_get_polys_for_slice(tracetable, 12);
    cpl_test_nonnull(ptrace);
    cpl_test(cpl_errorstate_is_equal(state));
    muse_trace_polys_delete(ptrace);
    state = cpl_errorstate_get();
    ptrace = muse_trace_table_get_polys_for_slice(tracetable, kMuseSlicesPerCCD);
    cpl_test_nonnull(ptrace);
    cpl_test(cpl_errorstate_is_equal(state));

    /* check muse_trace_polys_delete(),                    *
     * only thing to test is that it doesn't cause errors, *
     * a leak would show up as test failure later          */
    state = cpl_errorstate_get();
    muse_trace_polys_delete(ptrace);
    cpl_test(cpl_errorstate_is_equal(state));
    muse_trace_polys_delete(NULL); /* this should also not cause errors */
    cpl_test(cpl_errorstate_is_equal(state));

    cpl_table_delete(tracetable);
  } /* if tracetable */
  /* test again, this time giving a trace-samples table, *
   * and a smaller number of trace points                */
  state = cpl_errorstate_get();
  cpl_table *samples = NULL;
  tracetable = muse_trace(flat, 10, 0.5, 5, &samples);
  cpl_test_nonnull(tracetable);
  cpl_test_nonnull(samples);
  cpl_test(cpl_errorstate_is_equal(state));
#if 0
  cpl_table_save(tracetable, NULL, NULL, "trace1.fits", CPL_IO_CREATE);
#endif

  /* Now place some bad columns in the test image, and check *
   * that the trace positions are still the same as before.  */
  cpl_image_fill_window(flat->data, 77, 1, 77, 2056, 10.); /* slice 1 */
  cpl_image_fill_window(flat->data, 246, 3333, 247, 4000, 300.); /* slice 3 */
  cpl_image_fill_window(flat->data, 605, 2057, 605, 3333, 70.); /* slice 7 */
  cpl_image_fill_window(flat->data, 1285, 1500, 1286, 2056, 150.); /* slice 15 */
  cpl_image_fill_window(flat->data, 3926, 1000, 3926, 2056, 47.); /* slice 47, bottom */
  cpl_image_fill_window(flat->data, 3926, 2057, 3927, 2500, 470.); /* dito, top */
  /* add two entries just to trigger the "Faulty" cases */
  cpl_image_fill_window(flat->data, 3490, 2302, 3495, 2340, 42.); /* slice 42 */
  cpl_image_fill_window(flat->data, 3680, 1600, 3685, 1635, 44.); /* slice 44 */
#if 0
  cpl_propertylist_update_string(flat->header, "BUNIT", "count");
  muse_image_save(flat, "flattest.fits");
#endif
  state = cpl_errorstate_get();
  cpl_table *trace2 = muse_trace(flat, 10, 0.5, 5, NULL);
#if 0
  cpl_table_save(trace2, NULL, NULL, "trace2.fits", CPL_IO_CREATE);
#endif
  cpl_test_nonnull(trace2);
  cpl_test(cpl_errorstate_is_equal(state));
  cpl_array *w1 = muse_cpltable_extract_column(tracetable, "Width"),
            *w2 = muse_cpltable_extract_column(trace2, "Width"),
            *t01 = muse_cpltable_extract_column(tracetable, "tc0_00"),
            *t02 = muse_cpltable_extract_column(trace2, "tc0_00"),
            *t11 = muse_cpltable_extract_column(tracetable, "tc1_00"),
            *t12 = muse_cpltable_extract_column(trace2, "tc1_00"),
            *t21 = muse_cpltable_extract_column(tracetable, "tc2_00"),
            *t22 = muse_cpltable_extract_column(trace2, "tc2_00"),
            *m01 = muse_cpltable_extract_column(tracetable, "MSE0"),
            *m02 = muse_cpltable_extract_column(trace2, "MSE0"),
            *m11 = muse_cpltable_extract_column(tracetable, "MSE1"),
            *m12 = muse_cpltable_extract_column(trace2, "MSE1"),
            *m21 = muse_cpltable_extract_column(tracetable, "MSE2"),
            *m22 = muse_cpltable_extract_column(trace2, "MSE2");
  /* Compare widths and zero orders, FLT_EPSILON is too strict for slices 44 *
   * and 47, so use less strict limits, be better than 0.001 everywhere!     */
  cpl_test_array_abs(w1, w2, 0.0005);
  cpl_test_array_abs(t01, t02, 0.0005);
  cpl_test_array_abs(t11, t12, 0.0008);
  cpl_test_array_abs(t21, t22, 0.0005);
  cpl_test_array_abs(m01, m02, 5e-5);
  cpl_test_array_abs(m11, m12, 5e-5);
  cpl_test_array_abs(m21, m22, 5e-5);
  cpl_array_unwrap(w1);
  cpl_array_unwrap(w2);
  cpl_array_unwrap(t01);
  cpl_array_unwrap(t02);
  cpl_array_unwrap(t11);
  cpl_array_unwrap(t12);
  cpl_array_unwrap(t21);
  cpl_array_unwrap(t22);
  cpl_array_unwrap(m01);
  cpl_array_unwrap(m02);
  cpl_array_unwrap(m11);
  cpl_array_unwrap(m12);
  cpl_array_unwrap(m21);
  cpl_array_unwrap(m22);
  cpl_table_delete(trace2);

  /* test that it finds data "from Paranal", i.e. data *
   * from 2014 or later, which is special for IFU 24   */
  cpl_propertylist_update_string(flat->header, "DATE-OBS",
                                 "2017-05-30T01:00:00.303");
  cpl_propertylist_update_string(flat->header, "EXTNAME", "CHAN24");
  cpl_table *tt24 = muse_trace(flat, 32, 0.5, 5, NULL);
  cpl_test_nonnull(tt24);
  cpl_test(cpl_errorstate_is_equal(state));
  cpl_table_delete(tt24);

  /* try again, but first add a dark column to the data (but not the DQ!) */
  muse_image *flat2 = muse_image_new();
  flat2->data = cpl_image_duplicate(flat->data);
  if (flat->dq) {
    flat2->dq = cpl_image_duplicate(flat->dq);
  } else {
    flat2->dq = cpl_image_new(cpl_image_get_size_x(flat->data),
                              cpl_image_get_size_y(flat->data),
                              CPL_TYPE_INT);
  }
  if (flat->header) {
    flat2->header = cpl_propertylist_duplicate(flat->header);
  }
  /* three adjacent dark columns */
  cpl_image_fill_window(flat2->data, 200, 1, 202, 4112, 0.);
  state = cpl_errorstate_get();
  cpl_table *tt2 = muse_trace(flat2, 60, 0.5, 5, NULL); /* should fail to find slices */
  cpl_test(!cpl_errorstate_is_equal(state) && /* "slice to wide" */
           cpl_error_get_code() == CPL_ERROR_ACCESS_OUT_OF_RANGE);
  cpl_errorstate_set(state);
  cpl_test_null(tt2);
  /* now add these columns to DQ, then it should work again! */
  int i, j;
  for (i = 200; i <= 202; i++) {
    for (j = 1; j <= 4112; j++) {
      cpl_image_set(flat2->dq, i, j, EURO3D_DARKPIXEL);
    } /* for j */
  } /* for i */
  state = cpl_errorstate_get();
  tt2 = muse_trace(flat2, 50, 0.5, 5, NULL); /* should fail to find slices */
  cpl_test(cpl_errorstate_is_equal(state));
  cpl_test_nonnull(tt2);
  cpl_table_delete(tt2);
  muse_image_delete(flat2);

  /* test muse_trace_plot_samples(), failure cases only -- don't want plots! */
  state = cpl_errorstate_get();
  cpl_error_code rc = muse_trace_plot_samples(NULL, tracetable, 24, 25, 0, flat);
  cpl_test(!cpl_errorstate_is_equal(state) && rc == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(state);
  cpl_table *samples2 = cpl_table_duplicate(samples);
  cpl_table_erase_column(samples2, "slice");
  rc = muse_trace_plot_samples(samples2, tracetable, 24, 25, 0, flat);
  cpl_test(!cpl_errorstate_is_equal(state) && rc == CPL_ERROR_ILLEGAL_INPUT);
  cpl_errorstate_set(state);
  /* cannot test missing gnuplot of popen() failure etc. */
  /* test muse_trace_plot_widths() failure cases, too */
  state = cpl_errorstate_get();
  rc = muse_trace_plot_widths(NULL, 24, 25, 0);
  cpl_test(!cpl_errorstate_is_equal(state) && rc == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(state);
  rc = muse_trace_plot_samples(samples2, tracetable, 24, 25, 0, flat);
  cpl_test(!cpl_errorstate_is_equal(state) && rc == CPL_ERROR_ILLEGAL_INPUT);
  cpl_errorstate_set(state);
  cpl_table_delete(samples2);
  /* cannot test missing gnuplot of popen() failure etc. */

  /* clean up now */
  cpl_table_delete(tracetable);
  cpl_table_delete(samples);
  muse_image_delete(flat);

  return cpl_test_end(0);
}
