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

uint32_t Threads = 2;
size_t Memory = 536870912; //bytes
size_t Memory_MB = 512;
pthread_mutex_t Mutex1 = PTHREAD_MUTEX_INITIALIZER;
//pthread_mutex_t Mutex2 = PTHREAD_MUTEX_INITIALIZER;
pthread_barrier_t Barrier1;
pthread_barrier_t Barrier2;
pthread_barrier_t Barrier3;
pthread_barrier_t Barrier4;
int No_Exit = 1;
int Manager_t = 0;
char *memory;
struct timeval Starttime, Endtime;
double time1 = 0;

//typedef unsigned int uint128_t __attribute__((mode(TI)));
//typedef struct{
//	size_t high;
//	size_t low;
//}b128;


void Usage(void){
	printf("burnls [-t threads] [-m memory] [-v/-h]\n");
	printf("\t[optional]\n");
	printf("\tthreads\t= unsigned integer number of Action threads to use (Defaults to 2).\n");
	printf("\tmemory\t= unsigned integer number of megabytes to use (Defaults to 512).\n");
	printf("\t-v or -h\t= print this menu and exit\n");
}

/*
uint32_t lrand(uint64_t *s){
    // initial value of s must be 2 or higher
    uint64_t hi = *(uint64_t *)s / 41943011;
    uint64_t lo = *(uint64_t *)s % 41943011;
    *(uint64_t *)s = *(uint64_t *)s * lo - 2147483647 * hi;
    return (*(uint64_t *)s & 0xFFFFFFFF00000000) >> 32;
}

#define LRAND(s) \
(((s) = (s) * 41943011 - 2147483647) >> 32)
*/
/*
#define SET_ZERO(array, src_y, src_x) \
((array)[(size_t)(src_y)][(size_t)(src_x)/8] ^= 1 << ((src_x) % 8))
#define SET_ONE(array, src_y, src_x) \
((array)[(size_t)(src_y)][(size_t)(src_x)/8] |= 1 << ((src_x) % 8))
#define IS_SET(array, src_y, src_x) \
((array)[(size_t)(src_y)][(size_t)(src_x)/8] & 1 << ((src_x) % 8))

#define SET_ZERO(array, byte, bit) \
((array)[(size_t)(byte)] ^= 1 << (bit))
#define SET_ONE(array, byte, bit) \
((array)[(size_t)(byte)] |= 1 << (bit))
#define IS_SET(array, byte, bit) \
((array)[(size_t)(byte)] & 1 << (bit))
*/


//2^b7 * 2^b6 * 2^b5 * 2^b4 * 2^b3 * 2^b2 * 2^b1 * 2^b0

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

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


char Gen_Char(char bit){
	char result = 0;
	char b0 = (bit == 0) ? 1 : 0;
	char b1 = (bit == 1) ? 1 : 0;
	char b2 = (bit == 2) ? 1 : 0;
	char b3 = (bit == 3) ? 1 : 0;
	char b4 = (bit == 4) ? 1 : 0;
	char b5 = (bit == 5) ? 1 : 0;
	char b6 = (bit == 6) ? 1 : 0;
	char b7 = (bit == 7) ? 1 : 0;


	result = (b7*2^7) + (b6*2^6) + (b5*2^5) + (b4*2^4) + (b3*2^3) + (b2*2^2) + (b1*2^1) + b0;



	return result;
}
/*
0

1

2

3

4

5

6

7

#define SET_ZERO(array, byte, bit) \
((array)[(size_t)(byte)] ^= 1 << ((bit)%8))
#define SET_ONE(array, byte, bit) \
((array)[(size_t)(byte)] |= 1 << ((bit)%8))
#define IS_SET(array, byte, bit) \
(((array)[(size_t)(byte)] & 1 << ((bit)%8))>>(bit))


uint32_t Is_Offset_Bit(size_t offset, size_t byte, size_t bit){
	//b128 big_number = (b128)byte*bit;
	if(byte == 0){
		if(bit == 0)
			return 0;
	}
	uint128_t result = ((uint128_t)byte*8 + bit + 1);
	if(result%offset == 0)
		return 1;
	return 0;
}


uint8_t Is_Thread_Offset(t, byte, bit){
	if(byte == 0){
		if(bit == 0)
			return 0;
	}
	uint128_t result = ((uint128_t) byte * bit);

	if(result%t == 0)
		return 1;
	return 0;
}
*/

void *Action_Thread(void *tid){
	uint32_t t = *(unsigned int *)tid;
	//uint64_t s = (uint64_t)&t;
	//uint32_t num1 = 0;
	size_t mem_byte = 0;
	char byte_s = 0;
	char byte_c = 0;
	uint32_t offset = 2;
	uint32_t region = 0;
	uint64_t pass_num = 0;
	uint32_t burn_max = 16;
	uint32_t burn_count = 0;
	double tmp = 0;
	struct timeval cur;
	//double time1 = 0;
	gettimeofday(&cur, NULL);
	uint64_t rseed = (uint64_t)cur.tv_usec + 3 + t;
	uint32_t rand1 = LRAND(rseed) % 2;
	//uint128_t bit_c = 0;
	//uint128_t region_t = 0;

	while(No_Exit){
		//Set Offsets
		region = 0;
		byte_s = Gen_Char(byte_c);
		if(t == Manager_t){
			gettimeofday(&Starttime, NULL);
			printf("Pass %lu started with %u byte offset\n", pass_num, offset);
		}
		for(mem_byte=0; mem_byte<Memory; mem_byte+=offset){
			//if(mem_byte % offset == 0){
			if(region == t)
				memory[mem_byte] = byte_s;
			if(region == Threads-1)
				region = 0;
			else
				region++;
			//}
		}
		(void)pthread_barrier_wait(&Barrier1);
		if(t == Manager_t){
			gettimeofday(&Endtime, NULL);
			time1 = ((double)(Endtime.tv_sec*1000000-Starttime.tv_sec*1000000+Endtime.tv_usec-Starttime.tv_usec))/1000000;
			printf("\tpass %lu: offsets zeroed in %.04lfs %.04lfMB/s\n", pass_num, time1, Memory_MB/offset/time1);
		}

		for(burn_count=0; burn_count<burn_max; burn_count++){
			region = 0;
			//Set Non-Offsets
			if(t == Manager_t)
				gettimeofday(&Starttime, NULL);
			for(mem_byte=0; mem_byte<Memory; mem_byte++){
				if(mem_byte % offset != 0){
					if(region == t){
						rand1 = LRAND(rseed) % 8;
						memory[mem_byte] = Gen_Char((char)rand1);
					}
					if(region == Threads-1)
						region = 0;
					else
						region++;
				}
			}
		}
		(void)pthread_barrier_wait(&Barrier2);
		if(t == Manager_t){
			gettimeofday(&Endtime, NULL);
			time1 = ((double)(Endtime.tv_sec*1000000-Starttime.tv_sec*1000000+Endtime.tv_usec-Starttime.tv_usec))/1000000;
			tmp = (double)Memory_MB;
			tmp -= (double)Memory_MB/offset;
			printf("\tpass %lu: non-offset burn in complete in %.04lfs %.04lfMB/s\n", pass_num, time1, tmp*burn_max/time1);
		}


		//Check Offsets
		region = 0;
		if(t == Manager_t)
			gettimeofday(&Starttime, NULL);
		for(mem_byte=0; mem_byte<Memory; mem_byte+=offset){
			if(region == t){
				if(memory[mem_byte] != byte_s){
					printf("Error Detected, bit flip! %lu\n", mem_byte);
					(void)pthread_mutex_lock( &Mutex1 );
					No_Exit = 0;
					(void)pthread_mutex_unlock( &Mutex1 );
					break;
				}
				if(region == Threads-1)
					region = 0;
				else
					region++;
			}
		}
		(void)pthread_barrier_wait(&Barrier3);
		if(No_Exit == 0)
			pthread_exit(NULL);
		if(t == Manager_t){
			gettimeofday(&Endtime, NULL);
			time1 = ((double)(Endtime.tv_sec*1000000-Starttime.tv_sec*1000000+Endtime.tv_usec-Starttime.tv_usec))/1000000;
			printf("\tpass %lu: offsets verified in %.04lfs %.04lfMB/s\n", pass_num, time1, Memory_MB/offset/time1);
		}
		if(offset == 21){
			offset = 2;
			if(t == Manager_t){
				printf("Pass %lu completed (no errors).\n", pass_num);
				free(memory);
				sleep(1);
				memory = (char *)malloc(Memory);
			}
			pass_num++;
		}else{
			offset++;
			pass_num++;
		}
		if(byte_c < 7)
			byte_c++;
		else
			byte_c = 0;

		(void)pthread_barrier_wait(&Barrier4);
	}
	pthread_exit(NULL);
}


int main(int argc, char **argv){
	uint32_t n1 = 0;
	uint32_t n2 = 0;
	uint32_t Tcount = 0;
	uint32_t ret_val = 0;
	//uint32_t *options = (uint32_t *)malloc( sizeof(uint32_t) * 2 );
	uint32_t options[2];
	(void)memset((void *)options, '\0', sizeof(uint32_t) * 2 );
	if(argc > 1){
		for(n1=1; n1<argc; n1++){
			if( strcmp( (const char *)argv[n1], "-t") == 0){
				options[0] = n1+1;
				if(n1+1 > argc-1)
					n2 = 1;
			}
			if( strcmp( (const char *)argv[n1], "-m") == 0){
				options[1] = n1+1;
				if(n1+1 > argc-1)
					n2 = 1;
			}
			if( strcmp( (const char *)argv[n1], "-v") == 0){
				if(n1+1 > argc-1)
					n2 = 1;
			}
			if( strcmp( (const char *)argv[n1], "-h") == 0){
				if(n1+1 > argc-1)
					n2 = 1;
			}
			if(n2){
				Usage();
				//free(options);
				exit(1);
			}
		}
	}
	n2 = 1;
	
	if(options[0] != 0){
		Threads = atoi(argv[options[0]]);
		if(Threads == 0){
			printf("Error: invalid Threads number!\n");
			Usage();
			exit(1);
		}
	}
	if(options[1] != 0){
		Memory = (size_t)((double)atoi(argv[options[1]])*1024*1024);
		if(Memory == 0){
			printf("Error: invalid Memory number!\n");
			Usage();
			exit(1);
		}
	}

	//allocate memory
	memory = (char *)malloc(Memory);
	printf("%lu bytes of memory allocated\n", Memory);
	(void)memset(memory, '\0', Memory);
	Memory_MB = Memory/1024/1024;

	ret_val = pthread_barrier_init(&Barrier1, NULL, Threads);
	if(ret_val != 0){
		printf("Error: pthread_barrier_init Failed: %d\n", ret_val);
		printf("\tExiting!\n");
		free(memory);
		exit(1);
	}
	ret_val = pthread_barrier_init(&Barrier2, NULL, Threads);
	if(ret_val != 0){
		printf("Error: pthread_barrier_init Failed: %d\n", ret_val);
		printf("\tExiting!\n");
		free(memory);
		(void)pthread_barrier_destroy(&Barrier1);
		exit(1);
	}
	ret_val = pthread_barrier_init(&Barrier3, NULL, Threads);
	if(ret_val != 0){
		printf("Error: pthread_barrier_init Failed: %d\n", ret_val);
		printf("\tExiting!\n");
		free(memory);
		(void)pthread_barrier_destroy(&Barrier1);
		(void)pthread_barrier_destroy(&Barrier2);
		exit(1);
	}
	ret_val = pthread_barrier_init(&Barrier4, NULL, Threads);
	if(ret_val != 0){
		printf("Error: pthread_barrier_init Failed: %d\n", ret_val);
		printf("\tExiting!\n");
		free(memory);
		(void)pthread_barrier_destroy(&Barrier1);
		(void)pthread_barrier_destroy(&Barrier2);
		(void)pthread_barrier_destroy(&Barrier3);
		exit(1);
	}

	pthread_t *Actions_thread_ptrs = (pthread_t *)malloc(sizeof(pthread_t) * Threads );
	unsigned int *Actions_t_ids    = (unsigned int *)malloc( sizeof(unsigned int) * Threads );
	//Actions_t_info = (struct Thread_I *)malloc( sizeof(struct Thread_I) * Threads );
	for(n1=0; n1<Threads; n1++){
		Actions_t_ids[n1] = n1;
	}
	printf("Launching %u Action threads.\n", Threads);
	for(Tcount = 0; Tcount < Threads; Tcount++){
		if(pthread_create(&Actions_thread_ptrs[Tcount], NULL, Action_Thread, (void*)&Actions_t_ids[Tcount]) != 0){
			printf("Error: pthread_create failed for action thread: %d !\n", Tcount);
			printf("\tExiting!\n");
			free(memory);
			free(Actions_thread_ptrs);
			free(Actions_t_ids);
			//free(Actions_t_info);
			(void)pthread_barrier_destroy(&Barrier1);
			(void)pthread_barrier_destroy(&Barrier2);
			(void)pthread_barrier_destroy(&Barrier3);
			(void)pthread_barrier_destroy(&Barrier4);
			exit(1);
		}
	}
	for(Tcount=0; Tcount < Threads; Tcount++){
		if(pthread_join(Actions_thread_ptrs[Tcount], NULL) != 0){
			printf("Error: pthread_join failed for action thread: %d !\n", Tcount);
			printf("\tExiting!\n");
			free(memory);
			free(Actions_thread_ptrs);
			free(Actions_t_ids);
			//free(Actions_t_info);
			(void)pthread_barrier_destroy(&Barrier1);
			(void)pthread_barrier_destroy(&Barrier2);
			(void)pthread_barrier_destroy(&Barrier3);
			(void)pthread_barrier_destroy(&Barrier4);
			exit(1);
		}
	}

	printf("Done, Exiting!\n");
	free(memory);
	free(Actions_thread_ptrs);
	free(Actions_t_ids);
	//free(Actions_t_info);
	(void)pthread_barrier_destroy(&Barrier1);
	(void)pthread_barrier_destroy(&Barrier2);
	(void)pthread_barrier_destroy(&Barrier3);
	(void)pthread_barrier_destroy(&Barrier4);
	return 0;
}
