/* $Id: moses.c,v 1.116 2013/10/15 09:27:38 cgarcia Exp $
 *
 * This file is part of the MOSES library
 * 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/10/15 09:27:38 $
 * $Revision: 1.116 $
 * $Name:  $
 */

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

#include <stdexcept>
#include <string>
#include <cpl.h>
#include <hdrl.h>
#include "fors_overscan.h"
#include "fors_image.h"
#include "ccd_config.h"

/**
 * @brief
 *   Subtract the overscan from a CCD exposure
 *
 * @param image       Image containing the data to correct
 * @param header      Header of the image
 *
 * @return A newly allocated overscan subtracted image
 *
 */
fors_image * fors_subtract_prescan(const fors_image * image, 
                                   const mosca::ccd_config& ccd_config)
{
    int box_hsize = HDRL_OVERSCAN_FULL_BOX;

    //Check inputs 
    if (image == NULL) 
    {
        cpl_error_set(cpl_func, CPL_ERROR_NULL_INPUT);
        return NULL;
    }

    //Get number of ports
    size_t nports = ccd_config.nports();
    
    //Create a copy where to store the results
    cpl_image * image_err = cpl_image_power_create(image->variance, 0.5);
    cpl_mask * old_bpm = cpl_image_set_bpm(image_err, 
                            cpl_mask_duplicate(cpl_image_get_bpm(image->data)));
    cpl_mask_delete(old_bpm);
    hdrl_image * target_image = hdrl_image_create(image->data, image_err);
    cpl_image_delete(image_err);

    //Loop on the ports
    for(size_t iport = 0; iport < nports; iport++)
    {
        hdrl_parameter * collapse_method =  
                hdrl_collapse_median_parameter_create();
        mosca::rect_region ps_reg = ccd_config.prescan_region(iport).coord_0to1();
        hdrl_parameter * prescan_region = ps_reg.hdrl_param(); //deleted by ps_reg

        hdrl_direction correction_direction;
        if(ccd_config.prescan_region(iport).length_x() > 
           ccd_config.prescan_region(iport).length_y())
            correction_direction = HDRL_Y_AXIS;
        else 
            correction_direction = HDRL_X_AXIS;
        
        double ron = ccd_config.computed_ron(iport);
        hdrl_parameter * prescan_params =
                hdrl_overscan_parameter_create(correction_direction,
                                               ron,
                                               box_hsize,
                                               collapse_method,
                                               prescan_region);
                
        hdrl_overscan_compute_result * os_computation =
                hdrl_overscan_compute(image->data, prescan_params);

        mosca::rect_region por_reg = ccd_config.validpix_region(iport).coord_0to1(); 
        hdrl_parameter * port_region = por_reg.hdrl_param(); //deleted by por_reg

        hdrl_overscan_correct_result * os_correction =
            hdrl_overscan_correct(target_image, port_region, os_computation);

        hdrl_image * os_corrected_ima = 
            hdrl_overscan_correct_result_get_corrected(os_correction);
        
        hdrl_image * port_image = hdrl_image_extract(os_corrected_ima,
                       ccd_config.validpix_region(iport).coord_0to1().llx(),
                       ccd_config.validpix_region(iport).coord_0to1().lly(),
                       ccd_config.validpix_region(iport).coord_0to1().urx(),
                       ccd_config.validpix_region(iport).coord_0to1().ury());

        hdrl_image_copy(target_image, port_image, 
                        ccd_config.validpix_region(iport).coord_0to1().llx(), 
                        ccd_config.validpix_region(iport).coord_0to1().lly());
        
        hdrl_overscan_compute_result_delete(os_computation);
        hdrl_overscan_correct_result_delete(os_correction);
        hdrl_image_delete(port_image);
        hdrl_parameter_delete(prescan_params);
        
    }
    
    fors_image * os_subtracted = (fors_image*)cpl_malloc(sizeof(fors_image));
    
    //The rest of the FORS pipeline works with float images
    os_subtracted->data = cpl_image_cast
            (hdrl_image_get_image_const(target_image), CPL_TYPE_FLOAT);
    
    //First the variance has to be computed and then casted to float
    cpl_image_power(hdrl_image_get_error(target_image), 2);
    cpl_image * variance = cpl_image_cast
            (hdrl_image_get_error_const(target_image), CPL_TYPE_FLOAT);
    os_subtracted->variance= variance;
    
    hdrl_image_delete(target_image);
    return os_subtracted;
}

fors_image_list * fors_subtract_prescan(const fors_image_list * ima_list, 
                                        const mosca::ccd_config& ccd_config)
{
    int nima = fors_image_list_size(ima_list);
    fors_image_list * ima_list_os_sub = fors_image_list_new();

    const fors_image * target_ima = fors_image_list_first_const(ima_list);
    for(int ima = 0; ima < nima; ++ima)
    {
        fors_image * ima_os_sub =
                fors_subtract_prescan(target_ima, ccd_config);
        fors_image_list_insert(ima_list_os_sub, ima_os_sub);
        target_ima = fors_image_list_next_const(ima_list);
    }
    return ima_list_os_sub;
}

void fors_trimm_preoverscan(fors_image * image, 
                            const mosca::ccd_config& ccd_config)
{
    mosca::rect_region crop_region = ccd_config.whole_valid_region();
    mosca::rect_region crop_region_1 = crop_region.coord_0to1(); 
    if(crop_region_1.is_empty())
        throw std::invalid_argument("Region to crop is empty");
    fors_image_crop(image, 
                    crop_region_1.llx(), crop_region_1.lly(),
                    crop_region_1.urx(), crop_region_1.ury());
    
}

void fors_trimm_preoverscan(fors_image_list * ima_list, 
                            const mosca::ccd_config& ccd_config)
{
    int nima = fors_image_list_size(ima_list);

    fors_image * target_ima = fors_image_list_first(ima_list);
    for(int ima = 0; ima < nima; ++ima)
    {
        fors_trimm_preoverscan(target_ima, ccd_config);
        target_ima = fors_image_list_next(ima_list);
    }    
}

void fors_trimm_preoverscan(cpl_mask *& mask, 
                            const mosca::ccd_config& ccd_config)
{
    mosca::rect_region crop_region = ccd_config.whole_valid_region();
    mosca::rect_region crop_region_1 = crop_region.coord_0to1(); 
    if(crop_region_1.is_empty())
        throw std::invalid_argument("Region to crop is empty");
    cpl_mask *new_mask = cpl_mask_extract(mask,
            crop_region_1.llx(), crop_region_1.lly(),
            crop_region_1.urx(), crop_region_1.ury());
    
    cpl_mask_delete(mask);
    mask = new_mask;
}

void fors_trimm_fill_info(cpl_propertylist * header,
                          const mosca::ccd_config& ccd_config)
{
    mosca::rect_region crop_region = ccd_config.whole_valid_region();
    mosca::rect_region crop_region_1 = crop_region.coord_0to1(); 

    cpl_propertylist_append_int(header, "ESO QC TRIMM LLX",
                                crop_region_1.llx());
    cpl_propertylist_append_int(header, "ESO QC TRIMM LLY",
                                crop_region_1.lly());
    cpl_propertylist_append_int(header, "ESO QC TRIMM URX",
                                crop_region_1.urx());
    cpl_propertylist_append_int(header, "ESO QC TRIMM URY",
                                crop_region_1.ury());
}

void fors_trimm_fix_wcs(cpl_propertylist * header)
{
    mosca::fiera_config ccd_config(header);
    mosca::rect_region crop_region = ccd_config.whole_valid_region();
    mosca::rect_region crop_region_1 = crop_region.coord_0to1(); 
    if(crop_region_1.is_empty())
        throw std::invalid_argument("Cannot fix WCS from overscan trimming");
    cpl_propertylist_update_double(header, "CRPIX1",
        cpl_propertylist_get_double(header, "CRPIX1") - crop_region_1.llx() + 1);
    cpl_propertylist_update_double(header, "CRPIX2",
        cpl_propertylist_get_double(header, "CRPIX2") - crop_region_1.lly() + 1);
}

bool fors_is_preoverscan_empty(const mosca::ccd_config& ccd_config)
{
    for(size_t iport = 0; iport < ccd_config.nports(); iport++)
    {
        if(!ccd_config.prescan_region(iport).is_empty() ||
           !ccd_config.overscan_region(iport).is_empty())
            return false;
    }
    
    return true;
}

bool fors_is_master_bias_preoverscan_corrected(cpl_propertylist * mbias_header)
{
    if(cpl_propertylist_has(mbias_header, "ESO QC BIAS OVSC_SUB"))
    {
        if(cpl_propertylist_get_bool(mbias_header, "ESO QC BIAS OVSC_SUB"))
            return true;
        else 
            return false;
    }
    
    //The absence of the keyword implies that the master bias was created
    //with a version of the pipeline that didn't support images without
    //pre/overscan (PIPE-5643).
    return true; 
}

std::vector<double> fors_get_bias_levels_from_overscan
(fors_image *image, const mosca::ccd_config& ccd_config)
{
    std::vector<double> overscan_level; 
    //Loop on each port:
    for(size_t iport = 0; iport < ccd_config.nports(); iport++)
    {
        mosca::rect_region ps_reg = 
                ccd_config.prescan_region(iport).coord_0to1();
        if(ps_reg.is_empty())
            throw std::invalid_argument("Prescan area is empty. Cannot compute "
                    "detector noise model");

        double os_level = cpl_image_get_median_window
          (image->data, ps_reg.llx(), ps_reg.lly(), ps_reg.urx(), ps_reg.ury());
        
        overscan_level.push_back(os_level);
    }
    return overscan_level;
}

std::vector<double> fors_get_bias_levels_from_mbias
(fors_image *mbias, const mosca::ccd_config& ccd_config)
{
    std::vector<double> overscan_level; 
    //Loop on each port:
    for(size_t iport = 0; iport < ccd_config.nports(); iport++)
    {
        mosca::rect_region valid_port_reg = 
                ccd_config.validpix_region(iport).coord_0to1();
        
        double os_level = cpl_image_get_median_window
          (mbias->data, valid_port_reg.llx(), valid_port_reg.lly(), 
                  valid_port_reg.urx(), valid_port_reg.ury());
        
        overscan_level.push_back(os_level);
    }
    return overscan_level;
}
