import java.util.Random;

/**************************************************************************
*        The FFT algorithm was borrowed, with permission, from:           *
*                                                                         *
*                  Java Grande Benchmarking Project                       *
*                                                                         *
*                                at                                       *
*                                                                         *
*                Edinburgh Parallel Computing Centre                      *
*                                                                         *
*                email: epcc-javagrande@epcc.ed.ac.uk                     *
*                                                                         *
*      adapted from SciMark 2.0, author Roldan Pozo (pozo@cam.nist.gov)   *
*             see below for previous history of this code                 *
*                                                                         *
*      This version copyright (c) The University of Edinburgh, 1999.      *
*                         All rights reserved.                            *
*                                                                         *
**************************************************************************/

public class FFT2 {
    static long wantedTime = 1000;
    static double[] benchmarks = new double[] {775,991,1158,1260,1332,1219,957,273,232,183,134}; // compared to C results
    static boolean useBenchmark = true;

    static String java_bmark_list = "";
    static long rateFactor;
    static int rerunMainLoopCount = 5; // has to be at least 3
    static int[] use_Sizes = new int[] {3,4,5,6,8,10,12,13,14,15,16};

    double testArray[];
    public static double JDKtotal = 0.0;
    public static double JDKtotali = 0.0;

  /** Simple Test routine. */
  public static void main(String args[]){
      if (args.length>0) wantedTime *= 10;
      double[] it_rates = new double[rerunMainLoopCount];

      if (useBenchmark) {
          System.out.println("Results are relative to baseline");
      } else {
          System.out.println("Results are in raw format");
      }
      System.out.println("Size\tbest\taverage\tworst");

      for (int c=0; c<use_Sizes.length; c++) {
          FFT2 fft_ob = new FFT2(1 << use_Sizes[c]);

          double rate = fft_ob.calcRate(1,wantedTime/100);
          long cycles = (long)(rate*wantedTime/10d);
          rate = fft_ob.calcRate(cycles,wantedTime/10);
          cycles = (long)(rate*wantedTime);
          for (int d=0; d<5; d++) {
              rate = fft_ob.calcRate(cycles,(long)(wantedTime*0.9));
              long check = (long)(rate*wantedTime);
              if (check > (cycles * 2)) {
                  System.out.println("Adjusting "+cycles+" to "+check);
                  cycles = check;
              }

              rate *= rateFactor;
              if (useBenchmark) {
                  it_rates[d] = 100d * rate / benchmarks[c];
              } else {
                  it_rates[d] = rate;
              }
          }
          System.out.print(use_Sizes[c]+"\t");
          fft_ob.calcPerformance(it_rates,(c==0));
      }
      System.out.println("Averages: "+java_bmark_list);
  }

    double calcRate(long cycles_per_time, long max_time) {
        long big_counts = 0;
        long time_taken = 0;
        double diff = 0d;
        long now = getTimeAtTick();
        long was = now;
        while (time_taken <= max_time) {
            big_counts ++;
            for (long i=0; i<cycles_per_time; i++) {
                diff += test(testArray);
            }
            now = System.currentTimeMillis();
            time_taken = now-was;
        }
        double rate = (double)(big_counts * cycles_per_time)/(double)time_taken;
        rateFactor = testArray.length;
        if (diff > 10) {
            System.out.println("Diff too big? "+diff);
        }
        return rate;
    }

    static long getTimeAtTick() {
        long now = System.currentTimeMillis();
        final long was = now;
        while (was >= now) {
            now = System.currentTimeMillis();
        }
        return now;
    }

       private void calcPerformance(double[] it_rates, boolean is_first) {
        sortArray(it_rates);
        for (int rep=0; rep<(rerunMainLoopCount-3); rep++) {
            it_rates[1] += it_rates[rep+2];
        }
        it_rates[1] /= (rerunMainLoopCount-2);
        it_rates[2] = it_rates[rerunMainLoopCount-1];

        String[] outputs = new String[3];
        for (int rep=0; rep<3; rep++) {
            outputs[rep] = pretty_number(it_rates[rep]);
        }
        String output = "";
        for (int j=2; j>=0; j--) {
            if (j != 2) output += "\t";
            output += outputs[j];
        }
        if (!is_first) java_bmark_list += ",";
        java_bmark_list += outputs[1];
        System.out.println(output);
    }

    static private String pretty_number(double num) {
        double cutoff = 120;
        if (num > cutoff) return ""+(int)num;
        if (num < 0.001) return "0";
        double fac = 1d;
        while (num < cutoff) {
            num *= 10d;
            fac *= 10d;
        }
        int tt = (int)num;
        num = tt / fac;
        return ""+num;
    }

    static private void sortArray(double[] array) {
        if (array.length < 2) return;
        boolean changed = true;
        while (changed) {
            changed = false;
            for (int c=1; c<array.length; c++) {
                if (array[c-1] > array[c]) {
                    changed = true;
                    double tmp = array[c];
                    array[c] = array[c-1];
                    array[c-1] = tmp;
                }
            }
        }
    }


  /** Compute Fast Fourier Transform of (complex) data, in place.*/
  public static void transform (double data[]) {
    transform_internal(data, -1);

    int JDKrange = data.length;
    for (int i=0; i<JDKrange; i++) {
     JDKtotal += data[i];
    }

  }

  /** Compute Inverse Fast Fourier Transform of (complex) data, in place.*/
  public static void inverse (double data[]) {
    transform_internal(data, +1);
    // Normalize
    int nd=data.length;
    int n =nd/2;
    double norm=1/((double) n);
    for (int i=0; i<nd; i++)
      data[i] *= norm;

    for (int i=0; i<nd; i++) {
      JDKtotali += data[i];
    }

  }

    public FFT2(int size) {
        testArray = makeRandom(size);
    }

  /** Accuracy check on fft of data. Make a copy of data, Compute the fft, then
    * the inverse and compare to the original.  Returns the rms difference.*/
  public static double test(double data[]){
    int nd = data.length;
    // Make duplicate for comparison
    double copy[] = new double[nd];
    System.arraycopy(data,0,copy,0,nd);
    // Transform & invert
    transform(data);
    inverse(data);
    // Compute RMS difference.
    double diff = 0.0;
    for(int i=0; i<nd; i++) {
      double d = data[i]-copy[i];
      diff += d*d;
    }
    return Math.sqrt(diff/nd);
  }

  /** Make a random array of n (complex) elements. */
  public static double[] makeRandom(int n){
    int nd = 2*n;
    Random rnd = new Random(141432);
    double data[] = new double[nd];
    for(int i=0; i<nd; i++) {
      // Okay, this isn't exactly random, but it's simple and cross-platform, though the actual numbers don't make a lot of difference.
      data[i]= (double)((i*i+1)%10) / 10.0;
    }
    return data;
  }

  /* ______________________________________________________________________*/

  protected static int log2 (int n){
    int log = 0;
    for(int k=1; k < n; k *= 2, log++);
    if (n != (1 << log))
      throw new Error("fft: Data length is not a power of 2!: "+n);
    return log;
  }

  protected static void transform_internal (double data[], int direction) {
    int n = data.length/2;
    if (n == 1) return;         // Identity operation!
    int logn = log2(n);

    /* bit reverse the input data for decimation in time algorithm */
    bitreverse(data) ;

    /* apply fft recursion */
    for (int bit = 0, dual = 1; bit < logn; bit++, dual *= 2) {
      double w_real = 1.0;
      double w_imag = 0.0;

      double theta = 2.0 * direction * Math.PI / (2.0 * (double) dual);
      double s = Math.sin(theta);
      double t = Math.sin(theta / 2.0);
      double s2 = 2.0 * t * t;

      /* a = 0 */
      for (int b = 0; b < n; b += 2 * dual) {
        int i = 2*b ;
        int j = 2*(b + dual);

        double wd_real = data[j] ;
        double wd_imag = data[j+1] ;

        data[j]   = data[i]   - wd_real;
        data[j+1] = data[i+1] - wd_imag;
        data[i]  += wd_real;
        data[i+1]+= wd_imag;
      }

      /* a = 1 .. (dual-1) */
      for (int a = 1; a < dual; a++) {
        /* trignometric recurrence for w-> exp(i theta) w */
        {
          double tmp_real = w_real - s * w_imag - s2 * w_real;
          double tmp_imag = w_imag + s * w_real - s2 * w_imag;
          w_real = tmp_real;
          w_imag = tmp_imag;
        }
        for (int b = 0; b < n; b += 2 * dual) {
          int i = 2*(b + a);
          int j = 2*(b + a + dual);

          double z1_real = data[j];
          double z1_imag = data[j+1];

          double wd_real = w_real * z1_real - w_imag * z1_imag;
          double wd_imag = w_real * z1_imag + w_imag * z1_real;

          data[j]   = data[i]   - wd_real;
          data[j+1] = data[i+1] - wd_imag;
          data[i]  += wd_real;
          data[i+1]+= wd_imag;
        }
      }
    }
  }


  protected static void bitreverse(double data[]) {
    /* This is the Goldrader bit-reversal algorithm */
    int n=data.length/2;
    for (int i = 0, j=0; i < n - 1; i++) {
      int ii = 2*i;
      int jj = 2*j;
      int k = n / 2 ;
      if (i < j) {
        double tmp_real    = data[ii];
        double tmp_imag    = data[ii+1];
        data[ii]   = data[jj];
        data[ii+1] = data[jj+1];
        data[jj]   = tmp_real;
        data[jj+1] = tmp_imag; }

      while (k <= j) {
        j = j - k ;
        k = k / 2 ; }
      j += k ;
    }
  }
}





