/*! \file pifft_p.h
 * \brief Class for FFT, IFFT and Hilbert transformations
 */
/*
    PIP - Platform Independent Primitives
    Private header for fftw3
    Ivan Pelipenko peri4ko@yandex.ru, Andrey Bychkov work.a.b@yandex.ru

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 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 Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef PIFFT_P_H
#define PIFFT_P_H

#include "picout.h"
#include "pimathcomplex.h"
#include "pivector.h"
#if defined(PIP_FFTW) || defined(PIP_FFTWf) || defined(PIP_FFTWl) || defined(PIP_FFTWq)
#  include "fftw3.h"
#else
#  define FFTW_FORWARD  0
#  define FFTW_BACKWARD 0
#  define FFTW_ESTIMATE 0
#  define FFTW_MEASURE  0
#endif


template<typename T>
class PIFFTW_Private {
public:
	explicit PIFFTW_Private() {
		plan = 0;
		// #ifndef PIP_FFTW
		// piCout << "[PIFFTW]" << "Warning: PIFFTW is disabled, to enable install libfftw3-dev library and build pip with -DFFTW=1";
		// #endif
		p_makeThreadSafe();
	}
	~PIFFTW_Private() { p_destroyPlan(plan); }

	const PIVector<complex<T>> & calcFFT(const PIVector<complex<T>> & in) {
		if (prepare != PlanParams(in.size(), fo_complex)) {
			p_out.resize(in.size());
			// piCout << "[PIFFTW]" << "creating plan";
			p_createPlan_c2c_1d(plan, in.size(), in.data(), p_out.data(), FFTW_FORWARD, FFTW_ESTIMATE | FFTW_UNALIGNED);
			prepare = PlanParams(in.size(), fo_complex);
		}
		p_executePlan_c2c(plan, in.data(), p_out.data());
		return p_out;
	}
	const PIVector<complex<T>> & calcFFT(const PIVector<T> & in) {
		if (prepare != PlanParams(in.size(), fo_real)) {
			p_out.resize(in.size());
			// piCout << "[PIFFTW]" << "creating plan";
			p_createPlan_r2c_1d(plan, in.size(), in.data(), p_out.data(), FFTW_ESTIMATE | FFTW_UNALIGNED);
			prepare = PlanParams(in.size(), fo_real);
		}
		p_executePlan_r2c(plan, in.data(), p_out.data());
		return p_out;
	}
	const PIVector<complex<T>> & calcFFTinverse(const PIVector<complex<T>> & in) {
		if (prepare != PlanParams(in.size(), fo_inverse)) {
			p_out.resize(in.size());
			// piCout << "[PIFFTW]" << "creating plan";
			p_createPlan_c2c_1d(plan, in.size(), in.data(), p_out.data(), FFTW_BACKWARD, FFTW_ESTIMATE | FFTW_UNALIGNED);
			prepare = PlanParams(in.size(), fo_inverse);
		}
		p_executePlan_c2c(plan, in.data(), p_out.data());
		return p_out;
	}

	enum FFT_Operation {
		fo_real,
		fo_complex,
		fo_inverse
	};

	void preparePlan(int size, int op) {
		p_inr.clear();
		p_in.clear();
		p_out.clear();
		switch ((FFT_Operation)op) {
		case fo_real:
			p_inr.resize(size);
			p_out.resize(size);
			p_createPlan_r2c_1d(plan, size, p_inr.data(), p_out.data(), FFTW_MEASURE | FFTW_UNALIGNED);
			break;
		case fo_complex:
			p_in.resize(size);
			p_out.resize(size);
			p_createPlan_c2c_1d(plan, size, p_in.data(), p_out.data(), FFTW_FORWARD, FFTW_MEASURE | FFTW_UNALIGNED);
			break;
		case fo_inverse:
			p_in.resize(size);
			p_out.resize(size);
			p_createPlan_c2c_1d(plan, size, p_in.data(), p_out.data(), FFTW_BACKWARD, FFTW_MEASURE | FFTW_UNALIGNED);
			break;
		default: size = 0; break;
		}
		prepare = PlanParams(size, (FFT_Operation)op);
	}

	inline void p_createPlan_c2c_1d(void *& plan, int size, const void * in, void * out, int dir, int flags) {}
	inline void p_createPlan_r2c_1d(void *& plan, int size, const void * in, void * out, int flags) {}
	inline void p_executePlan(void * plan) {}
	inline void p_executePlan_c2c(void * plan, const void * in, void * out) {}
	inline void p_executePlan_r2c(void * plan, const void * in, void * out) {}
	inline void p_destroyPlan(void *& plan) {}
	inline void p_makeThreadSafe() {}

	struct PlanParams {
		PlanParams() {
			size = 0;
			op   = fo_complex;
		}
		PlanParams(int size_, FFT_Operation op_) {
			size = size_;
			op   = op_;
		}
		bool isValid() { return size > 0; }
		bool operator==(const PlanParams & v) const { return (v.size == size) && (v.op == op); }
		bool operator!=(const PlanParams & v) const { return !(*this == v); }
		int size;
		FFT_Operation op;
	};

	PIVector<complex<T>> p_in;
	PIVector<T> p_inr;
	PIVector<complex<T>> p_out;
	void * plan;
	PlanParams prepare;
};


#ifdef PIP_FFTWf
template<>
inline void PIFFTW_Private<float>::p_createPlan_c2c_1d(void *& plan, int size, const void * in, void * out, int dir, int flags) {
	plan = fftwf_plan_dft_1d(size, (fftwf_complex *)in, (fftwf_complex *)out, dir, flags);
}
template<>
inline void PIFFTW_Private<float>::p_createPlan_r2c_1d(void *& plan, int size, const void * in, void * out, int flags) {
	plan = fftwf_plan_dft_r2c_1d(size, (float *)in, (fftwf_complex *)out, flags);
}
template<>
inline void PIFFTW_Private<float>::p_executePlan(void * plan) {
	fftwf_execute((fftwf_plan)plan);
}
template<>
inline void PIFFTW_Private<float>::p_executePlan_c2c(void * plan, const void * in, void * out) {
	fftwf_execute_dft((fftwf_plan)plan, (fftwf_complex *)in, (fftwf_complex *)out);
}
template<>
inline void PIFFTW_Private<float>::p_executePlan_r2c(void * plan, const void * in, void * out) {
	fftwf_execute_dft_r2c((fftwf_plan)plan, (float *)in, (fftwf_complex *)out);
}
template<>
inline void PIFFTW_Private<float>::p_destroyPlan(void *& plan) {
	if (plan) fftwf_destroy_plan((fftwf_plan)plan);
	plan = 0;
}
#  ifdef PIP_FFTWf_THREADSAFE
template<>
inline void PIFFTW_Private<float>::p_makeThreadSafe() {
	fftwf_make_planner_thread_safe();
}
#  endif
#endif // PIP_FFTWf

#ifdef PIP_FFTW
template<>
inline void PIFFTW_Private<double>::p_createPlan_c2c_1d(void *& plan, int size, const void * in, void * out, int dir, int flags) {
	plan = fftw_plan_dft_1d(size, (fftw_complex *)in, (fftw_complex *)out, dir, flags);
}
template<>
inline void PIFFTW_Private<double>::p_createPlan_r2c_1d(void *& plan, int size, const void * in, void * out, int flags) {
	plan = fftw_plan_dft_r2c_1d(size, (double *)in, (fftw_complex *)out, flags);
}
template<>
inline void PIFFTW_Private<double>::p_executePlan(void * plan) {
	fftw_execute((fftw_plan)plan);
}
template<>
inline void PIFFTW_Private<double>::p_executePlan_c2c(void * plan, const void * in, void * out) {
	fftw_execute_dft((fftw_plan)plan, (fftw_complex *)in, (fftw_complex *)out);
}
template<>
inline void PIFFTW_Private<double>::p_executePlan_r2c(void * plan, const void * in, void * out) {
	fftw_execute_dft_r2c((fftw_plan)plan, (double *)in, (fftw_complex *)out);
}
template<>
inline void PIFFTW_Private<double>::p_destroyPlan(void *& plan) {
	if (plan) fftw_destroy_plan((fftw_plan)plan);
	plan = 0;
}
#  ifdef PIP_FFTW_THREADSAFE
template<>
inline void PIFFTW_Private<double>::p_makeThreadSafe() {
	fftw_make_planner_thread_safe();
}
#  endif
#endif // PIP_FFTW

#ifdef PIP_FFTWl
template<>
inline void PIFFTW_Private<ldouble>::p_createPlan_c2c_1d(void *& plan, int size, const void * in, void * out, int dir, int flags) {
	plan = fftwl_plan_dft_1d(size, (fftwl_complex *)in, (fftwl_complex *)out, dir, flags);
}
template<>
inline void PIFFTW_Private<ldouble>::p_createPlan_r2c_1d(void *& plan, int size, const void * in, void * out, int flags) {
	plan = fftwl_plan_dft_r2c_1d(size, (ldouble *)in, (fftwl_complex *)out, flags);
}
template<>
inline void PIFFTW_Private<ldouble>::p_executePlan(void * plan) {
	fftwl_execute((fftwl_plan)plan);
}
template<>
inline void PIFFTW_Private<ldouble>::p_executePlan_c2c(void * plan, const void * in, void * out) {
	fftwl_execute_dft((fftwl_plan)plan, (fftwl_complex *)in, (fftwl_complex *)out);
}
template<>
inline void PIFFTW_Private<ldouble>::p_executePlan_r2c(void * plan, const void * in, void * out) {
	fftwl_execute_dft_r2c((fftwl_plan)plan, (ldouble *)in, (fftwl_complex *)out);
}
template<>
inline void PIFFTW_Private<ldouble>::p_destroyPlan(void *& plan) {
	if (plan) fftwl_destroy_plan((fftwl_plan)plan);
	plan = 0;
}
#  ifdef PIP_FFTWl_THREADSAFE
template<>
inline void PIFFTW_Private<ldouble>::p_makeThreadSafe() {
	fftwl_make_planner_thread_safe();
}
#  endif
#endif // PIP_FFTWl


#endif // PIFFT_H
