#define _DEFAULT_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.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

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

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 create_annotation(char *filename) {
	size_t bufsize = 256;
	uint32_t i, j, n;
	uint32_t query;
	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__);
	}

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

			printf("%"PRIu32"\n", query);
			continue;
		}

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

	fclose(fd);

	bitnaive_destroy(naive);

	free(buf);
}

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

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

	FILE *ann_fd = fopen(ann, "r");
	if (ann_fd == NULL) {
		xerror("Unable to open input file\n", __LINE__, __FILE__);
	}

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

	int64_t v = 0;

	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) {
			count++;

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

			if (getline(&annbuf, &bufsize, ann_fd) < 0) {
				fprintf(stderr, "Annotated file not long enough\n");
				exit(EXIT_FAILURE);
			}

			res = sscanf(annbuf, "%"PRIu32"\n", &query);
			if (res != 1) {
				fprintf(stderr, "Badly annotated file\n");
				exit(EXIT_FAILURE);
			}

			v += query - sankowski_query(s);

			continue;
		}

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

	printf("%"PRIu64"\n", v);

	fclose(fd);

	sankowski_destroy(s);

	free(buf);
}

int main(int argc, char **argv) {
	int opt;
	uint32_t i;
	uint32_t filecount = 0;
	bool annotate = true;

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

	while ((opt = getopt(argc, argv, "i:f:c:p:")) != -1) {
		switch (opt) {
			case 'p':
				p = strtol(optarg, NULL, 10);
				break;
			case 'f':
				folder = strndup(optarg, 256);
				break;
			case 'i':
				input = strndup(optarg, 256);
				break;
			case 'c':
				annotate = false;
				ann = strndup(optarg, 256);
				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++;
			}
		}

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

	if (input == NULL) {
		if (annotate) {
			fprintf(stderr, "Annotate only works for a single file");
			exit(EXIT_FAILURE);
		}

		//for (i = 0; i < filecount; i++) {
		//	read_and_run(files[i]);
		//}
	} else {
		if (annotate) {
			create_annotation(input);
		} else {
			compare_annotation(input, ann);
		}
	}

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