/* $Id: fors_extract_objects.c,v 1.6 2013-04-24 14:14:13 cgarcia Exp $
 *
 * This file is part of the FORS Data Reduction Pipeline
 * Copyright (C) 2002-2010 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 St, Fifth Floor, Boston, MA  02110-1301  USA
 */

/*
 * $Author: cgarcia $
 * $Date: 2013-04-24 14:14:13 $
 * $Revision: 1.6 $
 * $Name: not supported by cvs2svn $
 */

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

#include <math.h>
#include <cpl.h>
#include <moses.h>
#include <fors_dfs.h>

static int fors_extract_objects_create(cpl_plugin *);
static int fors_extract_objects_exec(cpl_plugin *);
static int fors_extract_objects_destroy(cpl_plugin *);
static int fors_extract_objects(cpl_parameterlist *, cpl_frameset *);

static char fors_extract_objects_description[] =
"This recipe is used to extract scientific objects spectra on a resampled\n"
"image produced with recipe fors_resample, at the positions listed in the\n"
"object table produced by recipe fors_detect_objects. Please refer to the\n"
"FORS Pipeline User's Manual for more details on object extraction.\n"
"\n"
"In the table below the MXU acronym can be alternatively read as MOS and\n"
"LSS, and SCI as STD.\n\n"
"Input files:\n\n"
"  DO category:               Type:       Explanation:         Required:\n"
"  MAPPED_SCI_MXU             Calib       Resampled slit spectra  Y\n"
"  MAPPED_SKY_SCI_MXU         Calib       Resampled sky spectra   Y\n"
"  OBJECT_TABLE_SCI_MXU       Calib       Object table            Y\n\n"
"Output files:\n\n"
"  DO category:               Data type:  Explanation:\n"
"  REDUCED_SCI_MXU            FITS image  Extracted object spectra\n"
"  REDUCED_SKY_SCI_MXU        FITS image  Extracted sky spectra\n"
"  REDUCED_ERROR_SCI_MXU      FITS image  Error on extracted spectra\n\n";

#define fors_extract_objects_exit(message)    \
{                                             \
if ((const char *)message != NULL) cpl_msg_error(recipe, message);  \
cpl_image_delete(mapped);                     \
cpl_image_delete(skymapped);                  \
cpl_table_delete(slits);                      \
cpl_propertylist_delete(header);              \
cpl_msg_indent_less();                        \
return -1;                                    \
}

#define fors_extract_objects_exit_memcheck(message)    \
{                                                      \
if ((const char *)message != NULL) cpl_msg_info(recipe, message);            \
printf("free mapped (%p)\n", mapped);                  \
cpl_image_delete(mapped);                              \
printf("free skymapped (%p)\n", skymapped);            \
cpl_image_delete(skymapped);                           \
printf("free slits (%p)\n", slits);                    \
cpl_table_delete(slits);                               \
printf("free header (%p)\n", header);                  \
cpl_propertylist_delete(header);                       \
cpl_msg_indent_less();                                 \
return 0;                                              \
}


/**
 * @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,
                    FORS_BINARY_VERSION,
                    CPL_PLUGIN_TYPE_RECIPE,
                    "fors_extract_objects",
                    "Extract objects in slit spectra",
                    fors_extract_objects_description,
                    "Carlo Izzo",
                    PACKAGE_BUGREPORT,
    "This file is currently part of the FORS Instrument Pipeline\n"
    "Copyright (C) 2002-2010 European Southern Observatory\n\n"
    "This program is free software; you can redistribute it and/or modify\n"
    "it under the terms of the GNU General Public License as published by\n"
    "the Free Software Foundation; either version 2 of the License, or\n"
    "(at your option) any later version.\n\n"
    "This program is distributed in the hope that it will be useful,\n"
    "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
    "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
    "GNU General Public License for more details.\n\n"
    "You should have received a copy of the GNU General Public License\n"
    "along with this program; if not, write to the Free Software Foundation,\n"
    "Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n",
                    fors_extract_objects_create,
                    fors_extract_objects_exec,
                    fors_extract_objects_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 fors_extract_objects_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 (empty) parameters list in the cpl_recipe object 
     */

    recipe->parameters = cpl_parameterlist_new(); 

    /*
     * Object extraction method
     */

    p = cpl_parameter_new_value("fors.fors_extract_objects.ext_mode",
                                CPL_TYPE_INT,
                                "Object extraction method: 0 = aperture, "
                                "1 = Horne optimal extraction",
                                "fors.fors_extract_objects",
                                1);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "ext_mode");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    return 0;
}


/**
 * @brief    Execute the plugin instance given by the interface
 *
 * @param    plugin  the plugin
 *
 * @return   0 if everything is ok
 */

static int fors_extract_objects_exec(cpl_plugin *plugin)
{
    cpl_recipe *recipe;
    
    if (cpl_plugin_get_type(plugin) == CPL_PLUGIN_TYPE_RECIPE) 
        recipe = (cpl_recipe *)plugin;
    else 
        return -1;

    return fors_extract_objects(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 fors_extract_objects_destroy(cpl_plugin *plugin)
{
    cpl_recipe *recipe;
    
    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 set-of-frames
 *
 * @return   0 if everything is ok
 */

static int fors_extract_objects(cpl_parameterlist *parlist, 
                               cpl_frameset *frameset)
{

    const char *recipe = "fors_extract_objects";


    /*
     * Input parameters
     */

    int         ext_mode;

    /*
     * CPL objects
     */

    cpl_image       **images;
    cpl_image        *mapped    = NULL;
    cpl_image        *skymapped = NULL;
    cpl_table        *slits     = NULL;
    cpl_propertylist *header    = NULL;

    /*
     * Auxiliary variables
     */

    char    version[80];
    const char   *object_tag;
    const char   *science_tag;
    const char   *sky_tag;
    const char   *reduced_tag;
    const char   *reduced_sky_tag;
    const char   *reduced_err_tag;
    int     nframes;
    double  gain;
    double  ron;
    int     scimxu;
    int     scimos;
    int     scilss;
    int     stdmxu;
    int     stdmos;
    int     stdlss;

    char   *instrume = NULL;


    cpl_msg_set_indentation(2);


    /*
     * Get configuration parameters
     */

    cpl_msg_info(recipe, "Recipe %s configuration parameters:", recipe);
    cpl_msg_indent_more();

    ext_mode = dfs_get_parameter_int(parlist, 
                                     "fors.fors_extract_objects.ext_mode",
                                     NULL);
    if (ext_mode < 0 || ext_mode > 1)
        fors_extract_objects_exit("Invalid object extraction mode");

    if (cpl_error_get_code())
        fors_extract_objects_exit("Failure reading configuration parameters");


    cpl_msg_indent_less();
    cpl_msg_info(recipe, "Check input set-of-frames:");
    cpl_msg_indent_more();

    nframes  = scimxu = cpl_frameset_count_tags(frameset, "MAPPED_SCI_MXU");
    nframes += scimos = cpl_frameset_count_tags(frameset, "MAPPED_SCI_MOS");
    nframes += scilss = cpl_frameset_count_tags(frameset, "MAPPED_SCI_LSS");
    nframes += stdmxu = cpl_frameset_count_tags(frameset, "MAPPED_STD_MXU");
    nframes += stdmos = cpl_frameset_count_tags(frameset, "MAPPED_STD_MOS");
    nframes += stdlss = cpl_frameset_count_tags(frameset, "MAPPED_STD_LSS");

    if (nframes == 0) {
        fors_extract_objects_exit("Missing input scientific spectra");
    }
    if (nframes > 1) {
        cpl_msg_error(recipe, "Too many input scientific spectra (%d > 1)", 
                      nframes);
        fors_extract_objects_exit(NULL);
    }

    if (scimxu) {
        science_tag       = "MAPPED_SCI_MXU";
        sky_tag           = "MAPPED_SKY_SCI_MXU";
        object_tag        = "OBJECT_TABLE_SCI_MXU";
        reduced_tag       = "REDUCED_SCI_MXU";
        reduced_sky_tag   = "REDUCED_SKY_SCI_MXU";
        reduced_err_tag   = "REDUCED_ERROR_SCI_MXU";
    }
    else if (scimos) {
        science_tag       = "MAPPED_SCI_MOS";
        sky_tag           = "MAPPED_SKY_SCI_MOS";
        object_tag        = "OBJECT_TABLE_SCI_MOS";
        reduced_tag       = "REDUCED_SCI_MOS";
        reduced_sky_tag   = "REDUCED_SKY_SCI_MOS";
        reduced_err_tag   = "REDUCED_ERROR_SCI_MOS";
    }
    else if (scilss) {
        science_tag       = "MAPPED_SCI_LSS";
        sky_tag           = "MAPPED_SKY_SCI_LSS";
        object_tag        = "OBJECT_TABLE_SCI_LSS";
        reduced_tag       = "REDUCED_SCI_LSS";
        reduced_sky_tag   = "REDUCED_SKY_SCI_LSS";
        reduced_err_tag   = "REDUCED_ERROR_SCI_LSS";
    }
    else if (stdmxu) {
        science_tag       = "MAPPED_STD_MXU";
        sky_tag           = "MAPPED_SKY_STD_MXU";
        object_tag        = "OBJECT_TABLE_SCI_MXU";
        reduced_tag       = "REDUCED_STD_MXU";
        reduced_sky_tag   = "REDUCED_SKY_STD_MXU";
        reduced_err_tag   = "REDUCED_ERROR_STD_MXU";
    }
    else if (stdmos) {
        science_tag       = "MAPPED_STD_MOS";
        sky_tag           = "MAPPED_SKY_STD_MOS";
        object_tag        = "OBJECT_TABLE_SCI_MOS";
        reduced_tag       = "REDUCED_STD_MOS";
        reduced_sky_tag   = "REDUCED_SKY_STD_MOS";
        reduced_err_tag   = "REDUCED_ERROR_STD_MOS";
    }
    else if (stdlss) {
        science_tag       = "MAPPED_STD_LSS";
        sky_tag           = "MAPPED_SKY_STD_LSS";
        object_tag        = "OBJECT_TABLE_SCI_LSS";
        reduced_tag       = "REDUCED_STD_LSS";
        reduced_sky_tag   = "REDUCED_SKY_STD_LSS";
        reduced_err_tag   = "REDUCED_ERROR_STD_LSS";
    }

    if (!dfs_equal_keyword(frameset, "ESO INS GRIS1 ID"))
        fors_extract_objects_exit("Input frames are not from the same grism");

    if (!dfs_equal_keyword(frameset, "ESO INS FILT1 ID"))
        fors_extract_objects_exit("Input frames are not from the same filter");

    if (!dfs_equal_keyword(frameset, "ESO DET CHIP1 ID"))
        fors_extract_objects_exit("Input frames are not from the same chip");

    header = dfs_load_header(frameset, science_tag, 0);
    if (header == NULL)
        fors_extract_objects_exit("Cannot load scientific frame header");

    instrume = (char *)cpl_propertylist_get_string(header, "INSTRUME");
    if (instrume == NULL)
        fors_extract_objects_exit("Missing keyword INSTRUME in reference frame "
                                 "header");

    if (instrume[4] == '1')
        snprintf(version, 80, "%s/%s", "fors1", VERSION);
    if (instrume[4] == '2')
        snprintf(version, 80, "%s/%s", "fors2", VERSION);

    gain = cpl_propertylist_get_double(header, "ESO DET OUT1 CONAD");

    if (cpl_error_get_code() != CPL_ERROR_NONE)
        fors_extract_objects_exit("Missing keyword ESO DET OUT1 CONAD in "
                               "scientific frame header");

    cpl_msg_info(recipe, "The gain factor is: %.2f e-/ADU", gain);


    ron = cpl_propertylist_get_double(header, "ESO DET OUT1 RON");

    if (cpl_error_get_code() != CPL_ERROR_NONE)
        fors_extract_objects_exit("Missing keyword ESO DET OUT1 RON in "
                                  "scientific frame header");

    ron /= gain;     /* Convert from electrons to ADU */

    cpl_msg_info(recipe, "The read-out-noise is: %.2f ADU", ron);


    cpl_msg_indent_less();
    cpl_msg_info(recipe, "Load input frames...");
    cpl_msg_indent_more();

    mapped = dfs_load_image(frameset, science_tag, CPL_TYPE_FLOAT, 0, 0);
    if (mapped == NULL)
        fors_extract_objects_exit("Cannot load input scientific frame");

    skymapped = dfs_load_image(frameset, sky_tag, CPL_TYPE_FLOAT, 0, 0);
    if (skymapped == NULL)
        fors_extract_objects_exit("Cannot load input sky frame");

    slits = dfs_load_table(frameset, object_tag, 1);
    if (slits == NULL)
        fors_extract_objects_exit("Cannot load input object table");


    cpl_msg_indent_less();
    cpl_msg_info(recipe, "Object extraction...");
    cpl_msg_indent_more();

    //TODO: Add the real propagated variance here (the second argument)
    //It should be done like in fors_science  
    images = mos_extract_objects(mapped, NULL, skymapped, slits,
                                 ext_mode, ron, gain, 1);

    cpl_image_delete(mapped); mapped = NULL;
    cpl_image_delete(skymapped); skymapped = NULL;
    cpl_table_delete(slits); slits = NULL;

    if (images) {

        if (dfs_save_image(frameset, images[0], reduced_tag, header,
                           parlist, recipe, version))
            fors_extract_objects_exit(NULL);
        cpl_image_delete(images[0]);

        if (dfs_save_image(frameset, images[1], reduced_sky_tag, header,
                           parlist, recipe, version))
            fors_extract_objects_exit(NULL);
        cpl_image_delete(images[1]);

        if (dfs_save_image(frameset, images[2], reduced_err_tag, header,
                           parlist, recipe, version))
            fors_extract_objects_exit(NULL);
        cpl_image_delete(images[2]);

        cpl_free(images);
    }
    else {
        cpl_msg_warning(recipe, "No objects found: the products "
                        "%s, %s, and %s are not created",
                        reduced_tag, reduced_sky_tag, reduced_err_tag);
    }

    cpl_propertylist_delete(header); header = NULL;

    return 0;
}
