/* $Id: hawki_step_subtract_bkg.c,v 1.20 2013-09-02 14:24:14 cgarcia Exp $
 *
 * This file is part of the HAWKI Pipeline
 * Copyright (C) 2008 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
 */

/*
 * $Author: cgarcia $
 * $Date: 2013-09-02 14:24:14 $
 * $Revision: 1.20 $
 * $Name: not supported by cvs2svn $
 */

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

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

#include <string.h>
#include <math.h>
#include <cpl.h>

#include "irplib_utils.h"
#include "hawki_utils.h"
#include "hawki_load.h"
#include "hawki_save.h"
#include "hawki_pfits.h"
#include "hawki_dfs.h"
#include "hawki_calib.h"

/*-----------------------------------------------------------------------------
                                Structs
 -----------------------------------------------------------------------------*/

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

#ifdef __cplusplus
extern "C"
#endif
int cpl_plugin_get_info(cpl_pluginlist * list);

static int hawki_step_subtract_bkg_create(cpl_plugin *) ;
static int hawki_step_subtract_bkg_exec(cpl_plugin *) ;
static int hawki_step_subtract_bkg_destroy(cpl_plugin *) ;
static int hawki_step_subtract_bkg(cpl_parameterlist *, cpl_frameset *) ;

static int hawki_step_subtract_bkg_apply_one_to_one_save
(cpl_frameset        *  objframes,
 cpl_frameset        *  bkgframes,
 cpl_parameterlist   *  parlist,
 cpl_frameset        *  recipe_framelist);

static int hawki_step_subtract_bkg_apply_one_to_all_save
(cpl_frameset        *  objframes,
 cpl_frameset        *  bkgframes,
 cpl_parameterlist   *  recipe_parlist,
 cpl_frameset        *  recipe_framelist);

static int hawki_step_subtract_bkg_save
(cpl_imagelist     *  obj_images,
 int                  iserie,
 cpl_frameset      *  used_frameset,
 cpl_parameterlist *  recipe_parlist,
 cpl_frameset      *  recipe_framelist);

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

static char hawki_step_subtract_bkg_description[] =
"hawki_step_subtract_bkg -- hawki background subtraction utility.\n"
"This recipe will subtract the given background to the science images.\n"
"The background can be obtained from the sky or object images\n"
"using the hawki_util_compute_bkg utility.\n"
"There are two modes of operation:\n"
"One single background image that it is subtracted to all object images.\n"
"As many background images as objects. A one to one relationship is applied.\n"
"The files listed in the Set Of Frames (sof-file) must be tagged:\n"
"obj_basic_cal-file.fits "HAWKI_CALPRO_BASICCALIBRATED" or\n"
"background-file.fits "HAWKI_CALPRO_BKGIMAGE" \n";

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

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

  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,
                    HAWKI_BINARY_VERSION,
                    CPL_PLUGIN_TYPE_RECIPE,
                    "hawki_step_subtract_bkg",
                    "Background subtraction utility",
                    hawki_step_subtract_bkg_description,
                    "Cesar Enrique Garcia Dabo",
                    PACKAGE_BUGREPORT,  
                    hawki_get_license(),
                    hawki_step_subtract_bkg_create,
                    hawki_step_subtract_bkg_exec,
                    hawki_step_subtract_bkg_destroy) ;

    cpl_pluginlist_append(list, plugin) ;

    return 0;
}

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

  Create the recipe instance and make it available to the application using the
  interface.
 */
/*----------------------------------------------------------------------------*/
static int hawki_step_subtract_bkg_create(cpl_plugin * plugin)
{
    cpl_recipe      * recipe ;
    //cpl_parameter   * p ;

    /* Get the recipe out of the plugin */
    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() ;
    if (recipe->parameters == NULL)
        return 1;

    /* Fill the parameters list */
    /* None.. */

    /* Return */
    return 0;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Execute the plugin instance given by the interface
  @param    plugin  the plugin
  @return   0 if everything is ok
 */
/*----------------------------------------------------------------------------*/
static int hawki_step_subtract_bkg_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 ;

    /* Issue a banner */
    hawki_print_banner();

    return hawki_step_subtract_bkg(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 hawki_step_subtract_bkg_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    Get the command line options and execute the data reduction
  @param    parlist     the parameters list
  @param    framelist   the frames list
  @return   0 if everything is ok
 */
/*----------------------------------------------------------------------------*/
static int hawki_step_subtract_bkg(
        cpl_parameterlist   *   parlist,
        cpl_frameset        *   framelist)
{
    int                 nobj;
    int                 nbkg;
    cpl_frameset    *   objframes;
    cpl_frameset    *   bkgframes;


    /* Identify the RAW and CALIB frames in the input frameset */
    if (hawki_dfs_set_groups(framelist))
    {
        cpl_msg_error(__func__, "Cannot identify RAW and CALIB frames") ;
        return -1 ;
    }

    /* Identifying objects and bkg data frames */
    cpl_msg_info(__func__, "Identifying objects and background data");
    objframes = hawki_extract_frameset
        (framelist, HAWKI_CALPRO_BASICCALIBRATED);
    if (objframes == NULL)
    {
        cpl_msg_error(__func__, "No object frames provided (%s)",
                HAWKI_CALPRO_BASICCALIBRATED);
        return -1 ;
    }
    /* Retrieve bkg frames */
    bkgframes = hawki_extract_frameset
        (framelist, HAWKI_CALPRO_BKGIMAGE);
    if (bkgframes == NULL)
    {
        cpl_msg_error(__func__, "No background frames provided (%s)",
                HAWKI_CALPRO_BKGIMAGE);
        cpl_frameset_delete(objframes);
        return -1 ;
    }

    /* Subtract the background */
    nobj = cpl_frameset_get_size(objframes);
    nbkg = cpl_frameset_get_size(bkgframes);
    if(nobj == nbkg)
        hawki_step_subtract_bkg_apply_one_to_one_save
            (objframes, bkgframes, parlist, framelist);
    else if(nbkg == 1)
        hawki_step_subtract_bkg_apply_one_to_all_save
            (objframes, bkgframes, parlist, framelist);
    else
    {
        cpl_msg_error(__func__,"Incompatible number of science and background"
                               " images.");
        cpl_msg_error(__func__,"Supply only 1 bkg frame or as many as objects");
        cpl_frameset_delete(objframes);
        cpl_frameset_delete(bkgframes);
        return -1;
    }

    /* Free resources */
    cpl_frameset_delete(objframes);
    cpl_frameset_delete(bkgframes);

    /* Return */
    if (cpl_error_get_code())
    {
        cpl_msg_error(__func__,
                      "HAWK-I pipeline could not recover from previous errors");
        return -1 ;
    }
    else return 0 ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    This function will subtract each background frame to the
            corresponding object frame and will save later the results.
  @param    obj         the objects frames
  @param    bkg         the background frames
  @return   0 if everything is ok

 */
/*----------------------------------------------------------------------------*/
static int hawki_step_subtract_bkg_apply_one_to_one_save
(cpl_frameset        *  objframes,
 cpl_frameset        *  bkgframes,
 cpl_parameterlist   *  recipe_parlist,
 cpl_frameset        *  recipe_framelist)
{
    int          iobj;
    int          nobjs;

    /* Subtract the background to each object frame */
    cpl_msg_info(__func__,"Using a one to one relation btw objects and bkgs");
    nobjs = cpl_frameset_get_size(objframes);
    for(iobj = 0; iobj < nobjs; ++iobj)
    {
        cpl_frame     * obj_frame = NULL;
        cpl_frame     * bkg_frame = NULL;
        cpl_imagelist * obj_images = NULL;
        cpl_imagelist * bkg_images = NULL;
        cpl_frameset  * used_frameset;

        /* Allocate resources */
        used_frameset = cpl_frameset_new();

        /* Read the object frame */
        cpl_msg_indent_more();
        cpl_msg_info(__func__, "Applying correction to object %d", iobj+1) ;
        obj_frame = cpl_frameset_get_position(objframes, iobj);
        cpl_frameset_insert(used_frameset, cpl_frame_duplicate(obj_frame));
        if(obj_frame != NULL)
            obj_images = hawki_load_frame(obj_frame, CPL_TYPE_FLOAT);
        if(obj_images == NULL)
        {
            cpl_msg_indent_less();
            cpl_msg_error(__func__, "Error reading obj image") ;
            cpl_frameset_delete(used_frameset);
            return -1;
        }

        /* Read the bkg */
        bkg_frame = cpl_frameset_get_position(bkgframes, iobj);
        cpl_frameset_insert(used_frameset, cpl_frame_duplicate(bkg_frame));
        if(bkg_frame != NULL)
            bkg_images = hawki_load_frame(bkg_frame, CPL_TYPE_FLOAT);
        if(bkg_images == NULL)
        {
            cpl_msg_error(__func__, "Error reading background image") ;
            cpl_msg_indent_less();
            cpl_imagelist_delete(obj_images);
            cpl_frameset_delete(used_frameset);
            return -1;
        }

        /* Make the correction */
        hawki_bkg_imglist_calib(obj_images, bkg_images);

        /* Save the subtracted frame */
        if(hawki_step_subtract_bkg_save(obj_images,
                                        iobj,
                                        used_frameset,
                                        recipe_parlist,
                                        recipe_framelist) != 0)
            cpl_msg_warning(__func__,"Some data could not be saved. "
                            "Check permisions or disk space");

        /* Free in loop */
        cpl_msg_indent_less();
        cpl_imagelist_delete(obj_images);
        cpl_imagelist_delete(bkg_images);
        cpl_frameset_delete(used_frameset);
    }

    /* Exit */
    return 0;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    This function will subtract one background frame to all the
            object frames and will save later the results.
  @param    obj         the objects frames
  @param    bkg         the background frames
  @return   0 if everything is ok

 */
/*----------------------------------------------------------------------------*/
static int hawki_step_subtract_bkg_apply_one_to_all_save
(cpl_frameset        *  objframes,
 cpl_frameset        *  bkgframes,
 cpl_parameterlist   *  recipe_parlist,
 cpl_frameset        *  recipe_framelist)
{
    int             iobj;
    int             nobjs;
    cpl_frame     * bkg_frame;
    cpl_imagelist * bkg_images = NULL;

    /* Read the bkg */
    cpl_msg_info(__func__,"Using the same bkg for all the objects");
    bkg_frame = cpl_frameset_get_position(bkgframes, 0);
    if(bkg_frame != NULL)
        bkg_images = hawki_load_frame(bkg_frame, CPL_TYPE_FLOAT);
    if(bkg_images == NULL)
    {
        cpl_msg_error(__func__, "Error reading background image");
        return -1;
    }

    /* Subtract the background to each object frame */
    nobjs = cpl_frameset_get_size(objframes);
    for(iobj = 0; iobj < nobjs; ++iobj)
    {
        cpl_frame     * obj_frame;
        cpl_imagelist * obj_images = NULL;
        cpl_frameset  * used_frameset;

        /* Allocate resources */
        used_frameset = cpl_frameset_new();

        /* Read the object frame */
        cpl_msg_indent_more();
        cpl_msg_info(__func__, "Applying correction to object %d", iobj+1) ;
        obj_frame = cpl_frameset_get_position(objframes, iobj);
        if(obj_frame != NULL)
            obj_images = hawki_load_frame(obj_frame, CPL_TYPE_FLOAT);
        cpl_frameset_insert(used_frameset, cpl_frame_duplicate(obj_frame));
        cpl_frameset_insert(used_frameset, cpl_frame_duplicate(bkg_frame));
        if(obj_images == NULL)
        {
            cpl_msg_indent_less();
            cpl_msg_error(__func__, "Error reading obj image") ;
            cpl_frameset_delete(used_frameset);
            return -1;
        }

        /* Make the correction */
        hawki_bkg_imglist_calib(obj_images, bkg_images);

        /* Save the subtracted frame */
        hawki_step_subtract_bkg_save(obj_images,
                                     iobj,
                                     used_frameset,
                                     recipe_parlist,
                                     recipe_framelist);

        /* Free in loop */
        cpl_msg_indent_less();
        cpl_imagelist_delete(obj_images);
        cpl_frameset_delete(used_frameset);
    }

    /* Free and return */
    cpl_imagelist_delete(bkg_images);
    return 0;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Save the background subtracted products on disk
  @param    combined    the combined imagelist produced
  @param    objs_stats  the tables with the detected objects statistics or NULL
  @param    mean_sky_bg the vectors with the sky background values or NULL
  @param    parlist     the input list of parameters
  @param    set         the input frame set
  @return   0 if everything is ok, -1 otherwise
 */
/*----------------------------------------------------------------------------*/
static int hawki_step_subtract_bkg_save
(cpl_imagelist     *  obj_images,
 int                  iserie,
 cpl_frameset      *  used_frameset,
 cpl_parameterlist *  recipe_parlist,
 cpl_frameset      *  recipe_framelist)
{
    const cpl_frame     *   raw_reference;
    cpl_propertylist    **  extproplists;
    char                    filename[256] ;
    cpl_propertylist    *   inputlist ;
    int                     ext_nb ;
    const char          *   recipe_name = "hawki_step_subtract_bkg";
    int                     idet;
    cpl_errorstate          error_prevstate = cpl_errorstate_get();

    /* Get the reference frame (the raw frame) */
    raw_reference = irplib_frameset_get_first_from_group
        (used_frameset, CPL_FRAME_GROUP_RAW);

    /* Create the prop lists */
    cpl_msg_indent_more();
    cpl_msg_info(__func__, "Creating the keywords list") ;
    extproplists = cpl_malloc(HAWKI_NB_DETECTORS * sizeof(cpl_propertylist*));
    for (idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++)
    {
        /* Get the extension number */
        ext_nb=hawki_get_ext_from_detector
            (cpl_frame_get_filename(raw_reference), idet+1);

        /* Allocate this property list */
        extproplists[idet] = cpl_propertylist_new();

        /* Propagate some keywords from input raw frame extensions */
        inputlist = cpl_propertylist_load_regexp(
                cpl_frame_get_filename(raw_reference), ext_nb,
                HAWKI_HEADER_EXT_FORWARD, 0);
        cpl_propertylist_append(extproplists[idet], inputlist);
        cpl_propertylist_delete(inputlist);
        inputlist = cpl_propertylist_load_regexp(
                cpl_frame_get_filename(raw_reference), ext_nb,
                HAWKI_HEADER_WCS, 0);
        cpl_propertylist_append(extproplists[idet], inputlist);
        cpl_propertylist_delete(inputlist);
    }

    /* Write the image */
    snprintf(filename, 256, "hawki_step_subtract_bkg_%04d.fits", iserie+1);
    hawki_imagelist_save(recipe_framelist,
                         recipe_parlist,
                         used_frameset,
                         obj_images,
                         recipe_name,
                         HAWKI_CALPRO_BKG_SUBTRACTED,
                         HAWKI_PROTYPE_BKG_SUBTRACTED,
                         NULL,
                         (const cpl_propertylist**)extproplists,
                         filename);

    /* Free and return */
    cpl_msg_indent_less();
    for (idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++)
    {
        cpl_propertylist_delete(extproplists[idet]) ;
    }
    cpl_free(extproplists) ;
    if(!cpl_errorstate_is_equal(error_prevstate))
    {
        cpl_errorstate_set(CPL_ERROR_NONE);
        return -1;
    }
    return  0;
}
