/* === S Y N F I G ========================================================= */
/*!	\file synfig/rendering/software/function/fft.cpp
**	\brief FFT
**
**	\legal
**	......... ... 2015 Ivan Mahonin
**
**	This file is part of Synfig.
**
**	Synfig 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.
**
**	Synfig 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 Synfig.  If not, see <https://www.gnu.org/licenses/>.
**	\endlegal
*/
/* ========================================================================= */

/* === H E A D E R S ======================================================= */

#ifdef USING_PCH
#	include "pch.h"
#else
#ifdef HAVE_CONFIG_H
#	include <config.h>
#endif

#include <cassert>
#include <climits>
//#include <ccomplex>

#include <mutex>

#include <vector>
#include <set>

#include <fftw3.h>

#include "fft.h"

#endif

using namespace synfig;
using namespace rendering;

/* === M A C R O S ========================================================= */

/* === G L O B A L S ======================================================= */

/* === P R O C E D U R E S ================================================= */

/* === M E T H O D S ======================================================= */

class software::FFT::Internal
{
public:
	static std::set<int> counts;
	static std::mutex mutex;
};

std::set<int> software::FFT::Internal::counts;
std::mutex software::FFT::Internal::mutex;

void
software::FFT::initialize()
{
	const int max2 = INT_MAX/2 + 1;
	const int max3 = INT_MAX/3 + 1;
	const int max5 = INT_MAX/5 + 1;
	const int max7 = INT_MAX/7 + 1;
	for(int c2 = 1; c2 < max2; c2 *= 2)
		for(int c3 = c2; c3 < max3; c3 *= 3)
			for(int c5 = c3; c5 < max5; c5 *= 5)
				for(int c7 = c5; c7 < max7; c7 *= 7)
					Internal::counts.insert(c7);
	fftw_set_timelimit(0.0);
}

void
software::FFT::deinitialize()
{
	Internal::counts.clear();
}

int
software::FFT::get_valid_count(int x)
{
	if (x > 0)
	{
		std::set<int>::const_iterator i = Internal::counts.upper_bound(x - 1);
		if (i != Internal::counts.end())
			return *i;
	}
	assert(false);
	return 0;
}

bool
software::FFT::is_valid_count(int x)
{
	return Internal::counts.count(x);
}

void
software::FFT::fft(const Array<Complex, 1> &x, bool invert)
{
	if (x.count == 0 || x.count == 1) return;

	assert(is_valid_count(x.count));

	fftw_iodim iodim;
	iodim.n  = x.count;
	iodim.is = x.stride;
	iodim.os = x.stride;

	{
		std::lock_guard<std::mutex> lock(Internal::mutex);
		fftw_plan plan = fftw_plan_guru_dft(
			1, &iodim, 0, nullptr,
			(fftw_complex*)x.pointer, (fftw_complex*)x.pointer,
			invert ? FFTW_BACKWARD : FFTW_FORWARD, FFTW_ESTIMATE );
		fftw_execute(plan);
		fftw_destroy_plan(plan);
	}

	// divide by count to complete back-FFT
	if (invert)
		x.process< std::multiplies<Complex> >( Complex(1.0/(Real)x.count) );
}

void
software::FFT::fft2d(const Array<Complex, 2> &x, bool invert, bool do_rows, bool do_cols)
{
	if (x.count == 0 || x.sub().count == 0) return;
	if ( (!do_cols || x.count == 1)
	  && (!do_rows || x.sub().count == 1) )
		return;

	assert(is_valid_count(x.count) && is_valid_count(x.sub().count));

	if (!do_rows && !do_cols) return;

	fftw_iodim iodim[2];
	iodim[0].n  = x.sub().count;
	iodim[0].is = x.sub().stride;
	iodim[0].os = x.sub().stride;
	iodim[1].n  = x.count;
	iodim[1].is = x.stride;
	iodim[1].os = x.stride;

	{
		std::lock_guard<std::mutex> lock(Internal::mutex);

		fftw_plan plan;
		if (do_rows && do_cols)
		{
			plan = fftw_plan_guru_dft(
				2, iodim, 0, nullptr,
				(fftw_complex*)x.pointer, (fftw_complex*)x.pointer,
				invert ? FFTW_BACKWARD : FFTW_FORWARD, FFTW_ESTIMATE );
		}
		else
		{
			plan = fftw_plan_guru_dft(
				1, &iodim[do_rows ? 0 : 1], 1, &iodim[do_rows ? 1 : 0],
				(fftw_complex*)x.pointer, (fftw_complex*)x.pointer,
				invert ? FFTW_BACKWARD : FFTW_FORWARD, FFTW_ESTIMATE );
		}
		fftw_execute(plan);
		fftw_destroy_plan(plan);
	}

	// divide by count to complete back-FFT
	if (invert)
	{
		int count = (do_cols ? x.count : 1)
			      * (do_rows ? x.sub().count : 1);
		x.process< std::multiplies<Complex> >( Complex(1.0/(Real)count) );
	}
}

/* === E N T R Y P O I N T ================================================= */
