/* $Id: hawki_step_apply_dist.c,v 1.12 2013-09-02 14:29:09 cgarcia Exp $
 *
 * 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
 */

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

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

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

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

#include "irplib_utils.h"

#include "hawki_utils.h"
#include "hawki_pfits.h"
#include "hawki_dfs.h"
#include "hawki_load.h"
#include "hawki_save.h"
#include "hawki_distortion.h"

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

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

static int hawki_step_apply_dist_create(cpl_plugin *) ;
static int hawki_step_apply_dist_exec(cpl_plugin *) ;
static int hawki_step_apply_dist_destroy(cpl_plugin *) ;
static int hawki_step_apply_dist(cpl_parameterlist *, cpl_frameset *) ;
static int hawki_step_apply_dist_save
(cpl_imagelist      *   images,
 int                    iserie,
 cpl_parameterlist  *   recipe_parlist,
 cpl_frameset       *   used_frameset,
 cpl_frameset       *   recipe_frameset);

int hawki_step_apply_dist_compute_and_save
(cpl_frameset      * objects,
 cpl_frameset      * distortion_x,
 cpl_frameset      * distortion_y,
 cpl_parameterlist * parlist,
 cpl_frameset      * recipe_frameset);

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

static char hawki_step_apply_dist_description[] =
"hawki_step_apply_dist -- Distortion correction utility\n"
"This recipe accepts three types of frames:\n"
"object.fits  Images to correct (PRO.CATG = "HAWKI_CALPRO_BKG_SUBTRACTED")\n"
"distmap_x.fits The image with the distortion in X.\n"
"                   (PRO CATG = "HAWKI_CALPRO_DISTORTION_X")\n"
"distmap_y.fits The image with the distortion in Y.\n"
"                   (PRO CATG = "HAWKI_CALPRO_DISTORTION_Y")\n"
"\n"
"This recipe produces:\n"
"hawki_step_apply_dist.fits:     the corrected image.\n"
"                   (PRO CATG = "HAWKI_CALPRO_DIST_CORRECTED")\n" ;

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

/*----------------------------------------------------------------------------*/
/**
  @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,
                    HAWKI_BINARY_VERSION,
                    CPL_PLUGIN_TYPE_RECIPE,
                    "hawki_step_apply_dist",
                    "Distortion correction utility",
                    hawki_step_apply_dist_description,
                    "Cesar Enrique Garcia Dabo",
                    PACKAGE_BUGREPORT,  
                    hawki_get_license(),
                    hawki_step_apply_dist_create,
                    hawki_step_apply_dist_exec,
                    hawki_step_apply_dist_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 hawki_step_apply_dist_create(cpl_plugin * plugin)
{
    cpl_recipe      *   recipe ;
        
    /* 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() ; 

    /* 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_apply_dist_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_apply_dist(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_apply_dist_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
 */
/*----------------------------------------------------------------------------*/
static int hawki_step_apply_dist(
        cpl_parameterlist   *   parlist,
        cpl_frameset        *   frameset)
{
    cpl_frameset *   objframes;
    cpl_frameset *   distortion_x;
    cpl_frameset *   distortion_y;
    
    /* Retrieve input parameters */
 
    /* Identify the RAW and CALIB frames in the input frameset */
    if (hawki_dfs_set_groups(frameset)) {
        cpl_msg_error(__func__, "Cannot identify RAW and CALIB frames") ;
        return -1 ;
    }

    /* Identifying objects frames */
    cpl_msg_info(__func__, "Identifying objects");
    objframes = hawki_extract_frameset
        (frameset, HAWKI_CALPRO_BKG_SUBTRACTED);
    if (objframes == NULL)
    {
        cpl_msg_error(__func__, "No object frames provided (%s)",
                      HAWKI_CALPRO_BKG_SUBTRACTED);
        return -1 ;
    }
    
    /* Identifying distortion frames */
    cpl_msg_info(__func__, "Identifying distortion maps");
    distortion_x = hawki_extract_frameset
        (frameset, HAWKI_CALPRO_DISTORTION_X);
    distortion_y = hawki_extract_frameset
        (frameset, HAWKI_CALPRO_DISTORTION_Y);
    if(cpl_frameset_get_size(distortion_x) != 1 && 
       cpl_frameset_get_size(distortion_y) != 1 )
    {
        cpl_msg_error(__func__, "One X-distortion frame and one Y-distortion "
                      "must be provided (%s, %s)",
                      HAWKI_CALPRO_DISTORTION_X, HAWKI_CALPRO_DISTORTION_Y);
        cpl_frameset_delete(objframes);
        cpl_frameset_delete(distortion_x);
        cpl_frameset_delete(distortion_y);
        return -1 ;
    }
    
    /* Apply the correction and save */
    if(hawki_step_apply_dist_compute_and_save
        (objframes, distortion_x, distortion_y, parlist, frameset) == -1)
    {
        cpl_msg_error(__func__,"Could not correct the frames"); 
        cpl_frameset_delete(objframes);
        cpl_frameset_delete(distortion_x);
        cpl_frameset_delete(distortion_y);
        return -1;
    }
    
    /* Free and return */
    cpl_frameset_delete(objframes);
    cpl_frameset_delete(distortion_x);
    cpl_frameset_delete(distortion_y);
    
    /* 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    Apply the distortion and save the resulting object frames
  @param    objects         the frames which contain the objects 
  @param    distortion      the frame with the distortion map
  @param    parlist         the recipe parlist (for saving function)
  @param    recipe_frameset the recipe frameset (for saving function)
  @return   0 if everything is ok, -1 otherwise
 */
/*----------------------------------------------------------------------------*/
int hawki_step_apply_dist_compute_and_save
(cpl_frameset      * objects,
 cpl_frameset      * distortion_x,
 cpl_frameset      * distortion_y,
 cpl_parameterlist * parlist,
 cpl_frameset      * recipe_frameset)
{
    const cpl_frame   *  distframe_x;
    const cpl_frame   *  distframe_y;
    cpl_image        **  dist_x;
    cpl_image        **  dist_y;
    cpl_image         *  first_image;
    int                  nx;
    int                  ny;
    int                  iframe;
    int                  nframes;
    int                  idet;
    int                  jdet;
    cpl_errorstate       error_prevstate = cpl_errorstate_get();

    /* Get the distortion filename and distortion maps */
    cpl_msg_info(__func__,"Creating the distortion maps");
    distframe_x = cpl_frameset_get_position_const(distortion_x, 0);
    distframe_y = cpl_frameset_get_position_const(distortion_y, 0);
    first_image = hawki_load_image(objects, 0, 1, CPL_TYPE_FLOAT);
    nx = cpl_image_get_size_x(first_image);
    ny = cpl_image_get_size_y(first_image);
    cpl_image_delete(first_image);
    dist_x = cpl_malloc(HAWKI_NB_DETECTORS * sizeof(cpl_image *));
    dist_y = cpl_malloc(HAWKI_NB_DETECTORS * sizeof(cpl_image *));
    for (idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++)
    {
        hawki_distortion * distortion;
        dist_x[idet] = cpl_image_new(nx, ny, CPL_TYPE_DOUBLE);
        dist_y[idet] = cpl_image_new(nx, ny, CPL_TYPE_DOUBLE) ;
        
        /* Load the distortion */
        if ((distortion = hawki_distortion_load
                (distframe_x, distframe_y , idet+1)) == NULL) 
        {
            cpl_msg_error(__func__, "Cannot load the distortion for chip %d: %s", 
                    idet+1, cpl_error_get_message());
            for (jdet=0 ; jdet<=idet; jdet++)
            {
                cpl_image_delete(dist_x[jdet]);
                cpl_image_delete(dist_y[jdet]);
            }
            cpl_free(dist_x);
            cpl_free(dist_y);
            return -1 ;
        }
        if (hawki_distortion_create_maps_detector
                (distortion, dist_x[idet], dist_y[idet]))
        {
            cpl_msg_error(__func__, "Cannot create the distortion maps") ;
            for (jdet=0 ; jdet<=idet; jdet++)
            {
                cpl_image_delete(dist_x[jdet]);
                cpl_image_delete(dist_y[jdet]);
            }
            cpl_free(dist_x);
            cpl_free(dist_y);
            hawki_distortion_delete(distortion);
            return -1;
        }
        hawki_distortion_delete(distortion);
    }
    
    /* Loop on frames */
    nframes = cpl_frameset_get_size(objects);
    cpl_msg_info(__func__,"Number of frames to process: %d", nframes);
    cpl_msg_indent_more();
    for(iframe = 0 ; iframe < nframes; ++iframe)
    {
        cpl_imagelist * object_images;
        cpl_frame     * this_frame;
        cpl_frameset  * used_frameset;
    
        /* Msg */
        cpl_msg_info(__func__,"Correcting distortion in frame %d",iframe+1);
        
        /* Load the HAWKI images */
        this_frame = cpl_frameset_get_position(objects, iframe);
        if((object_images = hawki_load_frame(this_frame, CPL_TYPE_FLOAT)) == NULL)
        {
            cpl_msg_error(__func__,"Could not load input object images");
            cpl_msg_indent_less();
            for (idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++)
            {
                cpl_image_delete(dist_x[idet]);
                cpl_image_delete(dist_y[idet]);
            }
            cpl_free(dist_x);
            cpl_free(dist_y);
            return -1;
        }

        /* Apply the correction on the current image */
        if (hawki_distortion_apply_maps(object_images, dist_x, dist_y) == -1) 
        {
            cpl_msg_error(__func__, "Cannot correct distortion") ;
            cpl_msg_indent_less();
            cpl_imagelist_delete(object_images);
            for (idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++)
            {
                cpl_image_delete(dist_x[idet]);
                cpl_image_delete(dist_y[idet]);
            }
            cpl_free(dist_x);
            cpl_free(dist_y);
            return -1 ;
        }

        /* Set the used frameset */
        used_frameset = cpl_frameset_new(); 
        cpl_frameset_insert(used_frameset, cpl_frame_duplicate(this_frame));
        cpl_frameset_insert
            (used_frameset,cpl_frame_duplicate
                 (cpl_frameset_get_position_const(distortion_x, 0)));
        cpl_frameset_insert
            (used_frameset,cpl_frame_duplicate
                 (cpl_frameset_get_position_const(distortion_y, 0)));
            
        /* Save the corrected image */
        if (hawki_step_apply_dist_save
                (object_images, iframe, 
                 parlist, used_frameset, recipe_frameset) == -1)
        {
            cpl_errorstate_set(CPL_ERROR_NONE);
        }
        
        /* Free resources */
        cpl_frameset_delete(used_frameset);
        cpl_imagelist_delete(object_images);
        
    }
    cpl_msg_indent_less();
    
    for (idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++)
    {
        cpl_image_delete(dist_x[idet]);
        cpl_image_delete(dist_y[idet]);
    }
    cpl_free(dist_x);
    cpl_free(dist_y);
    if(!cpl_errorstate_is_equal(error_prevstate))
    {
        cpl_msg_warning(__func__,"Probably some data could not be saved. "
                        "Check permisions or disk space");
    }                           
    return 0;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Save the products on disk
  @param    in              the imagelist to save
  @param    parlist         the input list of parameters
  @param    used_frameset   the frameset used to correct this image
  @param    recipe_frameset the recipe input frameset
  @return   0 if everything is ok, -1 otherwise
 */
/*----------------------------------------------------------------------------*/
static int hawki_step_apply_dist_save
(cpl_imagelist      *   images,
 int                    iserie,
 cpl_parameterlist  *   recipe_parlist,
 cpl_frameset       *   used_frameset,
 cpl_frameset       *   recipe_frameset)
{
    const cpl_frame     *   raw_reference;
    cpl_propertylist    **  extproplists;
    char                    filename[256] ;
    cpl_propertylist    *   inputlist ;
    int                     ext_nb ;
    const char          *   recipe_name = "hawki_step_apply_dist";
    int                     idet;

    /* 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();
    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_apply_dist_%04d.fits", iserie+1);
    if(hawki_imagelist_save
        (recipe_frameset,
         recipe_parlist,
         used_frameset,
         images,
         recipe_name,
         HAWKI_CALPRO_DIST_CORRECTED,
         HAWKI_PROTYPE_DIST_CORRECTED,
         NULL,
         (const cpl_propertylist**)extproplists,
         filename) != 0)
    {
        for (idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++)
            cpl_propertylist_delete(extproplists[idet]) ;
        cpl_free(extproplists) ;
        cpl_msg_indent_less();
        return -1;
    }
    
    /* Free and return */
    for (idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++) {
        cpl_propertylist_delete(extproplists[idet]) ;
    }
    cpl_free(extproplists) ;
    cpl_msg_indent_less();
    return  0;
}

