/*
 * 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 <algorithm>
#include "image_normalisation.h"
#include "vector_utils.h"
#include "profile_providers.h"

/*--------------- Private functions declaration ---------------*/
template<typename  T,
         typename SpatialProfileProviderType,
         typename DispersionProfileProviderType>
static mosca::image image_normalise_internal(const mosca::image& slit_image,
    const mosca::image& slit_image_weight,
    const SpatialProfileProviderType& spa_profile_provider,
    const DispersionProfileProviderType& disp_profile_provider,
    std::vector<T>& slit_spa_norm_profile, std::vector<T>& slit_disp_norm_profile);

const char* mosca::no_flux_exception::what() const throw()
{
    const char* ret =
            "The sum of all the flux contributions for the provided slit "
            "is zero, making normalisation not possible";
    return ret;
}


template<typename  T>
mosca::image mosca::image_normalise(const mosca::image& slit_image,
const mosca::image& slit_image_weight,
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)
{
    spatial_profile_provider<T> spa_profile_provider{slit_image, slit_image_weight, spa_smooth_radius, spa_fit_polyorder, fit_threshold};
    dispersion_profile_provider<T> disp_profile_provider{slit_image, slit_image_weight, disp_smooth_radius, disp_fit_nknots, fit_threshold};

    return image_normalise_internal<T>(slit_image, slit_image_weight,
            spa_profile_provider, disp_profile_provider,
            slit_spa_norm_profile, slit_disp_norm_profile);
}


template<typename  T>
mosca::image mosca::image_normalise_spa_local(const mosca::image& slit_image,
 const mosca::image& slit_image_weight,
 int spa_smooth_radius, int disp_smooth_radius,
 int spa_fit_polyorder, int disp_fit_nknots,
 double fit_threshold,
 bool normalise_spa_local,
 std::vector<T>& slit_spa_norm_profile,
 std::vector<T>& slit_disp_norm_profile)
{
    if(!normalise_spa_local)
        return image_normalise(slit_image, slit_image_weight, spa_smooth_radius, disp_smooth_radius, spa_fit_polyorder,
                disp_fit_nknots, fit_threshold, slit_spa_norm_profile, slit_disp_norm_profile);

    local_spatial_profile_provider<T> spa_profile_provider{slit_image, slit_image_weight, spa_smooth_radius, spa_fit_polyorder, fit_threshold};
    dispersion_profile_provider<T> disp_profile_provider{slit_image, slit_image_weight, disp_smooth_radius, disp_fit_nknots, fit_threshold};

    return image_normalise_internal<T>(slit_image, slit_image_weight,
            spa_profile_provider, disp_profile_provider,
            slit_spa_norm_profile, slit_disp_norm_profile);
}

/*--------------- Private functions implementation ---------------*/

template<typename  T,
         typename SpatialProfileProviderType,
         typename DispersionProfileProviderType>
static mosca::image image_normalise_internal(const mosca::image& slit_image,
    const  mosca::image& slit_image_weight,
    const SpatialProfileProviderType& spa_profile_provider,
    const DispersionProfileProviderType& disp_profile_provider,
    std::vector<T>& slit_spa_norm_profile, std::vector<T>& slit_disp_norm_profile){

    if(slit_image.size_x() != slit_image_weight.size_x() ||
       slit_image.size_y() != slit_image_weight.size_y())
        throw std::invalid_argument("image and weight sizes do not match");

    if(slit_image.dispersion_axis() != slit_image_weight.dispersion_axis() ||
       slit_image.spatial_axis() != slit_image_weight.spatial_axis())
        throw std::invalid_argument("image and weight orientation do not match");

    slit_spa_norm_profile = spa_profile_provider.get_average_linear_profile();
    slit_disp_norm_profile = disp_profile_provider.get_average_linear_profile();

    cpl_size nx = slit_image.size_x();
    cpl_size ny = slit_image.size_y();
    mosca::image result(nx, ny, mosca::type_trait<T>::cpl_eq_type,
                        slit_image.dispersion_axis());
    T * p_res = result.get_data<T>();
    const T * p_weight = slit_image_weight.get_data<T>();
    for (cpl_size y = 0; y< ny; ++y)
    {
        for (cpl_size x = 0; x< nx; ++x, ++p_res, ++p_weight)
        {
            if(*p_weight != 0){
                *p_res = disp_profile_provider.get(x, y) * spa_profile_provider.get(x, y);
            }
            else{
                *p_res = 1;
            }
        }
    }

    return result;
}
