/* -*- 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-2011 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 <unistd.h> /* rmdir() */
#include <stdio.h> /* remove() */
#include <sys/stat.h> /* mkdir() */
#include <string.h>

#include <muse.h>
#include <muse_instrument.h>

#define DIR_TEMPLATE "/tmp/muse_sky_master_test_XXXXXX"

static void
test_sky_lines_file(const char *aDir)
{
  const char *filename = BASEFILENAME"_master_lines.fits";
  cpl_msg_info(__func__, "using SKY LINES file %s", filename);
  cpl_table *oh_transitions = muse_cpltable_load(filename, "OH_TRANSITIONS", 
                                                 muse_sky_lines_oh_transitions_def);
  cpl_test(oh_transitions != NULL);
  if (oh_transitions != NULL) {
    cpl_test_error(muse_cpltable_check(oh_transitions,
                                       muse_sky_lines_oh_transitions_def));
    cpl_test(cpl_table_get_nrow(oh_transitions) > 0);
  }
  cpl_table *lines = muse_cpltable_load(filename, "LINES", muse_sky_lines_lines_def);
  cpl_test(lines != NULL);
  if (lines != NULL) {
    cpl_test_error(muse_cpltable_check(lines, muse_sky_lines_lines_def));
    cpl_test_lt(0, cpl_table_get_nrow(lines));
  }
  cpl_table *sky_lines = muse_sky_lines_create(lines, oh_transitions, 200.0);
  cpl_test_nonnull(sky_lines);
  if (sky_lines != NULL) {
    cpl_test_error(muse_cpltable_check(sky_lines, muse_sky_lines_lines_def));
    if ((oh_transitions != NULL) && (lines != NULL)) {
      cpl_test_eq(cpl_table_get_nrow(sky_lines),
                  cpl_table_get_nrow(oh_transitions) + cpl_table_get_nrow(lines));
    }

    // restrict wavelength range to 5000...6000 Angstrom
    double min = 5000;
    double max = 6000;
    muse_sky_lines_set_range(sky_lines, min, max);
    cpl_test_lt(0, cpl_table_get_nrow(sky_lines));
    cpl_test_leq(min, cpl_table_get_column_min(sky_lines, "lambda"));
    cpl_test_leq(cpl_table_get_column_max(sky_lines, "lambda"), max);
    
    // save lines and reload them
    char *sfilename = cpl_sprintf("%s/sky_lines.fits", aDir);
    cpl_msg_info(__func__, "using LINES file %s", sfilename);
    cpl_propertylist_save(NULL, sfilename, CPL_IO_CREATE);
    if (sky_lines != NULL) {
      cpl_test(muse_cpltable_append_file(sky_lines, sfilename, 
                                         "LINES", muse_sky_lines_lines_def)
               == CPL_ERROR_NONE);
      cpl_table *lines2 = muse_cpltable_load(sfilename, "LINES", 
                                             muse_sky_lines_lines_def);
      cpl_test_nonnull(lines2);
      if (lines2 != NULL) {
        cpl_test_eq(muse_cpltable_check(lines2, muse_sky_lines_lines_def), CPL_ERROR_NONE);
        cpl_test_eq(cpl_table_get_nrow(lines2), cpl_table_get_nrow(sky_lines));
        cpl_table_delete(lines2);
      }
    }
    remove(sfilename);
    cpl_free(sfilename);
    cpl_table_delete(sky_lines);
    cpl_table_delete(lines);
    cpl_table_delete(oh_transitions);
  }
}

static muse_sky_master *
muse_sky_master_load_(const char *aFile) {
  cpl_ensure(aFile != NULL, CPL_ERROR_NULL_INPUT, NULL); 

  muse_sky_master *res = muse_sky_master_new();
  res->lines =  muse_cpltable_load(aFile, "LINES", muse_sky_lines_lines_def);
  if (res->lines == NULL) {
    muse_sky_master_delete(res);
    return NULL;
  }

  res->continuum = muse_cpltable_load(aFile, 
                                      "CONTINUUM", muse_fluxspectrum_def);
  if (res->continuum == NULL) {
    muse_sky_master_delete(res);
    return NULL;
  }
  return res;
}

static cpl_error_code
muse_sky_master_save(const muse_sky_master *aMaster, const char *aFile) {
  cpl_error_code r;
  r = muse_cpltable_append_file(aMaster->lines, aFile, "LINES", 
                                muse_sky_lines_lines_def);
  if (r != CPL_ERROR_NONE) {
    return r;
  }
  r = muse_cpltable_append_file(aMaster->continuum, aFile, 
                                "CONTINUUM", muse_fluxspectrum_def);
  return r;
}

static void
test_master_sky_file(const char *aDir)
{
  const char *filename = BASEFILENAME"_master_master_sky.fits";
  cpl_msg_info(__func__, "using MASTER SKY file %s", filename);
  muse_sky_master *master = muse_sky_master_load_(filename);
  cpl_test(master != NULL);
  if (master != NULL) {
    cpl_test(master->lines != NULL);
    
    if (master->lines != NULL) {
      cpl_test(muse_cpltable_check(master->lines,
                                   muse_sky_lines_lines_def) == CPL_ERROR_NONE);
      cpl_test(cpl_table_get_nrow(master->lines) > 0);
    }

    cpl_test(master->continuum != NULL);
    if (master->continuum != NULL) {
      cpl_test(muse_cpltable_check(master->continuum,
                                   muse_fluxspectrum_def) == CPL_ERROR_NONE);
      cpl_test(cpl_table_get_nrow(master->continuum) > 0);
    }
    
    char *sfilename = cpl_sprintf("%s/sky_master.fits", aDir);
    cpl_propertylist_save(NULL, sfilename, CPL_IO_CREATE);
    cpl_test(muse_sky_master_save(master, sfilename) == CPL_ERROR_NONE);
    muse_sky_master *smaster = muse_sky_master_load_(sfilename);

    cpl_test(smaster != NULL);
    if (smaster != NULL) {
      cpl_test(smaster->lines != NULL);
      if (smaster->lines != NULL) {
        cpl_test(muse_cpltable_check(smaster->lines,
                                     muse_sky_lines_lines_def) == CPL_ERROR_NONE);
        cpl_test(cpl_table_get_nrow(smaster->lines) == 
                 cpl_table_get_nrow(master->lines));
      }
      cpl_test(master->continuum != NULL);
      if (master->continuum != NULL) {
        cpl_test(muse_cpltable_check(master->continuum,
                                     muse_fluxspectrum_def) == CPL_ERROR_NONE);
        cpl_test(cpl_table_get_nrow(smaster->continuum) == 
                 cpl_table_get_nrow(master->continuum));
      }
      muse_sky_master_delete(smaster);
    }
    
    remove(sfilename);
    cpl_free(sfilename);
    muse_sky_master_delete(master);
  }
}

/*----------------------------------------------------------------------------*/
/**
  @brief Compare the computed lines with a reference.
  @param aLines Computer lines list, from muse_sky_master

  The comparison takes the strongest line and compares of each line group and 
  compares flux, wavelength and group/line name.

  The comparison table can be produced by running the following python script
  on the reference master file:
  @pseudocode
  import numpy, pyfits
  master_sky = pyfits.open('MASTER_SKY.fits')
  groups = numpy.unique(master_sky[1].data.field('group'))
  indices = [ numpy.argwhere(master_sky[1].data.field('group') == i)[0][0] 
              for i in groups ]
  master_sky[1].data = master_sky[1].data[indices]
  master_sky[1].data.field('flux')[master_sky[1].data.field('flux') < 1e-23] = 0
  master_sky[1].writeto('test_lines_reference.fits')
  @endpseudocode
  
  Lines that were used by 

 */
/*----------------------------------------------------------------------------*/
static int
check_lines(cpl_table *aLines, const char *aRefTableName) {

  cpl_table *reftable = cpl_table_load(aRefTableName, 1, 0);
  cpl_ensure_code(reftable != NULL, 1);

  cpl_test_nonnull(aLines);

  int ngroups = cpl_table_get_column_max(aLines, "group") + 1;
  int i;

  double max_flux = cpl_table_get_column_max(reftable, "flux");
  for (i = 0; i < ngroups; i++) {
    cpl_table_unselect_all(aLines);
    cpl_table_or_selected_int(aLines, "group", CPL_EQUAL_TO, i);
    cpl_table *gtable = cpl_table_extract_selected(aLines);
    cpl_size row;
    cpl_table_get_column_maxpos(gtable, "flux", &row);
    const char *name = cpl_table_get_string(gtable, "name", row);
    double wavelength = cpl_table_get_double(gtable, "lambda", row, NULL);
    double flux = cpl_table_get_double(gtable, "flux", row, NULL);
    double ref_flux = cpl_table_get(reftable, "flux", i, NULL);
    double ref_wavelength = cpl_table_get(reftable, "lambda", i, NULL);
    cpl_msg_info(__func__, "%s[%.2f] = %g", name, wavelength, flux);
    cpl_test(strncmp(name, cpl_table_get_string(reftable, "name", i), 
                     strlen("OH 4-0")) == 0);
    cpl_test_abs(ref_wavelength, wavelength, 0.1);
    if (ref_flux > 0.01 * max_flux) {
      cpl_test_rel(ref_flux, flux, 0.05);
    } else {
      cpl_test_abs(ref_flux, flux, 0.0005 * max_flux);
    }
    cpl_table_delete(gtable);
  }
  cpl_table_unselect_all(aLines);
  cpl_table_delete(reftable);
  return 0;

}

static int
check_continuum(cpl_table *aContinuum, const char *aContinuumRefName) {
  cpl_test_nonnull(aContinuum);
  cpl_table *continuumRef = cpl_table_load(aContinuumRefName, 1, 0);
  cpl_ensure_code(continuumRef != NULL, 1);

#if 0
  cpl_table_save(aContinuum, NULL, NULL, "continuum.fits", CPL_IO_CREATE);
#endif

  cpl_array *lambda = muse_cpltable_extract_column(aContinuum, "lambda");
  cpl_array *flux = muse_cpltable_extract_column(aContinuum, "flux");
  cpl_array *fluxRef 
    = muse_cplarray_interpolate_table_linear(lambda, continuumRef,
                                             "lambda", "flux");
  
  double mean = cpl_array_get_mean(fluxRef);

  // The factor 10 is chosen arbitrarily here -- in fact it should be much 
  // smaller. We still have some sky lines included, which would otherwise
  // bring the test to fail. I have still no good idea how to really check
  // the continuum.
  cpl_test_array_abs(flux, fluxRef, 10 * mean);
  cpl_array_unwrap(lambda);
  cpl_array_unwrap(flux);
  cpl_array_delete(fluxRef);
  cpl_table_delete(continuumRef);

  return 0;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Test the master fit. 
  @param aDataName Sky data file name, containing line tables etc.
  @param aPixtableName Input pixtable containing just sky spaxels
 */
/*----------------------------------------------------------------------------*/
static int
test_masterfit(const char *aPixtableName, const char *aLinesTableName,
               const char *aRefTableName, const char *aContinuumRefName)
{
  cpl_msg_info(__func__, "using input pixtable %s", aPixtableName);

  muse_pixtable *sky = muse_pixtable_load(aPixtableName);
  cpl_table_select_all(sky->table);
  cpl_table_and_selected_int(sky->table, "dq",CPL_NOT_EQUAL_TO , 0);
  cpl_table_erase_selected(sky->table);
  cpl_ensure_code(sky != NULL, 1);

  cpl_table *spectrum = muse_resampling_spectrum(sky, kMuseSpectralSamplingA/2.0);
  cpl_ensure_code(spectrum != NULL, 1);

  cpl_array *lambda = muse_cpltable_extract_column(spectrum, "lambda");
  double lambda_low = cpl_array_get_min(lambda) - 10;
  double lambda_high = cpl_array_get_max(lambda) + 10;
  
  cpl_table *oh_transitions = muse_cpltable_load(aLinesTableName, 
                                                 "OH_TRANSITIONS", 
                                                 muse_sky_lines_oh_transitions_def);
  cpl_test_nonnull(oh_transitions);
  cpl_table *lines = muse_cpltable_load(aLinesTableName, 
                                        "LINES", muse_sky_lines_lines_def);
  cpl_test_nonnull(lines);
  
  cpl_table *sky_lines = muse_sky_lines_create(lines, oh_transitions, 200.0);
  cpl_table_delete(lines);
  cpl_table_delete(oh_transitions);
  muse_sky_lines_set_range(sky_lines, lambda_low, lambda_high);
  
  cpl_array *datacol = muse_cpltable_extract_column(spectrum, "data");
  cpl_array *statcol = muse_cpltable_extract_column(spectrum, "stat");

  muse_sky_master *master
    = muse_sky_master_fit(lambda, datacol, statcol, sky_lines);

  check_lines(master->lines, aRefTableName);
  check_continuum(master->continuum, aContinuumRefName);

  /* use computed master sky to test the sky _subtraction_ from the pixel table */
  cpl_errorstate ps = cpl_errorstate_get();
  cpl_error_code rc = muse_sky_subtract_pixtable(sky, master, master->lsf);
  cpl_test(cpl_errorstate_is_equal(ps) && rc == CPL_ERROR_NONE);
  /* test success case of muse_pixtable_is_skysub() */
  ps = cpl_errorstate_get();
  cpl_boolean skysub = muse_pixtable_is_skysub(sky);
  cpl_test(skysub);
  cpl_test(cpl_errorstate_is_equal(ps));

  cpl_table_delete(sky_lines);
  cpl_array_unwrap(lambda);
  cpl_array_unwrap(datacol);
  cpl_array_unwrap(statcol);
  cpl_table_delete(spectrum);
  muse_pixtable_delete(sky);

  muse_sky_master_delete(master);

  return 0;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    short test program to check sky parameter related stuff.
 */
/*----------------------------------------------------------------------------*/
int main(int argc, char **argv)
{
  cpl_test_init(PACKAGE_BUGREPORT, CPL_MSG_INFO);

  char dirtemplate[FILENAME_MAX] = DIR_TEMPLATE;
  char *dir = mkdtemp(dirtemplate);

  test_sky_lines_file(dir);
  test_master_sky_file(dir);

  if (argc < 2) {
    test_masterfit(BASEFILENAME"_master_pixtable.fits", BASEFILENAME"_master_lines.fits",
                   BASEFILENAME"_master_reference_lines.fits",
                   BASEFILENAME"_master_reference_continuum.fits");
  } else {
    test_masterfit(argv[1], BASEFILENAME"_master_lines.fits",
                   BASEFILENAME"_master_reference_lines.fits",
                   BASEFILENAME"_master_reference_continuum.fits");
  }

  cpl_test_zero(rmdir(dir));
  return cpl_test_end(0);
}
