/* Compute time used by function f */
#include <stdlib.h>
#include <time.h>
#include <stdio.h>

#include "clock.h"
#include "fcyc.h"

#define K 3
#define MAXSAMPLES 20
#define EPSILON 0.01
#define COMPENSATE 0
#define CLEAR_CACHE 0
#define CACHE_BYTES (1<<19)
#define CACHE_BLOCK 32
#define MAX_ITER_TIMES 10

static long int kbest = K;
static long int compensate = COMPENSATE;
static long int clear_cache = CLEAR_CACHE;
static long int maxsamples = MAXSAMPLES;
static double epsilon = EPSILON;
static long int cache_bytes = CACHE_BYTES;
static long int cache_block = CACHE_BLOCK;

static long int *cache_buf = NULL;

static double *values = NULL;
static long int samplecount = 0;

#define KEEP_VALS 0
#define KEEP_SAMPLES 0

#if KEEP_SAMPLES
static double *samples = NULL;
#endif

/* Start new sampling process */
static void init_sampler(void)
{
    if (values)
	free(values);
    values = calloc(kbest, sizeof(double));
#if KEEP_SAMPLES
    if (samples)
	free(samples);
    /* Allocate extra for wraparound analysis */
    samples = calloc(maxsamples+kbest, sizeof(double));
#endif
    samplecount = 0;
}

/* Add new sample.  */
static void add_sample(double val)
{
    long int pos = 0;
    if (samplecount < kbest) {
	pos = samplecount;
	values[pos] = val;
    } else if (val < values[kbest-1]) {
	pos = kbest-1;
	values[pos] = val;
    }
#if KEEP_SAMPLES
    samples[samplecount] = val;
#endif
    samplecount++;
    /* Insertion sort */
    while (pos > 0 && values[pos-1] > values[pos]) {
	double temp = values[pos-1];
	values[pos-1] = values[pos];
	values[pos] = temp;
	pos--;
    }
}

/* Have kbest minimum measurements converged within epsilon? */
static long int has_converged(void)
{
    return
	(samplecount >= kbest) &&
	((1 + epsilon)*values[0] >= values[kbest-1]);
}

/* Code to clear cache */


static volatile long int sink = 0;

static void clear(void)
{
    long int x = sink;
    long int *cptr, *cend;
    long int incr = cache_block/sizeof(long int);
    if (!cache_buf) {
	cache_buf = malloc(cache_bytes);
	if (!cache_buf) {
	    fprintf(stderr, "Fatal error.  Malloc returned null when trying to clear cache\n");
	    exit(1);
	}
    }
    cptr = (long int *) cache_buf;
    cend = cptr + cache_bytes/sizeof(long int);
    while (cptr < cend) {
	x += *cptr;
	cptr += incr;
    }
    sink = x;
}

double fcyc(test_funct f, int *params)
{
		int i;
    double result;
    init_sampler();
    if (compensate) {
	do {
	    double cyc;
	    if (clear_cache)
		clear();
	    start_counter();
	    f(params);
	    cyc = get_counter();
	    if (cyc > 0.0)
		add_sample(cyc);
	} while (!has_converged() && samplecount < maxsamples);
    } else {
	do {
	    double cyc;
	    if (clear_cache)
		clear();
	    start_counter();
	    for (i=0;i<MAX_ITER_TIMES;i++)
    		f(params);
	    cyc = get_counter()/MAX_ITER_TIMES;
	    if (cyc > 0.0)
		add_sample(cyc);

	} while (!has_converged() && samplecount < maxsamples);
    }
#ifdef DEBUG
    {
	long int i;
	printf(" %ld smallest values: [", kbest);
	for (i = 0; i < kbest; i++)
	    printf("%.0f%s", values[i], i==kbest-1 ? "]\n" : ", ");
    }
#endif
    result = values[0];
#if !KEEP_VALS
    free(values);
    values = NULL;
#endif
    return result;
}


/***********************************************************/
/* Set the various parameters used by measurement routines */


/* When set, will run code to clear cache before each measurement
   Default = 0
*/
void set_fcyc_clear_cache(long int clear)
{
    clear_cache = clear;
}

/* Set size of cache to use when clearing cache
   Default = 1<<19 (512KB)
*/
void set_fcyc_cache_size(long int bytes)
{
    if (bytes != cache_bytes) {
	cache_bytes = bytes;
	if (cache_buf) {
	    free(cache_buf);
	    cache_buf = NULL;
	}
    }
}

/* Set size of cache block
   Default = 32
*/
void set_fcyc_cache_block(long int bytes) {
    cache_block = bytes;
}


/* When set, will attempt to compensate for timer interrupt overhead
   Default = 0
*/
void set_fcyc_compensate(long int compensate_arg)
{
    compensate = compensate_arg;
}

/* Value of K in K-best
   Default = 3
*/
void set_fcyc_k(long int k)
{
    kbest = k;
}

/* Maximum number of samples attempting to find K-best within some tolerance.
   When exceeded, just return best sample found.
   Default = 20
*/
void set_fcyc_maxsamples(long int maxsamples_arg)
{
    maxsamples = maxsamples_arg;
}

/* Tolerance required for K-best
   Default = 0.01
*/
void set_fcyc_epsilon(double epsilon_arg)
{
    epsilon = epsilon_arg;
}
