/*
 * This file is part of the KMOS Pipeline
 * Copyright (C) 2002,2003 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

/*-----------------------------------------------------------------------------
 *                              Includes
 *----------------------------------------------------------------------------*/

#include <string.h>

#include <cpl.h>

#include "kmo_utils.h"
#include "kmo_dfs.h"
#include "kmo_priv_sky_mask.h"
#include "kmo_priv_functions.h"
#include "kmo_error.h"
#include "kmo_constants.h"
#include "kmo_debug.h"

/*-----------------------------------------------------------------------------
 *                          Functions prototypes
 *----------------------------------------------------------------------------*/

static int kmo_sky_mask_create(cpl_plugin *);
static int kmo_sky_mask_exec(cpl_plugin *);
static int kmo_sky_mask_destroy(cpl_plugin *);
static int kmo_sky_mask(cpl_parameterlist *, cpl_frameset *);

/*-----------------------------------------------------------------------------
 *                          Static variables
 *----------------------------------------------------------------------------*/

static char kmo_sky_mask_description[] =
"This recipes calculates masks of the skies surrounding the objects in the diff-\n"
"erent IFUs of a reconstructed F3I frame. In the resulting mask pixels belonging\n"
"to objects have value 1 and sky pixels have value 0.\n"
"The noise and the background level of the input data cube are estimated using\n"
"the mode calculated in kmo_stats. If the results aren't satisfactory, try chan-\n"
"ging --cpos_rej and --cneg_rej. Then pixels are flagged in the data cube which\n"
"have a value less than the mode plus twice the noise (val < mode + 2*sigma).\n"
"For each spatial pixel the fraction of flagged pixels in its spectral channel\n"
"is determined.\n"
"Spatial pixels are selected where the fraction of flagged spectral pixels is\n"
"greater than 0.95 (corresponding to the 2*sigma above).\n"
"The input cube can contain noise extensions, but they will be ignored. The out-\n"
"put doesn’t contain noise extensions.\n"
"\n"
"BASIC PARAMETERS:\n"
"-----------------\n"
"--fraction\n"
"The fraction of pixels that have to be greater than the threshold can be defi-\n"
"ned with this parameter (value must be between 0 and 1).\n"
"\n"
"--range\n"
"If required, a limited wavelength range can be defined (e.g. \"1.8,2.1\").\n"
"\n"
"ADVANCED PARAMETERS\n"
"-------------------\n"
"--cpos_rej\n"
"--cneg_rej\n"
"--citer\n"
"An iterative sigma clipping is applied in order to calculate the mode (using\n"
"kmo_stats). For each position all pixels in the spectrum are examined. If they\n"
"deviate significantly, they will be rejected according to the conditions:\n"
"       val > mean + stdev * cpos_rej\n"
"   and\n"
"       val < mean - stdev * cneg_rej\n"
"In the first iteration median and percentile level are used.\n"
"\n"
"-------------------------------------------------------------------------------\n"
"  Input files:\n"
"\n"
"   DO                    KMOS                                                  \n"
"   category              Type   Explanation                    Required #Frames\n"
"   --------              -----  -----------                    -------- -------\n"
"   <none or any>         F3I    The datacube frame                 Y       1   \n"
"\n"
"  Output files:\n"
"\n"
"   DO                    KMOS\n"
"   category              Type   Explanation\n"
"   --------              -----  -----------\n"
"   SKY_MASK              F2I    The mask frame\n"
"-------------------------------------------------------------------------------\n"
"\n";

/*-----------------------------------------------------------------------------
 *                              Functions code
 *----------------------------------------------------------------------------*/

/**
 * @defgroup kmo_sky_mask kmo_sky_mask Create a mask of spatial pixels that indicates which pixels can be considered as sky
 *
 * See recipe description for details.
 */

/**@{*/

/**
  @brief    Build the list of available plugins, for this module. 
  @param    list    the plugin list
  @return   0 if everything is ok, -1 otherwise

  Create the recipe instance and make it available to the application using the 
  interface. This function is exported.
 */
int cpl_plugin_get_info(cpl_pluginlist *list)
{
    cpl_recipe *recipe = cpl_calloc(1, sizeof *recipe);
    cpl_plugin *plugin = &recipe->interface;

    cpl_plugin_init(plugin,
                        CPL_PLUGIN_API,
                        KMOS_BINARY_VERSION,
                        CPL_PLUGIN_TYPE_RECIPE,
                        "kmo_sky_mask",
                        "Create a mask of spatial pixels that indicates which "
                        "pixels can be considered as sky.",
                        kmo_sky_mask_description,
                        "Alex Agudo Berbel",
                        "usd-help@eso.org",
                        kmos_get_license(),
                        kmo_sky_mask_create,
                        kmo_sky_mask_exec,
                        kmo_sky_mask_destroy);

    cpl_pluginlist_append(list, plugin);

    return 0;
}

/**
  @brief    Setup the recipe options    
  @param    plugin  the plugin
  @return   0 if everything is ok

  Defining the command-line/configuration parameters for the recipe.
 */
static int kmo_sky_mask_create(cpl_plugin *plugin)
{
    cpl_recipe *recipe;
    cpl_parameter *p;

    /* Check that the plugin is part of a valid recipe */
    if (cpl_plugin_get_type(plugin) == CPL_PLUGIN_TYPE_RECIPE) 
        recipe = (cpl_recipe *)plugin;
    else
        return -1;

    /* Create the parameters list in the cpl_recipe object */
    recipe->parameters = cpl_parameterlist_new();

    /* Fill the parameters list */
    /* --range */
    p = cpl_parameter_new_value("kmos.kmo_sky_mask.range",
                                CPL_TYPE_STRING,
                                "Min & max wavelengths to use in sky pixel "
                                    "determination, e.g. [x1_start,x1_end]"
                                    " (microns).",
                                "kmos.kmo_sky_mask",
                                "");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "range");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --fraction */
    p = cpl_parameter_new_value("kmos.kmo_sky_mask.fraction",
                                CPL_TYPE_DOUBLE,
                                "Minimum fraction of spatial pixels to select "
                                "as sky (value between 0 and 1).",
                                "kmos.kmo_sky_mask",
                                0.95);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "fraction");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    return kmos_combine_pars_create(recipe->parameters,
                                   "kmos.kmo_sky_mask",
                                   DEF_REJ_METHOD,
                                   TRUE);
}

/**
  @brief    Execute the plugin instance given by the interface
  @param    plugin  the plugin
  @return   0 if everything is ok
 */
static int kmo_sky_mask_exec(cpl_plugin *plugin)
{
    cpl_recipe  *recipe;

    /* Get the recipe out of the plugin */
    if (cpl_plugin_get_type(plugin) == CPL_PLUGIN_TYPE_RECIPE) 
        recipe = (cpl_recipe *)plugin;
    else return -1;

    return kmo_sky_mask(recipe->parameters, recipe->frames);
}

/**
  @brief    Destroy what has been created by the 'create' function
  @param    plugin  the plugin
  @return   0 if everything is ok
 */
static int kmo_sky_mask_destroy(cpl_plugin *plugin)
{
    cpl_recipe *recipe;

    /* Get the recipe out of the plugin */
    if (cpl_plugin_get_type(plugin) == CPL_PLUGIN_TYPE_RECIPE) 
        recipe = (cpl_recipe *)plugin;
    else return -1 ;

    cpl_parameterlist_delete(recipe->parameters);
    return 0 ;
}

/**
  @brief    Interpret the command line options and execute the data processing
  @param    parlist     the parameters list
  @param    frameset   the frames list
  @return   0 if everything is ok

  Possible _cpl_error_code_ set in this function:

    @li CPL_ERROR_ILLEGAL_INPUT      if operator not valid,
                                     if first operand not 3d or
                                     if second operand not valid
    @li CPL_ERROR_INCOMPATIBLE_INPUT if the dimensions of the two operands
                                     do not match
 */
static int kmo_sky_mask(cpl_parameterlist *parlist, cpl_frameset *frameset)
{
    cpl_imagelist    *data_in            = NULL;

    cpl_image        *data_out           = NULL;

    cpl_vector       *ranges             = NULL;

    int              ret_val             = 0,
                     nr_devices          = 0,
                     i                   = 0,
                     valid_ifu           = FALSE,
                     citer               = 0,
                     devnr               = 0,
                     index_data          = 0;

    double           cpos_rej            = 0.0,
                     cneg_rej            = 0.0,
                     fraction            = 0.0,
                     ifu_crpix           = 0.0,
                     ifu_crval           = 0.0,
                     ifu_cdelt           = 0.0;

    const char       *ranges_txt         = NULL,
                     *cmethod            = NULL;

    cpl_propertylist *sub_header_data    = NULL;

    main_fits_desc   desc;

    cpl_frame        *frame              = NULL;

    KMO_TRY
    {
        kmo_init_fits_desc(&desc);

        // --- check input ---
        KMO_TRY_ASSURE((parlist != NULL) &&
                       (frameset != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data is provided!");

        KMO_TRY_ASSURE(cpl_frameset_get_size(frameset) == 1,
                       CPL_ERROR_NULL_INPUT,
                       "Exactly one data cube must be provided!");

        KMO_TRY_ASSURE(kmo_dfs_set_groups(frameset, "kmo_sky_mask") == 1,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Cannot identify RAW and CALIB frames!");

        KMO_TRY_EXIT_IF_NULL(
            frame = kmo_dfs_get_frame(frameset, "0"));

        cpl_msg_info("", "--- Parameter setup for kmo_sky_mask ------");

        ranges_txt = kmo_dfs_get_parameter_string(parlist,
                                                  "kmos.kmo_sky_mask.range");
        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_shift.range"));

        ranges = kmo_identify_ranges(ranges_txt);
        KMO_TRY_CHECK_ERROR_STATE();

        fraction = kmo_dfs_get_parameter_double(parlist,
                                           "kmos.kmo_sky_mask.fraction");
        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_shift.fraction"));

        KMO_TRY_ASSURE((fraction >= 0.0) &&
                       (fraction <= 1.0),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "fraction must be between 0.0 and 1.0!!");

        KMO_TRY_EXIT_IF_ERROR(
            kmos_combine_pars_load(parlist,
                                  "kmos.kmo_sky_mask",
                                  &cmethod,
                                  &cpos_rej,
                                  &cneg_rej,
                                  &citer,
                                  NULL,
                                  NULL,
                                  FALSE));

        cpl_msg_info("", "-------------------------------------------");

        // load descriptor, header and data of first operand
        desc = kmo_identify_fits_header(
                    cpl_frame_get_filename(frame));
        KMO_TRY_CHECK_ERROR_STATE_MSG("Provided fits file doesn't seem to be "
                                      "in KMOS-format!");

        KMO_TRY_ASSURE(desc.fits_type == f3i_fits,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "The input file hasn't correct data type "
                       "(KMOSTYPE must be F3I)!");

        // --- load, update & save primary header ---
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_save_main_header(frameset, SKY_MASK, "", frame,
                                     NULL, parlist, cpl_func));

        // --- load data ---
        if (desc.ex_noise == TRUE) {
            nr_devices = desc.nr_ext / 2;
        } else {
            nr_devices = desc.nr_ext;
        }

        for (i = 1; i <= nr_devices; i++) {
            if (desc.ex_noise == FALSE) {
                devnr = desc.sub_desc[i - 1].device_nr;
            } else {
                devnr = desc.sub_desc[2 * i - 1].device_nr;
            }

            if (desc.ex_badpix == FALSE) {
                index_data = kmo_identify_index_desc(desc, devnr, FALSE);
            } else {
                index_data = kmo_identify_index_desc(desc, devnr, 2);
            }
            KMO_TRY_CHECK_ERROR_STATE();

            KMO_TRY_EXIT_IF_NULL(
                sub_header_data = kmo_dfs_load_sub_header(frameset, "0", devnr,
                                                          FALSE));

            // check if IFU is valid
            valid_ifu = FALSE;
            if (desc.sub_desc[index_data-1].valid_data == TRUE) {
                valid_ifu = TRUE;
            }

            if (valid_ifu) {
                // load data
                KMO_TRY_EXIT_IF_NULL(
                    data_in = kmo_dfs_load_cube(frameset, "0", devnr, FALSE));

                if (ranges != NULL) {
                    ifu_crpix = cpl_propertylist_get_double(sub_header_data, CRPIX3);
                    KMO_TRY_CHECK_ERROR_STATE_MSG(
                                   "CRPIX3 keyword in FITS-header is missing!");

                    ifu_crval = cpl_propertylist_get_double(sub_header_data, CRVAL3);
                    KMO_TRY_CHECK_ERROR_STATE_MSG(
                                   "CRVAL3 keyword in FITS-header is missing!");

                    ifu_cdelt = cpl_propertylist_get_double(sub_header_data, CDELT3);
                    KMO_TRY_CHECK_ERROR_STATE_MSG(
                                   "CDELT3 keyword in FITS-header is missing!");
                }

                cpl_propertylist_erase(sub_header_data, CRPIX3);
                cpl_propertylist_erase(sub_header_data, CRVAL3);
                cpl_propertylist_erase(sub_header_data, CDELT3);
                cpl_propertylist_erase(sub_header_data, CTYPE3);

                // calc mode and noise
                KMO_TRY_EXIT_IF_NULL(
                    data_out = kmo_calc_sky_mask(data_in,
                                                 ranges,
                                                 fraction,
                                                 ifu_crpix,
                                                 ifu_crval,
                                                 ifu_cdelt,
                                                 cpos_rej,
                                                 cneg_rej,
                                                 citer));

                // save data
                KMO_TRY_EXIT_IF_ERROR(
                    kmo_dfs_save_image(data_out, SKY_MASK, "", sub_header_data, 0.));

                // free memory
                cpl_imagelist_delete(data_in); data_in = NULL;
                cpl_image_delete(data_out); data_out = NULL;
            } else {
                // invalid IFU, just save sub_headers
                KMO_TRY_EXIT_IF_ERROR(
                    kmo_dfs_save_sub_header(SKY_MASK, "", sub_header_data));
            }

            // free memory
            cpl_propertylist_delete(sub_header_data); sub_header_data = NULL;
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();

        ret_val = -1;
    }

    kmo_free_fits_desc(&desc);
    cpl_propertylist_delete(sub_header_data); sub_header_data = NULL;
    cpl_imagelist_delete(data_in); data_in = NULL;
    cpl_image_delete(data_out); data_out = NULL;
    cpl_vector_delete(ranges); ranges = NULL;
    return ret_val;
}

/**@}*/
