#include "PGFFT.h"
#include <sys/time.h>
#include <sys/resource.h>
#include <iostream>
#include <algorithm>
#include <limits>
#include <cstdlib>
#include <cstring>


using namespace std;


// RandomBnd(n) returns a random number in [0..n).
// Assumes n > 0.
// FIXME: uses brain-dead rand() function
long
RandomBnd(long n)
{
   const int BPL = std::numeric_limits<unsigned long>::digits;
   const int ROTAMT = 7;
   unsigned long x = 0;
   for (long i = 0;  i < 12; i++) {
      unsigned long x1 = std::rand();
      // rotate x ROTAMT bits
      x = (x << ROTAMT) | (x >> (BPL-ROTAMT));
      x = x ^ x1;
   }

   return long(x % ((unsigned long) n));
}

void
SetSeed()
{
   srand(time(0));
}

double GetTime()
{
   struct rusage used;

   getrusage(RUSAGE_SELF, &used);
   return (used.ru_utime.tv_sec + used.ru_stime.tv_sec +
      (used.ru_utime.tv_usec + used.ru_stime.tv_usec) / 1e6);
}

#define TIME_IT(t, action) \
do { \
   double _t0, _t1; \
   long _iter = 1; \
   long _cnt = 0; \
   do { \
      _t0 = GetTime(); \
      for (long _i = 0; _i < _iter; _i++) { action; _cnt++; } \
      _t1 = GetTime(); \
   } while ( _t1 - _t0 < 3 && (_iter *= 2)); \
   t = (_t1 - _t0)/_iter; \
} while(0)


typedef complex<double> cmplx_t;





int main()
{
   SetSeed();
   std::cout << "simd_enabled=" << PGFFT::simd_enabled() << "\n";


   vector<vector<long>> nvecs = {
     { 1*1024, 4*1024, 16*1024, 64*1024, 1409,   6007,   24001, 96001 },
     { 256*1024, 1024*1024, 4*1024*1024, 16*1024*1024 },
     { 19683 /* 3^9 */, 30030 /* 2*3*5*7*11*13 */ },
     { 18631 /* 31*601 */,  21845 /* 5*17*257*/, 28679  /* 7*17*241 */ },
   };

   for (auto& nvec: nvecs) {
      std::cout << "\n";

      long N = nvec.size();


      for (long i = 0; i < N; i++) {
	 long n = nvec[i];

	 std::cout << "n=" << n << "\n";

	 vector<cmplx_t> v(n);
	 for (long i = 0; i < n; i++)
	    v[i] = RandomBnd(10) - 5;

	 vector<cmplx_t> w(n);

	 PGFFT pgfft(n);

	 double t, t_min;
	 TIME_IT(t, pgfft.apply(&v[0], &w[0])); 
	 t_min = t;
	 TIME_IT(t, pgfft.apply(&v[0], &w[0])); 
	 t_min = min(t_min, t);
	 TIME_IT(t, pgfft.apply(&v[0], &w[0])); 
	 t_min = min(t_min, t);

	 std::cout << t_min << "\n";
      }
   }

}
