/* 
 * Free FFT and convolution (C++)
 * 
 * Copyright (c) 2017 Project Nayuki. (MIT License)
 * https://www.nayuki.io/page/free-small-fft-in-multiple-languages
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 * - The above copyright notice and this permission notice shall be included in
 *   all copies or substantial portions of the Software.
 * - The Software is provided "as is", without warranty of any kind, express or
 *   implied, including but not limited to the warranties of merchantability,
 *   fitness for a particular purpose and noninfringement. In no event shall the
 *   authors or copyright holders be liable for any claim, damages or other
 *   liability, whether in an action of contract, tort or otherwise, arising from,
 *   out of or in connection with the Software or the use or other dealings in the
 *   Software.
 */

#include <algorithm>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include "FftComplex.h"

using std::complex;
using std::size_t;
using std::vector;


// Private function prototypes
static size_t reverseBits(size_t x, int n);


void Fft::transform(vector<complex<double> > &vec) {
    size_t n = vec.size();
    if (n == 0)
        return;
    else if ((n & (n - 1)) == 0)  // Is power of 2
        transformRadix2(vec);
    else  // More complicated algorithm for arbitrary sizes
        transformBluestein(vec);
}


void Fft::inverseTransform(vector<complex<double> > &vec) {
    std::transform(vec.cbegin(), vec.cend(), vec.begin(),
        static_cast<complex<double> (*)(const complex<double> &)>(std::conj));
    transform(vec);
    std::transform(vec.cbegin(), vec.cend(), vec.begin(),
        static_cast<complex<double> (*)(const complex<double> &)>(std::conj));
}


void Fft::transformRadix2(vector<complex<double> > &vec) {
    // Length variables
    size_t n = vec.size();
    int levels = 0;  // Compute levels = floor(log2(n))
    for (size_t temp = n; temp > 1U; temp >>= 1)
        levels++;
    if (static_cast<size_t>(1U) << levels != n)
        throw "Length is not a power of 2";
    
    // Trignometric table
    vector<complex<double> > expTable(n / 2);
    for (size_t i = 0; i < n / 2; i++)
        expTable[i] = std::exp(complex<double>(0, -2 * M_PI * i / n));
    
    // Bit-reversed addressing permutation
    for (size_t i = 0; i < n; i++) {
        size_t j = reverseBits(i, levels);
        if (j > i)
            std::swap(vec[i], vec[j]);
    }
    
    // Cooley-Tukey decimation-in-time radix-2 FFT
    for (size_t size = 2; size <= n; size *= 2) {
        size_t halfsize = size / 2;
        size_t tablestep = n / size;
        for (size_t i = 0; i < n; i += size) {
            for (size_t j = i, k = 0; j < i + halfsize; j++, k += tablestep) {
                complex<double> temp = vec[j + halfsize] * expTable[k];
                vec[j + halfsize] = vec[j] - temp;
                vec[j] += temp;
            }
        }
        if (size == n)  // Prevent overflow in 'size *= 2'
            break;
    }
}


void Fft::transformBluestein(vector<complex<double> > &vec) {
    // Find a power-of-2 convolution length m such that m >= n * 2 + 1
    size_t n = vec.size();
    size_t m = 1;
    while (m / 2 <= n) {
        if (m > SIZE_MAX / 2)
            throw "Vector too large";
        m *= 2;
    }
    
    // Trignometric table
    vector<complex<double> > expTable(n);
    for (size_t i = 0; i < n; i++) {
        unsigned long long temp = static_cast<unsigned long long>(i) * i;
        temp %= static_cast<unsigned long long>(n) * 2;
        double angle = M_PI * temp / n;
        // Less accurate alternative if long long is unavailable: double angle = M_PI * i * i / n;
        expTable[i] = std::exp(complex<double>(0, -angle));
    }
    
    // Temporary vectors and preprocessing
    vector<complex<double> > av(m);
    for (size_t i = 0; i < n; i++)
        av[i] = vec[i] * expTable[i];
    vector<complex<double> > bv(m);
    bv[0] = expTable[0];
    for (size_t i = 1; i < n; i++)
        bv[i] = bv[m - i] = std::conj(expTable[i]);
    
    // Convolution
    vector<complex<double> > cv(m);
    convolve(av, bv, cv);
    
    // Postprocessing
    for (size_t i = 0; i < n; i++)
        vec[i] = cv[i] * expTable[i];
}


void Fft::convolve(
        const vector<complex<double> > &xvec,
        const vector<complex<double> > &yvec,
        vector<complex<double> > &outvec) {
    
    size_t n = xvec.size();
    if (n != yvec.size() || n != outvec.size())
        throw "Mismatched lengths";
    vector<complex<double> > xv(xvec);
    vector<complex<double> > yv(yvec);
    transform(xv);
    transform(yv);
    for (size_t i = 0; i < n; i++)
        xv[i] *= yv[i];
    inverseTransform(xv);
    for (size_t i = 0; i < n; i++)  // Scaling (because this FFT implementation omits it)
        outvec[i] = xv[i] / static_cast<double>(n);
}


static size_t reverseBits(size_t x, int n) {
    size_t result = 0;
    for (int i = 0; i < n; i++, x >>= 1)
        result = (result << 1) | (x & 1U);
    return result;
}
