/* -*- 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) 2014 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 _DEFAULT_SOURCE
#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 double
get_mean_flux(muse_pixtable *aPixtable, cpl_table *ref)
{
  // estimate the total flux.
  double flux = 0;
  int res;
  cpl_size nbins = cpl_table_get_nrow(ref);
  double lambda_min = cpl_table_get(ref, "lambda", 0, &res);
  cpl_test_eq(res, 0);
  double lambda_max = cpl_table_get(ref, "lambda", nbins-1, &res);
  cpl_test_eq(res, 0);
  double binwidth = (lambda_max - lambda_min) / (nbins - 1);
  cpl_size i;
  for (i = 0; i < nbins; i++) {
    cpl_table_select_all(aPixtable->table);
    cpl_table_and_selected_float(aPixtable->table, "lambda", CPL_NOT_LESS_THAN,
                                 lambda_min + i * binwidth);
    cpl_table_and_selected_float(aPixtable->table, "lambda", CPL_LESS_THAN,
                                 lambda_min + (i+1) * binwidth);
    cpl_table *sel = cpl_table_extract_selected(aPixtable->table);
    if (cpl_table_get_nrow(sel) > 0) {
      flux += cpl_table_get_column_mean(sel, "data");
    }
    cpl_table_delete(sel);
  }
  cpl_table_select_all(aPixtable->table);
  return flux * binwidth;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Test program for the sky_subtract related public functions.

  This is basically a simple re-work of muse_scipost_subtract_sky.
 */
/*----------------------------------------------------------------------------*/
int main(int argc, char **argv)
{
  UNUSED_ARGUMENTS(argc, argv);
  cpl_test_init(PACKAGE_BUGREPORT, CPL_MSG_INFO);

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

  cpl_error_code rc = CPL_ERROR_NONE;
  muse_pixtable *pixtable = muse_pixtable_load(BASEFILENAME"_pixtable.fits");
  double binning_spectrum = 0.3125;
  cpl_table *spectrum = muse_resampling_spectrum(pixtable, binning_spectrum);
  cpl_table *lines = cpl_table_load(BASEFILENAME"_lines.fits", 1, 1);
  muse_lsf_cube *lsfCube = muse_lsf_cube_load(BASEFILENAME"_lsf_cube.fits", 7);
  cpl_test_nonnull(lsfCube);

  muse_lsf_cube *lsfCubes[] = {
    NULL, NULL, NULL, lsfCube, NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
  };
  cpl_image *lsfImage = muse_lsf_average_cube_all(lsfCubes, pixtable);
  cpl_test_nonnull(lsfImage);
  muse_wcs *lsfWCS = muse_lsf_cube_get_wcs_all(lsfCubes);
  cpl_test_nonnull(lsfWCS);

  cpl_table_unselect_all(lines);
  cpl_table_or_selected_double(lines, "lambda", CPL_GREATER_THAN,
                               cpl_table_get_column_max(spectrum, "lambda"));
  cpl_table_or_selected_double(lines, "lambda", CPL_LESS_THAN,
                               cpl_table_get_column_min(spectrum, "lambda"));
  cpl_table_erase_selected(lines);

  rc = muse_sky_lines_fit(spectrum, lines, lsfImage, lsfWCS);
  cpl_test_eq(rc, CPL_ERROR_NONE);

  double binning_continuum = .3125;
  cpl_table *continuum = muse_sky_continuum_create(spectrum, lines,
                                                   lsfImage, lsfWCS,
                                                   binning_continuum);
  cpl_test_nonnull(continuum);

  // Calc the fluxes of the lines, the continuum, and the pixtable
  double flux_lines = cpl_table_get_column_mean(lines, "flux")
                    * cpl_table_get_nrow(lines);
  double flux_continuum = cpl_table_get_column_mean(continuum, "flux")
                        * cpl_table_get_nrow(continuum) * binning_continuum;
  double flux_orig = get_mean_flux(pixtable, continuum);

  // 1. Subtract continuum
  rc = muse_sky_subtract_continuum(pixtable, continuum);
  cpl_test_eq(rc, CPL_ERROR_NONE);

  // Check for flux conservation in continuum subtraction.
  double flux = get_mean_flux(pixtable, continuum);
  cpl_test_rel(flux_orig, flux + flux_continuum, 0.01);

  // 2. Subtract lines
  rc = muse_sky_subtract_lines(pixtable, lines, lsfCubes);
  cpl_test_eq(rc, CPL_ERROR_NONE);

  // Check for flux conservation in lines subtraction.
  flux = get_mean_flux(pixtable, continuum);

  cpl_test_rel(flux_orig, flux + flux_continuum + flux_lines, 0.01);

  // Check that we are approx at zero.
  cpl_test_abs(flux/flux_orig, 0, 0.02);

  // check that we don't get too much additional noise
  cpl_table_power_column(pixtable->table, "stat", 0.5);
  cpl_table_divide_columns(pixtable->table, "data", "stat");
  cpl_test_lt(cpl_table_get_column_stdev(pixtable->table, "data"), 2.0);

  // cleanup
  muse_pixtable_delete(pixtable);
  cpl_table_delete(spectrum);
  cpl_table_delete(continuum);
  cpl_table_delete(lines);
  cpl_image_delete(lsfImage);
  muse_lsf_cube_delete(lsfCube);

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