#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/time.h>

uint64_t hi, low;
char *data;
struct timeval cur;

/*
static __inline__ uint64_t read_rdtsc(void)
{
  uint64_t hi, lo;
  __asm__ ("cpuid\n");
  __asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi));
  return ( (uint64_t)lo)|( ((uint64_t)hi)<<32 );
}
*/

#define LRAND(s) \
(((s) = (s) * 41943011 - 2147483647))

//uint32_t lrand(uint64_t *s){
//        return (*(uint64_t *)s = *(uint64_t *)s * 41943011 - 2147483647);
//}


void Choose_Eleven(uint32_t set_min, uint32_t set_max, uint64_t **eleven){
	gettimeofday(&cur, NULL);
	uint64_t seed = (uint64_t)cur.tv_usec + 3;
	uint32_t num1 = 0;
	uint32_t num2 = 0;
	//uint32_t last = 0;
	uint32_t tmp = 0;
	uint32_t range = set_max - set_min;

	while(num1 < 11){
		//eleven[num1] = LRAND(seed) % set_m;
		while(num2 < 2){
			tmp = LRAND(seed) % range;
			tmp += set_min;
			if(tmp < 8)
				tmp += 8;
			if(tmp > 134217728-8)
				tmp -= 8;
			eleven[num1][num2] = tmp;
			num2++;
		}
//		eleven[num1] = (uint64_t) ((uint64_t)LRAND(seed) % (uint64_t)set_m);
		//eleven[num1] = (uint64_t)(LRAND(&seed) % set_m);
		//eleven[num1] = (uint64_t)(lrand(&seed) % set_m);
		num1++;
	}
}

/*
static void Get_Eleven(uint64_t *eleven, uint64_t *cycles){
	char tmp = 0;
	uint32_t num1 = 0;
	uint64_t cycle_s = 0;
	uint64_t cycle_e = 0;
	uint32_t ind = 0;
//cycle = read_rdtsc();
//sleep(1);
//cycle = read_rdtsc() - cycle;

//printf("cycle: %lu\n", cycle);
	while(num1 < 11){
		ind = eleven[num1];

		cycle_s = read_rdtsc();
		tmp = data[ind];
		cycle_e = read_rdtsc();

		cycles[num1] = cycle_e - cycle_s;
		num1++;
	}
}
*/

double Remove_Outlier(uint64_t *cycles){
	double avg = 0;
	uint32_t num1 = 0;
	//uint64_t highest = 0;
	//uint64_t lowest = UINT64_MAX; //fix for 32-bit
	//uint32_t h_indx = 0;
	//uint32_t l_indx = 0;
	uint32_t ignore[11] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};// = 0;
	uint32_t good = 11;

	//fix 
	//while(num1 < 11){
	//	if(cycles[num1] > 1000000000)
	//		cycles[num1] = 1;
	//	num1++;
	//}

/*
	while(num1 < 11){
		//find highest
		if(cycles[num1] > highest ){
			highest = cycles[num1];
			h_indx = num1;
		}
		if(cycles[num1] < lowest){
			lowest = cycles[num1];
			l_indx = num1;
		}
		//num1++;
		avg += (double)cycles[(size_t)num1];
		num1++;
	}

	avg /= 11;
	if( (avg - lowest) > (highest - avg) )
		ignore = l_indx;
	else
		ignore = h_indx;

	avg = 0;
	for(num1=0; num1<11; num1++){
		if(num1 != ignore)
			avg += (double)cycles[(size_t)num1];
	}
	avg /= 10;
*/
	while(num1 < 11){
		if(cycles[num1] > 1000000000){
			//ignore[num1] = 1;
			good--;
		}else{
			avg += cycles[num1];
		}
		num1++;
	}
	if(good == 0)
		return -1;
	avg /= good;

	return avg;
}

void Randomize(uint32_t max){
	uint32_t num1 = 0;
	gettimeofday(&cur, NULL);
	uint64_t seed = (uint64_t)cur.tv_usec + 7;
	while(num1 < max){
		data[num1] = (char)(LRAND(seed) % 256);
		num1++;
	}
}

/*
static void Thrash(uint32_t max){
	uint32_t loops = UINT32_MAX / max;
	uint32_t num1 = 0;
	uint32_t num2 = 0;
	char tmp1 = 0;
	//static char tmp2 = 0;
	for(num1=0; num1<loops; num1++){
		for(num2=0; num2 < max; num2++)
			tmp1 = data[num2];
	}
}
*/

int main(int argc, char **argv){
	//const uint64_t sf = 68719476736;
	const uint64_t sf = UINT32_MAX/2;
	//const int32_t data_m = 67108864/4;

	//uint64_t eleven[11][2];
	uint64_t **eleven;
	uint64_t pass[2];
	uint32_t num1, num2, num3;
	uint64_t cycles[11];
	double avg = 0;
	uint32_t sets = 21;
	uint32_t set_m[21] = {1024, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 786432, 1048576, 1572864,
						 2097152, 2621440, 3145728, 4194304, 8388608, 16777216, 33554432, 67108864, 134217728};
	uint64_t loop_t[21] = {sf/1024, sf/4096, sf/8192, sf/16384, sf/32768, sf/65536, sf/131072,
						sf/262144, sf/524288, sf/786432, sf/1048576, sf/1572864, sf/2097152, sf/2621440,
						sf/3145728, sf/4194304, sf/8388608, sf/16777216, sf/33554432, sf/67108864, sf/134217728};
	double offset = 0;
	//UINT32_MAX
	//68719476736 //64GB
	uint64_t highest = 0;
	//gettimeofday(&cur, NULL);

	eleven = malloc(sizeof(uint64_t *) * 11);
	for(num1=0; num1<11; num1++){
		eleven[num1] = malloc(sizeof(uint64_t)*2);
		//for(num2=0; num2<2; num2++)
		//	eleven[num1][num2] = malloc(sizeof(uint64_t)*2);
	}

	//int32_t data[data_m]; //64MB

//	char *data; //= malloc(67108864);

	//char data[67108864]; //64MB
	//uint64_t hi, low;

	hi = 1;
	low = 2;

	//estimate non-read overhead
	//for(num1=0; num1<11; num1++){
	//	Time_Offset(&low, &hi);
	//	cycles[num1] = (uint64_t)low | ((uint64_t)hi<<32);
	//}
	//offset = Remove_Outlier(cycles);

	//printf("Estimated overhead offset: %.03lf\n", offset);

	for(num1=0; num1<sets; num1++){
		printf("Set: %ubytes\n", set_m[num1]);
		if(num1 == 0)
			num3 = 0;
		else
			num3 = set_m[num1-1];
		Choose_Eleven(num3, set_m[num1], eleven);
		//printf("b\n");
		//printf("here 1\n");
		//printf("grabbing: %u\n", eleven[num1]);

		data = malloc(set_m[num1]);
		//printf("c\n");
		Randomize(set_m[num1]);
		//printf("d\n");

		pass[0] = 5;
		pass[1] = 7;
		for(num2=0; num2<11; num2++){
			//printf("1: %lu 2: %lu\n", eleven[num2][0], eleven[num2][1]);
			//pass = eleven[num2];
			Reader(data, (uint64_t)set_m[num1], (uint64_t)loop_t[num1], eleven[num2][0], &low, &hi);
			cycles[num2] = (uint64_t)low | ((uint64_t)hi<<32);
			//cycles[num2] = (uint64_t)low | (uint64_t)hi<<32;
			printf("%u %u eleven: %lu\n", hi, low, eleven[num2][0]);
		}
		free(data);

		//printf("%u %u\n", hi, low);

		//cycles[num1] 

		//printf("h1\n");
		//Choose_Eleven(set_m[num1], eleven);
		//printf("h2\n");
		//Randomize(data, set_m[num1]);
		//Thrash(data, set_m[num1]);
		//Thrash(data, set_m[num1]);
		//Thrash(data, set_m[num1]);
		//Thrash(data, set_m[num1]);
		//printf("h3\n");
		//Get_Eleven(eleven, cycles, data);
		//printf("h4\n");

		avg = Remove_Outlier(cycles);
		//printf("\t%.03lf\n", avg);
		if(avg != -1)
			printf("\tcycles(Avg):     %.03lf\n", avg);
		else
			printf("\tcycles(Avg):     %.03lf (Warning invalid!)\n", avg);
		highest = 0;
		for(num2=0; num2<11; num2++){
			if(cycles[num2] < 1000000000){
				if(cycles[num2] > highest)
					highest = cycles[num2];
			}
		}
		printf("\tcycles(highest): %lu\n", highest);

		//avg = Average(cycles);

		//scratch = (uint64_t)low | ((uint64_t)hi<<32);

		//printf("%u\n", set_m[num1]);
		//printf("\t%lu %lu scratch: %lu\n", hi, low, scratch);
	}
	for(num1=0; num1<11; num1++)
		free(eleven[num1]);
	free(eleven);
	//free(data);
	return 0;
}