/* 
 * This file is part of the HAWKI 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

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

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

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

#include "irplib_utils.h"
#include "irplib_calib.h"

#include "hawki_utils_legacy.h"
#include "hawki_calib.h"
#include "hawki_load.h"
#include "hawki_save.h"
#include "hawki_pfits_legacy.h"
#include "hawki_dfs_legacy.h"
#include "hawki_saa.h"
#include "hawki_bkg.h"
#include "hawki_distortion.h"
#include "hawki_properties_tel.h"
#include "hawki_image_stats.h"

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

static int hawki_step_combine_create(cpl_plugin *) ;
static int hawki_step_combine_exec(cpl_plugin *) ;
static int hawki_step_combine_destroy(cpl_plugin *) ;
static int hawki_step_combine(cpl_parameterlist *, cpl_frameset *) ;

static int hawki_step_combine_retrieve_input_param
(cpl_parameterlist  *  parlist);
static cpl_image ** hawki_step_combine_apply_comb
(cpl_frameset    * obj,
 cpl_frameset    * offsets,
 cpl_frameset    * bpm,
 cpl_frameset    * bkg_bpm_frames);
static cpl_image **  hawki_step_combine_chip
(cpl_imagelist   * in,
 cpl_bivector    * offsets,
 double          * pos_x,
 double          * pos_y);
static int hawki_step_combine_interpolate_badpix
(cpl_image           *  image);
static int hawki_step_combine_save
(cpl_image           ** combined,
 cpl_image           ** contrib_map,
 cpl_frameset        *  used_frames,
 cpl_parameterlist   *  parlist,
 cpl_frameset        *  recipe_frameset);

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

static struct 
{
    /* Inputs */
    int                 offset_max ;
    int                 borders ;
    cpl_geom_combine    comb_meth ;
    int                 rej_low;
    int                 rej_high;
    cpl_kernel          resamp_kernel;
} hawki_step_combine_config;

static struct 
{
    /* Outputs */
    double  mean_airmass;
    double  combined_pos_x[HAWKI_NB_DETECTORS];
    double  combined_pos_y[HAWKI_NB_DETECTORS];
    double  combined_cumoffset_x[HAWKI_NB_DETECTORS];
    double  combined_cumoffset_y[HAWKI_NB_DETECTORS];
} hawki_step_combine_output;

static char hawki_step_combine_description[] =
"(OBSOLETE) hawki_step_combine -- hawki combine jitter images.\n"
"The files listed in the Set Of Frames (sof-file) must be tagged:\n"
"science-file.fits "HAWKI_CALPRO_DIST_CORRECTED" or\n"
"science-file.fits "HAWKI_CALPRO_BKG_SUBTRACTED" or\n"
"bpm-file.fits "HAWKI_CALPRO_BPM" (optional) \n"
"bkg_bpm-file.fits "HAWKI_CALPRO_BKGBPM" (optional) \n"
"offsets-file.fits "HAWKI_CALPRO_OFFSETS" (optional) \n"
"The recipe creates as an output:\n"
"hawki_step_combine.fits ("HAWKI_CALPRO_COMBINED"): \n"
"The recipe does the following steps:\n"
"-Allocate an image with the proper combined size \n"
"   (depends on parameters --comb_meth and --borders)\n"
"-Retrieve the offsets either from the offsets-file.fits or from the header\n"
"-For each combined pixel, the contribution of each individual frame \n"
"   is added using a resampling kernel. If any of the pixels involved in\n"
"   the resampling is a bad pixel (defined in bpm-file.fits), it is not\n"
"   taken into account.\n"
"   With the remaining pixels a minmax rejection is performed\n"
"Return code:\n"
"esorex exits with an error code of 0 if the recipe completes successfully\n"
"or 1 otherwise";

/*-----------------------------------------------------------------------------
                                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_combine",
                    "(OBSOLETE) Jitter image combination recipe",
                    hawki_step_combine_description,
                    "ESO Pipeline Group",
                    PACKAGE_BUGREPORT,  
                    hawki_get_license_legacy(),
                    hawki_step_combine_create,
                    hawki_step_combine_exec,
                    hawki_step_combine_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_combine_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 */
    /* --offset_max */
    p = cpl_parameter_new_value("hawki.hawki_step_combine.offset_max",
                                CPL_TYPE_INT,
                                "Maximum offset allowed",
                                "hawki.hawki_step_combine",
                                1500) ;
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "offset_max") ;
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV) ;
    cpl_parameterlist_append(recipe->parameters, p) ;

    /* --comb_meth */
    p = cpl_parameter_new_value("hawki.hawki_step_combine.comb_meth",
                                CPL_TYPE_STRING,
                                "Final size of combination (union / inter / first)",
                                "hawki.hawki_step_combine",
                                "union") ;
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "comb_meth") ;
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV) ;
    cpl_parameterlist_append(recipe->parameters, p) ;
  
    /* --rej */
    p = cpl_parameter_new_value("hawki.hawki_step_combine.rej",
                                CPL_TYPE_STRING,
                                "Low and high number of rejected values",
                                "hawki.hawki_step_combine",
                                "1,1") ;
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "rej") ;
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV) ;
    cpl_parameterlist_append(recipe->parameters, p) ;

    /* --borders */
    p = cpl_parameter_new_value("hawki.hawki_step_combine.borders",
                                CPL_TYPE_INT,
                                "Border pixels trimmed",
                                "hawki.hawki_step_combine",
                                4) ;
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "borders") ;
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV) ;
    cpl_parameterlist_append(recipe->parameters, p) ;

    /* --resamp_kernel */
    p = cpl_parameter_new_value("hawki.hawki_step_combine.resamp_kernel",
                                CPL_TYPE_STRING,
                                "Resampling kernel (default/tanh/sinc/sinc2/lanczos/hamming/hann)",
                                "hawki.hawki_step_combine",
                                "default") ;
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "resamp_kernel") ;
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV) ;
    cpl_parameterlist_append(recipe->parameters, p) ;

    /* 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_combine_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_combine(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_combine_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_combine(
        cpl_parameterlist   *   parlist, 
        cpl_frameset        *   framelist)
{
    cpl_frameset    *  objframes ;
    cpl_frameset    *  offsets;
    cpl_frameset    *  bpm;
    cpl_frameset    *  bpmbkg;
    cpl_frameset    *  used_frames;
    cpl_image       ** combined_contrib;
    cpl_image       ** combined;
    cpl_image       ** contrib_map;
    int                idet;

    /* Retrieve input parameters */
    if(hawki_step_combine_retrieve_input_param(parlist))
    {
        cpl_msg_error(__func__, "Wrong parameters");
        return -1;
    }

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

    /* Retrieve raw frames */
    objframes = hawki_extract_frameset(framelist, HAWKI_CALPRO_DIST_CORRECTED);
    if (objframes == NULL) 
    {
        objframes = hawki_extract_frameset
            (framelist, HAWKI_CALPRO_BKG_SUBTRACTED);
        if (objframes == NULL) 
        {
            cpl_msg_error(__func__,"Cannot find objs frames in the input list (%s or %s)",
                    HAWKI_CALPRO_DIST_CORRECTED, HAWKI_CALPRO_BKG_SUBTRACTED);
            return -1 ;
        }
    }
    /* Check that pointing is the same for all the frames */
    if(!hawki_utils_check_equal_double_keys(objframes, &hawki_pfits_get_targ_alpha) ||
       !hawki_utils_check_equal_double_keys(objframes, &hawki_pfits_get_targ_delta))
    {
        cpl_msg_error(__func__, "Not all input science frames belong to the "
                "same pointing/target. Check keywords TEL TARG ALPHA/DELTA");
        cpl_frameset_delete(objframes);
        return -1;        
    }
    used_frames = cpl_frameset_duplicate(objframes);
    
    /* Retrieve the refined offsets, if provided */
    offsets = hawki_extract_frameset(framelist, HAWKI_CALPRO_OFFSETS);
    if(offsets)
        cpl_frameset_insert(used_frames, cpl_frame_duplicate(
                cpl_frameset_get_position(offsets, 0)));
    
    /* Retrieve the general bad pixel mask, if provided */
    bpm = hawki_extract_frameset(framelist, HAWKI_CALPRO_BPM);
    if(bpm)
        cpl_frameset_insert(used_frames, cpl_frame_duplicate(
                cpl_frameset_get_position(bpm, 0)));

    /* Retrieve the background bad pixel masks, if provided */
    bpmbkg = hawki_extract_frameset(framelist, HAWKI_CALPRO_BKGBPM);
    if(bpmbkg)
    {
        int iframe;
        for(iframe=0; iframe < cpl_frameset_get_size(bpmbkg); iframe++)
            cpl_frameset_insert(used_frames, cpl_frame_duplicate(
                    cpl_frameset_get_position(bpmbkg, iframe)));
        if(cpl_frameset_get_size(bpmbkg) != cpl_frameset_get_size(objframes))
        {
            cpl_msg_error(__func__,"Incompatible number of science and bad bkg"
                                   " images.");
            cpl_msg_error(__func__,"Supply as many bad bkg images as objects");
            cpl_frameset_delete(objframes);
            cpl_frameset_delete(used_frames);
            cpl_frameset_delete(offsets);
            cpl_frameset_delete(bpm);
            cpl_frameset_delete(bpmbkg);
            return -1;
        }
    }
    
    /* Apply the combination */
    cpl_msg_info(__func__, "Apply the data recombination");
    cpl_msg_indent_more() ;
    if ((combined_contrib = hawki_step_combine_apply_comb
             (objframes, offsets, bpm, bpmbkg)) == NULL)
    {
        cpl_msg_error(__func__, "Cannot combine the data");
        cpl_frameset_delete(objframes);
        cpl_frameset_delete(used_frames);
        if(offsets != NULL)
            cpl_frameset_delete(offsets);
        if(bpm != NULL)
            cpl_frameset_delete(bpm);
        cpl_msg_indent_less() ;
        return -1 ;
    }
    
    /* Get both the combination and the contribution map */
    combined   = combined_contrib;
    contrib_map = combined_contrib + HAWKI_NB_DETECTORS;
    cpl_msg_indent_less() ;
    cpl_frameset_delete(objframes);
    if(offsets != NULL)
        cpl_frameset_delete(offsets);
    if(bpm != NULL)
        cpl_frameset_delete(bpm);
    if(bpmbkg != NULL)
        cpl_frameset_delete(bpmbkg);

    /* Save the products */
    cpl_msg_info(__func__, "Save the products") ;
    cpl_msg_indent_more() ;
    if (hawki_step_combine_save(combined, contrib_map, 
                                used_frames, parlist, framelist) != 0)
    {
        cpl_msg_warning(__func__, "Some error happened saving the data. "
                        "Check permisions or disk space") ;
        for(idet=0; idet< 2 * HAWKI_NB_DETECTORS; ++idet)
            cpl_image_delete(combined_contrib[idet]);
        cpl_frameset_delete(used_frames);
        cpl_free(combined_contrib);
        cpl_msg_indent_less() ;
        return -1 ;
    }
    cpl_msg_indent_less() ;
    
    /* Return */
    for(idet=0; idet< 2 * HAWKI_NB_DETECTORS; ++idet)
        cpl_image_delete(combined_contrib[idet]);
    cpl_free(combined_contrib);
    cpl_frameset_delete(used_frames);

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

int hawki_step_combine_retrieve_input_param
(cpl_parameterlist  *  parlist)
{
    cpl_parameter   *   par ;
    const char      *   sval ;
    par = NULL ;
    par = cpl_parameterlist_find(parlist,
            "hawki.hawki_step_combine.offset_max");
    hawki_step_combine_config.offset_max = cpl_parameter_get_int(par);
    par = cpl_parameterlist_find(parlist,
            "hawki.hawki_step_combine.comb_meth");
    sval = cpl_parameter_get_string(par);
    if (!strcmp(sval, "union"))
        hawki_step_combine_config.comb_meth = CPL_GEOM_UNION;
    else if (!strcmp(sval, "inter"))
        hawki_step_combine_config.comb_meth = CPL_GEOM_INTERSECT;
    else if (!strcmp(sval, "first"))
        hawki_step_combine_config.comb_meth = CPL_GEOM_FIRST;
    else
    {
        cpl_msg_error(__func__, "Invalid combine method specified");
        return -1;
    }
    par = cpl_parameterlist_find(parlist,
            "hawki.hawki_step_combine.borders");
    hawki_step_combine_config.borders = cpl_parameter_get_int(par);
    if(hawki_step_combine_config.borders < 0 )
    {
        cpl_msg_error(__func__, "Borders cannot be less than zero");
        return -1;
    }
    par = cpl_parameterlist_find(parlist, 
            "hawki.hawki_step_combine.rej");
    sval = cpl_parameter_get_string(par);
    setlocale(LC_NUMERIC, "C"); // Behaviour of sscanf depends on the locale
    if (sscanf(sval, "%d,%d",
               &hawki_step_combine_config.rej_low,
               &hawki_step_combine_config.rej_high)!=2)
    {
        return -1;
    }
    par = cpl_parameterlist_find(parlist, 
            "hawki.hawki_step_combine.resamp_kernel");
    sval = cpl_parameter_get_string(par);
    if (!strcmp(sval, "tanh"))
        hawki_step_combine_config.resamp_kernel = CPL_KERNEL_TANH;
    else if (!strcmp(sval, "sinc"))
        hawki_step_combine_config.resamp_kernel = CPL_KERNEL_SINC;
    else if (!strcmp(sval, "sinc2"))
        hawki_step_combine_config.resamp_kernel = CPL_KERNEL_SINC2;
    else if (!strcmp(sval, "lanczos"))
        hawki_step_combine_config.resamp_kernel = CPL_KERNEL_LANCZOS;
    else if (!strcmp(sval, "hamming"))
        hawki_step_combine_config.resamp_kernel = CPL_KERNEL_HAMMING;
    else if (!strcmp(sval, "hann"))
        hawki_step_combine_config.resamp_kernel = CPL_KERNEL_HANN;
    else if (!strcmp(sval, "default"))
        hawki_step_combine_config.resamp_kernel = CPL_KERNEL_DEFAULT;
    else
    {
        cpl_msg_error(__func__, "Invalid resampling kernel specified");
        return -1;
    }

    return 0;
}



/*----------------------------------------------------------------------------*/
/**
  @brief    The recipe data reduction part is implemented here 
  @param    obj         the objects frames
  @return   the combined images of the chips or NULL in error case
 */
/*----------------------------------------------------------------------------*/
static cpl_image ** hawki_step_combine_apply_comb
(cpl_frameset    * obj,
 cpl_frameset    * offsets_frames,
 cpl_frameset    * bpm_frame,
 cpl_frameset    * bkg_bpm_frames)
{
    cpl_image           **  combined_contrib;
    cpl_bivector        **  offsets;
    cpl_mask             *  bpm_masks[HAWKI_NB_DETECTORS];
    int                     idet;
    int                     ioff;

    if(offsets_frames == NULL)
    {
        cpl_bivector        *   offsets_single_chip;
        if ((offsets_single_chip = hawki_get_header_tel_offsets(obj)) == NULL) 
        {
            cpl_msg_error(__func__, "Cannot load the header offsets");
            return NULL;
        }
        offsets = cpl_malloc(HAWKI_NB_DETECTORS * sizeof(cpl_bivector *));
        for(idet = 0; idet < HAWKI_NB_DETECTORS; ++idet)
            offsets[idet] =  cpl_bivector_duplicate(offsets_single_chip);
        cpl_bivector_delete(offsets_single_chip);
    }
    else
    {
        offsets = hawki_load_refined_offsets
            (cpl_frameset_get_position(offsets_frames, 0));
        if(offsets == NULL)
        {
            cpl_msg_error(__func__, "Cannot load the refined offsets");
            return NULL;
        }
    }
    /* Get the oposite offsets. This is to change from 
     * telescope convention to cpl convention 
     * WARNING: It may appear that the img_jitter function 
     * does not apply the multiplication by -1, but it really does it in 
     * hawki_img_jitter_saa instead of when it reads the offsets */
    for(idet = 0; idet < HAWKI_NB_DETECTORS; ++idet)
    {
        cpl_vector_multiply_scalar(cpl_bivector_get_x(offsets[idet]), -1.0);
        cpl_vector_multiply_scalar(cpl_bivector_get_y(offsets[idet]), -1.0);
    }
    
    /* Load the bpm */
    if(bpm_frame != NULL)
    {
        cpl_imagelist *  bpm_images = NULL;
        bpm_images = hawki_load_frame
            (cpl_frameset_get_position(bpm_frame, 0), CPL_TYPE_INT);
        if(bpm_images == NULL)
        {
            cpl_msg_error(__func__, "Cannot load the bad pixel mask");
            return NULL;
        }
        for(idet = 0; idet < HAWKI_NB_DETECTORS; ++idet)
        {
            bpm_masks[idet] = cpl_mask_threshold_image_create
                (cpl_imagelist_get(bpm_images, idet), 0.5, 1.5);
        }
        cpl_imagelist_delete(bpm_images);
    }

    /* Create output object */
    combined_contrib = cpl_malloc(2 * HAWKI_NB_DETECTORS * sizeof(cpl_image *));
 
    /* Loop on the detectors */
    for (idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++)
    {
        cpl_imagelist   * in ;
        cpl_imagelist   * bpm_bkg_im = NULL;
        cpl_image      ** comb_contrib_chip ;
        double          * offs_est_x ;
        double          * offs_est_y ;
        double            max_x, max_y ;
        double            off_0_x;
        double            off_0_y;
        int               jdet;
        int               iframe;

        cpl_msg_info(__func__, "Combine chip number %d", idet+1) ;
        cpl_msg_indent_more() ;
        
        /* Print the offsets */
        offs_est_x = cpl_bivector_get_x_data(offsets[idet]) ;
        offs_est_y = cpl_bivector_get_y_data(offsets[idet]) ;
        for (ioff=0 ; ioff<cpl_bivector_get_size(offsets[idet]) ; ioff++) {
            cpl_msg_info(__func__,"Telescope offsets (Frame %d): %g %g", ioff+1,
                    -offs_est_x[ioff], -offs_est_y[ioff]) ;
        }

        /* Subtract the first offset to all offsets */
        max_x = max_y = 0.0 ;
        off_0_x = offs_est_x[0];
        off_0_y = offs_est_y[0];
        for (ioff=1 ; ioff<cpl_bivector_get_size(offsets[idet]) ; ioff++) 
        {
            offs_est_x[ioff] -= offs_est_x[0] ;
            offs_est_y[ioff] -= offs_est_y[0] ;
            if (fabs(offs_est_x[ioff]) > max_x) max_x = fabs(offs_est_x[ioff]);
            if (fabs(offs_est_y[ioff]) > max_y) max_y = fabs(offs_est_y[ioff]);
        }
        offs_est_x[0] = offs_est_y[0] = 0.00 ;

        /* Check if the max offset is not too big */
        if (max_x > hawki_step_combine_config.offset_max || 
                max_y > hawki_step_combine_config.offset_max) 
        {
            cpl_msg_error(__func__,"Sorry, no support for offsets larger than %d",
                          hawki_step_combine_config.offset_max);
            for(idet = 0; idet < HAWKI_NB_DETECTORS; ++idet)
            {
                cpl_bivector_delete(offsets[idet]);
                if(bpm_frame != NULL)
                    cpl_mask_delete(bpm_masks[idet]);
            }
            for(jdet = 0; jdet < idet; ++jdet)
            {
                cpl_image_delete(combined_contrib[idet]);
                cpl_image_delete(combined_contrib[idet+HAWKI_NB_DETECTORS]);
            }
            cpl_free(combined_contrib);
            return NULL ;
        }

        /* Load the input data */
        cpl_msg_info(__func__, "Load the input data") ;
        cpl_msg_indent_more();
        if ((in = hawki_load_detector(obj, idet+1, CPL_TYPE_FLOAT)) == NULL) {
            cpl_msg_error(__func__, "Cannot load chip %d",idet+1);
            //TODO: there is probably a memory leak here. It should be checked.
            for(idet = 0; idet < HAWKI_NB_DETECTORS; ++idet)
            {
                cpl_bivector_delete(offsets[idet]);
                if(bpm_frame != NULL)
                    cpl_mask_delete(bpm_masks[idet]);
            }
            for(jdet = 0; jdet < idet; ++jdet)
            {
                cpl_image_delete(combined_contrib[idet]);
                cpl_image_delete(combined_contrib[idet+HAWKI_NB_DETECTORS]);
            }
            cpl_free(combined_contrib);
            cpl_free(offsets);
            cpl_msg_indent_less() ;
            cpl_msg_indent_less() ;
            return NULL ;
        }

        /* Load the bad bkg images */
        if(bkg_bpm_frames != NULL)
        {
            cpl_msg_info(__func__, "Load the bad bkg images");
            cpl_msg_indent_more() ;
            if ((bpm_bkg_im = hawki_load_detector(bkg_bpm_frames, idet+1,
                              CPL_TYPE_FLOAT)) == NULL)
            {
                cpl_msg_error(__func__, "Cannot load chip %d",idet+1);
                for(idet = 0; idet < HAWKI_NB_DETECTORS; ++idet)
                {
                    cpl_bivector_delete(offsets[idet]);
                    if(bpm_frame != NULL)
                        cpl_mask_delete(bpm_masks[idet]);
                }
                for(jdet = 0; jdet < idet; ++jdet)
                {
                    cpl_image_delete(combined_contrib[idet]);
                    cpl_image_delete(combined_contrib[idet+HAWKI_NB_DETECTORS]);
                }
                cpl_free(combined_contrib);
                cpl_imagelist_delete(in);
                cpl_free(offsets);
                cpl_msg_indent_less() ;
                cpl_msg_indent_less() ;
                return NULL ;
            }
            cpl_msg_indent_less() ;
        }
        cpl_msg_indent_less() ;
        
        /* Add the general bpm or background bpms in case they were specified */
        if(bpm_frame != NULL || bkg_bpm_frames != NULL)
        {
            for(iframe = 0 ; iframe <cpl_imagelist_get_size(in) ; ++iframe)
            {
                cpl_mask  * final_mask;
                cpl_image * target_image =  cpl_imagelist_get(in, iframe);
                final_mask = cpl_mask_new(cpl_image_get_size_x(target_image),
                                          cpl_image_get_size_y(target_image));
                //Add the common bpm
                if(bpm_frame != NULL)
                    cpl_mask_or(final_mask, bpm_masks[idet]);
                //Add the background mask if provided
                if(bkg_bpm_frames != NULL)
                {
                    cpl_mask * bpm_bkg_mask = 
                        cpl_mask_threshold_image_create
                          (cpl_imagelist_get(bpm_bkg_im, iframe), 0.5, FLT_MAX);
                    cpl_mask_or(final_mask, bpm_bkg_mask);
                    cpl_mask_delete(bpm_bkg_mask);
                }
                cpl_image_reject_from_mask(target_image, final_mask);
                cpl_mask_delete(final_mask);
            }
        }
        
        if(bkg_bpm_frames != NULL)
            cpl_imagelist_delete(bpm_bkg_im);

        /* Apply the shift and add */
        cpl_msg_info(__func__, "Shift and add") ;
        cpl_msg_indent_more() ;
        comb_contrib_chip = hawki_step_combine_chip(in, offsets[idet], 
                &(hawki_step_combine_output.combined_pos_x[idet]),
                &(hawki_step_combine_output.combined_pos_y[idet])) ;
        if (comb_contrib_chip == NULL) 
        {
            cpl_msg_error(__func__, "Cannot apply the shift and add") ;
            cpl_imagelist_delete(in) ;
            for(jdet = 0; jdet < HAWKI_NB_DETECTORS; ++jdet)
                cpl_bivector_delete(offsets[jdet]);
            {
                cpl_image_delete(combined_contrib[idet]);
                cpl_image_delete(combined_contrib[idet+HAWKI_NB_DETECTORS]);
            }
            cpl_free(combined_contrib);
            cpl_free(offsets);
            cpl_msg_indent_less() ;
            cpl_msg_indent_less() ;
            return NULL ;
        }
        
        /* The cumoffset have the opposite criteria as cpl */
        hawki_step_combine_output.combined_cumoffset_x[idet] = 
            hawki_step_combine_output.combined_pos_x[idet] - off_0_x;
        hawki_step_combine_output.combined_cumoffset_y[idet] = 
            hawki_step_combine_output.combined_pos_y[idet] - off_0_y;
        cpl_imagelist_delete(in) ;
        cpl_msg_indent_less() ;

        /* Interpolate bad pixels */
        hawki_step_combine_interpolate_badpix(comb_contrib_chip[0]);        

        /* Put the results in the image list */
        combined_contrib[idet] = comb_contrib_chip[0];
        combined_contrib[idet+HAWKI_NB_DETECTORS] = comb_contrib_chip[1];
        cpl_free(comb_contrib_chip);
        cpl_msg_indent_less() ;
    }
    
    /* Compute the mean airmass */
    hawki_step_combine_output.mean_airmass = hawki_get_mean_airmass(obj);
    
    for(idet = 0; idet < HAWKI_NB_DETECTORS; ++idet)
    {
        cpl_bivector_delete(offsets[idet]);
        if(bpm_frame != NULL)
            cpl_mask_delete(bpm_masks[idet]);
    }
    cpl_free(offsets);
    return combined_contrib;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Apply the shift and add in jitter mode
  @param    in          the images stack to combine
  @param    offsets     the list of offsets in x and y
  @param    pos_x       the x position of the first image in the combined 
  @param    pos_y       the y position of the first image in the combined
  @return   the combined image or NULL in error case
 */
/*----------------------------------------------------------------------------*/
static cpl_image ** hawki_step_combine_chip(
        cpl_imagelist   *  in,
        cpl_bivector    *  offsets,
        double          *  pos_x,
        double          *  pos_y)
{
    cpl_image        **  combined_contrib;
    cpl_imagelist    *   in_ext ;
    cpl_image        *   tmp1 ;
    cpl_image        *   tmp2 ;
    int                  nfiles, nx, ny ;
    int                  i;

    /* Check entries */
    if (pos_x == NULL || pos_y == NULL) return NULL ;
    if (offsets == NULL) return NULL ;

    /* Get the number of images */
    nfiles = cpl_imagelist_get_size(in) ;
    if (cpl_bivector_get_size(offsets) != nfiles) {
        cpl_msg_error(__func__, "Number of refined offsets in table """
                      "is different than number of frames to combine"); 
        return NULL ;
    }
    
    /* Discard the pixels on the sides */
    if (hawki_step_combine_config.borders > 0) {
        nx = cpl_image_get_size_x(cpl_imagelist_get(in, 0)) ;
        ny = cpl_image_get_size_y(cpl_imagelist_get(in, 0)) ;
        in_ext = cpl_imagelist_new() ;
        for (i=0 ; i<cpl_imagelist_get_size(in) ; i++) {
            tmp1 = cpl_imagelist_get(in, i) ;
            tmp2 = cpl_image_extract(tmp1, 
                    hawki_step_combine_config.borders+1, 
                    hawki_step_combine_config.borders+1, 
                    nx-hawki_step_combine_config.borders, 
                    ny-hawki_step_combine_config.borders) ;
            cpl_imagelist_set(in_ext, tmp2, i) ;
        }
    }
    else
    {
        in_ext = cpl_imagelist_duplicate(in);
    }

    /* Apply the shift & add */
    cpl_msg_info(__func__, "Recombine the images set") ;
    cpl_msg_indent_more() ;
    if ((combined_contrib=cpl_geom_img_offset_saa(in_ext, offsets,
            hawki_step_combine_config.resamp_kernel, 
            hawki_step_combine_config.rej_low,
            hawki_step_combine_config.rej_high,
            hawki_step_combine_config.comb_meth,
            pos_x, pos_y)) == NULL) {
        cpl_msg_error(cpl_func, "Cannot apply the shift and add") ;
        cpl_msg_indent_less();
        return NULL;
    }
    cpl_msg_indent_less();
    *pos_x -= hawki_step_combine_config.borders;
    *pos_y -= hawki_step_combine_config.borders;

    /* Free and return */
    cpl_imagelist_delete(in_ext);
    return combined_contrib;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Interpolate the pixels in the bad pixel mask
  @param    image    the image to interpolate
  @return   0 if everything is ok, -1 otherwise
 */
/*----------------------------------------------------------------------------*/
static int hawki_step_combine_interpolate_badpix
(cpl_image           *  image)
{
    int nbadpixels = cpl_image_count_rejected(image); 
    if(nbadpixels !=0)
        cpl_msg_info(__func__,"Number of pixels with no combined value available: %d ",
                     nbadpixels);
    if(cpl_image_count_rejected(image) > 0)
    {
        //I use this even if DFS08929 is still not solved
        cpl_detector_interpolate_rejected(image);
    }
    return 0;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Save the jitter recipe products on disk
  @param    combined    the combined imagelist produced
  @param    objs_stats  the tables with the detected objects statistics 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_combine_save
(cpl_image           ** combined,
 cpl_image           ** contrib_map,
 cpl_frameset        *  used_frames,
 cpl_parameterlist   *  parlist,
 cpl_frameset        *  recipe_frameset)
{
    cpl_propertylist    **  extproplists ;
    const cpl_frame     *   ref_frame ;
    cpl_propertylist    *   wcslist ;
    cpl_propertylist    *   inputlist ;
    double                  crpix1, crpix2 ;
    int                     ext_nb ;
    const char          *   recipe_name = "hawki_step_combine" ;
    int                     idet;
    cpl_errorstate          error_prevstate = cpl_errorstate_get();

    /* Get a reference frame for the WCS keys */
    ref_frame = irplib_frameset_get_first_from_group
        (recipe_frameset, CPL_FRAME_GROUP_RAW) ;
    
    if(ref_frame == NULL)
    {
        cpl_msg_error(__func__, "Cannot get a reference frame");
        return -1;
    }

    /* Create the QC lists */
    extproplists = cpl_malloc(HAWKI_NB_DETECTORS * sizeof(cpl_propertylist*)) ;
    for (idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++) 
    {

        /* Initialize qclists */
        extproplists[idet] = cpl_propertylist_new() ;

        /* Get the extension number */
        ext_nb=hawki_get_ext_from_detector(cpl_frame_get_filename(ref_frame), idet+1);

        /* Handle WCS keys */
        wcslist = cpl_propertylist_load_regexp(
                cpl_frame_get_filename(ref_frame), ext_nb, HAWKI_HEADER_WCS, 0);

        /* Update WCS and write them */
        crpix1 = cpl_propertylist_get_double(wcslist, "CRPIX1"); 
        crpix1 += hawki_step_combine_output.combined_pos_x[idet];
        cpl_propertylist_update_double(wcslist, "CRPIX1", crpix1) ;
        crpix2 = cpl_propertylist_get_double(wcslist, "CRPIX2"); 
        crpix2 += hawki_step_combine_output.combined_pos_y[idet] ;
        cpl_propertylist_update_double(wcslist, "CRPIX2", crpix2) ;
        cpl_propertylist_copy_property_regexp
            (extproplists[idet], wcslist, HAWKI_HEADER_WCS, 0);
        cpl_propertylist_delete(wcslist) ;
        
        /* Keywords for the relative position of the combined image */
        cpl_propertylist_append_double
            (extproplists[idet], "ESO QC COMBINED CUMOFFSETX",
             hawki_step_combine_output.combined_cumoffset_x[idet]);
        cpl_propertylist_append_double
            (extproplists[idet], "ESO QC COMBINED CUMOFFSETY",
             hawki_step_combine_output.combined_cumoffset_y[idet]);
        cpl_propertylist_append_double
            (extproplists[idet], "ESO QC COMBINED POSX",
             hawki_step_combine_output.combined_pos_x[idet]);
        cpl_propertylist_append_double
            (extproplists[idet], "ESO QC COMBINED POSY",
             hawki_step_combine_output.combined_pos_y[idet]);
        cpl_propertylist_append_double
            (extproplists[idet], "ESO QC AIRMASS MEAN",
             hawki_step_combine_output.mean_airmass);

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

    /* Write the combined image */
    if(hawki_images_save(recipe_frameset,
                         parlist,
                         used_frames,
                         (const cpl_image **)combined,
                         recipe_name,
                         HAWKI_CALPRO_COMBINED,
                         HAWKI_PROTYPE_COMBINED, 
                         NULL,
                         (const cpl_propertylist**)extproplists,
                         "hawki_step_combine.fits")  != 0)
    {
        for (idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++) {
            cpl_propertylist_delete(extproplists[idet]) ;
        }
        cpl_free(extproplists) ;
        return -1;
    }

    /* Write the contrib map */
    if(hawki_images_save(recipe_frameset,
                         parlist,
                         used_frames,
                         (const cpl_image **)contrib_map,
                         recipe_name,
                         HAWKI_CALPRO_COMB_CONTRIB_MAP,
                         HAWKI_PROTYPE_COMB_CONTRIB_MAP,
                         NULL,
                         (const cpl_propertylist**)extproplists,
                         "hawki_step_combine_contrib_map.fits")  != 0)
    {
        for (idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++) {
            cpl_propertylist_delete(extproplists[idet]);
        }
        cpl_free(extproplists) ;
        return -1;
    }

    /* Free and return */
    for (idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++) {
        cpl_propertylist_delete(extproplists[idet]) ;
    }
    cpl_free(extproplists) ;

    if(!cpl_errorstate_is_equal(error_prevstate))
    {
        cpl_error_reset();
        return 1;
    }

    return  0;
}
 
