#include "hl.h"

#include <assert.h>

vector_t token_table = {
	.data          = NULL,
	.element_size  = sizeof(token_t *),
	.element_count = 0UL
};

display_t * display_table = NULL;

// -------------------------
// ### Library Mangement ###
// -------------------------
hl_group_t * special_hl          = NULL;
hl_group_t * control_hl          = NULL;
hl_group_t * keyword_hl          = NULL;
hl_group_t * block_hl            = NULL;
hl_group_t * separator_hl        = NULL;
hl_group_t * operator_hl         = NULL;
hl_group_t * comment_hl          = NULL;
hl_group_t * string_literal_hl   = NULL;

// --------------------------------
// ### Constructors/Destructors ###
// --------------------------------

void new_display_mode(display_t * mode) {
	HASH_ADD_STR(display_table,
	             key,
	             mode);
}

int free_token(token_t * token) {
	free(token->hl);
	regex_free(token->syntax);

	return 0;
}

int append_token(token_t * token) {
	vector_push(&token_table, &token);

	return 0;
}

token_t * new_symbol_token(const char         * const c,
                                 hl_group_t   * const g) {

	token_t * mt = (token_t*)malloc(sizeof(token_t));

	mt->hl     = g;
	mt->t      = KEYSYMBOL;
	mt->syntax = regex_compile(c);

	append_token(mt);

	return mt;
}

int new_symbol_tokens(const char       * const *     symbols,
                            hl_group_t * const             g) {
	int i = 0;

	while (*symbols) {
		if(new_symbol_token(*symbols, g)) {
			++i;
		} else {
			assert(!(bool)"Kinda failed to new symbol token thing.");
		}
		++symbols;
	}

	return i;
}

int new_char_tokens(const char       *              str,
                          hl_group_t * const          g) {
	int i = 0;

	char buffer[3];
	buffer[0] = '\\';
	buffer[2] = '\0';

	for(const char * s = str; *s != '\0'; s++) {
		buffer[1] = *s;
		if(new_symbol_token(is_magic(*s) ? buffer : buffer + 1, g)) {
			++i;
		} else {
			assert(!(bool)"Kinda failed to new char token thing.");
		}
	}

	return i;
}

token_t * new_keyword_token(const char         * const word,
                                  hl_group_t   * const    g) {
	//char   * new_word = strdup(word);
	//size_t   word_length = strlen(word);
	//char   * new_word    = (char*)malloc(word_length + 4 + 1);

	//memcpy(new_word, "\\<", 2);
	//memcpy(new_word + 2, word, word_length);
	//strcpy(new_word + 2 + word_length, "\\>");

	token_t * mt = (token_t*)malloc(sizeof(token_t));

	mt->hl     = g;
	mt->t      = KEYWORD;
	//mt->syntax = regex_compile(new_word);
	mt->syntax = regex_compile(word);

	append_token(mt);

	return mt;
}

int new_keyword_tokens(const char       * const * words,
                             hl_group_t * const   g) {
	int i = 0;

	while (*words) {
		if(new_keyword_token(*words, g)) {
			++i;
		}
		++words;
	}

	return i;
}

token_t * new_region_token(const char       * start,
                           const char       *   end,
                                 hl_group_t *     g) {
	char buffer[100];
	buffer[0] = '\0';
	strcat(buffer, start);
	strcat(buffer, "[\\d\\D]*");
	strcat(buffer, end);

	token_t * mt = (token_t*)malloc(sizeof(token_t));

	mt->hl     = g;
	mt->t      = KEYSYMBOL;
	mt->syntax = regex_compile(buffer);

	append_token(mt);

	return mt;
}

token_t * new_token(const char         * const word,
                    const token_type_t            t,
                          hl_group_t   * const    g) {
	switch (t) {
		case KEYSYMBOL: {
			return new_symbol_token(word, g);
		}
		case KEYWORD: {
			return new_keyword_token(word, g);
		}
		case MATCH: {
			token_t * mt = (token_t*)malloc(sizeof(token_t));
				mt->hl     = g;
				mt->t      = MATCH;
				mt->syntax = regex_compile(word);
			append_token(mt);
		} break;
		case REGION: {
		} break;
	}

	return NULL;
}

// --------------------
// ### Highlighting ###
// --------------------

int token_fits(const token_t * const            token,
               const char    * const               to,
               const int                string_offset,
               const bool            is_start_of_line,
                     int     *           match_offset) {
	return 0;
	UNUSED(match_offset);

	match_t * matches = regex_match(token->syntax, to, is_start_of_line);

	if (matches->position == -1) {
		return 0;
	}

	const int r   = matches->width;
	*match_offset = matches->position;

	free(matches);

	return r;
}

void render_string(const char * const string,
                   const char * const   mode) {
	display_t * display;
	HASH_FIND_STR(display_table,
	              mode,
	              display);

	typedef struct {
		const token_t * t;
		const match_t * m;
		      int       i;
	} result_t;

	result_t * const r = (result_t *)malloc(sizeof(result_t) * 1024);	// XXX: dont
	int rrs = 0;
	for (size_t i = 0; i < token_table.element_count; i++) {
		token_t * t = *(token_t**)vector_get(&token_table,
		                                     i);
		match_t * match = regex_match(t->syntax, string, true);
		for(match_t * m = match; m->position != -1; m++){
			printf("%s: %d %d\n", t->syntax->str, m->position, m->width);
		}
		if (match->position == -1) {
			free(match);
			continue;
		}
		r[rrs++] = (result_t){
			.t = t,
			.m = match,
			.i = 0,
		};
	}

	for (const char * s = string; *s != '\00';) {
		const result_t sentinel = (result_t){NULL, &(match_t){ -1, -1}, -1};
		const result_t * max;
		max = &sentinel;
		for (int h = 0; h < rrs; h++) {
			result_t * const current_result = r + h;
			for (int j = 0; current_result->m[j].position != -1; j++) {
				if (current_result->m[j].position == (s - string)) {
					if (current_result->m[j].width > max->m->width) {
						current_result->i = j;
						max = current_result;
					}
					break;
				}
			}
		}
		if (max != &sentinel) {
			const match_t * mymatch = &(max->m[max->i]);
			const int padding = mymatch->position - (s - string);
			if (padding) {
				display->callback(s,
								  padding,
								  NULL);
			}
			display->callback(s + padding,
							  mymatch->width,
							  max->t->hl->attributes);
			s += padding + mymatch->width;
		} else {
			display->callback(s, 1, NULL);
			++s;
		}
	}
}

int hl_init(void) {
	return 0;
}

int hl_deinit(void) {
	//for (size_t i = 0; i < token_table.element_count; i++) {
	//	free_token(*(token_t**)vector_get(&token_table, i));
	//}

	return 0;
}