#include <err.h>
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

#define TODO() errx(EXIT_FAILURE, "%s:%d: todo", __FILE__,  __LINE__)

struct fwdval;
struct fwdvals;

struct fwdcnt {
	size_t pc;
	struct fwdvalues *ctx;
};

enum fwdtype {
	FWDSCRIPT_NUM,
	FWDSCRIPT_STR,
	FWDSCRIPT_CNT,
	FWDSCRIPT_TBL,
	FWDSCRIPT_OPQ
};

struct fwdval {
	enum fwdtype kind;

	union {
		double num;
		char *str;
		struct fwdcnt cnt;
		struct fwdtbl *tbl;
		void *opq;
	};
};

#define FWDSTR(s) (struct fwdval){.kind = FWDSCRIPT_STR, .str = (s)}

static int fwdcmp(const struct fwdval *a, const struct fwdval *b)
{
	if (a->kind != b->kind)
		return a->kind - b->kind;

	switch (a->kind) {
	case FWDSCRIPT_NUM: return a->num - b->num;
	case FWDSCRIPT_STR: return strcmp(a->str, b->str);
	case FWDSCRIPT_CNT: return a->cnt.pc - b->cnt.pc;
	case FWDSCRIPT_TBL: return (intptr_t)a->tbl - (intptr_t)b->tbl;
	case FWDSCRIPT_OPQ: return (intptr_t)a->opq - (intptr_t)b->opq;
	default: abort();
	}

	return 0;
}

static int fwdhash(const struct fwdval *a)
{
	switch (a->kind) {
	/* trash hashes, lol */
	case FWDSCRIPT_NUM: return a->num * 15647839;
	case FWDSCRIPT_STR: return a->str[0];
	case FWDSCRIPT_CNT: return a->cnt.pc * 15647839;
	case FWDSCRIPT_TBL: return 10;
	case FWDSCRIPT_OPQ: return (intptr_t)a->opq;
	default: abort();
	}

	return 0;
}

#define MAP_KEY struct fwdval
#define MAP_TYPE struct fwdval
#define MAP_CMP(A, B) fwdcmp(&(A), &(B))
#define MAP_HASH(A) fwdhash(&(A))
#define MAP_NAME fwdtbl
#include <conts/map.h>

#define MAP_KEY const char *
#define MAP_TYPE size_t
#define MAP_CMP(A, B) strcmp((A), (B))
#define MAP_HASH(A) (A)[0]
#define MAP_NAME fwdfuncs
#include <conts/map.h>

#define VEC_TYPE struct fwdval
#define VEC_NAME fwdvals
#include <conts/vec.h>

#define VEC_TYPE enum fwdtype
#define VEC_NAME fwdtypes
#include <conts/vec.h>

struct fwdinsn {
	/* use args[0] as destination, if /cnt then call the
	 * continuation, if /opq call builtin, otherwise abort */
	struct fwdvals args;
};

#define VEC_TYPE struct fwdinsn
#define VEC_NAME fwdinsns
#include <conts/vec.h>

struct fwdscript;

typedef int (*fwd_typecheck_t)(struct fwdscript *s, struct fwdtypes *types);
typedef int (*fwd_callback_t)(struct fwdscript *s, struct fwdvals *args);

struct fwdbuiltin {
	fwd_callback_t cb;
	fwd_typecheck_t tp;
};

#define MAP_KEY const char *
#define MAP_TYPE struct fwdbuiltin
#define MAP_CMP(A, B) strcmp((A), (B))
#define MAP_HASH(A) (A)[0]
#define MAP_NAME fwdbuiltins
#include <conts/map.h>

struct fwdscript {
	struct fwdfuncs funcs;
	struct fwdinsns insns;
	struct fwdbuiltins builtins;
};

static struct fwdscript fwdscript_create()
{
	struct fwdscript inst = {
		.funcs = fwdfuncs_create(1),
		.insns = fwdinsns_create(1),
		.builtins = fwdbuiltins_create(1)
	};

	return inst;
}

static int fwdscript_register(struct fwdscript *s, const char *name,
		fwd_callback_t cb, fwd_typecheck_t tp)
{
	struct fwdbuiltin builtin = {
		.cb = cb,
		.tp = tp
	};

	struct fwdbuiltin *b = fwdbuiltins_insert(&s->builtins,
			name, builtin);

	if (b == NULL) {
		fprintf(stderr, "failed registering %s\n", name);
		return -1;
	}

	return 0;
}

static int fwdscript_get(struct fwdscript *s, struct fwdvals *args)
{
	TODO();
}

static int fwdscript_get_typecheck(struct fwdscript *s, struct fwdtypes *types)
{
	TODO();
}

static int fwdscript_abort(struct fwdscript *s, struct fwdvals *args)
{
	TODO();
}

static int fwdscript_abort_typecheck(struct fwdscript *s, struct fwdtypes *types)
{
	TODO();
}

static int fwdscript_print(struct fwdscript *s, struct fwdvals *args)
{
	TODO();
}

static int fwdscript_print_typecheck(struct fwdscript *s, struct fwdtypes *args)
{
	TODO();
}

static int fwdscript_register_builtins(struct fwdscript *s)
{
	if (fwdscript_register(s, "get", fwdscript_get, fwdscript_get_typecheck))
		return -1;

	if (fwdscript_register(s, "abort", fwdscript_abort, fwdscript_abort_typecheck))
		return -1;

	if (fwdscript_register(s, "print", fwdscript_print, fwdscript_print_typecheck))
		return -1;

	return 0;
}

static const char *skip_ignored(const char *buf)
{
	while (*buf && isspace(*buf))
		buf++;

	if (*buf == '#')
	while (*buf && *buf != '\n')
		buf++;

	return buf;
}

static int separator(int c)
{
	return c == ';' || c == '{' || c == '}'
	    || c == ';' || c == '=' || c == '"'
	    || c == '\n';
}

static const char *next_separator(const char *buf)
{
	while (*buf && !isspace(*buf) && !separator(*buf))
		buf++;

	return buf;
}

struct fwdtoken {
	enum {
		FWDTOKEN_ID,
		FWDTOKEN_STR,
		FWDTOKEN_NUM,
		FWDTOKEN_OPEN,
		FWDTOKEN_CLOSE,
		FWDTOKEN_TO,
		FWDTOKEN_END
	} kind;

	size_t line;
	union {
		char *s;
		double d;
	};
};

#define VEC_TYPE struct fwdtoken
#define VEC_NAME fwdtokens
#include <conts/vec.h>

static int fwdscript_tokenize(struct fwdtokens *tokens, const char *buf)
{
	size_t line = 0;

	const char *start = buf;
	while (*start) {
		const char *tok = skip_ignored(start);
		if (!*tok)
			break;

		if (*tok == '\n') {
			line += 1;
			start = tok + 1;
			continue;
		}

		if (*tok == ';') {
			struct fwdtoken end = {
				.kind = FWDTOKEN_END,
				.line = line,
				.s    = NULL
			};

			if (!fwdtokens_append(tokens, end)) {
				fprintf(stderr, "failed appending end token\n");
				return -1;
			}

			start = tok + 1;
			continue;
		}

		if (*tok == '{') {
			struct fwdtoken open = {
				.kind = FWDTOKEN_OPEN,
				.line = line,
				.s    = NULL
			};

			if (!fwdtokens_append(tokens, open)) {
				fprintf(stderr, "failed appending open token\n");
				return -1;
			}

			start = tok + 1;
			continue;
		}

		if (*tok == '}') {
			struct fwdtoken close = {
				.kind = FWDTOKEN_CLOSE,
				.line = line,
				.s    = NULL
			};

			if (!fwdtokens_append(tokens, close)) {
				fprintf(stderr, "failed appending close token\n");
				return -1;
			}

			start = tok + 1;
			continue;
		}

		if (*tok == '=' && *(tok + 1) == '>') {
			struct fwdtoken to = {
				.kind = FWDTOKEN_TO,
				.line = line,
				.s    = NULL
			};

			if (!fwdtokens_append(tokens, to)) {
				fprintf(stderr, "failed appending to token\n");
				return -1;
			}

			start = tok + 2;
			continue;
		}

		if (*tok == '"') {
			size_t start_line = line;
			const char *end = tok + 1;
			while (*end && *end != '"') {
				if (*end == '\n') {
					line++;

					while (isblank(*end))
						end++;
				}

				if (*end == '\'')
					end++;

				end++;
			}

			if (!*end) {
				fprintf(stderr, "unterminated string at %zu\n", start_line);
				return -1;
			}

			char *s = strndup(tok + 1, end - tok - 2);
			if (!s) {
				fprintf(stderr, "failed duping str token\n");
				return -1;
			}

			struct fwdtoken str = {
				.kind = FWDTOKEN_STR,
				.line = start_line,
				.s    = s
			};

			if (!fwdtokens_append(tokens, str)) {
				fprintf(stderr, "failed appending str token\n");
				free(s);
				return -1;
			}

			start = end + 1;
			continue;
		}

		/* otherwise, read until next separation and check if we're
		 * maybe dealing with a number or a regular identifier */
		const char *end = next_separator(tok);

		char *dparse = NULL;
		double d = strtod(tok, &dparse);
		if (end == dparse) {
			/* all of the non-separators were consumed, so we have a
			 * number */
			struct fwdtoken num = {
				.kind = FWDTOKEN_NUM,
				.line = line,
				.d    = d
			};

			if (!fwdtokens_append(tokens, num)) {
				fprintf(stderr, "failed appending num token\n");
				return -1;
			}

			start = end + 1;
			continue;
		}

		char *s = strndup(tok, end - tok);
		if (!s) {
			fprintf(stderr, "failed duping id token\n");
			return -1;
		}

		struct fwdtoken id = {
			.kind = FWDTOKEN_ID,
			.line = line,
			.s    = s
		};

		if (!fwdtokens_append(tokens, id)) {
			fprintf(stderr, "failed appending id token\n");
			free(s);
			return -1;
		}

		start = end;
	}

	return 0;
}

static void free_tokens(struct fwdtokens *tokens)
{
	foreach(fwdtokens, token, tokens) {
		switch (token->kind) {
		case FWDTOKEN_STR:
		case FWDTOKEN_ID: free(token->s);
		default: break;
		}
	}

	fwdtokens_destroy(tokens);
}

static int fwdscript_parse(struct fwdscript *s, const char *buf)
{
	struct fwdtokens tokens = fwdtokens_create(0);

	if (fwdscript_tokenize(&tokens, buf)) {
		free_tokens(&tokens);
		return -1;
	}

	/* convert tokens into some kind of structure */
	TODO();
	free_tokens(&tokens);
	return 0;
}

static int fwdscript_run(struct fwdscript *s, const char *name, struct fwdvals *args)
{
	TODO();
}

static void fwdscript_destroy(struct fwdscript *s)
{
	fwdfuncs_destroy(&s->funcs);
	fwdinsns_destroy(&s->insns);
	fwdbuiltins_destroy(&s->builtins);
}

int main()
{
	int ret = 0;
	struct fwdscript inst = fwdscript_create();

	ret = fwdscript_register_builtins(&inst);
	assert(ret == 0);

	ret = fwdscript_parse(&inst,
			"main /tbl args {\n"
			"	get /str args 0 => {abort \"impossible\"}\n"
			"	=> /str line;\n"
			"	print line\n"
			"}\n");

	assert(ret == 0);

	struct fwdvals args = fwdvals_create(1);
	fwdvals_append(&args, FWDSTR(strdup("Hello world!\n")));

	fwdscript_run(&inst, "main", &args);

	fwdvals_destroy(&args);
	fwdscript_destroy(&inst);
}
