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

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

#include "../xutil.h"
#include "../sankowski.h"
#include "../naive.h"

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

bool optimized = false;

void printusage(char *argv[]) {
	fprintf(stdout, "Usage: %s \n"
			"\t[-f test folder]\n"
			"\t[-i input file]\n"
			, argv[0]);
}

void read_and_run(char *filename) {
	size_t bufsize = 256;
	uint32_t i, j, n;
	uint32_t query;
	uint32_t count = 0,
			 tccount = 0;
	int c = 0;
	int res;

	FILE *fd;
	char *buf = xmalloc(bufsize);

	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);
	naive_t *naive = naive_init(n);
	bitnaive_t *bitnaive = bitnaive_init(n);

	while ((c = getline(&buf, &bufsize, fd)) != -1) {
		count++;

		if (count % 1000 == 0) {
			printf("Tested %"PRIu32" lines of file: %s\n", count, filename);
		}

		res = sscanf(buf, "insert(%"PRIu32",%"PRIu32")\n", &i, &j);
		if (res == 2) {
			naive_insert(naive, i, j);
			bitnaive_insert(bitnaive, i, j);
			sankowski_insert(s, i, j);
			continue;
		}

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

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

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

		res = strncmp(buf, "transitive closure?\n", c);
		if (res == 0) {
			tccount++;
			query = sankowski_query(s);
			if (query != naive_query(naive)) {
				printf("Count: %"PRIu32"\n", tccount);
				matrix_print(s->B);
				printf("\n\n\n");
				matrix_print(naive->T0);
				printf("\n\n\n");
				printf("\n\n\n");
				matrix_print(s->C);
				printf("\n\n\n");
				matrix_print(naive->Tn);
				xerror("The Sankowski and naive implementation did not "
					   "reply the same", __LINE__, __FILE__);
			}

			if (bitnaive_query(bitnaive) != naive_query(naive)) {
				printf("Count: %"PRIu32"\n", tccount);
				bitmatrix_print(bitnaive->T0);
				printf("\n\n\n");
				matrix_print(naive->T0);
				printf("\n\n\n");
				printf("\n\n\n");
				bitmatrix_print(bitnaive->Tn);
				printf("\n\n\n");
				matrix_print(naive->Tn);
				xerror("The bitnaive and naive implementation did not "
					   "reply the same", __LINE__, __FILE__);
			}

			continue;
		}

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

	fclose(fd);

	naive_destroy(naive);
	sankowski_destroy(s);

	free(buf);
}

void print_progress_sankowski(char *input) {
	size_t bufsize = 256;
	uint32_t i, j, n, count = 0;
	int c = 0;
	int res;

	FILE *fd;
	char *buf = xmalloc(bufsize);

	fd = fopen(input, "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, optimized);
			continue;
		}

		res = strncmp(buf, "transitive closure?\n", c);
		if (res == 0) {
			count++;

			printf("Count: %"PRIu32"\n", count);
			printf("B MATRIX:\n");
			matrix_print(s->B);

			printf("C MATRIX:\n");
			matrix_print(s->C);

			printf("\n");
			continue;
		}

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

	fclose(fd);

	sankowski_destroy(s);

	free(buf);
}

void print_progress_naive(char *input) {
	size_t bufsize = 256;
	uint32_t i, j, n;
	int c = 0;
	int res;

	FILE *fd;
	char *buf = xmalloc(bufsize);

	fd = fopen(input, "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 *s = naive_init(n);

	while ((c = getline(&buf, &bufsize, fd)) != -1) {

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

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

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

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

		res = strncmp(buf, "transitive closure?\n", c);
		if (res == 0) {
			printf("0 MATRIX:\n");
			matrix_print(s->T0);

			printf("n MATRIX:\n");
			matrix_print(s->Tn);

			printf("\n");
			continue;
		}

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

	fclose(fd);

	naive_destroy(s);

	free(buf);
}

void print_progress_bit(char *input) {
	size_t bufsize = 256;
	uint32_t i, j, n;
	int c = 0;
	int res;

	FILE *fd;
	char *buf = xmalloc(bufsize);

	fd = fopen(input, "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 *s = bitnaive_init(n);

	while ((c = getline(&buf, &bufsize, fd)) != -1) {

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

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

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

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

		res = strncmp(buf, "transitive closure?\n", c);
		if (res == 0) {
			printf("0 MATRIX:\n");
			bitmatrix_print(s->T0);

			printf("n MATRIX:\n");
			bitmatrix_print(s->Tn);

			printf("\n");
			continue;
		}

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

	fclose(fd);

	bitnaive_destroy(s);

	free(buf);
}

int main(int argc, char **argv) {
	int opt;
	uint32_t i;
	uint32_t filecount = 0;
	bool show  = false,
		 naive = false,
		 bit   = false;

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

	while ((opt = getopt(argc, argv, "i:f:p:snob")) != -1) {
		switch (opt) {
			case 'f':
				folder = strndup(optarg, 256);
				break;
			case 'i':
				input = strndup(optarg, 256);
				break;
			case 'p':
				p = strtol(optarg, NULL, 10);
				break;
			case 's':
				show = true;
				break;
			case 'n':
				naive = true;
				break;
			case 'o':
				optimized = true;
				break;
			case 'b':
				bit = true;
				break;
			default:
				printusage(argv);
				exit(EXIT_FAILURE);
		}
	}

	// 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++;
			}
		}
	}

	if (input == NULL) {
		for (i = 0; i < filecount; i++) {
			if (show) {
				if(naive) {
					print_progress_naive(files[i]);
				} else if(bit) {
					print_progress_bit(files[i]);
				} else {
					print_progress_sankowski(files[i]);
				}
			} else {
				read_and_run(files[i]);
			}
		}
	} else {
		if (show) {
			if(naive) {
				print_progress_naive(input);
			} else if(bit) {
				print_progress_bit(input);
			} else {
				print_progress_sankowski(input);
			}
		} else {
			read_and_run(input);
		}
	}

	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);
	}

	printf("SUCCESS\n");

	return EXIT_SUCCESS;
}
