#define _GNU_SOURCE

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

#include <string.h>

#include <sys/types.h>
#include <stdint.h> 
#include <errno.h>
#include <assert.h>

#include <papi.h>

#include <time.h>

#include "measure.h"

int *events[18];

__attribute__ ((visibility("default")))
int measure(char *testname, char *testfile, testfunc fp, void *up[], int ups) {
	int res, i, j, timemeasured = 0;
	uint64_t total_ns, total_s;
	struct timespec begin, end;

	long long meas[5] = {0, 0, 0, 0, 0};

	printf("%s,%s", testname, testfile);

	for (i = 0; i < 3; i++) {
		if (!timemeasured) {
			clock_gettime(CLOCK_REALTIME, &begin);
		}

		if ((res = PAPI_start_counters(&events[i][1], events[i][0])) != PAPI_OK) {
			//fprintf(stderr, "Error starting counter: %s\n", PAPI_strerror(res));
			for (j = 1; j <= events[i][0]; j++) {
				printf(",0");
			}
			continue;
		};

		for (j = 0; j < ups; j++) {
			(fp)(up[j]);
		}

		PAPI_stop_counters(meas, events[i][0]);

		if (!timemeasured) {
			clock_gettime(CLOCK_REALTIME, &end);

			if (begin.tv_nsec <= end.tv_nsec) {
				total_ns = end.tv_nsec - begin.tv_nsec;
				total_s  = end.tv_sec  - begin.tv_sec;
			} else {
				total_ns = end.tv_nsec + (1e9 - begin.tv_nsec);
				total_s  = end.tv_sec - begin.tv_sec - 1;
			}

			printf(",%f,%f", (double)total_s/ups, (double)total_ns/ups);
			timemeasured = 1;
		}

		for (j = 1; j <= events[i][0]; j++) {
			printf(",%lld", meas[j-1]/ups);
		}
	}

	printf("\n");
	fflush(stdout);

	return 0;
}

__attribute__ ((visibility("default")))
int
measure_init() {
	int i, j;
	char buf[1024];

	for (i = 0; i < 3; i++) {
		events[i] = calloc(sizeof(int), 4);
		if (events[i] == NULL) {
			return 0;
		}
	}

	// Reference clock cycles
	// Cycles stalled on any resource
	// Total cycles
	events[0][0] = 3;
	events[0][1] = PAPI_RES_STL;
	events[0][2] = PAPI_TOT_CYC;
	events[0][3] = PAPI_REF_CYC;

	//Instructions issued
	//Instructions completed
	events[1][0] = 2;
	events[1][1] = PAPI_TOT_IIS;
	events[1][2] = PAPI_TOT_INS;

	//Floating point instructions
	events[2][0] = 1;
	events[2][1] = PAPI_FP_INS;

	int res;

	if ( (res = PAPI_library_init(PAPI_VER_CURRENT)) != PAPI_VER_CURRENT) {
		fprintf(stderr, "Unable to initialize PAPI: %s\n", PAPI_strerror(res));
		return 0;
	}

	printf("Testfile,Testsize,Seconds,Nanoseconds");
	for (i = 0; i < 3; i++) {
		for (j = 1; j <= events[i][0]; j++) {
			PAPI_event_code_to_name(events[i][j], buf);
			printf(",%s", buf);
		}
	}
	printf("\n");
	fflush(stdout);

	return 1;
}

__attribute__ ((visibility("default")))
void measure_destroy() {
	int i;
	for (i = 0; i < 18; i++) {
		if (NULL != events[i]) {
			free(events[i]);
		}
	}
	PAPI_shutdown();
}
