/*
 * 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 <math.h>

#include <cpl.h>

#include "kmo_utils.h"
#include "kmo_dfs.h"
#include "kmo_error.h"
#include "kmo_priv_extract_spec.h"
#include "kmo_priv_functions.h"
#include "kmo_cpl_extensions.h"
#include "kmo_constants.h"
#include "kmo_priv_fit_profile.h"
#include "kmo_debug.h"

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

static int kmos_extract_spec_create(cpl_plugin *);
static int kmos_extract_spec_exec(cpl_plugin *);
static int kmos_extract_spec_destroy(cpl_plugin *);
static int kmos_extract_spec(cpl_parameterlist *, cpl_frameset *);

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

static char kmos_extract_spec_description[] =
"This recipe extracts a spectrum from a datacube. The datacube is with or \n"
"without noise). The output will be a similarly formatted FITS file.\n"
"\n"
"---------------------------------------------------------------------------\n"
"  Input files:\n"
"\n"
"   DO                    KMOS                                              \n"
"   category              Type   Explanation                Required #Frames\n"
"   --------              -----  -----------                -------- -------\n"
"   <none or any>         F3I    The datacubes                 Y        1   \n"
"   <none or any>         F2I    The mask                      N       0,1  \n"
"\n"
"  Output files:\n"
"\n"
"   DO                    KMOS\n"
"   category              Type   Explanation\n"
"   --------              -----  -----------\n"
"   EXTRACT_SPEC          F1I    Extracted spectrum                         \n"
"   EXTRACT_SPEC_MASK     F2I    (optional, if --save_mask=true and         \n"
"                            --mask_method='optimal': The calculated mask)  \n"
"---------------------------------------------------------------------------\n"
"\n";

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

/*----------------------------------------------------------------------------*/
/**
  @defgroup kmos_extract_spec   Extract a spectrum from a cube
 */
/*----------------------------------------------------------------------------*/

/**@{*/

/*----------------------------------------------------------------------------*/
/**
  @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,
            "kmos_extract_spec",
            "Extract a spectrum from a cube",
            kmos_extract_spec_description,
            "Alex Agudo Berbel, Y. Jung",
            "usd-help@eso.org",
            kmos_get_license(),
            kmos_extract_spec_create,
            kmos_extract_spec_exec,
            kmos_extract_spec_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 kmos_extract_spec_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 */
    /* --mask_method */
    p = cpl_parameter_new_value("kmos.kmos_extract_spec.mask_method",
            CPL_TYPE_STRING, "Method used : mask, integrated or optimal",
            "kmos.kmos_extract_spec", "integrated");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "mask_method");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --centre */
    p = cpl_parameter_new_value("kmos.kmos_extract_spec.centre",
            CPL_TYPE_STRING, "The centre of the circular mask (pixel)",
            "kmos.kmos_extract_spec", "7.5,7.5");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "centre");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --radius */
    p = cpl_parameter_new_value("kmos.kmos_extract_spec.radius",
            CPL_TYPE_DOUBLE, "The radius of the circular mask (pixel)",
            "kmos.kmos_extract_spec", 3.0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "radius");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --save_mask */
    p = cpl_parameter_new_value("kmos.kmos_extract_spec.save_mask",
            CPL_TYPE_BOOL, "Flag to save the mask", "kmos.kmos_extract_spec",
            FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "save_mask");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    return kmos_combine_pars_create(recipe->parameters,
            "kmos.kmos_extract_spec", DEF_REJ_METHOD, FALSE);
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Execute the plugin instance given by the interface
  @param    plugin  the plugin
  @return   0 if everything is ok
 */
/*----------------------------------------------------------------------------*/
static int kmos_extract_spec_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 kmos_extract_spec(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 kmos_extract_spec_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 kmos_extract_spec(
        cpl_parameterlist   *   parlist, 
        cpl_frameset        *   frameset)
{
    const cpl_parameter *   par ;
    const char          *   mask_method ;
    const char          *   cmethod ;
    const char          *   centre_txt ;
    cpl_vector          *   centre ;
    int                     cmin, cmax, valid_ifu, citer, save_mask,
                            devnr1, index_data, index_noise, 
                            nr_devices ;
    double                  cpos_rej, cneg_rej, radius, r, x_lo, y_lo,
                            x_hi, y_hi, cen_x, cen_y, loc_cen_x, loc_cen_y ;
    cpl_size                auto_cen_x, auto_cen_y ;
    cpl_imagelist       *   data_in ; 
    cpl_imagelist       *   noise_in ; 
    cpl_image           *   mask ;
    cpl_image           *   made_data_img ;
    cpl_vector          *   spec_data_out ;
    cpl_vector          *   spec_noise_out ;
    cpl_vector          *   fit_par ;
    cpl_propertylist    *   sub_header_data ;
    cpl_propertylist    *   sub_header_noise ;
    cpl_propertylist    *   sub_header_mask ;
    cpl_propertylist    *   fit_pl ;
    cpl_frame           *   op1_frame ;
    cpl_frame           *   op2_frame ;
    float               *   pmask ;
    main_fits_desc          desc1, desc2;
    int                     i, x, y ;

    /* Check entries */
    if (parlist == NULL || frameset == NULL) {
        cpl_msg_error(__func__, "Null Inputs") ;
        cpl_error_set(__func__, CPL_ERROR_NULL_INPUT) ;
        return -1 ;
    }

    /* Initialise */
    spec_data_out = spec_noise_out = NULL ;

    /* Get parameters */
    par = cpl_parameterlist_find_const(parlist, 
            "kmos.kmos_extract_spec.mask_method");
    mask_method = cpl_parameter_get_string(par) ;
    par = cpl_parameterlist_find_const(parlist,
                "kmos.kmos_extract_spec.save_mask");
    save_mask = cpl_parameter_get_bool(par);
    if (!strcmp(mask_method, "integrated")) {
        par = cpl_parameterlist_find_const(parlist, 
                "kmos.kmos_extract_spec.centre");
        centre_txt = cpl_parameter_get_string(par) ;
        centre = kmo_identify_ranges(centre_txt);
        if (cpl_vector_get_size(centre) != 2) {
            cpl_msg_error(__func__, "centre must have 2 values like a,b") ;
            cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
            return -1 ;
        }
        cen_x = cpl_vector_get(centre, 0);
        cen_y = cpl_vector_get(centre, 1);
        cpl_vector_delete(centre);
        par = cpl_parameterlist_find_const(parlist,
                "kmos.kmos_extract_spec.radius");
        radius = cpl_parameter_get_double(par) ;
        if (radius < 0.0) {
            cpl_msg_error(__func__, "radius must be greater than 0.0") ;
            cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
            return -1 ;
        }
    } else if (strcmp(mask_method, "mask") == 0) {
    } else if (strcmp(mask_method, "optimal") == 0) {
        kmos_combine_pars_load(parlist, "kmos.kmos_extract_spec", &cmethod,
                &cpos_rej, &cneg_rej, &citer, &cmin, &cmax, FALSE);
    } else {
        cpl_msg_error(__func__, "Unsupported mask method: %s", mask_method) ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }

    /* Identify the RAW and CALIB frames in the input frameset */
    if (kmo_dfs_set_groups(frameset) != 1) {
        cpl_msg_error(__func__, "Cannot identify RAW and CALIB frames") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }
    
    /* Check Inputs */
    if (cpl_frameset_get_size(frameset) != 1 &&
            cpl_frameset_get_size(frameset) != 2) {
        cpl_msg_error(__func__, "1 or 2 frames expected") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }

    /* Load frames */
    op1_frame = kmo_dfs_get_frame(frameset, "0");
    kmo_init_fits_desc(&desc1);
    kmo_init_fits_desc(&desc2);
    desc1 = kmo_identify_fits_header(cpl_frame_get_filename(op1_frame));
    if (cpl_frameset_get_size(frameset) == 2) {
        op2_frame = kmo_dfs_get_frame(frameset, "1");
        desc2 = kmo_identify_fits_header(cpl_frame_get_filename(op2_frame));
    } else {
        op2_frame = NULL ;
    }
    
   /* --- load, update & save primary header --- */
    kmo_dfs_save_main_header(frameset, EXTRACT_SPEC, "", op1_frame, NULL, 
            parlist, cpl_func);
    if (save_mask) {
        kmo_dfs_save_main_header(frameset, EXTRACT_SPEC_MASK, "", op1_frame, 
                NULL, parlist, cpl_func);
    }
     
    /* Number of extensions to extract */
    if (desc1.ex_noise == TRUE) {
        nr_devices = desc1.nr_ext / 2;
    } else {
        nr_devices = desc1.nr_ext;
    }

    /* Loop on the devices */
    for (i = 1; i <= nr_devices ; i++) {
        if (desc1.ex_noise == FALSE) {
            devnr1 = desc1.sub_desc[i - 1].device_nr;
        } else {
            devnr1 = desc1.sub_desc[2 * i - 1].device_nr;
        }

        if (desc1.ex_badpix == FALSE) {
            index_data = kmo_identify_index_desc(desc1, devnr1, FALSE);
        } else {
            index_data = kmo_identify_index_desc(desc1, devnr1, 2);
        }
        if (desc1.ex_noise) {
            index_noise = kmo_identify_index_desc(desc1, devnr1, TRUE);
        }
        sub_header_data = kmo_dfs_load_sub_header(frameset, "0", devnr1, FALSE);

        /* Check if IFU is valid */
        valid_ifu = FALSE;
        if (desc1.sub_desc[index_data-1].valid_data == TRUE) {
            if ((strcmp(mask_method, "mask") != 0) ||
                ((strcmp(mask_method, "mask") == 0) &&
                (desc2.sub_desc[i - 1].valid_data == TRUE))) valid_ifu = TRUE;
        }
        if (desc1.ex_noise) {
            sub_header_noise = kmo_dfs_load_sub_header(frameset, "0", devnr1, 
                    TRUE);
        }

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

            // load noise, if existing
            if (desc1.ex_noise && desc1.sub_desc[index_noise-1].valid_data) {
                noise_in = kmo_dfs_load_cube(frameset, "0", devnr1, TRUE);
            } else {
                noise_in = NULL ;
            }

            /* Create the mask */
            if (!strcmp(mask_method, "mask")) {
                mask = kmo_dfs_load_image(frameset, "1", 
                        desc2.sub_desc[i - 1].device_nr, FALSE, FALSE, NULL);
            } else if (!strcmp(mask_method, "optimal")) {
                kmclipm_make_image(data_in, NULL, &made_data_img, NULL, NULL,
                        cmethod, cpos_rej, cneg_rej, citer, cmax, cmin);
                fit_par = kmo_fit_profile_2D(made_data_img, NULL, "gauss",
                        &mask, &fit_pl);

                /* Update subheader with fit parameters */
                cpl_propertylist_append(sub_header_data, fit_pl);
                cpl_propertylist_delete(fit_pl);

                /* Normalise mask */
                cpl_image_subtract_scalar(mask, cpl_vector_get(fit_par, 0));
                cpl_image_divide_scalar(mask, cpl_vector_get(fit_par, 1));
                cpl_vector_delete(fit_par); 
                cpl_image_delete(made_data_img);
            } else if (!strcmp(mask_method, "integrated")) {
                if (cen_x < 1.0 || cen_y < 1.0) {
                    kmclipm_make_image(data_in, NULL, &made_data_img, NULL, 
                            NULL, "median", 3.0, 3.0, 3, 1, 1);
                    cpl_image_get_maxpos(made_data_img,&auto_cen_x,&auto_cen_y);
                    loc_cen_x = (double)auto_cen_x - 1.0 ;
                    loc_cen_y = (double)auto_cen_y - 1.0 ;
                    cpl_image_delete(made_data_img);
                } else {
                    loc_cen_x = cen_x - 1.0 ;
                    loc_cen_y = cen_y - 1.0 ;
                }
                mask = cpl_image_new(desc1.naxis1, desc1.naxis2,CPL_TYPE_FLOAT);
                kmo_image_fill(mask,0.0);
                pmask = cpl_image_get_data_float(mask);

                /* draw circle */
                x_lo = floor(loc_cen_x - radius);
                if (x_lo < 0) x_lo = 0;
                y_lo = floor(loc_cen_y - radius);
                if (y_lo < 0) y_lo = 0;
                x_hi = ceil(loc_cen_x + radius);
                if (x_hi > desc1.naxis1) x_hi = desc1.naxis1;
                y_hi = ceil(loc_cen_y + radius);
                if (y_hi > desc1.naxis2) y_hi = desc1.naxis2;
                for (x = x_lo; x < x_hi; x++) {
                    for (y = y_lo; y < y_hi; y++) {
                        r = sqrt(pow(x - loc_cen_x,2) + pow(y - loc_cen_y,2));
                        if (r <= radius) pmask[x + y * desc1.naxis1] = 1.0;
                    }
                }            
            }

            /* Process & save data */
            kmo_priv_extract_spec(data_in, noise_in, mask, &spec_data_out,
                    &spec_noise_out);

            sub_header_mask = cpl_propertylist_duplicate(sub_header_data);

            /* Change WCS here (CRPIX3 goes to CRPIX1 etc...) */
            sub_header_data = kmo_priv_update_header(sub_header_data);

            kmclipm_vector *ddd = kmclipm_vector_create(spec_data_out);
            kmo_dfs_save_vector(ddd, EXTRACT_SPEC, "", sub_header_data, 0./0.);
            kmclipm_vector_delete(ddd); 
            if (save_mask) {
                /* Delete WCS for 3rd dimension since mask is 2D */
                cpl_propertylist_erase(sub_header_mask, CRPIX3);
                cpl_propertylist_erase(sub_header_mask, CRVAL3);
                cpl_propertylist_erase(sub_header_mask, CDELT3);
                cpl_propertylist_erase(sub_header_mask, CTYPE3);
                cpl_propertylist_erase(sub_header_mask, CD1_3);
                cpl_propertylist_erase(sub_header_mask, CD2_3);
                cpl_propertylist_erase(sub_header_mask, CD3_3);
                cpl_propertylist_erase(sub_header_mask, CD3_1);
                cpl_propertylist_erase(sub_header_mask, CD3_2);
                kmo_dfs_save_image(mask, EXTRACT_SPEC_MASK, "", 
                        sub_header_mask, 0.);
            }
            cpl_propertylist_delete(sub_header_mask);

            /* Process & save noise, if existing */
            if (desc1.ex_noise) {
                kmclipm_vector *nnn = NULL;
                if (spec_noise_out != NULL) {
                    nnn = kmclipm_vector_create(spec_noise_out);
                }
                sub_header_noise = kmo_priv_update_header(sub_header_noise);

                kmo_dfs_save_vector(nnn, EXTRACT_SPEC, "", sub_header_noise, 
                        0./0.);
                kmclipm_vector_delete(nnn); 
            }
            cpl_imagelist_delete(data_in); 
            cpl_imagelist_delete(noise_in);
            cpl_image_delete(mask);
        } else {
            /* Invalid IFU */
            kmo_dfs_save_sub_header(EXTRACT_SPEC, "", sub_header_data);
            if (desc1.ex_noise) {
                kmo_dfs_save_sub_header(EXTRACT_SPEC, "", sub_header_noise);
            }
        }
        cpl_propertylist_delete(sub_header_data); 
        if (desc1.ex_noise) cpl_propertylist_delete(sub_header_noise);
    }
    kmo_free_fits_desc(&desc1);
    kmo_free_fits_desc(&desc2);
    return 0 ;
}

/**@}*/
