#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <conio.h>
#include <assert.h>
#include <windows.h>
#include <math.h>

typedef struct argParMap_tag {
	int id;
	void *data;
	size_t item_size;
	size_t from;
	size_t to;
	void (*f)(void*);
	pthread_barrier_t *bar;
} argParMap_t;

void* parMapTask(void *args) {
	argParMap_t *arg = (argParMap_t*) args;
	char     *from = (char*)arg->data + arg->from * arg->item_size;
	const char *to = (char*)arg->data + arg->to * arg->item_size;
	size_t size = arg->item_size;
	void (*f)(void*) = arg->f;
	int status;

	while (from < to) {
		f(from);
		from += size;
	}

	status = pthread_barrier_wait(arg->bar);
	if (status != 0 && status != PTHREAD_BARRIER_SERIAL_THREAD) {
		exit(status);
	}

	return 0;
}

void seqMap(void *data, size_t size, size_t item_size, void(*f)(void*)) {
	char     *from;
	const char *to;
	
	assert(data);
	assert(f);

	from = (char*)data;
	to   = (char*)data + size * item_size;

	while (from < to) {
		f(from);
		from += item_size;
	}

	return;
}

void parMap(void *data, size_t size, size_t item_size, void (*f)(void*), unsigned num_of_threads) {
	pthread_t *threads = NULL;	//потоки
	argParMap_t  *args = NULL;	//аргументы
	unsigned items_per_thread;	//число элементов массива на поток
	unsigned residue;			//остаток
	unsigned i;
	int status;
	pthread_barrier_t bar;		//барьер

	assert(num_of_threads <= size);
	assert(data);
	assert(f);

	threads = (pthread_t*)malloc(sizeof(pthread_t)*num_of_threads);
	assert(threads);
	args = (argParMap_t*)malloc(sizeof(argParMap_t)*num_of_threads);
	assert(args);

	status = pthread_barrier_init(&bar, NULL, num_of_threads + 1);
	if (status != 0) {
		exit(status);
	}

	items_per_thread = size / num_of_threads;
	residue = size % num_of_threads;

	for (i = 0; i < num_of_threads; i++) {
		args[i].id = i;
		args[i].data = data;
		args[i].f = f;
		args[i].from = i*items_per_thread;
		args[i].to = (i + 1)*items_per_thread;
		args[i].item_size = item_size;
		args[i].bar = &bar;
	}
	args[num_of_threads - 1].to += residue;
	
	for (i = 0; i < num_of_threads; i++) {
		status = pthread_create(&threads[i], NULL, parMapTask, (void*) &args[i]);
		if (status != 0) {
			exit(status);
		}
	}

	
	pthread_barrier_wait(&bar);
	for (i = 0; i < num_of_threads; i++) {
		pthread_join(threads[i], NULL);
	}
	pthread_barrier_destroy(&bar);

	free(threads);
	free(args);

	return;
}

void doubleInt(void *arg) {
	*((int*)arg) *= 2;
	return;
}

void doubleFloat(void *arg) {
	*((float*)arg) *= 2.0f;
	return;
}

void processDouble(void *arg) {
	double val = *(double*)arg;
	val = log(sqrt(1.0/val) + log(pow(val, 3.141592))) / (val*val);
	*(double*)arg = val;
	return;
}

void test_works() {
	int   datai[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	float dataf[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	int i;

	parMap(datai, 10, sizeof(int), doubleInt, 2);
	for (i = 0; i < 10; i++) {
		printf("%3d ", datai[i]);
	}
	printf("\n");
	seqMap(datai, 10, sizeof(int), doubleInt);
	for (i = 0; i < 10; i++) {
		printf("%3d ", datai[i]);
	}
	printf("\n-------------------\n");

	parMap(dataf, 10, sizeof(float), doubleFloat, 5);
	for (i = 0; i < 10; i++) {
		printf("%5.2f ", dataf[i]);
	}
	printf("\n");
	seqMap(dataf, 10, sizeof(float), doubleFloat);
	for (i = 0; i < 10; i++) {
		printf("%5.2f ", dataf[i]);
	}
	printf("\n");
	printf("press any key to continue...\n");
	_getch();
}

static const int test_data_size = 10000000;
static const int num_of_threads = 4;

void test_performance() {
	double *data = NULL;
	LARGE_INTEGER frequency;        // ticks per second
	LARGE_INTEGER t1, t2;           // ticks
	double elapsedTime;

	QueryPerformanceFrequency(&frequency);
	data = (double*)malloc(sizeof(double)*test_data_size);

	QueryPerformanceCounter(&t1); {
		seqMap(data, test_data_size, sizeof(double), processDouble);
	} QueryPerformanceCounter(&t2);
	elapsedTime = (t2.QuadPart - t1.QuadPart) * 1000.0 / frequency.QuadPart;
	printf("seq ready in %.3f ms\npress any key to continue...\n", elapsedTime);
	_getch();

	QueryPerformanceCounter(&t1); {
		parMap(data, test_data_size, sizeof(double), processDouble, num_of_threads);
	} QueryPerformanceCounter(&t2);
	elapsedTime = (t2.QuadPart - t1.QuadPart) * 1000.0 / frequency.QuadPart;
	printf("par ready in %.3f ms\npress any key to continue...\n", elapsedTime);
	_getch();

	free(data);
}

void main() {
	test_works();
	test_performance();
}