#include <stdint.h>

#include "iden.h"
#include "lex.h"
#include "../lib/stdio.h"

static int lex_error(struct file* state, const char* err) {
	printf("%s:%d:%d: %s\n", state->name, state->row+1, state->col+1, err);
	return 1;
}

static size_t strlen(const char* str) {
	size_t len = 0;
	while(*(str++))
		len++;
	return len;
}

static bool strneq(const char* l, const char* r, size_t n) {
	for(size_t i = 0; i < n; i++) {
		if(r[i] != l[i])
			return false;
	}

	return true;
}

static char* index(char* str, int ch) {
	for(;*str && *str != ch; str++)
		;
	return *str?str : NULL;
}


#define MAX_TOK (1024*1024)
struct token token_pool[MAX_TOK];
size_t token_next = 0;

static struct token* tok(enum tokentype type, struct file* state, int col, void* val) {
	struct token* ret = &token_pool[token_next];
	if(++token_next >= MAX_TOK) {
		printf("OUT OF TOKENS!!!!");
		hang();
	}

	ret->type = type;
	ret->row = state->row;
	ret->col = col;
	ret->file = state;
	ret->next = NULL;
	ret->len = state->col-col;

	switch(type) {
		case TOK_OP:
			ret->op = *(enum op*)val;
		break;
		case TOK_KEYWORD:
			ret->keyword = *(enum keyword*)val;
		break;
		case TOK_NUM:
			ret->num = *(int64_t*)val;
		break;
		case TOK_IDEN:
			ret->iden = *(size_t*)val;
		break;
		default:
		break;
	}

	return ret;
}



static bool isdigit(int ch) {
	return (ch <= '9' && ch >= '0');
}

static bool isalpha(int ch) {
	return (ch <= 'z' && ch >= 'a')
		|| (ch <= 'Z' && ch >= 'A');
}

static bool isalnum(int ch) {
	return isdigit(ch) || isalpha(ch);
}

static int toupper(int ch) {
	if(ch >= 'a' && ch <= 'z')
		ch += 'A'-'a';
	return ch;
}

static int tolower(int ch) {
	if(ch >= 'A' && ch <= 'Z')
		ch += 'a'-'A';
	return ch;
}

static bool isxdigit(int ch) {
	return isdigit(ch) || (isalpha(ch) && tolower(ch) <= 'f');
}

// cribbed from musl
static bool isspace(int ch) {
	return ch == ' ' || (unsigned)ch-'\t' < 5;
}

static int parse_num(struct file* state, int save, int sign) {
	int64_t num = 0;
	int ch = state->lines[state->row][state->col];
	if(ch == '0' && state->lines[state->row][state->col+1] == 'x') {
		state->col+=2;
		if(!isxdigit(state->lines[state->row][state->col])) {
			printf("On input: '%c'\n", state->lines[state->row][state->col]);
			lex_error(state, "expected hexadecimal digits in hex literal");
			return 1;
		}

		while(isxdigit(state->lines[state->row][state->col])) {
			num <<= 4;
			ch = toupper(state->lines[state->row][state->col]);

			if(ch <= '9') {
				num += (ch - '0');
			} else {
				num += (ch - 'A' + 10);
			}
		}
	} else {
		while(isdigit(state->lines[state->row][state->col])) {
			num *= 10;
			num += state->lines[state->row][state->col] - '0';
			state->col++;
		}
		if(isalpha(state->lines[state->row][state->col])) {
			lex_error(state, "invalid decimal digit in decimal literal");
			return 1;
		}
	}

	num *= sign;
	*state->tail = tok(TOK_NUM, state, save, &num);
	state->tail = &((*state->tail)->next);
	return 0;
}

/* Lexing
	number :- [0-9]+ | 0x[0-9A-Fa-f]+
	keyword :- 'if' | 'else'
	identifier :- [A-z_][0-9A-z_]*
	operator :- .....
*/
int lex(struct file* state) {
	if(state->row >= state->rows)
		return 0;
	while(isspace(state->lines[state->row][state->col])) {
		if(state->lines[state->row][state->col] == '\n' || !state->lines[state->row][state->col]) {
			state->col = 0;
			state->row++;
			if(state->row >= state->rows)
				return 0;
		} else {
			state->col++;
		}
	}

	if(state->lines[state->row][state->col] == '/' && state->lines[state->row][state->col+1] == '*') {
		state->col += 2;
		while(!(state->lines[state->row][state->col] == '*' && state->lines[state->row][state->col+1] == '/')) {
			if(state->lines[state->row][state->col] == '\n' || !state->lines[state->row][state->col]) {
				state->col = -1;
				state->row++;
				if(state->row >= state->rows)
					return lex_error(state, "unexpected EOF, comment not closed");
			}
			state->col++;
		}
		state->col += 2;
		if(state->lines[state->row][state->col] == '\n' || !state->lines[state->row][state->col]) {
			state->row++;
			state->col = 0;
		}
		return lex(state);
	}


	int ch = state->lines[state->row][state->col];
	int nextch = state->lines[state->row][state->col+1];

	/* single line comment */
	if(ch == nextch && ch == '/') {
		state->row++;
		state->col = 0;
		return lex(state);
	}

	int save = state->col;

	if(ch == '-' && isdigit(state->lines[state->row][state->col+1])) {
		state->col++;
		int e = parse_num(state, save, -1);
		return e + lex(state);
	} else if(isdigit(ch)) {
		int e = parse_num(state, save, 1);
		return e + lex(state);
	} else if(isalpha(ch) || ch == '_') {
		size_t len = 0;
		char* str = &state->lines[state->row][state->col];	
		u64 hash = 0xcbf29ce484222325;
		while(state->lines[state->row][state->col] == '_' || isalnum(state->lines[state->row][state->col])) {
			hash = (hash ^ state->lines[state->row][state->col]) * 0x100000001b3;
			len++;
			state->col++;
		}
		for(size_t i = 0; i < sizeof(keywords)/sizeof(char*); i++) {
			if(strlen(keywords[i]) == len && strneq(keywords[i], str, len)) {
				*state->tail = tok(TOK_KEYWORD, state, save, &i);
			}
		}
		if(!*state->tail) {
			size_t idx = register_iden(str, len, hash);
			*state->tail = tok(TOK_IDEN, state, save, &idx);
		}
	} else if(index("+-/%*|&^~><=!",state->lines[state->row][state->col])) {
		for(int i = 0; (size_t)i < sizeof(ops)/sizeof(char*); i++) {
			if(strneq(&state->lines[state->row][state->col], ops[i], strlen(ops[i]))) {
				state->col += strlen(ops[i]);
				*state->tail = tok(TOK_OP, state, save, &i);
				break;
			}
		}
	} else if(index("[({,;})]", state->lines[state->row][state->col])) {
		state->col++;
		*state->tail = tok(state->lines[state->row][state->col-1], state, save, NULL);
	} else {
		lex_error(state, "unrecognized character");
		printf("Character: (0x%x) '%c'\n", ch, ch);
		state->col++;
		return 1 + lex(state);
	}

	state->tail = &(*state->tail)->next;

	return lex(state);
}
