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

/* If the conversion is supposed to be accurate against the Morton formula *
 * to at least below the typical 3 digit limit, then set aLimit = 0.0005.  *
 * The accuracy given by SDSS is just 2-3 digits, so give 0.004.           */
static void
muse_test_phys_run(unsigned int aTestFlags, double aLimit, double aLimitSDSS)
{
  cpl_msg_info(__func__, "\n____________ testing with flags 0x%x ______________",
               aTestFlags);

  /* create a minimal pixel table using information from */
  muse_pixtable *pt = cpl_calloc(1, sizeof(muse_pixtable));
  pt->table = muse_cpltable_new(muse_pixtable_def, 19);
  /* use range of wavelengths covering at least MUSE completely */
  cpl_table_set_float(pt->table, MUSE_PIXTABLE_LAMBDA, 0, 4000.);
  cpl_table_set_float(pt->table, MUSE_PIXTABLE_LAMBDA, 1, 5000.);
  cpl_table_set_float(pt->table, MUSE_PIXTABLE_LAMBDA, 2, 5500.);
  cpl_table_set_float(pt->table, MUSE_PIXTABLE_LAMBDA, 3, 6000.);
  cpl_table_set_float(pt->table, MUSE_PIXTABLE_LAMBDA, 4, 7000.);
  cpl_table_set_float(pt->table, MUSE_PIXTABLE_LAMBDA, 5, 8000.);
  cpl_table_set_float(pt->table, MUSE_PIXTABLE_LAMBDA, 6, 8500.);
  cpl_table_set_float(pt->table, MUSE_PIXTABLE_LAMBDA, 7, 9000.);
  cpl_table_set_float(pt->table, MUSE_PIXTABLE_LAMBDA, 8, 10000.);
  /* set up example wavelengths from                             *
   * http://www.sdss3.org/dr10/spectro/spectro_basics.php#vacuum *
   *   Line    Air      Vacuum                                   *
   *   H-beta  4861.363 4862.721                                 *
   *   [O III] 4958.911 4960.295                                 *
   *   [O III] 5006.843 5008.239                                 *
   *   [N II]  6548.05  6549.86                                  *
   *   H-alpha 6562.801 6564.614                                 *
   *   [N II]  6583.45  6585.27                                  *
   *   [S II]  6716.44  6718.29                                  *
   *   [S II]  6730.82  6732.68                                  */
  cpl_table_set_float(pt->table, MUSE_PIXTABLE_LAMBDA, 10, 4861.363);
  cpl_table_set_float(pt->table, MUSE_PIXTABLE_LAMBDA, 11, 4958.911);
  cpl_table_set_float(pt->table, MUSE_PIXTABLE_LAMBDA, 12, 5006.843);
  cpl_table_set_float(pt->table, MUSE_PIXTABLE_LAMBDA, 13, 6548.05);
  cpl_table_set_float(pt->table, MUSE_PIXTABLE_LAMBDA, 14, 6562.801);
  cpl_table_set_float(pt->table, MUSE_PIXTABLE_LAMBDA, 15, 6583.45);
  cpl_table_set_float(pt->table, MUSE_PIXTABLE_LAMBDA, 16, 6716.44);
  cpl_table_set_float(pt->table, MUSE_PIXTABLE_LAMBDA, 17, 6730.82);
  /* and a Krypton line wavelength, taken from                   *
   *    http://idlastro.gsfc.nasa.gov/ftp/pro/astro/airtovac.pro */
  cpl_table_set_float(pt->table, MUSE_PIXTABLE_LAMBDA, 18, 6056.125);
  /* set the supposed vacuum wavelengths in the unused stat column */
  cpl_table_set_float(pt->table, MUSE_PIXTABLE_STAT, 10, 4862.721);
  cpl_table_set_float(pt->table, MUSE_PIXTABLE_STAT, 11, 4960.295);
  cpl_table_set_float(pt->table, MUSE_PIXTABLE_STAT, 12, 5008.239);
  cpl_table_set_float(pt->table, MUSE_PIXTABLE_STAT, 13, 6549.86);
  cpl_table_set_float(pt->table, MUSE_PIXTABLE_STAT, 14, 6564.614);
  cpl_table_set_float(pt->table, MUSE_PIXTABLE_STAT, 15, 6585.27);
  cpl_table_set_float(pt->table, MUSE_PIXTABLE_STAT, 16, 6718.29);
  cpl_table_set_float(pt->table, MUSE_PIXTABLE_STAT, 17, 6732.68);
  /* again, Krypton line position, taken from airtovac.pro */
  cpl_table_set_float(pt->table, MUSE_PIXTABLE_STAT, 18, 6057.8019);
  /* duplicate these original air wavelengths in the unused data column */
  int i, n = muse_pixtable_get_nrow(pt);
  for (i = 0; i < n; i++) {
    int err;
    double l_air = cpl_table_get_float(pt->table, MUSE_PIXTABLE_LAMBDA, i, &err);
    if (err) {
      continue;
    }
    cpl_table_set_float(pt->table, MUSE_PIXTABLE_DATA, i, l_air);
  } /* for i (all pixel table rows) */
  cpl_boolean paranalair = aTestFlags & (1<<10);
  pt->header = cpl_propertylist_new();
  if (paranalair) {
    cpl_msg_info(__func__, "humid atmosphere in header (%s correction)",
                 ((aTestFlags & 0x3) >> 1) ? "and real air" : "but std air");
    cpl_propertylist_append_float(pt->header, "ESO TEL AMBI RHUM", 0.25);
    /* set a temperature below freezing to trigger that case in the code */
    cpl_propertylist_append_float(pt->header, "ESO TEL AMBI TEMP", -1.);
    cpl_propertylist_append_float(pt->header, "ESO TEL AMBI PRES START", 750.);
    cpl_propertylist_append_float(pt->header, "ESO TEL AMBI PRES END", 740.);
  } else {
    cpl_msg_info(__func__, "International Standard Atmosphere in header (%s correction)",
                 ((aTestFlags & 0x3) >> 1) ? "real air" : "std air");
    cpl_propertylist_append_float(pt->header, "ESO TEL AMBI RHUM", 0.);
    cpl_propertylist_append_float(pt->header, "ESO TEL AMBI TEMP", 15.);
    cpl_propertylist_append_float(pt->header, "ESO TEL AMBI PRES START", 1013.25);
    cpl_propertylist_append_float(pt->header, "ESO TEL AMBI PRES END", 1013.25);
  }
  unsigned int aFlags = aTestFlags & ~(1<<10); /* remove the high bit */
#if 0
  cpl_table_dump(pt->table, 0, 20, stdout);
  fflush(stdout);
#endif

  cpl_errorstate state = cpl_errorstate_get();
  cpl_error_code rc = muse_phys_air_to_vacuum(pt, aFlags);
  cpl_test(rc == CPL_ERROR_NONE && cpl_errorstate_is_equal(state));

  cpl_test(cpl_propertylist_has(pt->header, MUSE_HDR_PT_SPEC_TYPE));
  cpl_test_eq_string(cpl_propertylist_get_string(pt->header, MUSE_HDR_PT_SPEC_TYPE),
                     "WAVE");

#if 0
  cpl_table_dump(pt->table, 0, 20, stdout);
  fflush(stdout);
#endif
  /* use the formula from Morton (1991, ApJS, 77, 119) which    *
   * in turn points to Oosterhoff (1957 Trans IAU Vol. IX pp.   *
   * 69. 202) and Edlen (1953 JOSA 43, 339) to check the result */
  for (i = 0; i < n; i++) {
    int err;
    double l_orig = cpl_table_get_float(pt->table, MUSE_PIXTABLE_DATA, i, &err);
    if (err) {
      continue;
    }
    double l_vac = cpl_table_get_float(pt->table, MUSE_PIXTABLE_LAMBDA, i, NULL),
           div_lvac_sq = pow(1e4 / l_vac, 2),
           l_air = l_vac / (6.4328e-5 + 2.94981e-2 / (146. - div_lvac_sq)
                            + 2.5540e-4 / (41. - div_lvac_sq) + 1.);
    cpl_msg_debug(__func__, "orig %.4f vac %.4f back %.4f", l_orig, l_vac, l_air);
    if (paranalair && ((aFlags & 0x3) >> 1)) {
      /* expect a pretty big offset for Paranal air and measured values */
      cpl_test_abs(l_orig, l_air + 0.45, aLimit);
      continue; /* skip SDSS comparison */
    } else {
      cpl_test_abs(l_orig, l_air, aLimit);
    }
    double l_vac_sdss = cpl_table_get_float(pt->table, MUSE_PIXTABLE_STAT, i, &err);
    if (err) {
      continue;
    }
    cpl_msg_debug(__func__, "vac %.4f sdss %.4f --> delta %.4f", l_vac,
                  l_vac_sdss, l_vac - l_vac_sdss);
    cpl_test_abs(l_vac, l_vac_sdss, aLimitSDSS);
  } /* for i (all pixel table rows) */
  muse_pixtable_delete(pt);
} /* muse_test_phys_run() */

/*----------------------------------------------------------------------------*/
/**
  @brief    Test program to check the muse_phys module.

  This program explicitely tests
    muse_phys_convert_air_to_vac
 */
/*----------------------------------------------------------------------------*/
int main(int argc, char **argv)
{
  UNUSED_ARGUMENTS(argc, argv);
  cpl_test_init(PACKAGE_BUGREPORT, CPL_MSG_DEBUG);

  /* test muse_phys_convert_air_to_vac() for success */
  muse_test_phys_run(MUSE_PHYS_AIR_STANDARD | MUSE_PHYS_METHOD_CIDDOR, 0.0006, 0.005);
  muse_test_phys_run(MUSE_PHYS_AIR_MEASURED | MUSE_PHYS_METHOD_CIDDOR, 0.0006, 0.005);
  muse_test_phys_run(MUSE_PHYS_AIR_STANDARD, 0.0006, 0.005);
  /* with "measured" entries that are off from the standard ones */
  muse_test_phys_run(MUSE_PHYS_AIR_MEASURED | (1<<10), 0.21, 1.);
  /* humid / non-standard entries in header, but standard atmosphere */
  muse_test_phys_run(MUSE_PHYS_AIR_STANDARD | (1<<10), 0.0006, 0.005);

  muse_test_phys_run(MUSE_PHYS_AIR_STANDARD | MUSE_PHYS_METHOD_FILIPPENKO, 0.0005, 0.004);
  muse_test_phys_run(MUSE_PHYS_AIR_STANDARD | MUSE_PHYS_METHOD_OWENS, 0.0006, 0.005);
  muse_test_phys_run(MUSE_PHYS_AIR_STANDARD | MUSE_PHYS_METHOD_EDLEN, 0.0006, 0.005);

  /* test failures of muse_phys_convert_air_to_vac() */
  /* create proper but very short pixel table to start with */
  muse_pixtable *pt = cpl_calloc(1, sizeof(muse_pixtable));
  pt->table = muse_cpltable_new(muse_pixtable_def, 3);
  cpl_table_set_float(pt->table, MUSE_PIXTABLE_LAMBDA, 0, 4000.);
  cpl_table_set_float(pt->table, MUSE_PIXTABLE_LAMBDA, 1, 7000.);
  cpl_table_set_float(pt->table, MUSE_PIXTABLE_LAMBDA, 2, 10000.);
  pt->header = cpl_propertylist_new();
  cpl_propertylist_append_float(pt->header, "ESO TEL AMBI RHUM", 0.25);
  cpl_propertylist_append_float(pt->header, "ESO TEL AMBI TEMP", 5.);
  cpl_propertylist_append_float(pt->header, "ESO TEL AMBI PRES START", 750.);
  cpl_propertylist_append_float(pt->header, "ESO TEL AMBI PRES END", 740.);

  cpl_errorstate state = cpl_errorstate_get();
  cpl_error_code rc = muse_phys_air_to_vacuum(NULL, MUSE_PHYS_AIR_STANDARD);
  cpl_test(rc == CPL_ERROR_NULL_INPUT && !cpl_errorstate_is_equal(state));
  cpl_errorstate_set(state);
  /* table without "lambda" column */
  muse_pixtable *pt2 = muse_pixtable_duplicate(pt);
  cpl_table_erase_column(pt2->table, MUSE_PIXTABLE_LAMBDA);
  state = cpl_errorstate_get();
  rc = muse_phys_air_to_vacuum(pt2, MUSE_PHYS_AIR_STANDARD);
  cpl_test(rc == CPL_ERROR_DATA_NOT_FOUND && !cpl_errorstate_is_equal(state));
  cpl_errorstate_set(state);
  /* pixel table without table component */
  cpl_table_delete(pt2->table);
  pt2->table = NULL;
  state = cpl_errorstate_get();
  rc = muse_phys_air_to_vacuum(pt2, MUSE_PHYS_AIR_STANDARD);
  cpl_test(rc == CPL_ERROR_NULL_INPUT && !cpl_errorstate_is_equal(state));
  cpl_errorstate_set(state);
  muse_pixtable_delete(pt2);
  /* pixel table without header component */
  pt2 = muse_pixtable_duplicate(pt);
  cpl_propertylist_delete(pt2->header);
  pt2->header = NULL;
  state = cpl_errorstate_get();
  rc = muse_phys_air_to_vacuum(pt2, MUSE_PHYS_AIR_STANDARD);
  cpl_test(rc == CPL_ERROR_NULL_INPUT && !cpl_errorstate_is_equal(state));
  cpl_errorstate_set(state);
  muse_pixtable_delete(pt2);
  /* good pixel table, but bad flags */
  pt2 = muse_pixtable_duplicate(pt);
  state = cpl_errorstate_get();
  rc = muse_phys_air_to_vacuum(pt2, 0);
  cpl_test(rc == CPL_ERROR_UNSUPPORTED_MODE && !cpl_errorstate_is_equal(state));
  cpl_errorstate_set(state);
  /* all 8 first bits set in flags, that's OK, i.e success */
  state = cpl_errorstate_get();
  rc = muse_phys_air_to_vacuum(pt2, 0xFF);
  cpl_test(rc == CPL_ERROR_NONE && cpl_errorstate_is_equal(state));
  /* conversion already done */
  rc = muse_phys_air_to_vacuum(pt2, 0xFF);
  cpl_test(rc == CPL_ERROR_TYPE_MISMATCH && !cpl_errorstate_is_equal(state));
  cpl_errorstate_set(state);
  /* bad input spectral type */
  cpl_propertylist_update_string(pt2->header, MUSE_HDR_PT_SPEC_TYPE, "WAVV");
  state = cpl_errorstate_get();
  rc = muse_phys_air_to_vacuum(pt2, 0xFF);
  cpl_test(rc == CPL_ERROR_TYPE_MISMATCH && !cpl_errorstate_is_equal(state));
  cpl_errorstate_set(state);
  cpl_test_eq_string(cpl_propertylist_get_string(pt2->header, MUSE_HDR_PT_SPEC_TYPE),
                     "WAVV"); /* unchanged type string */
  muse_pixtable_delete(pt2);
  /* missing humidity keyword */
  cpl_propertylist_erase(pt->header, "ESO TEL AMBI RHUM");
  state = cpl_errorstate_get();
  rc = muse_phys_air_to_vacuum(pt, MUSE_PHYS_AIR_MEASURED);
  cpl_test(rc == CPL_ERROR_DATA_NOT_FOUND && !cpl_errorstate_is_equal(state));
  cpl_errorstate_set(state);
  cpl_propertylist_append_float(pt->header, "ESO TEL AMBI RHUM", 0.25);
  /* missing temperature keyword */
  cpl_propertylist_erase(pt->header, "ESO TEL AMBI TEMP");
  state = cpl_errorstate_get();
  rc = muse_phys_air_to_vacuum(pt, MUSE_PHYS_AIR_MEASURED);
  cpl_test(rc == CPL_ERROR_DATA_NOT_FOUND && !cpl_errorstate_is_equal(state));
  cpl_errorstate_set(state);
  cpl_propertylist_append_float(pt->header, "ESO TEL AMBI TEMP", 5.);
  /* missing pressure keyword */
  cpl_propertylist_erase(pt->header, "ESO TEL AMBI PRES END");
  state = cpl_errorstate_get();
  rc = muse_phys_air_to_vacuum(pt, MUSE_PHYS_AIR_MEASURED);
  cpl_test(rc == CPL_ERROR_DATA_NOT_FOUND && !cpl_errorstate_is_equal(state));
  cpl_errorstate_set(state);
  muse_pixtable_delete(pt);

  return cpl_test_end(0);
}
