/* -*- 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-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>
#include <string.h>

/*----------------------------------------------------------------------------*/
/**
 * @defgroup tools_musecubefilter           Tool muse_cube_filter
 *
 * @brief <b>muse_cube_filter</b>: Add filtered images for existing MUSE cubes
 *
 * Create reconstructed images over filter functions for already existing MUSE
 * datacubes.
 *
 * Just as with the muse_scipost and muse_exp_combine pipeline recipes, the
 * images created by this tool can be either saved as separate images or as new
 * extensions of the input file.
 *
 * <b>Command line arguments:</b>
 *   - <tt>-x</tt> (optional)\n
 *     save new reconstructed images as extensions to the input cube
 *   - <tt>-f filter,names</tt> (optional, default: all)\n
 *     comma-separated filter names (EXTNAMEs) to load from FILTER_LIST;
 *     by default all table extensions of FILTER_LIST are used to reconstruct
 *     images
 *   - <tt>-l</tt> (optional)\n
 *     give wavelength range (with two extra numerical arguments) to create
 *     an ad-hoc filter
 *   - <tt><b>CUBE</b></tt>\n
 *     the filename of the MUSE cube to use
 *   - <tt><b>FILTER_LIST</b></tt>\n
 *     the filename of the FILTER_LIST file containing the filter functions to
 *     use
 *
 * <b>Return values:</b>
 *   - <tt> 0</tt>\n   Success
 *   - <tt> 1</tt>\n   less than two filenames given
 *   - <tt> 2</tt>\n   argument <tt>-f</tt> given without filter names
 *   - <tt> 3</tt>\n   argument <tt>-l</tt> given without wavelength values
 *   - <tt> 9</tt>\n   unknown option given
 *   - <tt>10</tt>\n   either CUBE or FILTER_LIST filenames are missing
 *   - <tt>11</tt>\n   CUBE could not be loaded from file
 *   - <tt>12</tt>\n   illegal wavelength range given
 */
/*----------------------------------------------------------------------------*/

/**@{*/

#define PRINT_USAGE(rc)                                                        \
  fprintf(stderr, "Usage: %s [ -x ] [ -f filter,names ] [ -l <lambda1> <lambda"\
          "2> ] CUBE FILTER_LIST\n", argv[0]);                                                            \
  cpl_end(); return (rc);

int main(int argc, char **argv)
{
  const char *idstring = "muse_cube_filter";
  cpl_init(CPL_INIT_DEFAULT);
  cpl_msg_set_time_on();
  muse_processing_recipeinfo(NULL);
  cpl_msg_set_level(CPL_MSG_DEBUG);
  cpl_msg_set_component_on();
  cpl_errorstate state = cpl_errorstate_get();

  if (argc < 3) {
    /* two filenames are needed at least */
    PRINT_USAGE(1);
  }

  cpl_array *filters = NULL; /* filter names */
  char *iname = NULL, /* input name */
       *list = NULL; /* filter list names */
  double lambda1 = NAN,
         lambda2 = NAN;
  cpl_boolean extended = CPL_FALSE;
  int i;

  /* argument processing */
  for (i = 1; i < argc; i++) {
    if (strncmp(argv[i], "-f", 3) == 0) {
      /* skip to next arg to get start value */
      i++;
      if (i < argc) {
        filters = muse_cplarray_new_from_delimited_string(argv[i], ",");
      } else {
        PRINT_USAGE(2);
      }
    } else if (strncmp(argv[i], "-x", 3) == 0) {
      extended = CPL_TRUE;
    } else if (strncmp(argv[i], "-l", 3) == 0) {
      /* skip to next arg to get start value */
      if (i+2 < argc) {
        lambda1 = atof(argv[++i]);
        lambda2 = atof(argv[++i]);
      } else {
        PRINT_USAGE(3);
      }
    } else if (strncmp(argv[i], "-", 1) == 0) { /* unallowed options */
      PRINT_USAGE(9);
    } else {
      if (iname && list) {
        break; /* we have the required names, skip the rest */
      }
      if (!iname) {
        iname = argv[i]; /* set the name for the input cube */
      } else {
        list = argv[i]; /* set the name for the filter list table */
      }
    }
  } /* for i (all arguments) */
  if (!iname || !list) {
    PRINT_USAGE(10);
  }
  int iext = 0;
  if (!filters) {
    cpl_msg_info(idstring, "No filter names given, will use all from \"%s\"",
                 list);
    /* create the array from the filter list file extension names */
    filters = cpl_array_new(0, CPL_TYPE_STRING);
    cpl_errorstate es = cpl_errorstate_get();
    do {
      iext++;
      cpl_propertylist *header = cpl_propertylist_load(list, iext);
      if (!header) {
        break;
      }
      if (!cpl_propertylist_has(header, "EXTNAME")) {
        continue;
      }
      const char *extname = muse_pfits_get_extname(header);
      cpl_array_set_size(filters, cpl_array_get_size(filters) + 1);
      cpl_array_set_string(filters, cpl_array_get_size(filters) - 1, extname);
      cpl_msg_info(idstring, "Added filter \"%s\"", extname);
      cpl_propertylist_delete(header);
    } while (cpl_errorstate_is_equal(es));
    cpl_errorstate_set(es);
  } /* if no filters given */

  muse_table *fcustom = NULL;
  if (isnan(lambda1) && isnan(lambda2)) {
    /* do nothing, this is expected if the -l parameter was not used! */
  } else if (!isfinite(lambda1) || !isfinite(lambda2) || lambda1 >= lambda2) {
    cpl_msg_error(idstring, "Bad wavelength range given %f ... %f Angstrom!",
                  lambda1, lambda2);
    cpl_array_delete(filters);
    PRINT_USAGE(12); /* better stop, before time is spent to load the cube */
  } else {
    /* add custom filter */
    char *fname = cpl_sprintf("CUSTOM_%.3f_%.3f", lambda1, lambda2);
    cpl_array_set_size(filters, cpl_array_get_size(filters) + 1);
    cpl_array_set_string(filters, cpl_array_get_size(filters) - 1, fname);
    /* create ad-hoc filter, using minimal rectangular filter function table */
    extern const muse_cpltable_def muse_filtertable_def[];
    cpl_table *ftable = muse_cpltable_new(muse_filtertable_def, 4);
    cpl_table_set(ftable, "lambda", 0, lambda1 - 1e-5);
    cpl_table_set(ftable, "lambda", 1, lambda1);
    cpl_table_set(ftable, "lambda", 2, lambda2);
    cpl_table_set(ftable, "lambda", 3, lambda2 + 1e-5);
    cpl_table_set(ftable, "throughput", 0, 0.);
    cpl_table_set(ftable, "throughput", 1, 1.);
    cpl_table_set(ftable, "throughput", 2, 1.);
    cpl_table_set(ftable, "throughput", 3, 0.);
    /* now convert into MUSE table with header */
    fcustom = muse_table_new();
    fcustom->table = ftable;
    fcustom->header = cpl_propertylist_new();
    cpl_propertylist_append_string(fcustom->header, "EXTNAME", fname);
    cpl_free(fname);
  } /* else: valid wavelengths */
#if 0
  cpl_array_dump(filters, 0, 10000, stdout);
  fflush(stdout);
#endif

  cpl_msg_info(idstring, "Loading cube \"%s\"", iname);
  muse_datacube *cube = muse_datacube_load(iname);
  if (!cube) {
    cpl_msg_error(idstring, "MUSE cube could not be loaded from \"%s\"!", iname);
    PRINT_USAGE(11);
  }

  /* get data sizes, mainly for display */
  int nx = cpl_image_get_size_x(cpl_imagelist_get(cube->data, 0)),
      ny = cpl_image_get_size_y(cpl_imagelist_get(cube->data, 0)),
      nl = cpl_imagelist_get_size(cube->data);
  cpl_msg_info(idstring, "Loaded cube \"%s\" (%dx%dx%d)", iname, nx, ny, nl);

  muse_imagelist *images = muse_imagelist_new();
  cpl_array *names = cpl_array_new(0, CPL_TYPE_STRING);
  int idx, n = cpl_array_get_size(filters);
  for (i = 0, idx = 0; i < n; i++) {
    const char *fname = cpl_array_get_string(filters, i);
    muse_table *ftable = NULL;
    if (fname && strncmp(fname, "white", 6) && strncmp(fname, "CUSTOM_", 7)) {
      iext = cpl_fits_find_extension(list, fname);
      if (iext < 1) {
        cpl_msg_warning(idstring, "No filter \"%s\" found in table \"%s\"", fname,
                        list);
        continue;
      }
      cpl_msg_info(idstring, "Integrating over filter \"%s\" (from %s)", fname,
                   MUSE_TAG_FILTER_LIST);
      ftable = muse_table_new();
      ftable->table = cpl_table_load(list, iext, 1);
      ftable->header = cpl_propertylist_load(list, 0);
      cpl_propertylist *hext = cpl_propertylist_load(list, iext);
      cpl_propertylist_copy_property_regexp(ftable->header, hext,
                                            "^EXTNAME$|^Z|^COMMENT", 0);
      cpl_propertylist_delete(hext);
    } else if (fname && !strncmp(fname, "CUSTOM_", 7)) {
      cpl_msg_info(idstring, "Integrating over \"%s\" filter (wavelength range "
                   "%.3f ... %.3f Angstrom)", fname, lambda1, lambda2);
      ftable = fcustom;
    } else {
      cpl_msg_info(idstring, "Integrating over filter \"%s\" (builtin)", fname);
      ftable = muse_table_load_filter(NULL, fname);
    }
    muse_image *fov = muse_datacube_collapse(cube, ftable);
    muse_table_delete(ftable);
    /* we want NANs instead of DQ in the output images */
    muse_image_dq_to_nan(fov);
    muse_imagelist_set(images, fov, idx++);
    cpl_array_set_size(names, cpl_array_get_size(names) + 1);
    cpl_array_set_string(names, cpl_array_get_size(names) - 1, fname);
  } /* for i (all filters) */
  cpl_array_delete(filters);
  muse_datacube_delete(cube);

#if 0
  cpl_array_dump(names, 0, n, stdout);
  fflush(stdout);
#endif

  /* extended format: append the new images to the original cube file */
  n = muse_imagelist_get_size(images);
  if (extended) {
    cpl_errorstate es = cpl_errorstate_get();
    cpl_error_code rc = muse_datacube_save_recimages(iname, images, names);
    if (rc == CPL_ERROR_NONE && cpl_errorstate_is_equal(es)) {
      cpl_msg_info(idstring, "Appended %d images into cube \"%s\"", n, iname);
    } else {
      cpl_msg_error(idstring, "Appending %d images into cube \"%s\" failed: %s",
                    n, iname, cpl_error_get_message());
    }
  } else { /* not extended */
    /* normal case: save new images for each filtered image */
    char *fn = cpl_sprintf("%s", iname),
         *basename = strstr(fn, ".fits");
    basename[0] = '\0'; /* clip off the .fits extension to get the base name */
    for (i = 0; i < n; i++) {
      const char *fname = cpl_array_get_string(names, i);
      muse_image *image = muse_imagelist_get(images, i);
      char *outname = cpl_sprintf("%s_%s.fits", fn, fname);
      // XXX treat the FITS header!!
      muse_image_save(image, outname);
      cpl_msg_info(idstring, "Saved image for filter \"%s\" into \"%s\"", fname,
                   outname);
      cpl_free(outname);
    } /* for i (all images) */
    cpl_free(fn);
  } /* else: not extended */
  cpl_array_delete(names);
  muse_imagelist_delete(images);

  if (!cpl_errorstate_is_equal(state)) {
    cpl_errorstate_dump(state, CPL_FALSE, muse_cplerrorstate_dump_some);
  }
  cpl_memory_dump();
  cpl_end();
  return 0;
}

/**@}*/
