#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <stdlib.h>
#include <string.h>
#include <math.h>

#include "fft.h"
#include "misc.h"

/* ---------------------------------------------------------------------- */

#ifdef HAVE_LIBFFTW

#define	FLAGS	(FFTW_ESTIMATE | FFTW_OUT_OF_PLACE | FFTW_USE_WISDOM)

struct fft *init_fft(int len)
{
	struct fft *s;

	if ((s = calloc(1, sizeof(struct fft))) == NULL)
		return NULL;

	s->plan = fftw_create_plan(len, FFTW_FORWARD, FLAGS);

	if (!s->plan) {
		clear_fft(s);
		return NULL;
	}

	return s;
}

struct fft *init_ifft(int len)
{
	struct fft *s;

	if ((s = calloc(1, sizeof(struct fft))) == NULL)
		return NULL;

	s->plan = fftw_create_plan(len, FFTW_BACKWARD, FLAGS);

	if (!s->plan) {
		clear_fft(s);
		return NULL;
	}

	return s;
}

void clear_fft(struct fft *s)
{
	if (s) {
		fftw_destroy_plan(s->plan);
		free(s);
	}
}

void fft(struct fft *s, complex *in, complex *out)
{
	fftw_one(s->plan, in, out);
}

/* ---------------------------------------------------------------------- */

#else		/* HAVE_LIBFFTW */

/* ---------------------------------------------------------------------- */

#define	FFTDIR_FWD	0
#define	FFTDIR_REV	1

static struct fft *fftinit(int len, int dir)
{
	struct fft *f;
	int i;

	if ((f = calloc(1, sizeof(struct fft))) == NULL)
		return NULL;

	f->twiddles = calloc(len, sizeof(complex));
	f->bitrev = calloc(len, sizeof(unsigned int));

	if (!f->twiddles || !f->bitrev) {
		clear_fft(f);
		return NULL;
	}

	f->fftlen = len;
	f->fftlenlog = log2(len);

	for (i = 0; i < f->fftlen; i++) {
		f->twiddles[i].re = cos((double) i / f->fftlen * 2.0 * M_PI);
		f->twiddles[i].im = sin((double) i / f->fftlen * 2.0 * M_PI);
		f->bitrev[i] = rbits32(i) >> (32 - f->fftlenlog);
	}

	f->dir = dir;

	return f;
}

struct fft *init_fft(int len)
{
	return fftinit(len, FFTDIR_FWD);
}

struct fft *init_ifft(int len)
{
	return fftinit(len, FFTDIR_REV);
}

void clear_fft(struct fft *f)
{
	if (f) {
		free(f->twiddles);
		free(f->bitrev);
		free(f);
	}
}

/*
 * Radix-2 decimation-in-time (I)FFT routine.
 */
void fft(struct fft *f, complex *in, complex *out)
{
	int i, j, k;
	int s, sep, width, top, bot;
	float tr, ti, dir;

	/*
	 * Order the samples in bit reverse order.
	 */
	for (i = 0; i < f->fftlen; i++)
		out[f->bitrev[i]] = in[i];

	if (f->dir == FFTDIR_FWD)
		dir =  1.0;
	else
		dir = -1.0;

	/*
	 * In-place (I)FFT.
	 */
	for (sep = s = 1; s <= f->fftlenlog; s++) {
		width = sep;	/* butterfly width      = 2^(s-1)  */
		sep <<= 1;	/* butterfly separation = 2^s      */

		for (j = 0; j < width; j++) {
			k = f->fftlen * j / sep;

			for (top = j; top < f->fftlen; top += sep) {
				bot = top + width;

				tr = out[bot].re * f->twiddles[k].re +
				     out[bot].im * f->twiddles[k].im * dir;

				ti = out[bot].im * f->twiddles[k].re -
				     out[bot].re * f->twiddles[k].im * dir;

				out[bot].re = out[top].re - tr;
				out[bot].im = out[top].im - ti;

				out[top].re = out[top].re + tr;
				out[top].im = out[top].im + ti;
			}
		}
	}

	if (f->dir == FFTDIR_REV) {
		for (i = 0; i < f->fftlen; i++) {
			out[i].re /= f->fftlen;
			out[i].im /= f->fftlen;
		}
	}
}

#endif		/* HAVE_LIBFFTW */

/* ---------------------------------------------------------------------- */

