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

/*
 * image_normalisation.cpp
 *
 *  Created on: 2014 3 28
 *      Author: cgarcia
 */

#include <cpl.h>
#include <vector>
#include <iostream>
#include <iterator>
#include <numeric>
#include <exception>
#include <image_normalisation.h>

class no_flux_exception :  public std::exception
{
	  virtual const char* what() const throw()
	  {
	    const char* ret =
	    		"The sum of all the flux contributions for the extracted "
	    		"is zero, making normalisation not possible. Disabling slit "
	    		"identification might solve the issue.";
	    return ret;
	  }
};

template<typename  T>
mosca::image mosca::image_normalise
(mosca::image& image,
 int spa_smooth_radius, int disp_smooth_radius, 
 int spa_fit_polyorder, int disp_fit_nknots, double fit_threshold,
 std::vector<T>& slit_spa_norm_profile, std::vector<T>& slit_disp_norm_profile)
{
    
    //Collapsing to get the profiles in each direction
    std::vector<T> slit_spa_profile = image.collapse<T>(mosca::DISPERSION_AXIS);
    std::vector<T> slit_disp_profile = image.collapse<T>(mosca::SPATIAL_AXIS);

    T * p_ima = image.get_data<T>();
    T total_flux = 
         std::accumulate(p_ima, p_ima + image.size_x() * image.size_y(), T(0));
    
    if(total_flux == T(0)){
        slit_spa_norm_profile = slit_spa_profile;
        slit_disp_norm_profile = slit_disp_profile;
    	return image;
    }
    //If we are doing any fitting/smoothing in that direction, 
    //initialise it to the current profile, if not initialise it to a constant
    if (spa_smooth_radius > 0 || spa_fit_polyorder > 0)
        slit_spa_norm_profile = slit_spa_profile;
    else 
        slit_spa_norm_profile = std::vector<T>(slit_spa_profile.size(), 
                T(total_flux / slit_spa_profile.size()));
    
    if (disp_smooth_radius > 0 || disp_fit_nknots > 0)
        slit_disp_norm_profile = slit_disp_profile;
    else 
        slit_disp_norm_profile = std::vector<T>(slit_disp_profile.size(), 
                T(total_flux / slit_disp_profile.size()));

    if (spa_smooth_radius > 0)
        mosca::vector_smooth<T>(slit_spa_norm_profile, spa_smooth_radius);

    if (spa_fit_polyorder > 0)
    {
        size_t used_spa_fit_polyorder = spa_fit_polyorder;  
        mosca::vector_polynomial polfit;
        polfit.fit<T>(slit_spa_norm_profile, used_spa_fit_polyorder, 
                fit_threshold);
    }
    
    if (disp_smooth_radius > 0)
        mosca::vector_smooth<T>(slit_disp_norm_profile, disp_smooth_radius);

    if (disp_fit_nknots > 0)
    {
        size_t used_disp_fit_nknots = disp_fit_nknots;  
        mosca::vector_cubicspline splfit;
        splfit.fit<T>(slit_disp_norm_profile, 
                      used_disp_fit_nknots, fit_threshold);
    }
    
    cpl_size nx = image.size_x();
    cpl_size ny = image.size_y();
    mosca::image result(nx, ny, mosca::type_trait<T>::cpl_eq_type,
                        image.dispersion_axis());
    T * p_res = result.get_data<T>();
    for (cpl_size j = 0; j< ny; ++j)
    {
        for (cpl_size i = 0; i< nx; ++i, ++p_res)
        {
            if(image.dispersion_axis() == mosca::X_AXIS)
                *p_res = slit_spa_norm_profile[j] * slit_disp_norm_profile[i] / 
                total_flux;
            else
                *p_res = slit_spa_norm_profile[i] * slit_disp_norm_profile[j] / 
                total_flux;
        }
    }

    return result;
}
