#ifndef PROFILE_PROVIDERS_H_
#define PROFILE_PROVIDERS_H_

#include <vector>

namespace mosca
{

class profile_smoother{
private:
    int m_smooth_radius;

public:

    profile_smoother(const int smooth_radius);
    bool is_enabled() const;
    template<typename T>
    void smooth(std::vector<T>& profile, std::vector<T>& weight_profile) const;
};

class profile_spatial_fitter{
private:
    int m_fit_polyorder;
    double m_fit_threshold;

public:

    profile_spatial_fitter(const int fit_polyorder, const double fit_threshold);
    bool is_enabled() const;
    template<typename T>
    void fit(std::vector<T>& profile, std::vector<T>& weight_profile) const;
};


class profile_dispersion_fitter{
private:
    int m_disp_fit_nknots;
    double m_fit_threshold;

public:

    profile_dispersion_fitter(const int disp_fit_nknots, const double fit_threshold);
    bool is_enabled() const ;
    template<typename T>
    void fit(std::vector<T>& profile, std::vector<T>& weight_profile) const;
};



template<typename T>
class profile_provider_base{

private:
    std::vector<T> m_slit_norm_profile;
    T m_total_weight{0};
    T m_total_flux{0};
    const mosca::axis m_profile_axis;
    const mosca::axis m_collapse_axis;
public:

    template<typename profile_smootherType, typename FitterType>
    profile_provider_base(const mosca::image& slit_image,
            const mosca::image& slit_image_weight,
            profile_smootherType profile_smoother,
            FitterType fitter,
            const mosca::axis profile_axis,
            const mosca::axis collapse_axis);

    T get(size_t x, size_t y) const noexcept;
    T get_total_flux() const noexcept;
    T get_total_weight() const noexcept;
    const std::vector<T>& get_average_linear_profile()  const noexcept;
    
    virtual ~profile_provider_base();
};


template<typename T>
class dispersion_profile_provider : public profile_provider_base<T>{

public:

    dispersion_profile_provider(const mosca::image& slit_image,
                            const mosca::image& slit_image_weight,
                            const int disp_smooth_radius,
                            const int disp_fit_nknots,
                            const double fit_threshold);
};

template<typename T>
class spatial_profile_provider : public profile_provider_base<T>{

public:
    spatial_profile_provider(const mosca::image& slit_image,
                            const mosca::image& slit_image_weight,
                            const int spa_smooth_radius,
                            const int spa_fit_nknots,
                            const double fit_threshold);
};

template<typename T>
class local_spatial_profile_provider {

private:

    std::vector<spatial_profile_provider<T>> m_providers;
    std::vector<T> m_average_profile;
    mosca::axis m_dispersion_profile_axis;
    T m_avg_flux_image{0};
public:

    local_spatial_profile_provider(
                            const mosca::image& slit_image,
                            const mosca::image& slit_image_weight,
                            const int spa_smooth_radius,
                            const int spa_fit_polyorder,
                            const double fit_threshold);

    T get(size_t x, size_t y) const  noexcept;
    const std::vector<T>& get_average_linear_profile()  const noexcept;

};

}

#include "profile_providers.tcc" 

#endif
