/*
 *
 *    soniK digital audio editor
 *    Copyright (C) 2003-2006  Robert Walker <rob@tenfoot.org.uk>
 *
 *    This program 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.
 *
 *    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 General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */
#include "sonik_sigproc.h"
#include "sonik_types.h"

#include <cppunit/extensions/HelperMacros.h>

#include "test.h"
#include "complex_test.h"

#include <cstdio>
#include <iterator>
#include <fstream>
#include <iostream>

namespace Sonik
{
  class FFTTest : public CppUnit::TestFixture
  {
    CPPUNIT_TEST_SUITE( FFTTest );
    CPPUNIT_TEST( testSineFFT );
    CPPUNIT_TEST( testZerosFFT );
    CPPUNIT_TEST( testOnesFFT );
    CPPUNIT_TEST( testNoiseFFT );
    CPPUNIT_TEST_SUITE_END();

  public:
    void testSineFFT();
    void testZerosFFT();
    void testOnesFFT();
    void testNoiseFFT();

  private:
    void checkFFT(const QString& type, int freq, size_t sz);
    void checkFFTType(const QString& type, int freq);
  };
}

CPPUNIT_TEST_SUITE_REGISTRATION(Sonik::FFTTest);

void Sonik::FFTTest::checkFFT(const QString& type, int freq, size_t sz)
{
  QString datFile = QString("data/tfft.%1.%2.%3.dat").arg(type).arg(freq).arg(sz);
  QString fftFile = QString("data/tfft.%1.%2.%3.fft").arg(type).arg(freq).arg(sz);

  FILE* datFd = std::fopen(datFile.latin1(), "r");
  if (datFd == 0)
    CPPUNIT_FAIL(QString("Failed to open data file: ") + datFile);

  FILE* fftFd = std::fopen(fftFile.latin1(), "r");
  if (datFd == 0)
    CPPUNIT_FAIL("Failed to open expected FFT file");

  CPPUNIT_ASSERT(sz <= 65536);

  const size_t fsz = sz/2 + 1;

  std::cout << datFile.latin1() << std::endl;

  // read in data
  Sonik::SampleBuffer data(sz);
  fread(data.data(), sizeof(Sonik::Sample), sz, datFd);
  fclose(datFd);

  // read in expected fft
  Sonik::ComplexSampleBuffer fftDataExp(fsz);
  Sonik::SampleBuffer fftMagExp(fsz);
  fread(fftDataExp.data(), sizeof(Sonik::ComplexSample), fsz, fftFd);
  fread(fftMagExp.data(), sizeof(Sonik::Sample), fsz, fftFd);
  fclose(fftFd);

  // calc fft 1
  Sonik::ComplexSampleBuffer fftData(fsz);
  Sonik::fft(data.data(), fftData.data(), sz);
  Sonik::SampleBuffer fftMag(fsz);
  fftData.abs(fftMag);

  // calc fft 2
  Sonik::ComplexSampleBuffer fftData2(fsz);
  Sonik::FFTParms* fftParms = Sonik::createFFTParms(sz);
  Sonik::fft(data.data(), fftData2.data(), sz, fftParms);
  Sonik::SampleBuffer fftMag2(fsz);
  fftData2.abs(fftMag2);
  delete fftParms;

  // compare ffts
  CHECK_BUFFER_EQ(fftData, fftData2);
  CHECK_BUFFER_EQ(fftMag, fftMag2);

  // compare with expected
  Sonik::SampleBuffer r(fsz), i(fsz), re(fsz), ie(fsz);
  fftData.real(r);
  fftData.imag(i);
  fftDataExp.real(re);
  fftDataExp.imag(ie);
  CHECK_BUFFER_EQ(r, re);
  CHECK_BUFFER_EQ(i, ie);
  CHECK_BUFFER_EQ(fftData, fftDataExp);

  CHECK_BUFFER_EQ(fftMag, fftMagExp);
}


void Sonik::FFTTest::checkFFTType(const QString& type, int freq)
{
  std::vector<size_t> sizes;

  std::ifstream in("tfft.dat");
  CPPUNIT_ASSERT(in.is_open());
  std::copy(std::istream_iterator<size_t>(in), std::istream_iterator<size_t>(),
            std::back_inserter(sizes));

  for (std::vector<size_t>::const_iterator s = sizes.begin();
       s != sizes.end();
       ++s)
  {
    checkFFT(type, freq, *s);
  }
}


void Sonik::FFTTest::testSineFFT()
{
  checkFFTType("sine", 1);
  checkFFTType("sine", 10);
  checkFFTType("sine", 100);
}


void Sonik::FFTTest::testZerosFFT()
{
  checkFFTType("zeros", 1);
}

void Sonik::FFTTest::testOnesFFT()
{
  checkFFTType("ones", 1);
}

void Sonik::FFTTest::testNoiseFFT()
{
  checkFFTType("noise", 1);
}
