/* $Id: fors_remove_bias.c,v 1.7 2013-04-24 14:14:14 cgarcia Exp $
 *
 * This file is part of the FORS Data Reduction Pipeline
 * Copyright (C) 2002-2010 European Southern Observatory
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

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

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

#include <stdexcept>
#include <memory>
#include <string>
#include <math.h>
#include <cpl.h>
#include <moses.h>
#include "fors_dfs.h"
#include "fors_utils.h"
#include "fors_detmodel.h"
#include "fors_ccd_config.h"
#include "fors_overscan.h"
#include "fors_subtract_bias.h"
#include "fors_ccd_config.h"
#include "fors_bpm.h"

static int fors_remove_bias_create(cpl_plugin *);
static int fors_remove_bias_exec(cpl_plugin *);
static int fors_remove_bias_destroy(cpl_plugin *);
static int fors_remove_bias(cpl_parameterlist *, cpl_frameset *);

static void fors_remove_bias_display_ccd_config(fors::fiera_config &config);

static char fors_remove_bias_description[] =
"This recipe is used to subtract the overscan and master bias"
"(produced by the recipe fors_bias) from one raw data frame. "
"The overscan regions, if present, are\n"
"used to compensate for variations of the overall bias level between master bias\n"
"and input raw frame. The overscan regions are then trimmed from the result.\n"
"The recipe should allow any fors frame.\n\n"
"Input files:\n\n"
"  DO category:               Type:       Explanation:         Required:\n"
"  ANY_CAT\n                  Raw         Raw data frame          Y\n"
"  MASTER_BIAS                Calib       Master bias frame       Y\n\n"
"Output files:\n\n"
"  DO category:               Data type:  Explanation:\n"
"  ANY_CAT_UNBIAS             FITS image  Bias subtracted frame\n\n";

#define fors_remove_bias_exit(message)        \
{                                             \
if ((const char *)message != NULL) cpl_msg_error(recipe, message);  \
fors_image_delete(&master_bias);                \
cpl_msg_indent_less();                        \
return -1;                                    \
}

#define fors_remove_bias_exit_memcheck(message) \
{                                               \
if ((const char *)message != NULL) cpl_msg_info(recipe, message);     \
printf("free raw_image (%p)\n", raw_image);     \
cpl_image_delete(raw_image);                    \
printf("free master_bias (%p)\n", master_bias); \
cpl_image_delete(master_bias);                  \
printf("free header (%p)\n", header);           \
cpl_propertylist_delete(header);                \
cpl_msg_indent_less();                          \
return 0;                                       \
}


/**
 * @brief    Build the list of available plugins, for this module. 
 *
 * @param    list    The plugin list
 *
 * @return   0 if everything is ok, -1 otherwise
 *
 * Create the recipe instance and make it available to the application 
 * using the interface. This function is exported.
 */

int cpl_plugin_get_info(cpl_pluginlist *plugin_list)
{
    cpl_recipe *recipe = static_cast<cpl_recipe *>(cpl_calloc(1, sizeof *recipe ));
    cpl_plugin *plugin = &recipe->interface;

    cpl_plugin_init(plugin,
                    CPL_PLUGIN_API,
                    FORS_BINARY_VERSION,
                    CPL_PLUGIN_TYPE_RECIPE,
                    "fors_remove_bias",
                    "Subtract bias from input frame",
                    fors_remove_bias_description,
                    "Carlo Izzo",
                    PACKAGE_BUGREPORT,
                    fors_get_license(),
                    fors_remove_bias_create,
                    fors_remove_bias_exec,
                    fors_remove_bias_destroy);

    cpl_pluginlist_append(plugin_list, plugin);
    
    return 0;
}


/**
 * @brief    Setup the recipe options    
 *
 * @param    plugin  The plugin
 *
 * @return   0 if everything is ok
 *
 * Defining the command-line/configuration parameters for the recipe.
 */

static int fors_remove_bias_create(cpl_plugin *plugin)
{
    cpl_recipe    *recipe;
    //cpl_parameter *p;

    /* 
     * Check that the plugin is part of a valid recipe 
     */

    if (cpl_plugin_get_type(plugin) == CPL_PLUGIN_TYPE_RECIPE) 
        recipe = (cpl_recipe *)plugin;
    else 
        return -1;

    /* 
     * Create the parameters list in the cpl_recipe object 
     */

    recipe->parameters = cpl_parameterlist_new(); 

    return 0;
}


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

static int fors_remove_bias_exec(cpl_plugin *plugin)
{
    cpl_recipe  *   recipe ;
    int             status = 1;

    /* 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 */
    fors_print_banner();

    try
    {
        status = fors_remove_bias(recipe->parameters, recipe->frames); 
    }
    catch(std::exception& ex)
    {
        cpl_msg_error(cpl_func, "Recipe error: %s", ex.what());
    }
    catch(...)
    {
        cpl_msg_error(cpl_func, "An uncaught error during recipe execution");
    }

    return status;
}


/**
 * @brief    Destroy what has been created by the 'create' function
 *
 * @param    plugin  The plugin
 *
 * @return   0 if everything is ok
 */

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

    cpl_parameterlist_delete(recipe->parameters); 

    return 0;
}


/**
 * @brief    Interpret the command line options and execute the data processing
 *
 * @param    parlist     The parameters list
 * @param    frameset    The set-of-frames
 *
 * @return   0 if everything is ok
 */

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

    const char *recipe = "fors_remove_bias";


    /*
     * CPL objects
     */

    fors_image       *master_bias   = NULL;
    cpl_propertylist *master_bias_header   = NULL;

    /*
     * Auxiliary variables
     */

    const char *master_bias_tag = "MASTER_BIAS";
    int         nbias, nframe;


    cpl_msg_set_indentation(2);


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

    //Check if bias is input
    nbias = cpl_frameset_count_tags(frameset, master_bias_tag);
    if (nbias != 1) {
        cpl_msg_error(recipe, "One master bias is needed in input: %s", 
                      master_bias_tag);
        fors_remove_bias_exit(NULL);
    }

    nframe  = cpl_frameset_get_size(frameset) - nbias;

    if (nframe == 0) {
        fors_remove_bias_exit("Missing required input raw frame");
    }

    //Get master bias
    std::auto_ptr<fors::fiera_config> bias_ccd_config;  
    if(nbias == 1)
    {
        cpl_msg_info(recipe, "Load master bias");

        master_bias_header = dfs_load_header(frameset, master_bias_tag, 0);
        cpl_frame * bias_frame = cpl_frameset_find(frameset, master_bias_tag);

        bias_ccd_config.reset(new fors::fiera_config(master_bias_header));
        master_bias = fors_image_load(bias_frame);
        
        if (master_bias == NULL)
            fors_remove_bias_exit("Cannot load master bias");
    }
    
    //Loop on the frames
    cpl_msg_info(recipe, "Looping on input files (total = %d)", nframe);
    for(int iframe = 0 ; iframe < nframe + nbias ; iframe++)
    {
        cpl_msg_indent_more();
        cpl_msg_info(recipe, "Working on file %d out of %d",iframe + 1, nframe);
        cpl_propertylist * target_header = NULL;
        cpl_frame * target_frame = cpl_frameset_get_position(frameset, iframe);
        
        std::string target_raw_tag = cpl_frame_get_tag(target_frame);
        
        //Skip the master bias and set type of frame
        if(target_raw_tag == "MASTER_BIAS")
            continue;
        cpl_frame_set_group(target_frame, CPL_FRAME_GROUP_RAW);
        
        //Read the target header
        cpl_msg_info(recipe, "Reading detector configuration");
        target_header = dfs_load_header(frameset, target_raw_tag.c_str(), 0);
        if (target_header == NULL) {
            cpl_msg_error(recipe, "Cannot load header of %s frame", 
                          target_raw_tag.c_str());
            fors_remove_bias_exit(NULL);
        }
        fors::fiera_config target_ccd_config(target_header);
        cpl_propertylist_delete(target_header);
        fors_remove_bias_display_ccd_config(target_ccd_config);

        /* Update RON estimation from bias */
        fors::update_ccd_ron(target_ccd_config, master_bias_header);
        
        //Check detector configs are the same as master bias
        if(nbias == 1)
            if(target_ccd_config != (*bias_ccd_config))
                std::invalid_argument("CCD configurations do not match");
        
        //Read the target image
        cpl_msg_info(recipe, "Reading file of type %s", target_raw_tag.c_str());
        fors_image * raw_image = fors_image_load(target_frame);
        if (raw_image == NULL) {
            cpl_msg_error(recipe, "Cannot load %s frame", target_raw_tag.c_str());
            fors_remove_bias_exit(NULL);
        }
        
        /* Check that the overscan configuration is consistent */
        bool perform_preoverscan = !fors_is_preoverscan_empty(target_ccd_config);
        if(perform_preoverscan != 
           fors_is_master_bias_preoverscan_corrected(master_bias_header))
        {
            cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                    "Master bias overscan configuration doesn't match science");
            return NULL;
        }

        /* Create variance */
        std::vector<double> overscan_levels; 
        if(perform_preoverscan)
            overscan_levels = 
               fors_get_bias_levels_from_overscan(raw_image, target_ccd_config);
        else
            overscan_levels = fors_get_bias_levels_from_mbias(master_bias,
                                                             target_ccd_config);
        cpl_msg_info(recipe, "Creating variance array");
        fors_image_variance_from_detmodel(raw_image, target_ccd_config,
                                          overscan_levels);

        /* Subtract overscan */
        cpl_msg_info(recipe, "Subctracting overscan");
        fors_image * image_os;
        if(perform_preoverscan)
            image_os = fors_subtract_prescan(raw_image, target_ccd_config);
        else 
        {
            image_os = fors_image_duplicate(raw_image); 
            //The rest of the recipe assumes that the images carry a bpm.
            fors_bpm_image_make_explicit(image_os); 
        }
        
        //Trimming overscan
        if(perform_preoverscan)
        {
            cpl_msg_info(recipe, "Trimming overscan");
            fors_trimm_preoverscan(image_os, target_ccd_config);
        }
        
        //Subtracting bias
        cpl_msg_info(recipe, "Subtracting master bias");
        fors_subtract_bias(image_os, master_bias);
       
        //Saving file
        cpl_msg_info(recipe, "Saving product");
        std::string pro_image_tag =  target_raw_tag + "_UNBIAS";
        fors_dfs_save_image_err(frameset, image_os, pro_image_tag.c_str(),
                                NULL, NULL, parlist, recipe, target_frame);
        if(cpl_error_get_code())
            fors_remove_bias_exit("Saving final product failed");

        cpl_msg_indent_less();
        fors_image_delete(&raw_image);
        fors_image_delete(&image_os);
    }
    

    //Bookkeeping
    fors_image_delete(&master_bias);
    cpl_propertylist_delete(master_bias_header);
    
    return 0;
}

static void fors_remove_bias_display_ccd_config(fors::fiera_config &config)
{
    for(size_t iport = 0; iport< config.nports(); ++iport)
    {
        cpl_msg_info(cpl_func, "CCD Port %zd",iport + 1);
        cpl_msg_indent_more();
        mosca::rect_region pres = config.prescan_region(iport).coord_0to1();
        if(pres.is_empty())
            cpl_msg_info(cpl_func, "Prescan region empty");
        else
            cpl_msg_info(cpl_func, "Prescan region: [%d,%d] - [%d,%d]",
                    pres.llx(), pres.lly(), pres.urx(), pres.ury());
        mosca::rect_region overs = config.overscan_region(iport).coord_0to1();
        if(overs.is_empty())
            cpl_msg_info(cpl_func, "Overscan region empty");
        else
            cpl_msg_info(cpl_func, "Overscan region: [%d,%d] - [%d,%d]",
                    overs.llx(), overs.lly(), overs.urx(), overs.ury());
        mosca::rect_region expo = config.validpix_region(iport).coord_0to1();
        if(expo.is_empty())
            cpl_msg_info(cpl_func, "Exposed region region empty");
        else
            cpl_msg_info(cpl_func, "Exposed region: [%d,%d] - [%d,%d]",
                    expo.llx(), expo.lly(), expo.urx(), expo.ury());
        cpl_msg_indent_less();
    }
}
