#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#include "papi.h"

#include "clockcore.h"

#define NUM_ITERS  1000000

static char *func_name[] = {
	"PAPI_get_real_cyc",
	"PAPI_get_real_usec",
	"PAPI_get_virt_cyc",
	"PAPI_get_virt_usec"
};
static int CLOCK_ERROR = 0;

static int
clock_res_check( int flag, int quiet )
{
	if ( CLOCK_ERROR ) {
		return -1;
	}

	long long *elapsed_cyc, total_cyc = 0, uniq_cyc = 0, diff_cyc = 0;
	int i;
	double min, max, average, std, tmp;

	elapsed_cyc = ( long long * ) calloc( NUM_ITERS, sizeof ( long long ) );

	/* Real */
	switch ( flag ) {
	case 0:
		for ( i = 0; i < NUM_ITERS; i++ )
			elapsed_cyc[i] = ( long long ) PAPI_get_real_cyc(  );
		break;
	case 1:
		for ( i = 0; i < NUM_ITERS; i++ )
			elapsed_cyc[i] = ( long long ) PAPI_get_real_usec(  );
		break;
	case 2:
		for ( i = 0; i < NUM_ITERS; i++ )
			elapsed_cyc[i] = ( long long ) PAPI_get_virt_cyc(  );
		break;
	case 3:
		for ( i = 0; i < NUM_ITERS; i++ )
			elapsed_cyc[i] = ( long long ) PAPI_get_virt_usec(  );
		break;
	default:
		return -1;

	}

	min = max = ( double ) ( elapsed_cyc[1] - elapsed_cyc[0] );

	for ( i = 1; i < NUM_ITERS; i++ ) {
		if ( elapsed_cyc[i] - elapsed_cyc[i - 1] < 0 ) {
			CLOCK_ERROR = 1;
			fprintf(stderr,"Error! Negative elapsed time\n");
			free( elapsed_cyc );
			return -1;
		}

		diff_cyc = elapsed_cyc[i] - elapsed_cyc[i - 1];
		if ( min > diff_cyc )
			min = ( double ) diff_cyc;
		if ( max < diff_cyc )
			max = ( double ) diff_cyc;
		if ( diff_cyc != 0 )
			uniq_cyc++;
		total_cyc += diff_cyc;
	}

	average = ( double ) total_cyc / ( NUM_ITERS - 1 );
	std = 0;

	for ( i = 1; i < NUM_ITERS; i++ ) {
		tmp = ( double ) ( elapsed_cyc[i] - elapsed_cyc[i - 1] );
		tmp = tmp - average;
		std += tmp * tmp;
	}

	if ( !quiet ) {
		std = sqrt( std / ( NUM_ITERS - 2 ) );
		printf( "%s: min %.3lf  max %.3lf \n", func_name[flag], min, max );
		printf( "                   average %.3lf std %.3lf\n", average, std );

		if ( uniq_cyc == NUM_ITERS - 1 ) {
			printf( "%s : %7.3f   <%7.3f\n", func_name[flag],
					( double ) total_cyc / ( double ) ( NUM_ITERS ),
					( double ) total_cyc / ( double ) uniq_cyc );
		} else if ( uniq_cyc ) {
			printf( "%s : %7.3f    %7.3f\n", func_name[flag],
					( double ) total_cyc / ( double ) ( NUM_ITERS ),
					( double ) total_cyc / ( double ) uniq_cyc );
		} else {
			printf( "%s : %7.3f   >%7.3f\n", func_name[flag],
					( double ) total_cyc / ( double ) ( NUM_ITERS ),
					( double ) total_cyc );
		}
	}

	free( elapsed_cyc );

	return PAPI_OK;
}

int
clockcore( int quiet )
{
	/* check PAPI_get_real_cyc */
	clock_res_check( 0, quiet );
	/* check PAPI_get_real_usec */
	clock_res_check( 1, quiet );

	/* check PAPI_get_virt_cyc */
	/* Virtual */
	if ( PAPI_get_virt_cyc(  ) != -1 ) {
		clock_res_check( 2, quiet );
	} else {
		return CLOCKCORE_VIRT_CYC_FAIL;
	}

	/* check PAPI_get_virt_usec */
	if ( PAPI_get_virt_usec(  ) != -1 ) {
		clock_res_check( 3, quiet );
	} else {
		return CLOCKCORE_VIRT_USEC_FAIL;
	}

	return PAPI_OK;
}
