
// system include files
//
#include <memory.h>
#include <time.h>

// isip include files
//
#include "isip_benchmarks.h"

boolean bench_fft(DebugLevel level_a, Long order_a, String algo_a) {

  // define local variables
  //
  FourierTransform ft;
  long i, j;
  long num_iter = FFT_NUM_ITER;
  float average_time, median_time, stddev_time, cpu_time_taken, base_time;
  VectorFloat timing_stats(num_iter / FFT_TIMING_RATIO);

  // get the algorithm from input
  //
  FourierTransform::ALGORITHM algo = FourierTransform::DF;
  if (algo_a.eq(L"df")) {
    algo = FourierTransform::DF;
  }
  else if (algo_a.eq(L"sr")) {
    algo = FourierTransform::SR;
  }
  else if (algo_a.eq(L"rad2")) {
    algo = FourierTransform::RAD2;
  }
  else if (algo_a.eq(L"rad4")) {
    algo = FourierTransform::RAD4;
  }
  else if (algo_a.eq(L"fh")) {
    algo = FourierTransform::FH;
  }
  else if (algo_a.eq(L"qf")) {
    algo = FourierTransform::QF;
  }
  else if (algo_a.eq(L"ditf")) {
    algo = FourierTransform::DITF;
  }
  else {
     Console::put(L"invalid algorithm option, it should be df, sr, rad2, rad4, fh, qf, ditf.");
  }
  
  // define default values
  //
  FourierTransform::DATATYPE data_type = FourierTransform::REAL;

  // set fourier transform
  //
  ft.set(algo, data_type);
  ft.setOrder(order_a);

  // output some fourier transform information
  //
  if (level_a > Integral::NONE) {
    ft.debug(L"transform");
  }
  
  // setup vectors for holding data
  //
  VectorFloat real_input_vector(order_a);
  VectorFloat freq_vector(order_a * 2);
  
  // generate the input data
  //
  real_input_vector.rand();
  
  // declare the output strings
  //
  String value;
  String output;

  // compute the fft for the number of iterations provided 
  //
  for (i = 0; i < num_iter / FFT_TIMING_RATIO; i++){
    
    // output some information
    //
    if (level_a > Integral::BRIEF) {
      value.assign(i);
      output.assign(L"  running iteration ");
      output.concat(value);
      value.assign(L" ...");
      output.concat(value);
      Console::put(output);
    }

    // begin timing the computations
    //
    base_time = clock();
    
    // clock for num_iter of  iterations
    //
    for (j = 0; j < num_iter ; j++){
      if (!ft.compute(freq_vector, real_input_vector)) {
	Error::handle(ft.name(), L"compute", Error::TEST,
		      __FILE__, __LINE__);
      }
    }
    
    // get the timing information
    //
    cpu_time_taken = clock();
    
    // get the time taken for this iteration
    //
    timing_stats(i) = (cpu_time_taken - base_time) / (float)num_iter;
  }

  // get detailed timing statistics
  //
  fft_get_stats(timing_stats, num_iter / FFT_TIMING_RATIO, median_time,
               average_time, stddev_time);	       
  if (level_a > Integral::NONE) {
    output.assign(L"median of time taken per iteration: ");
    value.assign(median_time);
    output.concat(value);
    Console::put(output);
  }

  // compute the inverse complex transform
  //
  VectorFloat inverse_vector(order_a * 2);
  VectorFloat real_diff_vector(order_a * 2);
  ft.setDatatype(FourierTransform::COMPLEX);
  if (!ft.compute(inverse_vector, freq_vector, FourierTransform::INVERSE)) {
    Error::handle(ft.name(), L"compute", Error::TEST, __FILE__,
		  __LINE__);
  }
	      
  // compute the snr for this operation
  //  get the sum of the squares for the input
  //
  double signal_power = real_input_vector.sumSquare();
  
  // generate the difference signal - for the real transform, we
  // need to remove the 0.0 imaginary parts - we'll just add them
  // to the noise component
  //
  long tmp_len = real_input_vector.length();
  for (long i = 0; i < tmp_len; i++) {
    real_diff_vector(2 * i) = real_input_vector(i);
    real_diff_vector(2 * i + 1) = 0.0;
  }
  real_diff_vector.sub(inverse_vector);
  double noise_power = real_diff_vector.sumSquare();
	      
  // compute the snr
  //
  Double snr = signal_power / noise_power;
  snr.log10();
  snr *= Integral::DB_POW_SCALE_FACTOR;
  
  // print the snr
  //
  if (level_a >= Integral::DETAILED) {
    value.assign(snr, L"%g");
    output.assign(L"SNR = ");
    output.concat(value);
    Console::put(output);
  }
  
  // exit gracefully
  //
  return true;
}

// method: get_stats_cc
//
// arguments:
//  float_8* input_stats_a: (input/output) initially unordered array of
//                        float values; they are ordered before exiting 
//  int_4 size_a: (input) order of the input statistics array
//  float_8& median_a: (output) median value 
//  float_8& average_a: (output) average value 
//  float_8& stddev_a: (output) standard deviation value 
//
// return: a logical_1 value indicating status
//
// method to produce the average, median and standard deviation of
// a sorted list of data.
//
boolean fft_get_stats(VectorFloat& input_stats_a, long size_a,
		      float& median_a, float& average_a,
		      float& stddev_a) {
  
  // declare loop counter
  //
  long k;
  
  // sort the input array
  //
  fft_sort_data(input_stats_a, size_a);
  
  // initialize the average and find the average value of the data
  //
  average_a = 0;
  for (k = 0; k < size_a ; k++) {
    average_a += input_stats_a(k);
  }
  average_a = average_a/size_a;
  
  // find the median value of the data
  //
  median_a = input_stats_a(size_a / 2);
  
  // find the standard deviation of the data
  //
  stddev_a = 0.0;  
  for (k = 0; k < size_a; k++) {
    stddev_a += (input_stats_a(k) - average_a) *
      (input_stats_a(k) - average_a);
  }
  stddev_a = stddev_a / size_a;
  stddev_a = (float) sqrt(stddev_a);
  
  // exit gracefully
  //
  return true;
}
  
// method: sort_data_cc
//
// arguments:
//  float_8* input_a: (input/output) array of float values
//  float_8 size_A: (input) size of the input statistics array
//
// return: a logical_1 value indicating status
//
// method to produce a sorted array using a selection sort.
//
boolean fft_sort_data(VectorFloat& input_a, long& size_a) {

  // declare local variables
  //
  long index1, index2, size_1;
  float temp_data;
  long smallest_index;

  // outer loop iterates for each array location
  //
  size_1 = size_a - 1;
  for (index1 = 0; index1 < size_1; index1++) {
    
    // set the current location equal to smallest
    //
    smallest_index = index1;
    
    // determine if any of the subsequent locations hold data
    // which is smaller than the current smallest
    //
    for (index2 = index1 + 1; index2 < size_a; index2++) {

      // if this data is smaller then set its index to the indicate
      // the smallest
      //
      if (input_a(index2) < input_a(smallest_index)) {
        smallest_index = index2;
      }
    }

    // if any smaller data items were found then swap them
    //
    if (index1 != smallest_index) {
      temp_data = input_a(index1);
      input_a(index1) = input_a(smallest_index);
      input_a(smallest_index) = temp_data;
    }
  }
  
  // exit gracefully
  //
  return true;
}
