#define _DEFAULT_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>

#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <getopt.h>
#include <dirent.h>

#include <measure.h>

#include "xutil.h"
#include "matrix.h"
#include "bitmatrix.h"
#include "sankowski.h"
#include "naive.h"

int64_t p = 2147483647; // 2^31 - 1
//int64_t p = 20021; // 2^31 - 1

static int
cmpstringp(const void *p1, const void *p2)
{
	/* The actual arguments to this function are "pointers to
	 * pointers to char", but strcmp(3) arguments are "pointers
	 * to char", hence the following cast plus dereference */

	return strcmp(* (char * const *) p1, * (char * const *) p2);
}


void printusage(char *argv[]) {
	fprintf(stdout, "Usage: %s [OPTIONS]\n"
			"OPTIONS:\n"
			"\t[-f <test folder>] Test all files for folder\n"
			"\t[-i <input file>]  Test a specific file\n"
			"\n"
			"\t[-s]               Run the sankowsi algorithm\n"
			"\t[-n]               Run the naive algorithm\n"
			"\n"
			"\t[-r <repetitions>] Do this many repetitions\n"
			"\t[-a <repetitions>] Average this many each repetition\n"
			"\n"
			"If neither -s or sankowski are specified, both algorithms are tested.\n"
			, argv[0]);
}

struct tc_measure {
	char *filename;
};

void read_and_run_bitnaive(struct tc_measure *restrict str) {
	char *filename = str->filename;
	FILE *fd;
	int res;
	int c = 0;
	uint32_t i, j, n;
	size_t bufsize = 256;
	char *buf = malloc(bufsize);
	uint32_t query;

	fd = fopen(filename, "r");
	if (fd == NULL) {
		xerror("Unable to open input file\n", __LINE__, __FILE__);
	}

	res = fscanf(fd, "init(%"PRIu32")\n", &n);
	if (res != 1) {
		xerror("Unable to match init\n", __LINE__, __FILE__);
	}

	bitnaive_t *naive = bitnaive_init(n);

	while ((c = getline(&buf, &bufsize, fd)) != -1) {
		res = sscanf(buf, "insert(%"PRIu32",%"PRIu32")\n", &i, &j);
		if (res == 2) {
			bitnaive_insert(naive, i, j);
			continue;
		}

		res = sscanf(buf, "delete(%"PRIu32",%"PRIu32")\n", &i, &j);
		if (res == 2) {
			bitnaive_delete(naive, i, j);
			continue;
		}

		/*
		res = strncmp(buf, "expand()\n", c);
		if (res == 0) {
			naive_expand(naive);
			continue;
		}

		res = sscanf(buf, "remove(%"PRIu32")\n", &i);
		if (res == 1) {
			naive_remove(naive, i);
			continue;
		}
		*/

		res = strncmp(buf, "transitive closure?\n", c);
		if (res == 0) {
			query = bitnaive_query(naive);
			(void) query;
			continue;
		}

		xerror("Unable to parse line of input", __LINE__, __FILE__);
	}

	fclose(fd);

	bitnaive_destroy(naive);

	free(buf);
}

void read_and_run_naive(struct tc_measure *restrict str) {
	char *filename = str->filename;
	FILE *fd;
	int res;
	int c = 0;
	uint32_t i, j, n;
	size_t bufsize = 256;
	char *buf = malloc(bufsize);
	uint32_t query;

	fd = fopen(filename, "r");
	if (fd == NULL) {
		xerror("Unable to open input file\n", __LINE__, __FILE__);
	}

	res = fscanf(fd, "init(%"PRIu32")\n", &n);
	if (res != 1) {
		xerror("Unable to match init\n", __LINE__, __FILE__);
	}

	naive_t *naive = naive_init(n);

	while ((c = getline(&buf, &bufsize, fd)) != -1) {
		res = sscanf(buf, "insert(%"PRIu32",%"PRIu32")\n", &i, &j);
		if (res == 2) {
			naive_insert(naive, i, j);
			continue;
		}

		res = sscanf(buf, "delete(%"PRIu32",%"PRIu32")\n", &i, &j);
		if (res == 2) {
			naive_delete(naive, i, j);
			continue;
		}

		res = strncmp(buf, "expand()\n", c);
		if (res == 0) {
			naive_expand(naive);
			continue;
		}

		res = sscanf(buf, "remove(%"PRIu32")\n", &i);
		if (res == 1) {
			naive_remove(naive, i);
			continue;
		}

		res = strncmp(buf, "transitive closure?\n", c);
		if (res == 0) {
			query = naive_query(naive);
			(void) query;
			continue;
		}

		xerror("Unable to parse line of input", __LINE__, __FILE__);
	}

	fclose(fd);

	naive_destroy(naive);

	free(buf);
}

void read_and_run_sankowski(struct tc_measure *restrict str) {
	char *filename = str->filename;
	FILE *fd;
	int res;
	int c = 0;
	uint32_t i, j, n;
	size_t bufsize = 256;
	char *buf = malloc(bufsize);
	uint32_t query;

	fd = fopen(filename, "r");
	if (fd == NULL) {
		xerror("Unable to open input file\n", __LINE__, __FILE__);
	}

	res = fscanf(fd, "init(%"PRIu32")\n", &n);
	if (res != 1) {
		xerror("Unable to match init\n", __LINE__, __FILE__);
	}

	sankowski_t *s = sankowski_init(n);

	while ((c = getline(&buf, &bufsize, fd)) != -1) {
		res = sscanf(buf, "insert(%"PRIu32",%"PRIu32")\n", &i, &j);
		if (res == 2) {
			sankowski_insert(s, i, j);
			continue;
		}

		res = sscanf(buf, "delete(%"PRIu32",%"PRIu32")\n", &i, &j);
		if (res == 2) {
			sankowski_delete(s, i, j);
			continue;
		}

		res = strncmp(buf, "expand()\n", c);
		if (res == 0) {
			sankowski_expand(s);
			continue;
		}

		res = sscanf(buf, "remove(%"PRIu32")\n", &i);
		if (res == 1) {
			sankowski_remove(s, i, true);
			continue;
		}

		res = strncmp(buf, "transitive closure?\n", c);
		if (res == 0) {
			query = sankowski_query(s);
			(void) query;
			continue;
		}

		xerror("Unable to parse line of input", __LINE__, __FILE__);
	}

	fclose(fd);

	sankowski_destroy(s);

	free(buf);
}


int main(int argc, char **argv) {
	int opt;
	uint32_t i, j;

	DIR *dir;
	struct dirent *entry;
	char **files = NULL;
	uint32_t filecount = 0;
	char *folder = NULL;
	char *input = NULL;

	uint32_t REPS = 10;
	uint32_t REPS_MEASURE = 10;

	struct tc_measure *tcm[REPS_MEASURE];

	bool sankowski = false, naive = false, bit = false;

	while ((opt = getopt(argc, argv, "a:r:i:f:nbsp:")) != -1) {
		switch (opt) {
			case 'p':
				p = strtol(optarg, NULL, 10);
				break;
			case 'f':
				folder = strndup(optarg, 256);
				break;
			case 'r':
				REPS = strtoul(optarg, NULL ,10);
				break;
			case 'a':
				REPS_MEASURE = strtoul(optarg, NULL, 10);
				break;
			case 'n':
				naive = true;
				break;
			case 's':
				sankowski = true;
				break;
			case 'b':
				bit = true;
				break;
			case 'i':
				input = strndup(optarg, 256);
				break;
			default:
				printusage(argv);
				exit(EXIT_FAILURE);
		}
	}

	// If neither sankowski or (bit)naive are specified, test both.
	if (sankowski == false && naive == false && bit == false) {
		sankowski = naive = bit = true;
	}

	// If neither input file or folder is given, we shutdown
	if (input == NULL && folder == NULL) {
		printusage(argv);
		exit(EXIT_FAILURE);
	}

	// If no specific input file was given
	if (input == NULL) {
		filecount = xfilesdir(folder);
		files = xcalloc(filecount, sizeof(char *));

		if ((dir = opendir(folder)) == NULL) {
			xerror(strerror(errno), __LINE__, __FILE__);
		}

		i = 0;
		while ( (entry = readdir(dir)) != NULL ) {
			if (entry->d_type == DT_REG) { /* If the entry is a regular file */
				files[i] = xmalloc(sizeof(char)*(256+strlen(folder)+1));
				memcpy(files[i], folder, strlen(folder));
				memcpy(&files[i][strlen(folder)], "/", 1);
				memcpy(&files[i][strlen(folder)+1], entry->d_name, 256);
				i++;
			}
		}


		qsort(files, filecount, sizeof(char *), cmpstringp);
	}

    if (!measure_init()) {
		xerror("Error initializing measure library", __LINE__, __FILE__);
	}

	// Allocate memory for doing repetitions.
	for (i = 0; i < REPS_MEASURE; i++) {
		tcm[i] = xmalloc(sizeof(struct tc_measure));
	}

	if (input == NULL) {

		// We are looking at a folder.
		for (i = 0; i < filecount; i++) {
			for (j = 0; j < REPS_MEASURE; j++) {
				tcm[j]->filename = files[i];
			}

			for (j = 0; j < REPS; j++) {
				if (naive) {
					measure("Naive", files[i], (testfunc)read_and_run_naive,
						(void*)&tcm, REPS_MEASURE);
				}

				if (sankowski) {
					measure("Sankowski", files[i], (testfunc)read_and_run_sankowski,
						(void*)&tcm, REPS_MEASURE);
				}

				if (bit) {
					measure("Bitnaive", files[i], (testfunc)read_and_run_bitnaive,
						(void*)&tcm, REPS_MEASURE);
				}
			}
		}
	} else {

		// We are looking at a specific file.
		for (j = 0; j < REPS; j++) {
			for (i = 0; i < REPS_MEASURE; i++) {
				tcm[i]->filename = input;
			}

			if (naive) {
				measure("Naive", input, (testfunc)read_and_run_naive,
					(void*)&tcm, REPS_MEASURE);
			}

			if (sankowski) {
				measure("Sankowski", input, (testfunc)read_and_run_sankowski,
					(void*)&tcm, REPS_MEASURE);
			}

			if (bit) {
				measure("Bitnaive", input, (testfunc)read_and_run_bitnaive,
					(void*)&tcm, REPS_MEASURE);
			}
		}
	}

	/*
	 * GENERIC CLEANUP BELOW
	 */

	measure_destroy();

	for (i = 0; i < REPS_MEASURE; i++) {
		if (tcm[i] != NULL) {
			free(tcm[i]);
		}
	}

	if (files != NULL) {
		for (i = 0; i < filecount; i++) {
			if (files[i] != NULL) {
				free(files[i]);
			}
		}
		free(files);
	}

	if (input != NULL) {
		free(input);
	}

	if (folder != NULL) {
		free(folder);
	}

	return EXIT_SUCCESS;
}
