#include "FourierTransform_pffft.h"

#include "pffft/pffft.h"

namespace staffpad::audio {

FourierTransform::FourierTransform(int32_t newBlockSize)
{
  _blockSize = newBlockSize;

  _pffft_scratch = (float*)pffft_aligned_malloc(_blockSize * sizeof(float));
  realFftSpec = pffft_new_setup(_blockSize, PFFFT_REAL);
}

FourierTransform::~FourierTransform()
{
  if (_pffft_scratch)
  {
    pffft_aligned_free(_pffft_scratch);
    _pffft_scratch = nullptr;
  }
  if (realFftSpec)
  {
    pffft_destroy_setup(realFftSpec);
    realFftSpec = nullptr;
  }
}

void FourierTransform::forwardReal(const SamplesReal& t, SamplesComplex& c)
{
  assert(t.getNumSamples() == _blockSize);

  for (auto ch = 0; ch < t.getNumChannels(); ++ch)
  {
    auto* spec = c.getPtr(ch); // interleaved complex numbers, size _blockSize + 2
    auto* cpx_flt = (float*)spec;
    pffft_transform_ordered(realFftSpec, t.getPtr(ch), cpx_flt, _pffft_scratch, PFFFT_FORWARD);
    // pffft combines dc and nyq values into the first complex value,
    // adjust to CCS format.
    auto dc = cpx_flt[0];
    auto nyq = cpx_flt[1];
    spec[0] = {dc, 0.f};
    spec[c.getNumSamples() - 1] = {nyq, 0.f};
  }
}

void FourierTransform::inverseReal(const SamplesComplex& c, SamplesReal& t)
{
  assert(c.getNumSamples() == _blockSize / 2 + 1);

  for (auto ch = 0; ch < c.getNumChannels(); ++ch)
  {
    auto* spec = c.getPtr(ch);
    // Use t to convert in-place from CCS to pffft format
    t.assignSamples(ch, (float*)spec);
    auto* ts = t.getPtr(ch);
    ts[0] = spec[0].real();
    ts[1] = spec[c.getNumSamples() - 1].real();
    pffft_transform_ordered(realFftSpec, ts, ts, _pffft_scratch, PFFFT_BACKWARD);
  }
}

} // namespace  staffpad::audio
