/* $Id: hawki_step_basic_calib.c,v 1.14 2013-03-25 11:36:35 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-03-25 11:36:35 $
 * $Revision: 1.14 $
 * $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_calib.h"
#include "hawki_load.h"
#include "hawki_save.h"
#include "hawki_pfits.h"
#include "hawki_dfs.h"

/*-----------------------------------------------------------------------------
                                Define
 -----------------------------------------------------------------------------*/

#define NEGLIG_OFF_DIFF     0.1
#define SQR(x) ((x)*(x))

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

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

static int hawki_step_basic_calib_create(cpl_plugin *) ;
static int hawki_step_basic_calib_exec(cpl_plugin *) ;
static int hawki_step_basic_calib_destroy(cpl_plugin *) ;
static int hawki_step_basic_calib(cpl_parameterlist *, cpl_frameset *) ;

static int hawki_step_basic_calib_applycal_qc_save
(cpl_frameset      *  raw_target,
 const cpl_frame   *  flat,
 const cpl_frame   *  dark,
 const cpl_frame   *  bpm,
 const char        *  filename_postfix,
 const char        *  procat,
 const char        *  protype,
 cpl_parameterlist *  recipe_parlist,
 cpl_frameset      *  recipe_framelist);
static void hawki_step_basic_calib_qc(void);
static int hawki_step_basic_calib_save
(cpl_imagelist     *  reduced,
 const char        *  filename_postfix,
 const char        *  procat,
 const char        *  protype,
 int                  iserie,
 cpl_frameset      *  used_frameset,
 cpl_parameterlist *  recipe_parlist,
 cpl_frameset      *  recipe_framelist);

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

static char hawki_step_basic_calib_description[] =
"hawki_step_basic_calib -- hawki basic reduction utility (flat, dark).\n"
"The files listed in the Set Of Frames (sof-file) must be tagged:\n"
"raw-file.fits "HAWKI_IMG_JITTER_RAW" or\n"
"raw-file.fits "HAWKI_IMG_JITTER_SKY_RAW" \n"
"flat-file.fits "HAWKI_CALPRO_FLAT" \n"
"dark-file.fits "HAWKI_CALPRO_DARK" \n"
"bpm-file.fits "HAWKI_CALPRO_BPM" \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_basic_calib",
                    "Basic reduction recipe",
                    hawki_step_basic_calib_description,
                    "Cesar Enrique Garcia Dabo",
                    "cgarcia@eso.org",
                    hawki_get_license(),
                    hawki_step_basic_calib_create,
                    hawki_step_basic_calib_exec,
                    hawki_step_basic_calib_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_basic_calib_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() ;

    /* 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_basic_calib_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_basic_calib(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_basic_calib_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_basic_calib(
        cpl_parameterlist   *   parlist, 
        cpl_frameset        *   framelist)
{
    const cpl_frame  * flat;
    const cpl_frame  * dark;
    const cpl_frame  * bpm;
    cpl_frameset     * objframes ;
    cpl_frameset     * skyframes ;

    /* 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 ;
    }

    /* Retrieve calibration data */
    cpl_msg_info(__func__, "Identifying calibration data");
    flat   = cpl_frameset_find_const(framelist, HAWKI_CALPRO_FLAT);
    dark   = cpl_frameset_find_const(framelist, HAWKI_CALPRO_DARK);
    bpm    = cpl_frameset_find_const(framelist, HAWKI_CALPRO_BPM);
    if (flat == NULL && dark == NULL && bpm == NULL)
    {
        cpl_msg_error(__func__, "No calibration data provided (%s and/or %s and/or %s)",
                HAWKI_CALPRO_FLAT, HAWKI_CALPRO_DARK, HAWKI_CALPRO_BPM);
        return -1 ;
    }
    
    /* Retrieve raw frames */
    cpl_msg_info(__func__, "Identifying objects and sky data");
    objframes = hawki_extract_frameset(framelist, HAWKI_IMG_JITTER_RAW) ;
    /* Retrieve sky frames */
    skyframes = hawki_extract_frameset(framelist, HAWKI_IMG_JITTER_SKY_RAW) ;
    if (objframes == NULL && skyframes == NULL)
    {
        cpl_msg_error(__func__, "Cannot find objs (%s) or sky frames (%s) in the input list",
                HAWKI_IMG_JITTER_RAW, HAWKI_IMG_JITTER_SKY_RAW);
        return -1 ;
    }
    
    /* Applying the calibrations, QC and saving the results */
    if (objframes != NULL)
    {
        const char * procat  = HAWKI_CALPRO_BASICCALIBRATED;
        const char * protype = HAWKI_PROTYPE_BASICCALIBRATED;
        cpl_msg_info(__func__, "Apply the basic reduction to object frames");
        hawki_step_basic_calib_applycal_qc_save
            (objframes, flat, dark, bpm, "obj", procat, protype,
             parlist, framelist);
        cpl_frameset_delete(objframes);
    }
    if (skyframes != NULL)
    {
        const char * procat  = HAWKI_CALPRO_SKY_BASICCALIBRATED;
        const char * protype = HAWKI_PROTYPE_SKY_BASICCALIBRATED;
        cpl_msg_info(__func__, "Apply the basic reduction to sky frames");
        hawki_step_basic_calib_applycal_qc_save
            (skyframes, flat, dark, bpm, "sky", procat, protype,
             parlist, framelist);
        cpl_frameset_delete(skyframes);
    }

    /* return */
    if (cpl_error_get_code()) return -1 ;
    else return 0 ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    The recipe data reduction part is implemented here 
  @param    obj         the objects frames
  @param    obj         the sky frames
  @param    flat        the flat field or NULL
  @param    bpm         the bad pixels map or NULL
  @param    skybg       the computed sky background values
  @return   the combined images of the chips or NULL in error case
 */
/*----------------------------------------------------------------------------*/
static int hawki_step_basic_calib_applycal_qc_save
(cpl_frameset      *  raw_target,
 const cpl_frame   *  flat,
 const cpl_frame   *  dark,
 const cpl_frame   *  bpm,
 const char        *  filename_postfix,
 const char        *  procat,
 const char        *  protype,
 cpl_parameterlist *  recipe_parlist,
 cpl_frameset      *  recipe_framelist)
{
    cpl_imagelist    *  flat_images;
    cpl_imagelist    *  dark_images;
    cpl_imagelist    *  bpm_images;
    cpl_frameset     *  calib_frameset;  
    cpl_propertylist *  plist ;
    cpl_errorstate      error_prevstate = cpl_errorstate_get();
    
    double              science_dit;
    int                 iframe;
    int                 ntarget;

    /* Initializing the pointers */
    flat_images = NULL;
    dark_images = NULL;
    bpm_images  = NULL;
    
    /* Indentation */
    cpl_msg_indent_more();
    
    /* Initializating the calibration frameset */
    calib_frameset = cpl_frameset_new();

    /* Loading the calibration files */
    cpl_msg_info(__func__, "Loading the calibration data") ;
    if(flat != NULL)
    {
        flat_images = hawki_load_frame(flat, CPL_TYPE_FLOAT);
        if(flat_images == NULL)
        {
            cpl_msg_error(__func__, "Error reading flat") ;
            cpl_frameset_delete(calib_frameset);
            return -1;
        }
        cpl_frameset_insert(calib_frameset, cpl_frame_duplicate(flat));
    }
    if(dark != NULL)
    {
        dark_images = hawki_load_frame(dark, CPL_TYPE_FLOAT);
        if(dark_images == NULL)
        {
            cpl_msg_error(__func__, "Error reading dark") ;
            cpl_imagelist_delete(flat_images);
            cpl_frameset_delete(calib_frameset);
            return -1;
        }
        cpl_frameset_insert(calib_frameset, cpl_frame_duplicate(dark));
    }
    if(bpm != NULL)
    {
        bpm_images = hawki_load_frame(bpm, CPL_TYPE_INT);
        if(bpm_images == NULL)
        {
            cpl_msg_error(__func__, "Error reading bpm") ;
            cpl_imagelist_delete(flat_images);
            cpl_imagelist_delete(dark_images);
            cpl_frameset_delete(calib_frameset);
            return -1;
        }
        cpl_frameset_insert(calib_frameset, cpl_frame_duplicate(bpm));
    }

    /* Multiply the dark image by the science exposure time */
    if(dark != NULL)
    {
        if ((plist=cpl_propertylist_load
                (cpl_frame_get_filename
                 (cpl_frameset_get_position_const(raw_target, 0)), 0)) == NULL) 
        {
            cpl_msg_error(__func__, "Cannot get header from frame");
            cpl_imagelist_delete(flat_images);
            cpl_imagelist_delete(dark_images);
            cpl_frameset_delete(calib_frameset);
            return -1 ;
        }
        science_dit = hawki_pfits_get_dit(plist);
        cpl_imagelist_multiply_scalar(dark_images, science_dit);
        cpl_propertylist_delete(plist);
    }

    /* Loop on the number of frames */
    ntarget = cpl_frameset_get_size(raw_target);
    cpl_msg_info(__func__, "Looping the science frames: %d frames", ntarget);
    for( iframe = 0 ; iframe < ntarget ; ++iframe)
    {
        /* Local storage variables */
        cpl_frame     * target_frame;
        cpl_imagelist * target_images;
        cpl_frameset  * used_frameset;
        target_images = NULL;

        /* Loading the target */
        cpl_msg_indent_more();
        cpl_msg_info(__func__, "Loading frame %d", iframe+1) ;
        target_frame = cpl_frameset_get_position(raw_target, iframe);
        if(target_frame != NULL)
            target_images = hawki_load_frame(target_frame, CPL_TYPE_FLOAT);
        if(target_images == NULL)
        {
            cpl_msg_error(__func__, "Error reading frame") ;
            cpl_imagelist_delete(flat_images);
            cpl_imagelist_delete(dark_images);
            cpl_imagelist_delete(bpm_images);
            return -1;
        }
        /* Creating the used frameset */
        used_frameset = cpl_frameset_duplicate(calib_frameset);
        cpl_frameset_insert(used_frameset, cpl_frame_duplicate(target_frame));
    
        /* TODO: Creating the variance array */
        /* cpl_create_variance_image(); */
        
        /* Applying the calibrations */
        cpl_msg_info(__func__, "Calibrating frame") ;
        if (hawki_flat_dark_bpm_imglist_calib
                (target_images, flat_images, dark_images, bpm_images) == -1) 
        {
            cpl_msg_error(__func__, "Cannot calibrate frame") ;
            cpl_imagelist_delete(flat_images);
            cpl_imagelist_delete(dark_images);
            cpl_imagelist_delete(bpm_images);
            cpl_imagelist_delete(target_images);
            cpl_frameset_delete(used_frameset);            
            cpl_frameset_delete(calib_frameset);
            cpl_msg_indent_less() ;
            cpl_msg_indent_less() ;
            return -1 ;
        }

        /* Compute quality control */
        hawki_step_basic_calib_qc();
        
        /* Save the products */
        cpl_msg_info(__func__, "Save the products") ;
        if (hawki_step_basic_calib_save
                (target_images, filename_postfix, procat, protype, iframe,
                 used_frameset, recipe_parlist, 
                 recipe_framelist) == -1)
        {
            cpl_msg_error(__func__, "Cannot save the products") ;
            cpl_imagelist_delete(flat_images);
            cpl_imagelist_delete(dark_images);
            cpl_imagelist_delete(bpm_images);
            cpl_imagelist_delete(target_images);
            cpl_frameset_delete(used_frameset);
            cpl_frameset_delete(calib_frameset);
            cpl_msg_indent_less() ;
            cpl_msg_indent_less() ;
            return -1 ;
        }

        /* Delete the target and the used frameset */
        cpl_imagelist_delete(target_images);
        cpl_frameset_delete(used_frameset);
        cpl_msg_indent_less();
    }
    
    cpl_msg_indent_less();
    /* Delete the calibration files */
    cpl_imagelist_delete(flat_images);
    cpl_imagelist_delete(dark_images);
    cpl_imagelist_delete(bpm_images);
    cpl_frameset_delete(calib_frameset);
    
    if(!cpl_errorstate_is_equal(error_prevstate))
    {
        cpl_msg_warning(__func__,"Probably some data could not be saved. "
                                 "Check permisions or disk space");
        cpl_errorstate_set(CPL_ERROR_NONE);
        return 1;
    }
    return 0;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Compute some QC parameters from the combined image
  @param    combined    the combined image produced
  @param    chip        the chip number (start from 0)
  @return   a newly allocated table with the stars stats or NULL in error case
 */
/*----------------------------------------------------------------------------*/
static void hawki_step_basic_calib_qc(void)
{
    /* Compute QC parameters from the combined image */
    //cpl_msg_info(__func__, "Compute QC parameters from the reduced images") ;
}
        
/*----------------------------------------------------------------------------*/
/**
  @brief    Save the basic calibrated products on disk
  @param    reduced     the basic calibrated 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_basic_calib_save
(cpl_imagelist     *  reduced,
 const char        *  filename_postfix,
 const char        *  procat,
 const char        *  protype,
 int                  iserie,
 cpl_frameset      *  used_frameset,
 cpl_parameterlist *  recipe_parlist,
 cpl_frameset      *  recipe_framelist)
{
    const cpl_frame     *   raw_reference;
    cpl_propertylist    *   proplist;
    cpl_propertylist    **  extproplists;
    char                    filename[256];
    cpl_propertylist    *   inputlist ;
    int                     ext_nb ;
    const char          *   recipe_name = "hawki_step_basic_calib";
    int i;

    /* 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();
    proplist  = cpl_propertylist_new();
    inputlist = cpl_propertylist_load_regexp(
                    cpl_frame_get_filename(raw_reference), 0,
                                           HAWKI_HEADER_EXT_FORWARD, 0);
    cpl_propertylist_append(proplist, inputlist);
    cpl_propertylist_delete(inputlist);
    extproplists = cpl_malloc(HAWKI_NB_DETECTORS * sizeof(cpl_propertylist*));
    for (i=0 ; i<HAWKI_NB_DETECTORS ; i++)
    {
        /* Get the extension number */
        ext_nb=hawki_get_ext_from_detector
            (cpl_frame_get_filename(raw_reference), i+1);

        /* Allocate this pro perty list */
        extproplists[i] = 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[i], 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[i], inputlist);
        cpl_propertylist_delete(inputlist);
    }
    
    /* Write the image */
    snprintf(filename, 256, "hawki_step_basic_calib_%s%04d.fits",
             filename_postfix, iserie+1);
    hawki_imagelist_save(recipe_framelist,
                         recipe_parlist,
                         used_frameset,
                         reduced,
                         recipe_name,
                         procat, 
                         protype, 
                         (const cpl_propertylist*)proplist,
                         (const cpl_propertylist**)extproplists,
                         filename);

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