//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Device/Resolution/Convolve.h
//! @brief     Defines class Math::Convolve.
//!
//! @homepage  http://www.bornagainproject.org
//! @license   GNU General Public License v3 or higher (see COPYING)
//! @copyright Forschungszentrum Jülich GmbH 2018
//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
//
//  ************************************************************************************************

#ifdef SWIG
#error no need to expose this header to Swig
#endif // SWIG
#ifndef BORNAGAIN_DEVICE_RESOLUTION_CONVOLVE_H
#define BORNAGAIN_DEVICE_RESOLUTION_CONVOLVE_H

#include "Base/Type/Field2D.h"
#include <fftw3.h>

//! Convolution of two real vectors (in 1D or 2D) using Fast Fourier Transform.
//!
//! Usage:
//! std::vector<double> signal, kernel, result;
//! Convolve cv;
//! cv.fftconvolve(signal, kernel, result)
//!
//! Inspired by code from Jeremy Fix.
//! Original provenance http://jeremy.fix.free.fr/spip.php?article15 no longer online.
//! Code repository now at https://github.com/jeremyfix/FFTConvolution.
//! Paper "Efficient convolution using the Fast Fourier Transform, Application in C++"
//! by Jeremy Fix, May 30, 2011, preserved in ba-inter/lit/JFix_FFTConvolution.pdf

class Convolve {
public:
    Convolve();

    //! convolution  modes
    //! use LINEAR_SAME or CIRCULAR_SAME_SHIFTED for maximum performance
    enum ConvolutionMode {
        FFTW_LINEAR_FULL,
        FFTW_LINEAR_SAME_UNPADDED,
        FFTW_LINEAR_SAME,
        FFTW_LINEAR_VALID,
        FFTW_CIRCULAR_SAME,
        FFTW_CIRCULAR_SAME_SHIFTED,
        FFTW_UNDEFINED
    };

    //! convolution in 1D
    void fftconvolve1D(const std::vector<double>& source, const std::vector<double>& kernel,
                       std::vector<double>& result);

    //! convolution in 2D
    void fftconvolve2D(const double2d_t& source, const double2d_t& kernel, double2d_t& result);

private:
    //! prepare arrays for 2D convolution of given vectors
    void init(int h_src, int w_src, int h_kernel, int w_kernel);
    //! compute circual convolution of source and kernel using fast Fourier transformation
    void fftw_circular_convolution(const double2d_t& src, const double2d_t& kernel);

    //! Workspace for Fourier convolution.

    //! Workspace contains input (source and kernel), intermediate and output
    //! arrays to run convolution via fft; 'source' it is our signal, 'kernel'
    //! it is our resolution function.
    //! Sizes of input arrays are adjusted; output arrays are alocated via
    //! fftw3 allocation for maximum performance.
    class Workspace {
    public:
        Workspace() = default;
        ~Workspace();
        void clear();
        friend class Convolve;

    private:
        int h_src{0}, w_src{0};       // size of original 'source' array in 2 dimensions
        int h_kernel{0}, w_kernel{0}; // size of original 'kernel' array in 2 dimensions
        // size of adjusted source and kernel arrays (in_src, out_src, in_kernel, out_kernel)
        int w_fftw{0}, h_fftw{0};
        //! adjusted input 'source' array
        double* in_src{nullptr};
        //! result of Fourier transformation of source
        double* out_src{nullptr};
        //! adjusted input 'kernel' array
        double* in_kernel{nullptr};
        //! result of Fourier transformation of kernel
        double* out_kernel{nullptr};
        //! result of product of FFT(source) and FFT(kernel)
        double* dst_fft{nullptr};
        int h_dst{0}, w_dst{0};       // size of resulting array
        int h_offset{0}, w_offset{0}; // offsets to copy result into output arrays
        fftw_plan p_forw_src{nullptr};
        fftw_plan p_forw_kernel{nullptr};
        fftw_plan p_back{nullptr};
    };

    //! input and output data for fftw3
    Workspace ws;
    //! convolution mode
    ConvolutionMode m_mode;
};

#endif // BORNAGAIN_DEVICE_RESOLUTION_CONVOLVE_H
