#define _XOPEN_SOURCE 700
#include <sys/types.h>

#include <mpi.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

enum HEADER {
	TRIP_ID = 0,
	ARRIVAL_TIME,
	DEPARTURE_TIME,
	STOP_ID,
	STOP_SEQUENCE,
	STOP_HEADSIGN,
	PICKUP_TYPE,
	DROP_OFF_TYPE,
	SHAPE_DIST_TRAVELED,
	TIMEPOINT,
	END
};

#define DELIM ','
#define OVERLAP 100
#define STOP_FILE "./stop_times.txt"

// adding key here!
int		do_map(char *, size_t, char *, int);
void	do_reduce(size_t);
int		parprocess(MPI_File *, const int, const int, char *);

int
main(int argc, char **argv)
{
	// Initialize the MPI environment
	MPI_Init(NULL, NULL);

	int world_size, world_rank;
	MPI_Comm_size(MPI_COMM_WORLD, &world_size);
	MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);

	char *key;
	if (argc != 2) {
		if (world_rank == 0)
			printf("no args ; indexing all\n");
		key = NULL;
	}
	else 
		key = argv[2];

	size_t res = 0;

	int number, size;
	ssize_t err;
	MPI_File in;

	if ((err = MPI_File_open(MPI_COMM_WORLD, STOP_FILE, MPI_MODE_RDONLY,
		 MPI_INFO_NULL, &in))) {
		fprintf(stderr, "%s: Couldn't open file %s\n", argv[0],
		    STOP_FILE);
		exit(-1);
	}
	MPI_Comm_size(MPI_COMM_WORLD, &size);

	res = parprocess(&in, world_rank, world_size, key);

	if (world_rank == 0) {
		for (char k = 0; k < world_size; k++) {
			MPI_Recv(&res, 1, MPI_UNSIGNED_LONG, MPI_ANY_SOURCE, 0,
			    MPI_COMM_WORLD, MPI_STATUS_IGNORE);
			if (res) {
				//printf("res: found in %lu ns\n", res); break;
			}
			else
				printf("res: not found\n");
		}
	}

	MPI_File_close(&in);
	MPI_Finalize();
	exit(0);
}

time_t
substr_time(struct tm a, struct tm b)
{
	return (a.tm_hour * 3600 + a.tm_min * 60 + a.tm_sec) -
		(b.tm_hour * 3600 + b.tm_min * 60 + b.tm_sec);
}

char *
get_word(char *lines, size_t x, size_t y, size_t num_attr, size_t max_attr)
{
	size_t offset = (x * (num_attr * max_attr) + y * (max_attr));
	return lines + offset;
}

void
fill_lines(char *chunk, size_t num_char, size_t num_lines, size_t num_attr,
	size_t max_attr, char *lines)
{
	size_t attr_pos = 0, line_pos = 0, word_pos = 0;
	for (size_t k = 0; k < num_char; ++k) {
		if (chunk[k] == DELIM) {
			// go to next attrib
			char *word = get_word(lines, line_pos, attr_pos,
				num_attr, max_attr);
			memcpy(word, chunk + k - word_pos, word_pos);
			word[word_pos] = '\0';
			++attr_pos;
			word_pos = 0;
		}
		else if (chunk[k] == '\n') {
			++line_pos;
			attr_pos = 0;
			word_pos = 0;
		}
		else if (chunk[k] == '\r') {}
		else {
			++word_pos;
		}
	}
}

void
get_lines_info(char *chunk, size_t num_char, size_t *max_attr_size,
	size_t *num_lines)
{
	// count max line size and number of lines
	size_t current_attr_size = 0;
	for (int k = 0; k < num_char; ++k) {
		// LINE
		if (chunk[k] == '\n' || chunk[k] == '\r')
			++(*num_lines);
		// ATTRIBUTES
		if (chunk[k] == DELIM) {
			if (current_attr_size > *max_attr_size)
				*max_attr_size = current_attr_size;
			current_attr_size = 0;
		} else
			++current_attr_size;
	}
}

// get num of cols of csv
size_t
get_num_attr(char *chunk)
{
	size_t num_attr = 0;
	for (size_t k = 0 ; chunk[k] != '\n' ; ++k)
		if (chunk[k] == DELIM)
			++num_attr;
	return num_attr;
}

int
search_key(char *lines, size_t num_lines, size_t num_attr, size_t max_attr,
    char *key)
{
	for (size_t k = 0; k < num_lines; ++k) {
		if (!strcmp(get_word(lines, k, TRIP_ID, num_attr, max_attr),
			key))
			return 1;
	}
	return 0;
}

int
get_max_time(char *lines, size_t num_lines, size_t num_attr, size_t max_attr)
{
	struct tm dep_time, arr_time;
	time_t max_time = 0;
	for (size_t k = 0; k < num_lines; ++k) {
		memcpy(&dep_time,
		    get_word(lines, k, DEPARTURE_TIME, num_attr, max_attr),
		    sizeof(struct tm));
		memcpy(&arr_time,
		    get_word(lines, k, ARRIVAL_TIME, num_attr, max_attr),
		    sizeof(struct tm));

		strptime(get_word(lines, k, DEPARTURE_TIME, num_attr, max_attr),
		    "%H:%M:%S", &dep_time);

		strptime(get_word(lines, k, ARRIVAL_TIME, num_attr, max_attr),
		    "%H:%M:%S", &arr_time);
		time_t tmp = substr_time(arr_time, dep_time);
		if (tmp > max_time)
			max_time = tmp;
	}
	return max_time;
}

int
do_map(char *chunk, size_t num_char, char *key, int rank)
{
	size_t num_lines = 0, num_attr = 0;
	size_t max_attr_size = 0;
	size_t time, res;

	get_lines_info(chunk, num_char, &max_attr_size, &num_lines);
	num_attr = get_num_attr(chunk);

	// allocate lines (just a big continuous chunk)
	// is a 2d arr of char*
	char *lines;
	lines = calloc(1, num_lines * num_attr * max_attr_size);

	fill_lines(chunk, num_char, num_lines, num_attr, max_attr_size, lines);

	struct timespec start_time, stop_time;

	// test all ; print all
	if (key == NULL) {
		char file_name[10];

		sprintf(file_name, "Out%d.txt", rank);
		FILE *file = fopen(file_name, "w");
		for (size_t k = 0; k < num_lines; ++k) {
			char *trip_name = get_word(lines, k, TRIP_ID, num_attr,
			    max_attr_size);
			clock_gettime(CLOCK_MONOTONIC, &start_time);
			// int res = get_max_time(lines, num_lines, num_attr,
			// max_attr_size);
			size_t res = search_key(lines, num_lines, num_attr,
			    max_attr_size, trip_name);

			clock_gettime(CLOCK_MONOTONIC, &stop_time);

			if (!res)
				continue; // dont print if err
			time = (stop_time.tv_sec - start_time.tv_sec) *
				100000000 +
			    (stop_time.tv_nsec - start_time.tv_nsec);
			fprintf(file, "%s:%lu\n", trip_name, time);
		}
		fclose(file);
		// just so we dont lock
		MPI_Send(&time, 1, MPI_UNSIGNED_LONG, 0, 0, MPI_COMM_WORLD);
	} else { // search for key
		clock_gettime(CLOCK_MONOTONIC, &start_time);
		// int res = get_max_time(lines, num_lines, num_attr,
		// max_attr_size);
		res = search_key(lines, num_lines, num_attr, max_attr_size,
		    key);

		clock_gettime(CLOCK_MONOTONIC, &stop_time);

		time = (stop_time.tv_sec - start_time.tv_sec) * 100000000 +
		    (stop_time.tv_nsec - start_time.tv_nsec);
		if (res)
			MPI_Send(&time, 1, MPI_UNSIGNED_LONG, 0, 0,
			    MPI_COMM_WORLD);
		else
			MPI_Send(&res, 1, MPI_UNSIGNED_LONG, 0, 0,
			    MPI_COMM_WORLD);
	}
	free(lines);
	free(chunk);
	return res;
}

// help from https://stackoverflow.com/questions/12939279/mpi-reading-from-a-text-file
// is hosted under a permissive licence, ty Jonathan Dursi :)
int
parprocess(MPI_File *in, const int rank, const int size, char *key)
{
	// reads revelant lines from file to chunk.
	// IN OUR CASE we will use overlap to reach EOF of this line. 
	// Duplicates dont matter in our case ; res will be the same either way.
	size_t proc_size, total_size, total_size_overlap;
	char *chunk;
	MPI_Offset globalstart;
	MPI_Offset globalend;
	MPI_Offset filesize;

	MPI_File_get_size(*in, &filesize);
	filesize--; /* get rid of text file eof */
	proc_size = filesize / size;
	globalstart = rank * proc_size;
	globalend = globalstart + proc_size - 1;
	if (rank == size - 1)
		globalend = filesize - 1;

	/* add overlap to the end of everyone's chunk except last
	 * proc... */
	size_t globalend_overlap = globalend;
	if (rank != size - 1)
		globalend_overlap += OVERLAP;

	total_size_overlap = globalend_overlap - globalstart + 1;
	total_size = globalend - globalstart + 1;

	/* allocate memory, filled with 0 */
	chunk = calloc(1, total_size);

	ssize_t err;
	{
		err = MPI_File_read_at_all_begin(*in, globalstart, chunk,
			total_size, MPI_CHAR);
		if (err) {
			printf("error %lu\n", err);
			MPI_Finalize();
		}
		err = MPI_File_read_at_all_end(*in, chunk, MPI_STATUS_IGNORE);
		if (err) {
			printf("error %lu\n", err);
			MPI_Finalize();
		}
	}

	// eh commenting this out, at worst we'll have one unusable line, but this
	// still works w/ padding
	// fills the first incoherent bytes with \0
	//size_t k = 0;
	//if (rank != 0) { // first has no incoherece at begining
	//	for (; chunk[k] != '\r' && chunk[k] != '\n';
	//		++k) // get number of incoherent bytes :)
	//		;
	//	// reset

	//	memmove(chunk, chunk + k, total_size); // - 2: dont count \n\r
	//}

	// fill char after next EOL wiht \0 ; starting from proc_size to end of
	// overlap
	//if (rank != size) { // last doesnt have padding, dont check it
	//	for (; (chunk[globalend] != '\n' && chunk[globalend] != '\r') &&
	//		globalend < globalend_overlap;
	//		++globalend)
	//		;
	//	memset(chunk + globalend, '\0', OVERLAP);
	//	//
	//}
	//chunk[total_size_overlap] = '\0'; // just to be sure!

	int max = do_map(chunk, total_size, key, rank);

	return max;
}
