#include <assert.h>
#include <string.h>

typedef struct {
	int in;
	char input;
	int to;
} delta_t;

typedef struct {
	int in;
	int to;
} offshoot_t;

typedef struct {
	char * str;
	std::vector<delta_t> delta_table;
	std::vector<offshoot_t> catch_table;
	int accepting_state;
} regex_t;

#define HALT_AND_CATCH_FIRE -1

#define HOOK_ALL(from, str, to) do {                   \
	for (char * s = str; *s != '\00'; s++) {           \
		reg.delta_table.push_back(                     \
			delta_t{state + from, *s, state + to}      \
		);                                             \
	}                                                  \
	if (do_catch) {                                    \
		reg.catch_table.push_back(                     \
			{state + from, state + to}                 \
		);                                             \
	}                                                  \
} while (0)

#define EAT(n) do { \
	s += n;         \
} while (0)

bool is_quantifier(const char c){
	for (const char * s = "+*?"; *s != '\00'; s++) {
		if (*s == c) {
			return true;
		}
	}
	return false;
}


int escape_1_to_1(const char c, char * whitelist) {
	switch(c) {
		case 't': {
			strcat(whitelist, "\t");
		} return 1;
		case 'n': {
			strcat(whitelist, "\n");
		} return 1;
		case 'r': {
			strcat(whitelist, "\r");
		} return 1;
		case 'b': {
			strcat(whitelist, "\b");
		} return 1;
		case '[': {
			strcat(whitelist, "[");
		} return 1;
		case ']': {
			strcat(whitelist, "]");
		} return 1;
		case '.': {
			strcat(whitelist, ".");
		} return 1;
		case '?': {
			strcat(whitelist, "?");
		} return 1;
		case '+': {
			strcat(whitelist, "+");
		} return 1;
		case '*': {
			strcat(whitelist, "*");
		} return 1;
		case '\\': {
			strcat(whitelist, "\\");
		} return 1;
	}

	return 0;
}

int escape_1_to_N(const char c, char * whitelist) {
	switch(c) {
		case 'i': {
			const char identifier_chars[] = "@0123456789_\300\301\302\303\304\305\306\307\310\311\312\313\314\315\316\317\320\321\322\323\324\325\326\327\330\331\332\333\334\335\336\337";
			strcpy(whitelist, identifier_chars);
			return sizeof(identifier_chars)-1;
		};
		case 'I': {
			const char identifier_chars[] = "@_\300\301\302\303\304\305\306\307\310\311\312\313\314\315\316\317\320\321\322\323\324\325\326\327\330\331\332\333\334\335\336\337";
			strcpy(whitelist, identifier_chars);
			return sizeof(identifier_chars)-1;
		};
		case 'k': {
			const char keyword_chars[] = "@0123456789_\300\301\302\303\304\305\306\307\310\311\312\313\314\315\316\317\320\321\322\323\324\325\326\327\330\331\332\333\334\335\336\337";
			strcpy(whitelist, keyword_chars);
			return sizeof(keyword_chars)-1;
		};
		case 'K': {
			const char keyword_chars[] = "@_\300\301\302\303\304\305\306\307\310\311\312\313\314\315\316\317\320\321\322\323\324\325\326\327\330\331\332\333\334\335\336\337";
			strcpy(whitelist, keyword_chars);
			return sizeof(keyword_chars)-1;
		};
		case 'f': {
			const char filename_chars[] = "@0123456789/.-_+,#$%~=";
			strcpy(whitelist, keyword_chars);
			return sizeof(keyword_chars)-1;
		};
		case 'F': {
			const char filename_chars[] = "@/.-_+,#$%~=";
			strcpy(whitelist, keyword_chars);
			return sizeof(keyword_chars)-1;
		};
		case 'p': {
			const char printable_chars[] = "@\241\242\243\244\245\246\247\250\251\252\253\254\255\256\257\260\261\262\263\264\265\266\267\270\271\272\273\274\275\276\277\300\301\302\303\304\305\306\307\310\311\312\313\314\315\316\317\320\321\322\323\324\325\326\327\330\331\332\333\334\335\336\337";
			strcpy(whitelist, printable_chars);
			return sizeof(printable_chars)-1;
		};
		case 'P': {
			const char printable_chars[] = "@\241\242\243\244\245\246\247\250\251\252\253\254\255\256\257\260\261\262\263\264\265\266\267\270\271\272\273\274\275\276\277\300\301\302\303\304\305\306\307\310\311\312\313\314\315\316\317\320\321\322\323\324\325\326\327\330\331\332\333\334\335\336\337";
			strcpy(whitelist, printable_chars);
			return sizeof(printable_chars)-1;
		};
		case 's': {
			const char whitespace_chars[] = " \t\v\n";
			strcpy(whitelist, whitespace_chars);
			return sizeof(whitespace_chars)-1;
		};
		case 'd': {
			const char digit_chars[] = "0123456789";
			strcpy(whitelist, digit_chars);
			return sizeof(digit_chars)-1;
		};
		case 'x': {
			const char hex_chars[] = "0123456789abcdefABCDEF";
			strcpy(whitelist, hex_chars);
			return sizeof(hex_chars)-1;
		};
		case 'o': {
			const char oct_chars[] = "01234567";
			strcpy(whitelist, oct_chars);
			return sizeof(oct_chars)-1;
		};
		case 'w': {
			const char word_chars[] = "0123456789abcdefghijklmnopqrstuwxyzABCDEFGHIJKLMNOPQRSTUWXYZ_";
			strcpy(whitelist, word_chars);
			return sizeof(word_chars)-1;
		};
		case 'h': {
			const char very_word_chars[] = "abcdefghijklmnopqrstuwxyzABCDEFGHIJKLMNOPQRSTUWXYZ_";
			strcpy(whitelist, very_word_chars);
			return sizeof(very_word_chars)-1;
		};
		case 'a': {
			const char alpha_chars[] = "abcdefghijklmnopqrstuwxyzABCDEFGHIJKLMNOPQRSTUWXYZ";
			strcpy(whitelist, alpha_chars);
			return sizeof(alpha_chars)-1;
		};
		case 'l': {
			const char lower_alpha_chars[] = "abcdefghijklmnopqrstuwxyz";
			strcpy(whitelist, lower_alpha_chars);
			return sizeof(lower_alpha_chars)-1;
		};
		case 'u': {
			const char upper_alpha_chars[] = "ABCDEFGHIJKLMNOPQRSTUWXYZ";
			strcpy(whitelist, upper_alpha_chars);
			return sizeof(upper_alpha_chars)-1;
		};
	}

	return 0;
}

int compile_range(const char * const     range,
                        char *       whitelist) {
	assert(range[0] == '[' && "Not a range.");

	int r = 0;
	const char * s;
	for (s = range+1; *s != ']'; s++) {
		assert(*s != '\00' && "Unclosed range.");
		char c = *s;
		if (escape_1_to_1(c, whitelist)
		||  escape_1_to_N(c, whitelist)) {
			;
		} else if (*(s+1) == '-') {
			char end = *(s+2);
			assert(c < end && "Endless range.");
			for (char cc = c; cc < end+1; cc++) {
				strncat(whitelist,   &cc, 1);
				strncat(whitelist, "\00", 1);
			}
			s += 2;
		} else {
			++r;
			strncat(whitelist,    &c, 1);
			strncat(whitelist, "\00", 1);
		}
	}

	return ((s - range) + 1);
}

regex_t * regex_compile(const char * const pattern) {
	regex_t * r = new regex_t;
	regex_t &reg = *r;
	reg.str = strdup(pattern);

	int state = 0;

	char whitelist[64];
	bool do_catch;
	for (const char * s = pattern; *s != '\00';) {
		// Get token
		assert(!is_quantifier(*pattern) && "Pattern starts with quantifier.");
		whitelist[0] = '\00';
		do_catch     = false;
		switch (*s) {
			case '.': {
				do_catch = true;
			} break;
			case '\\': {
				EAT(1);
				if(escape_1_to_1(*s, whitelist)
				|| escape_1_to_N(*s, whitelist)){
					;
				} else {
					assert(!"Unknown escape.");
				}
			} break;
			case '[': {
				EAT(compile_range(s, whitelist)-1);
			} break;
			default: {
				whitelist[0] = *s;
				whitelist[1] = '\00';
			} break;
		}

		EAT(1);

		// Quantifier
		switch (*s) {
			case '?': {
				HOOK_ALL(0, whitelist, +1);
				EAT(1);
			} break;
			case '*': {
				HOOK_ALL(0, whitelist,  0);
				EAT(1);
			} break;
			case '+': {
				HOOK_ALL(0, whitelist, +1);
				state += 1;
				HOOK_ALL(0, whitelist,  0);
				EAT(1);
			} break;
			default: { // Literal
				HOOK_ALL(0, whitelist, +1);
				state += 1;
			} break;
		}
	}

	reg.accepting_state = state;

	return r;
}

inline bool catch_(const regex_t * regex,
                        int     & state) {

	const regex_t &reg = *regex;
	for (int i = 0; i < reg.catch_table.size(); i++){
		if (reg.catch_table[i].in == state) {
			state = reg.catch_table[i].to;
			return true;
		}
	}
	return false;
}

bool regex_assert(const regex_t * const  regex,
                  const char    * const string,
				        int              state) {

	const regex_t &reg = *regex;
	for (const char * s = string; *s != '\00'; s++) {
		// delta
		for (int i = 0; i < reg.delta_table.size(); i++) {
			if ((reg.delta_table[i].in == state) 
			&&  (reg.delta_table[i].input == *s)) {
				if(regex_assert(regex, s+1, reg.delta_table[i].to)){
					return true;
				}
			}
		}

		if (catch_(regex, state)) {
			continue;
		}

		return false;
	}

	return (state == regex->accepting_state);
}

bool regex_search(      regex_t *        regex,
                  const char    * const string) {

	if (regex == NULL) {
		return false;
	}
	if (string == NULL) {
		return true;
	}

	return regex_assert(regex, string, 0);
}