/* SPDX-License-Identifier: copyleft-next-0.3.1 */
/* Copyright 2023 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */

/**
 * @file actualize.c
 *
 * Implementations for stuff actualization.
 */

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

#include <ek/actualize.h>
#include <ek/compiler.h>
#include <ek/debug.h>

#define UNUSED(x) do { (void)(x); } while (0)

static int replace_type_id(struct ast_node *nodes, struct ast_node *id,
                           struct ast_node *replacement);

struct act_stack {
	struct ast_node *node;
	struct act_stack *next;
};

enum act_flags {
	ACT_IN_LOOP = (1 << 0),
	ACT_IN_SWITCH = (1 << 1),
	ACT_HAS_RETURN = (1 << 2),
};

struct act_state {
	enum act_flags flags;

	struct ast_node *last_var;
	struct ast_node *cur_trait;
	struct ast_node *cur_proc;
	struct act_stack *defer_stack;
	struct act_stack *goto_stack;
	struct act_stack *label_stack;
};

static int is_lvalue(struct ast_node *n)
{
	if (n->node_type == AST_ARR_ACCESS)
		return 1;

	if (n->node_type == AST_ID)
		return 1;

	if (n->node_type == AST_UNOP && AST_UNOP(n).op == AST_DEREF)
		return 1;

	return 0;
}


static void act_set_flags(struct act_state *state, enum act_flags flags)
{
	state->flags |= flags;
}

static enum act_flags act_flags(struct act_state *state, enum act_flags flags)
{
	return state->flags & flags;
}

/* TODO: this could be improved if we get a global void thing */
static int is_void(struct ast_node *type)
{
	assert(type->node_type == AST_TYPE);
	if (AST_TYPE(type).kind != AST_TYPE_PRIMITIVE)
		return 0;

	return AST_PRIMITIVE_TYPE(type).type == AST_VOID;
}

static struct ast_node *void_type()
{
	struct ast_node *v = gen_primitive(AST_VOID, NULL, NULL_LOC());
	ast_set_flags(v, AST_FLAG_INIT | AST_FLAG_ACTUAL);
	v->type = v;
	return v;
}

static struct ast_node *i27_type(struct scope *scope)
{
	struct ast_node *i27 = gen_id(strdup("i27"), NULL_LOC());
	struct ast_node *def = file_scope_find_type(scope, i27);
	if (!def) {
		error("missing definition of type 'i27'");
		return NULL;
	}

	struct ast_node *a = gen_primitive(AST_I27, def, def->loc);
	ast_set_flags(a, AST_FLAG_INIT | AST_FLAG_ACTUAL);
	a->type = a;
	return a;
}

static struct ast_node *i9_type(struct scope *scope)
{
	struct ast_node *i27 = gen_id(strdup("i9"), NULL_LOC());
	struct ast_node *def = file_scope_find_type(scope, i27);
	if (!def) {
		error("missing definition of type 'i9'");
		return NULL;
	}

	struct ast_node *a = gen_primitive(AST_I9, def, def->loc);
	ast_set_flags(a, AST_FLAG_INIT | AST_FLAG_ACTUAL);
	a->type = a;
	return a;
}

static struct ast_node *str_type(struct scope *scope)
{
	struct ast_node *str = gen_id(strdup("str"), NULL_LOC());
	struct ast_node *def = file_scope_find_type(scope, str);
	if (!def) {
		error("missing definition of type 'str'");
		return NULL;
	}

	struct ast_node *a = gen_primitive(AST_STR, def, def->loc);
	ast_set_flags(a, AST_FLAG_INIT | AST_FLAG_ACTUAL);
	a->type = a;
	return a;
}

static struct ast_node *bool_type(struct scope *scope)
{
	struct ast_node *b = gen_id(strdup("bool"), NULL_LOC());
	struct ast_node *def = file_scope_find_type(scope, b);
	if (!def) {
		error("missing definition of type 'bool'");
		return NULL;
	}

	struct ast_node *a = gen_primitive(AST_BOOL, def, def->loc);
	ast_set_flags(a, AST_FLAG_INIT | AST_FLAG_ACTUAL);
	a->type = a;
	return a;
}

static int push_defer(struct act_state *state, struct ast_node *expr)
{
	struct act_stack *new = calloc(1, sizeof(struct act_stack));
	if (!new) {
		internal_error("failed allocating defer");
		return -1;
	}

	new->node = expr;
	new->next = state->defer_stack;
	state->defer_stack = new;
	return 0;
}

static int push_label(struct act_state *state, struct ast_node *label)
{
	struct act_stack *new = calloc(1, sizeof(struct act_stack));
	if (!new) {
		internal_error("failed allocating label");
		return -1;
	}

	new->node = label;
	new->next = state->label_stack;
	state->label_stack = new;
	return 0;
}

static struct ast_node *find_label(struct act_state *state,
                                   struct ast_node *label)
{
	assert(label->node_type == AST_LABEL);
	struct act_stack *prev = state->label_stack, *cur;
	if (prev)
		do {
			cur = prev->next;
			if (same_id(AST_LABEL(prev->node).id,
			            AST_LABEL(label).id))
				return prev->node;
		} while ((prev = cur));

	return NULL;
}

static int push_goto(struct act_state *state, struct ast_node *got)
{
	struct act_stack *new = calloc(1, sizeof(struct act_stack));
	if (!new) {
		internal_error("failed allocating goto");
		return -1;
	}

	new->node = got;
	new->next = state->goto_stack;
	state->goto_stack = new;
	return 0;
}

static struct ast_node *clone_defers(struct act_state *state,
                                     struct act_stack *to)
{
	struct act_stack *from = state->defer_stack;

	/* maintain reverse order */
	struct ast_node *defers = NULL, *prev = NULL;
	while (from != to) {
		struct ast_node *defer = clone_ast_node(from->node);
		if (prev)
			prev->next = defer;

		if (!defers)
			defers = defer;

		prev = defer;
		from = from->next;
	}

	return defers;
}

static void clear_stack(struct act_stack *from, struct act_stack *to)
{
	while (from != to) {
		struct act_stack *next = from->next;
		free(from);
		from = next;
	}
}

static void clear_defers(struct act_state *state, struct act_stack *to)
{
	struct act_stack *from = state->defer_stack;
	clear_stack(from, to);
	state->defer_stack = to;
}

static void clear_gotos(struct act_state *state, struct act_stack *to)
{
	struct act_stack *from = state->goto_stack;
	clear_stack(from, to);
	state->goto_stack = to;
}

static void clear_labels(struct act_state *state, struct act_stack *to)
{
	struct act_stack *from = state->label_stack;
	clear_stack(from, to);
	state->label_stack = to;
}

static void destroy_act_state(struct act_state *state)
{
	clear_defers(state, NULL);
	clear_labels(state, NULL);
	clear_gotos(state, NULL);
}

static int actualize(struct act_state *state, struct scope *scope,
                     struct ast_node *node);
static int analyze(struct scope *scope, struct ast_node *tree);

static int eval_const_if(struct scope *scope, struct ast_node *node)
{
	assert(node
	       && node->node_type == AST_IF
	       && ast_flags(node, AST_FLAG_CONST));

	int eval = 0;
	struct ast_node *cond = node->_if.cond;
	struct ast_node *next = node->next;

	switch (cond->node_type) {
	/* for now just check if the variable exists, might change in
	 * the future */
	case AST_ID: eval = file_scope_find_var(scope, cond) != NULL;
		break;
	default: semantic_error(scope->fctx, node,
		                "const if conditional at file scope unimplemented");
		return -1;
	}

	if (eval) {
		/* condition evaluated true, so keep the if block */
		struct ast_node *body = node->_if.body;
		ast_last_node(body)->next = node->next;

		/* an if must have a body, otherwise the parser messed up */
		assert(body);

		*node = *body;
		free(body);
		return 0;
	}

	struct ast_node *els = node->_if.els;
	if (els) {
		*node = *els;
		ast_last_node(els)->next = next;
		free(els);
		return 0;
	}

	if (next) {
		/* copy the next node into our place */
		*node = *next;
		free(next);
		return 0;
	}

	free(node);
	return 0;
}

static int analyze_visibility(struct scope *scope, struct ast_node *node)
{
	if (!node)
		return 0;

	int ret = 0;
	node->scope = scope;

	/* TODO: add error checking */
	switch (node->node_type) {
	case AST_PROC: {
		ret |= scope_add_proc(scope, node);
		break;
	}

	case AST_VAR: {
		ret |= scope_add_var(scope, node);
		break;
	}

	case AST_IMPORT: {
		const char *file = AST_IMPORT(node).file;
		ret |= process_file(&scope,
		                    (int)ast_flags(node, AST_FLAG_PUBLIC),
				    file);
		break;
	}

	case AST_IF: {
		assert(ast_flags(node, AST_FLAG_CONST));
		ret |= eval_const_if(scope, node);
		if (ret)
			return -1;

		/* since a const if likely replaced the current node with
		 * something else, we have to analyze the replacement */
		ret = analyze(scope, node);
		break;
	}

	case AST_STRUCT: {
		/* we shouldn't get any anonymous structs at this stage */
		struct ast_node *id = AST_STRUCT(node).id;
		ret |= scope_add_type(scope, id, node);
		break;
	}

	case AST_ENUM: {
		struct ast_node *id = AST_ENUM(node).id;
		ret |= scope_add_type(scope, id, node);
		break;
	}

	case AST_ALIAS: {
		struct ast_node *id = AST_ALIAS(node).id;
		ret |= scope_add_type(scope, id, node);
		break;
	}

	case AST_TRAIT: {
		struct ast_node *id = AST_TRAIT(node).id;
		ret |= scope_add_type(scope, id, node);
		break;
	}

	case AST_MACRO_CONSTRUCT: {
		ret |= scope_add_macro(scope, node);
		break;
	}

	case AST_EMPTY: {
		break;
	}

	default:
		ret = -1;
		semantic_error(scope->fctx, node, "unknown top element");
		break;
	};

	return ret;
}

static int analyze_var(struct scope *scope, struct ast_node *node)
{
	struct act_state state = {0};
	return actualize(&state, scope, node);
}

static void set_type(struct ast_node *node, struct ast_node *type)
{
	assert(type->node_type == AST_TYPE);
	node->type = clone_ast_node(type);
}

static int analyze_proc(struct scope *scope, struct ast_node *node)
{
	/* not sure if this is the best place for this */
	AST_PROC(node).id->scope = scope;

	struct scope *proc_scope = create_scope();
	scope_add_scope(scope, proc_scope);
	node->scope = proc_scope;

	struct ast_node *sign = AST_PROC(node).sign;

	struct act_state state = {0};
	int ret = actualize(&state, proc_scope, sign);
	set_type(node, sign);
	return ret;
}

static struct ast_node *analyze_type_expand(struct scope *scope,
                                            struct ast_node *n)
{
	assert(n->node_type == AST_TYPE_EXPAND);
	struct ast_node *trait = file_scope_find_type(scope,
	                                              AST_TYPE_EXPAND(n).id);
	if (!trait) {
		semantic_error(scope->fctx, n, "no such type");
		return NULL;
	}

	if (trait->node_type != AST_TRAIT) {
		semantic_error(scope->fctx, n, "not a trait");
		return NULL;
	}

	semantic_info(scope->fctx, n,
	              "FIXME: skipping type param check for now");
	struct ast_node *body = AST_TRAIT(trait).raw_body;
	body = clone_ast_node(body);

	struct ast_node *pa = AST_TYPE_EXPAND(n).args;
	foreach_node(pt, AST_TRAIT(trait).params) {
		replace_type_id(body, AST_VAR(pt).id, pa);
		pa = pa->next;
	}
	return body;
}

static int implements_trait(struct ast_node *body, struct ast_node *id)
{
	foreach_node(n, body) {
		if (n->node_type != AST_ID)
			continue;

		if (same_id(n, id))
			return 1;
	}

	return 0;
}

static void mark_implemented(struct ast_node *n)
{
	assert(n->node_type == AST_TYPE_EXPAND);
	/** @todo very hacky but good enough for now */
	struct ast_node *id = AST_TYPE_EXPAND(n).id;
	n->node_type = AST_ID;
	AST_ID(n).id = strdup(AST_ID(id).id);
}

static int analyze_struct(struct scope *scope, struct ast_node *node)
{
	assert(node->node_type == AST_STRUCT);
	struct ast_node *generics = AST_STRUCT(node).generics;
	struct scope *struct_scope = create_scope();
	if (!struct_scope)
		return -1;

	scope_add_scope(node->scope, struct_scope);
	node->scope = struct_scope;
	if (generics)
		ast_set_flags(node, AST_FLAG_GENERIC);

	struct ast_node *type = gen_type(AST_TYPE_STRUCT, node, NULL,
	                                 node->loc);

	foreach_node(n, AST_STRUCT(node).body) {
		if (n->node_type != AST_TYPE_EXPAND)
			continue;

		if (implements_trait(AST_STRUCT(node).body,
		                     AST_TYPE_EXPAND(n).id)) {
			n->node_type = AST_EMPTY;
			continue;
		}

		if (same_id(AST_STRUCT(node).id, AST_TYPE_EXPAND(n).id)) {
			semantic_error(scope->fctx, n,
			               "recursive trait implementations not allowed");
			return -1;
		}

		struct ast_node *body = analyze_type_expand(scope, n);
		if (!body) {
			n->node_type = AST_EMPTY;
			continue;
		}

		replace_type_id(body, AST_TYPE_EXPAND(n).id, type);
		ast_block_last(body)->next = n->next;
		n->next = body;

		mark_implemented(n);
	}

	foreach_node(n, AST_STRUCT(node).body) {
		switch (n->node_type) {
		case AST_EMPTY: continue;
		case AST_ID: continue;
		/* prototypes are checked later */
		case AST_PROC: if (!AST_PROC(n).body) continue;
		default:
		}

		if (analyze_visibility(struct_scope, n))
			return -1;
	}

	foreach_node(n, AST_STRUCT(node).body) {
		/* also checks prototypes */
		if (n->node_type != AST_PROC)
			continue;

		if (analyze_proc(struct_scope, n))
			return -1;
	}

	/* check that all prototypes are implemented */
	foreach_node(n, AST_STRUCT(node).body) {
		if (n->node_type != AST_PROC)
			continue;

		if (AST_PROC(n).body)
			continue;

		struct ast_node *proc = scope_find_proc(struct_scope,
		                                        AST_PROC(n).id);
		if (!proc) {
			semantic_error(scope->fctx, n,
			               "missing implementation");
			return -1;
		}

		if (!equiv_nodes(AST_PROC(n).sign, AST_PROC(proc).sign)) {
			semantic_error(scope->fctx, n, "mismatched signatures");
			semantic_info(scope->fctx, proc, "note: here");
			return -1;
		}
	}

	/** @todo there is the possibility that two different traits add the
	 * same prototype, which is reported in traits but not structs? */
	return 0;
}

/* quite a lot of overlap with analyze_struct, kind of ugly I guess */
static int analyze_trait(struct scope *scope, struct ast_node *node)
{
	assert(node->node_type == AST_TRAIT);
	struct ast_node *generics = AST_TRAIT(node).params;
	struct scope *trait_scope = create_scope();
	if (!trait_scope)
		return -1;

	scope_add_scope(node->scope, trait_scope);
	node->scope = trait_scope;
	/** @todo should probably add in aliases for the traits in scope? */
	if (generics)
		ast_set_flags(node, AST_FLAG_GENERIC);

	struct ast_node *type = gen_type(AST_TYPE_TRAIT, node, NULL, node->loc);

	AST_TRAIT(node).body = clone_ast_node(AST_TRAIT(node).raw_body);
	/* do type expansions */
	foreach_node(n, AST_TRAIT(node).body) {
		if (n->node_type != AST_TYPE_EXPAND)
			continue;

		/* don't re-expand already implemented traits */
		if (implements_trait(AST_TRAIT(node).body,
		                     AST_TYPE_EXPAND(n).id)) {
			/* not sure about this, but at least we don't have stray
			 * type expands everywhere */
			n->node_type = AST_EMPTY;
			continue;
		}

		if (same_id(AST_TRAIT(node).id, AST_TYPE_EXPAND(n).id)) {
			semantic_error(scope->fctx, n,
			               "recursive trait implementations not allowed");
			return -1;
		}

		struct ast_node *body = analyze_type_expand(scope, n);
		if (!body) {
			n->node_type = AST_EMPTY;
			continue;
		}

		replace_type_id(body, AST_TYPE_EXPAND(n).id, type);
		ast_last_node(body)->next = n->next;
		n->next = body;

		mark_implemented(n);
	}

	/* add all procedure definitions to scope */
	foreach_node(n, AST_TRAIT(node).body) {
		/* kind of a hack but these shouldn't be shown to
		 * analyze_visibility  */
		switch (n->node_type) {
		case AST_EMPTY: continue;
		case AST_ID: continue;
		/* prototypes are added later */
		case AST_PROC: if (!AST_PROC(n).body) continue;
		default:
		}

		if (analyze_visibility(trait_scope, n))
			return -1;
	}

	/** @todo I should really check that there's just one prototype and one
	 * implementation of that prototype, not sure what the best approach
	 * would be. Add a prototypes -list to scopes? */

	/* add all prototypes that don't have matching definition to scope */
	foreach_node(n, AST_TRAIT(node).body) {
		if (n->node_type != AST_PROC)
			continue;

		if (AST_PROC(n).body)
			continue;

		/* prototypes are checked only if there's no implementation */
		if (scope_find_proc(trait_scope, AST_PROC(n).id))
			continue;

		if (analyze_visibility(trait_scope, n))
			return -1;
	}

	foreach_node(n, AST_TRAIT(node).body) {
		if (n->node_type != AST_PROC)
			continue;

		if (analyze_proc(trait_scope, n))
			return -1;
	}

	return 0;
}

static int analyze_signs(struct scope *scope, struct ast_node *node)
{
	/** @todo aliases? */
	switch (node->node_type) {
	case AST_VAR: return analyze_var(scope, node); break;
	case AST_PROC: return analyze_proc(scope, node); break;
	case AST_STRUCT: return analyze_struct(scope, node); break;
	case AST_TRAIT: return analyze_trait(scope, node); break;
	default:
	}

	return 0;
}

static int analyze(struct scope *scope, struct ast_node *tree)
{
	foreach_node(node, tree) {
		if (analyze_visibility(scope, node))
			return -1;
	}

	foreach_node(node, tree) {
		if (analyze_signs(scope, node))
			return -1;
	}

	foreach_node(node, tree){
		struct act_state state = {0};
		if (actualize(&state, scope, node))
			return -1;

		printf("//actualized:\n");
		dump_ast_node(0, node);
	}

	return 0;
}

int analyze_root(struct scope *scope, struct ast_node *tree)
{
	if (analyze(scope, tree))
		return -1;

	return 0;
}

static int structs_match(struct ast_node *a, struct ast_node *b)
{
	/** @todo: iterate over elements in structure */
	/** @todo: check tag as well? */
	return 0;
}

static int primitives_match(struct ast_node *a, struct ast_node *b)
{
	enum ast_primitive at = AST_PRIMITIVE_TYPE(a).type;
	enum ast_primitive bt = AST_PRIMITIVE_TYPE(b).type;

	return at == bt;
}

int types_match(struct ast_node *a, struct ast_node *b)
{
	if (!a && !b)
		return 1;

	if (!a || !b)
		return 0;

	assert(a->node_type == AST_TYPE);
	assert(b->node_type == AST_TYPE);

	/* if the type kind doesn't match, we're done. */
	if (AST_TYPE(a).kind != AST_TYPE(b).kind)
		return 0;

	if (AST_TYPE(a).kind == AST_TYPE_STRUCT)
		return structs_match(a, b);

	if (AST_TYPE(a).kind == AST_TYPE_POINTER)
		return types_match(AST_PTR_TYPE(a).base,
		                   AST_PTR_TYPE(b).base);

	if (AST_TYPE(a).kind == AST_TYPE_PRIMITIVE)
		return primitives_match(a, b);

	return 0;
}

static int _replace_id(struct ast_node *node, void *data)
{
	if (!node)
		return 0;

	if (node->node_type != AST_ID)
		return ast_call_on(_replace_id, node, data);

	struct ast_node **pair = data;
	struct ast_node *id = pair[0];
	struct ast_node *expr = pair[1];

	if (!same_id(node, id))
		return ast_call_on(_replace_id, node, data);

	struct ast_node *clone = clone_ast_node(expr);
	if (!clone) {
		internal_error("failed cloning replacement expr");
		return -1;
	}

	clone->next = node->next;
	clone->scope = node->scope;
	*node = *clone;

	/* a succesful replacement needs no futher replacements, I think */
	return 0;
}
static int replace_id(struct ast_node *body, struct ast_node *id,
                      struct ast_node *expr)
{
	struct ast_node *pair[2] = {id, expr};
	return ast_call_on(_replace_id, body, pair);
}

static int actualize_macro_construct(struct act_state *state,
                                     struct scope *scope, struct ast_node *n)
{
	UNUSED(state);
	/* macro bodies, arguments, etc aren't expanded upon until the macro is
	 * called, so just try to add it to the local scope */
	assert(n && n->node_type == AST_MACRO_CONSTRUCT);
	return scope_add_macro(scope, n);
}

struct ast_node *extract_trait(struct ast_node *type)
{
	if (!type)
		return 0;

	assert(type->node_type == AST_TYPE);
	if (type->_type.kind == AST_TYPE_TRAIT)
		return type;

	return extract_trait(type->_type.next);
}

static void actualize_trait_types(struct ast_node *params,
                                  struct ast_node *args)
{
	/** @todo replace trait types with arg types, should probably be merged
	 * */
	assert(!args && !params);
}

static int actualize_macro_expand(struct act_state *state,
                                  struct scope *scope,
                                  struct ast_node *macro_expand)
{
	assert(macro_expand->node_type == AST_MACRO_EXPAND);
	struct ast_node *id = AST_MACRO_EXPAND(macro_expand).id;
	struct ast_node *macro = file_scope_find_macro(scope, id);
	if (!macro) {
		semantic_error(scope->fctx, macro_expand, "no such macro");
		return -1;
	}

	assert(macro->node_type == AST_MACRO_CONSTRUCT);
	if (ast_flags(macro, AST_FLAG_VARIADIC)) {
		semantic_error(scope->fctx, macro,
		               "variadic macros not yet implemented");
		return -1;
	}

	struct ast_node *body = clone_ast_node(AST_MACRO_CONSTRUCT(macro).body);
	if (!body) {
		internal_error("failed allocating body for macro expansion");
		return -1;
	}

	/** @todo update all macro IDs to the correct scope */
	body->scope = macro_expand->scope;
	body->next = macro_expand->next;

	struct ast_node *param = AST_MACRO_CONSTRUCT(macro).params;
	struct ast_node *arg = AST_MACRO_EXPAND(macro_expand).args;

	/* TODO: actual replacements */
	while (param && arg) {
		/* feels slightly hacky, but essentially replace each individual
		 * component in the arg list by breaking it out of the list
		 * temporarily. After the replacement, insert it back into the
		 * list so the cleanup is easier. */
		struct ast_node *next_arg = arg->next;
		arg->next = NULL;

		if (replace_id(body, param, arg)) {
			semantic_error(scope->fctx, macro_expand,
			               "failed replacing params with args");
			arg->next = next_arg;
			return -1;
		}

		param = param->next;
		arg = arg->next = next_arg;
	}

	*macro_expand = *body;
	/* actualize the new content */
	return actualize(state, scope, macro_expand);
}

static int actualize_call(struct act_state *state,
                          struct scope *scope, struct ast_node *call)
{
	assert(call && call->node_type == AST_CALL);

	/* check that arguments exist, make sure they have types etc. */
	/* TODO: procedure callbacks? */
	int ret = actualize(state, scope, AST_CALL(call).args);
	if (ret)
		return ret;

	ret = actualize(state, scope, AST_CALL(call).expr);
	if (ret)
		return ret;

	semantic_info(scope->fctx, call, "FIXME: skipping type checks for now");
	struct ast_node *expr = AST_CALL(call).expr;
	if (AST_TYPE(expr->type).kind != AST_TYPE_SIGN) {
		char *tstr = type_str(expr->type);
		semantic_info(scope->fctx, call, "not a callable type: %s",
		              tstr);
		free(tstr);
		return -1;
	}

	struct ast_node *sign = expr->type;
	set_type(call, AST_SIGN_TYPE(sign).ret);
	return 0;
}

static void warn_unused_labels(struct act_state *state, struct scope *scope)
{
	struct act_stack *labels = state->label_stack;
	if (!labels)
		return;

	do {
		struct ast_node *label = labels->node;
		if (!ast_flags(label, AST_FLAG_ACTUAL))
			semantic_warn(scope->fctx, label,
			              "unused label");

	} while ((labels = labels->next));
}

static int undefined_gotos(struct act_state *state, struct scope *scope)
{
	int ret = 0;
	struct act_stack *gotos = state->goto_stack;
	if (!gotos)
		return ret;

	do {
		struct ast_node *got = gotos->node;
		if (!ast_flags(got, AST_FLAG_ACTUAL)) {
			semantic_warn(scope->fctx, got,
			              "undefined label");
			ret = -1;
		}

	} while ((gotos = gotos->next));

	return ret;
}

static int actualize_proc(struct act_state *state,
                          struct scope *scope, struct ast_node *proc)
{
	/* actualize_proc is called on trait procs as well, but I believe
	 * that's fine? */
	assert(proc && proc->node_type == AST_PROC);
	struct ast_node *sign = AST_PROC(proc).sign;

	/* signature should already be typed etc. */
	struct act_state new_state = {0};
	if (actualize(&new_state, proc->scope, sign))
		return -1;

	set_type(proc, sign);

	/* actualize body */
	new_state.cur_proc = proc;
	if (actualize(&new_state, proc->scope, AST_PROC(proc).body))
		return -1;

	if (!act_flags(&new_state, ACT_HAS_RETURN)) {
		if (!is_void(AST_SIGN_TYPE(sign).ret)) {
			semantic_error(scope->fctx, proc,
			               "no return with non-void return type");
			return -1;
		}

		/* add 'implicit' return */
		struct ast_node *body = AST_PROC(proc).body;
		struct ast_node *r = gen_return(NULL, NULL_LOC());
		r->scope = body->scope;
		ast_append(AST_BLOCK(body).body, r);
	}
	else if (ast_block_last(AST_PROC(proc).body)->node_type != AST_RETURN) {
		/* TODO: something more sophisticated than this */
		semantic_warn(scope->fctx, proc,
		              "unable to determine explicit return for all branches");
		return -1;
	}

	warn_unused_labels(&new_state, scope);
	if (undefined_gotos(&new_state, scope))
		return -1;

	/* if we're main, don't mangle the entry point */
	struct ast_node *id = AST_PROC(proc).id;
	if (strcmp("main", AST_ID(id).id) == 0)
		ast_set_flags(id, AST_FLAG_NOMANGLE);

	/* we have successfully actualized the procedure */
	return 0;
}

static int actualize_binop(struct act_state *state,
                           struct scope *scope, struct ast_node *binop)
{
	assert(binop && binop->node_type == AST_BINOP);

	struct ast_node *left = AST_BINOP(binop).left;
	struct ast_node *right = AST_BINOP(binop).right;

	int ret = 0;
	ret |= actualize(state, scope, left);
	ret |= actualize(state, scope, right);
	if (ret)
		return ret;

	if (!left->type) {
		semantic_error(scope->fctx, binop,
		               "unable to detect lefthand type");
		return -1;
	}

	if (!right->type) {
		semantic_error(scope->fctx, binop,
		               "unable to detect righthand type");
		return -1;
	}

	if (!types_match(left->type, right->type)) {
		char *left_type = type_str(left);
		char *right_type = type_str(right);
		semantic_error(scope->fctx, binop,
		               "type mismatch (%s vs %s)", left_type,
		               right_type);
		free(left_type);
		free(right_type);
		return -1;
	}

	/* TODO: also check trait types, just because two traits collapse
	 * to the same actual type doesn't mean that the two trait types
	 * should be allowed to operate on eachother */

	/* types are the same, so the type of this expression is whichever */
	set_type(binop, left->type);
	return 0;
}


static int actualize_block(struct act_state *state,
                           struct scope *scope, struct ast_node *node)
{
	struct scope *block_scope = scope;
	if (!ast_flags(node, AST_FLAG_UNHYGIENIC)) {
		if (!(block_scope = create_scope())) {
			/* internal error */
			internal_error("failed to allocate block scope");
			return -1;
		}

		scope_add_scope(scope, block_scope);
	}

	struct act_stack *defers = state->defer_stack;
	foreach_node(pt, node->_block.body) {
		if (actualize(state, block_scope, pt))
			return -1;
	}

	if (node->_block.body == NULL) {
		node->type = void_type();
		node->_block.body = gen_empty();
		return 0;
	}

	/* the block type is the last statement in the block's type */
	set_type(node, ast_last_node(node->_block.body)->type);
	if (!node->type) {
		semantic_error(scope->fctx, node,
		               "unable to detect block type");
		return -1;
	}

	/* TODO: currently defers are sort of duplicated after a return, unsure
	 * if they should be handled here or somewhere else */
	if (state->defer_stack) {
		node->_block.defers = clone_defers(state, defers);
		if (!node->_block.defers) {
			internal_error("failed cloning defers");
			return -1;
		}

		clear_defers(state, defers);
	}

	return 0;
}

static int actualize_id(struct act_state *state,
                        struct scope *scope, struct ast_node *id)
{
	UNUSED(state);
	assert(id && id->node_type == AST_ID);
	id->scope = scope;

	/** @todo vars and procs kind of override eachother, i.e.
	 *	do_something(){..}
	 *	^() do_something;
	 *
	 *	do_something_else(){
	 *		...
	 *		do_something(); // calls variable at the moment
	 *	}
	 *
	 *	Either add in some syntax to distinguish procedure calls and
	 *	pointer calls or make procs and vars share the same namespace.
	 *	*/
	struct ast_node *decl = file_scope_find_var(scope, id);
	if (decl) {
		set_type(id, decl->type);
		decl->uses++;
		return 0;
	}

	decl = file_scope_find_proc(scope, id);
	if (decl) {
		set_type(id, decl->type);
		decl->uses++;
		return 0;
	}

	semantic_error(scope->fctx, id, "no such object");
	return -1;
}

static int actualize_var(struct act_state *state,
                         struct scope *scope, struct ast_node *var)
{
	assert(var && var->node_type == AST_VAR);
	struct ast_node *init = AST_VAR(var).init;
	struct ast_node *type = AST_VAR(var).type;
	/* one of these must be defined, otherwise the parser fucked up */
	assert(type || init);

	if (init && actualize(state, scope, init))
		return -1;

	if (type && actualize(state, scope, type))
		return -1;

	if (init && init->node_type == AST_INIT) {
		assert(!init->type);
		set_type(init, type);
		/* TODO: some kind of check_init() */
	}

	if (init && type) {
		/* make sure the asked type and the actualized types match */
		if (!types_match(init->type, type)) {
			char *init_type = type_str(init->type);
			char *req_type = type_str(type);
			semantic_error(scope->fctx, var,
			               "type mismatch (%s vs %s)",
			               req_type, init_type);
			free(init_type);
			free(req_type);
			return -1;
		}
	}

	/* this is important for lowering */
	AST_VAR(var).id->scope = scope;
	var->scope = scope;

	if (init)
		/* infer */
		set_type(var, init->type);

	if (type)
		/* TODO: should there be some default value? */
		/* declare */
		set_type(var, type);

	/* an unnamed var is a var in a signature that should not produce a
	 * warning on not being used (if I ever get around to adding those kinds
	 * of warnings) */
	if (AST_VAR(var).id && !ast_flags(var, AST_FLAG_MEMBER))
		return scope_add_var(scope, var);

	/* TODO: we should make sure the type is fully qualified in bodies */
	return 0;
}

#define ENTER_ACT()                              \
	enum act_flags old_flags = state->flags; \
	struct ast_node *old_trait = state->cur_trait;

#define EXIT_ACT(r)                           \
	do {                                  \
		state->cur_trait = old_trait; \
		state->flags = old_flags;     \
		return r;                     \
	} while (0);

static enum ast_primitive id_to_primitive(struct ast_node *id)
{
	const char *name = AST_ID(id).id;
	if (strcmp(name, "i27") == 0)
		return AST_I27;

	if (strcmp(name, "i9") == 0)
		return AST_I9;

	if (strcmp(name, "bool") == 0)
		return AST_BOOL;

	/** @todo some better error reporting should probably be used */
	internal_error( "illegal primitive: %s\n", name);
	return AST_VOID;
}

static int struct_is_primitive(struct ast_node *s)
{
	struct ast_node *id = AST_STRUCT(s).id;
	const char *name = AST_ID(id).id;
	if (strcmp(name, "i27") == 0)
		return 1;

	if (strcmp(name, "i9") == 0)
		return 1;

	if (strcmp(name, "bool") == 0)
		return 1;

	/* special case of a special case?
	   if (strcmp(name, "str"))
	        return 1;
	 */

	return 0;
}

static int actualize_type(struct act_state *state,
                          struct scope *scope, struct ast_node *type)
{
	/* TODO: there's gotta be a better way to handle flags. Maybe macros
	 * like ENTER_ACTUALIZE and EXIT_ACTUALIZE? */
	/* TODO: get rid of aliases, expand implements, fill out structures,
	 * etc. */
	assert(type->node_type == AST_TYPE);
	ENTER_ACT();

	if (actualize(state, scope, AST_TYPE(type).next)) {
		EXIT_ACT(-1);
	}

	switch (AST_TYPE(type).kind) {
	case AST_TYPE_ID: {
		/* type IDs can really only be aliases to something else, or if
		 * they're missing, void */
		if (!AST_ID_TYPE(type).id) {
			/* no ID means void */
			AST_ID_TYPE(type).id =
				gen_id(strdup("void"), NULL_LOC());
		}

		struct ast_node *exists = file_scope_find_type(scope,
		                                               AST_ID_TYPE(
								       type).id);
		if (!exists) {
			semantic_error(scope->fctx, type, "no such type");
			EXIT_ACT(-1);
		}

		if (exists->node_type == AST_TYPE) {
			assert(AST_TYPE(exists).kind == AST_TYPE_PRIMITIVE);
			AST_TYPE(type) = AST_TYPE(exists);
			break;
		}

		/*
		   if (actualize(state, exists->scope, exists))
		        EXIT_ACT(-1);
		 */

		assert(AST_TYPE(type).next == NULL);
		if (exists->node_type == AST_ALIAS) {
			AST_TYPE(type).aliased = exists;
			AST_TYPE(type) = AST_TYPE(exists);
		}
		else if (exists->node_type == AST_TRAIT) {
			/* this is kind of weird, have to think about it */
			*type = *gen_type(AST_TYPE_TRAIT, exists,
			                  NULL, exists->loc);
		}
		else if (exists->node_type == AST_STRUCT) {
			if (struct_is_primitive(exists)) {
				*type = *gen_primitive(
					id_to_primitive(AST_STRUCT(exists).id),
					exists,
					exists->loc);
			} else {
				*type = *gen_type(AST_TYPE_STRUCT, exists,
				                  NULL, exists->loc);
			}
		}
		else if (exists->node_type == AST_ENUM) {
			*type = *gen_type(AST_TYPE_ENUM, exists,
			                  NULL, exists->loc);
		}

		break;
	}

	case AST_TYPE_CONSTRUCT:
		/** @todo fully qualified constructs should be added to the
		 * actual list for code generation */
		semantic_info(scope->fctx, type,
		              "constructs unimplemented, continuing with compilation to see what breaks");
		break;

	case AST_TYPE_ARR:
		/* TODO: expression should be expandable to integer constant */
		semantic_info(scope->fctx, type,
		              "arrays unimplemented, continuing with compilation to see what breaks");
		break;

	case AST_TYPE_POINTER:
		assert(AST_PTR_TYPE(type).base);
		if (actualize(state, scope, AST_PTR_TYPE(type).base))
			EXIT_ACT(-1);
		break;

	case AST_TYPE_SIGN: {
		struct ast_node *params = AST_SIGN_TYPE(type).params;
		struct ast_node *ret = AST_SIGN_TYPE(type).ret;

		foreach_node(p, params){
			if (actualize(state, scope, p))
				EXIT_ACT(-1);
		}

		if (!ret)
			ret = AST_SIGN_TYPE(type).ret = void_type();

		if (actualize(state, scope, ret))
			EXIT_ACT(-1);

		break;
	}

	case AST_TYPE_TRAIT: {
		assert(ast_flags(type, AST_FLAG_ACTUAL));
		semantic_info(scope->fctx, type,
		              "FIXME skipping trait type checks");
		break;
	}

	case AST_TYPE_STRUCT: {
		assert(ast_flags(type, AST_FLAG_ACTUAL));
		semantic_info(scope->fctx, type,
		              "FIXME skipping struct type checks");
		break;
	}

	case AST_TYPE_PRIMITIVE: {
		assert(ast_flags(type, AST_FLAG_ACTUAL));
		semantic_info(scope->fctx, type,
		              "FIXME skipping primitive type checks");
		break;
	}

	default:
		semantic_error(scope->fctx, type, "unimplemented type");
		EXIT_ACT(-1);
	}

	/* generally speaking */
	EXIT_ACT(0);
}

static int actualize_empty(struct act_state *state,
                           struct scope *scope, struct ast_node *node)
{
	UNUSED(state);
	/* TODO: converting to void is common enough that it might be worth
	 * creating a function for */
	struct ast_node *void_id = gen_id(strdup("void"), NULL_LOC());
	if (!void_id) {
		internal_error(
			"couldn't allocate type id for empty statement\n");
		return -1;
	}

	node->type = gen_type(AST_TYPE_ID, void_id, NULL, NULL_LOC());
	if (!node->type) {
		internal_error("couldn't allocate type for empty statement\n");
		return -1;
	}

	return 0;
}

static int integral_type(struct ast_node *type)
{
	assert(type->node_type == AST_TYPE);
	if (AST_TYPE(type).kind != AST_TYPE_PRIMITIVE)
		return 0;

	/* here would be awesome with an enum of our base types */
	enum ast_primitive p = AST_PRIMITIVE_TYPE(type).type;
	switch (p) {
	case AST_I9: return 1;
	case AST_I27: return 1;
	default: return 0;
	}

	return 0;
}

static int pointer_type(struct ast_node *type)
{
	assert(type->node_type == AST_TYPE);
	return type->_type.kind == AST_TYPE_POINTER;
}

static int pointer_conversion(struct ast_node *a, struct ast_node *b)
{
	assert(a->node_type == AST_TYPE);
	assert(b->node_type == AST_TYPE);
	if (pointer_type(a)) {
		if (AST_TYPE(b).kind != AST_TYPE_PRIMITIVE)
			return 0;

		/* for now */
		return AST_PRIMITIVE_TYPE(b).type == AST_I27;
	}

	return 0;
}

static size_t member_count(struct ast_node *exists)
{
	assert(exists->node_type == AST_STRUCT);
	struct ast_node *body = exists->_struct.body;
	return ast_list_len(body);
}

static struct ast_node *lookup_member_idx(struct ast_node *body,
                                          struct ast_node *find,
                                          size_t *idx)
{
	/* micro-optimisation, likely way premature but speeds up selection
	 * between lookup_struct_member_idx and *_name by a tiny amount */
	(void)(find);
	assert(idx);
	size_t i = *idx;
	struct ast_node *m = body;
	while (i != 0 && m) {
		m = m->next;
		i--;
	}

	return m;
}

static struct ast_node *lookup_member_name(struct ast_node *body,
                                           struct ast_node *find,
                                           size_t *idx)
{
	assert(find->node_type == AST_ID);
	size_t i = 0;
	struct ast_node *m = body;
	while (m) {
		assert(m->node_type == AST_VAR);
		if (same_id(find, AST_VAR(m).id))
			break;
		m = m->next;
		i++;
	}

	if (idx)
		*idx = i;

	return m;
}

static struct ast_node *lookup_struct_member(struct ast_node *struc,
                                             struct ast_node *find, size_t *idx)
{
	if (find)
		return lookup_member_name(struc->_struct.body, find, idx);

	return lookup_member_idx(struc->_struct.body, find, idx);
}

static struct ast_node *lookup_enum_member(struct ast_node *enu,
                                           struct ast_node *find)
{
	size_t i = 0;
	struct ast_node *m = enu->_enum.body;
	while (m) {
		assert(m->node_type == AST_VAL);
		if (same_id(find, AST_VAL(m).id))
			break;
		m = m->next;
		i++;
	}

	return m;
}

static int init_struct(struct act_state *state, struct scope *scope,
                       struct ast_node *exists, struct ast_node *init)
{
	size_t i = 0;
	size_t mcount = member_count(exists);

	int ret = 0;
	char *initd = calloc(sizeof(char), mcount);
	if (!initd) {
		internal_error("failed allocating initd array");
		return -1;
	}

	struct ast_node *args = init->_init.body;
	while (args) {
		if (i >= mcount) {
			semantic_error(scope->fctx, args,
			               "excessive elements in initializer");
			ret = -1;
			break;
		}

		if (initd[i]) {
			semantic_error(scope->fctx, args,
			               "overwriting already initialized elements");
			ret = -1;
			break;
		}

		if ((ret = actualize(state, scope, args)))
			break;

		struct ast_node *find = NULL;
		if (ast_flags(args, AST_FLAG_MEMBER))
			find = args->_var.id;

		struct ast_node *member =
			lookup_struct_member(exists, find, &i);

		if (!member) {
			char *sstr = type_str(exists->type);
			semantic_error(scope->fctx, args,
			               "no such member in %s",
			               sstr);
			free(sstr);
			ret = -1;
			break;
		}

		if (!types_match(args->type, member->type)) {
			char *astr = type_str(args->type);
			char *mstr = type_str(member->type);
			semantic_error(scope->fctx, args,
			               "%s does not match %s", astr, mstr);
			free(astr);
			free(mstr);
			ret = -1;
			break;
		}

		initd[i] = 1;
		args = args->next;
		i++;
	}

	free(initd);
	return ret;
}

static int actualize_struct_init_cast(struct act_state *state,
                                      struct scope *scope,
                                      struct ast_node *init,
                                      struct ast_node *actual)
{
	struct ast_node *def = AST_STRUCT_TYPE(actual).def;
	return init_struct(state, scope, def, init);
}

static int actualize_init_cast(struct act_state *state,
                               struct scope *scope, struct ast_node *init,
                               struct ast_node *type)
{
	if (AST_TYPE(type).kind == AST_TYPE_STRUCT)
		return actualize_struct_init_cast(state, scope, init, type);

	semantic_error(scope->fctx, type,
	               "type is not a struct");
	return -1;

}

static int proc_pointer(struct ast_node *type)
{
	if (type->_type.kind != AST_TYPE_POINTER)
		return 0;

	struct ast_node *next = type->_type.next;
	if (next->_type.kind != AST_TYPE_SIGN)
		return 0;

	return 1;
}

static int proc_choice(struct ast_node *expr, struct ast_node *type)
{
	if (expr->node_type != AST_ID)
		return 0;

	if (!proc_pointer(type))
		return 0;

	return 1;
}

/* still slightly unsure about this, but hey ho */
static int match_proc(struct act_state *state, struct scope *scope,
                      struct ast_node *cast)
{
	(void)(state);
	semantic_error(scope->fctx, cast,
	               "procedure signature casts not yet implemented");
	return -1;
}

static int actualize_cast(struct act_state *state,
                          struct scope *scope, struct ast_node *cast)
{
	assert(cast->node_type == AST_CAST);
	struct ast_node *expr = cast->_cast.expr;
	struct ast_node *type = cast->_cast.type;

	if (actualize(state, scope, type))
		return -1;

	if (proc_choice(expr, type)) {
		set_type(cast, type);
		return match_proc(state, scope, cast);
	}

	if (actualize(state, scope, expr))
		return -1;

	if (expr->node_type == AST_INIT) {
		set_type(cast, type);
		return actualize_init_cast(state, scope, expr, type);
	}

	if (types_match(expr->type, type)) {
		set_type(cast, type);
		return 0;
	}

	if (integral_type(expr->type) && integral_type(type)) {
		set_type(cast, type);
		return 0;
	}

	if (pointer_type(expr->type) && pointer_type(type)) {
		set_type(cast, type);
		return 0;
	}

	if (pointer_conversion(expr->type, type)
	    || pointer_conversion(type, expr->type)) {
		set_type(cast, type);
		return 0;
	}

	/* TODO: arrays? */

	char *left_type = type_str(expr);
	char *right_type = type_str(type);
	semantic_error(scope->fctx, cast, "illegal cast (%s vs %s)",
	               left_type, right_type);
	free(left_type);
	free(right_type);
	return -1;
}

static int actualize_const(struct act_state *state, struct scope *scope,
                           struct ast_node *cons)
{
	UNUSED(state);
	assert(cons->node_type == AST_CONST);
	if (AST_CONST(cons).kind == AST_CONST_INTEGER)
		cons->type = i27_type(scope);

	else if (AST_CONST(cons).kind == AST_CONST_STRING)
		cons->type = str_type(scope);

	if (cons->type)
		return 0;

	semantic_error(scope->fctx, cons, "unimplemented constant");
	return 1;
}

static int actualize_alias(struct act_state *state, struct scope *scope,
                           struct ast_node *alias)
{
	/* I shall have to think about things, as currently very deeply nested
	 * aliases might be a bit cumbersome to work with. Still, this works
	 * well enough I suppose. */
	assert(alias->node_type == AST_ALIAS);
	if (actualize(state, scope, AST_ALIAS(alias).type)) {
		/* usually we don't want to output errors upon errors, but this
		 * is likely a useful message as it might show where a loop is
		 * occuring */
		semantic_error(scope->fctx, alias, "failed actualizing alias");
		return -1;
	}

	ast_set_flags(alias, AST_FLAG_ACTUAL);
	return 0;
}

static int actualize_defer(struct act_state *state,
                           struct scope *scope, struct ast_node *node)
{
	struct ast_node *expr = node->_defer.expr;
	/* TODO: should the actualization only happen when the defers are
	 * called? */
	if (actualize(state, scope, expr))
		return -1;

	if (push_defer(state, expr))
		return -1;

	node->type = void_type();
	return 0;
}

static int actualize_return(struct act_state *state, struct scope *scope,
                            struct ast_node *node)
{
	act_set_flags(state, ACT_HAS_RETURN);
	struct ast_node *expr = node->_return.expr;
	if (expr) {
		if (actualize(state, scope, expr))
			return -1;

		set_type(node, expr->type);
	}
	else {
		node->type = void_type();
	}

	assert(state->cur_proc);
	struct ast_node *cur_proc = state->cur_proc;
	struct ast_node *sign = AST_PROC(cur_proc).sign;
	struct ast_node *ret = AST_SIGN_TYPE(sign).ret;
	if (!types_match(node->type, ret)) {
		char *rt = type_str(ret);
		char *et = type_str(node);
		semantic_error(scope->fctx, node,
		               "return type mismatch: %s", et);

		semantic_info(scope->fctx, ret,
		              "vs %s", rt);
		free(rt);
		free(et);
		return -1;
	}

	if (state->defer_stack) {
		AST_RETURN(node).defers = clone_defers(state, NULL);
		if (!node->_return.defers) {
			internal_error("failed cloning return defers");
			return -1;
		}
	}
	return 0;
}

/* Still slightly unsure if this works in all cases, but a good start
 * nonetheless. */
static void actualize_goto_defer(struct ast_node *got, struct ast_node *label)
{
	struct ast_node *goto_defers = AST_GOTO(got).defers;
	struct ast_node *label_defers = AST_LABEL(label).defers;
	/* since we're dealing with singly linked lists, keep a reference to one
	 * node before the current goto defer. */
	struct ast_node *prev_defer = NULL;

	/* this goto has a defined label */
	ast_set_flags(got, AST_FLAG_ACTUAL);
	ast_set_flags(label, AST_FLAG_ACTUAL);

	size_t goto_len = ast_list_len(goto_defers);
	size_t label_len = ast_list_len(label_defers);

	/* find first common defer statement */
	while (goto_len > label_len) {
		prev_defer = goto_defers;
		goto_defers = goto_defers->next;
		goto_len--;
	}

	while (label_len > goto_len) {
		label_defers = label_defers->next;
		label_len--;
	}

	/* TODO: possible optimisation could be maintaining references to the
	 * defer statements themselves, building up a sort of tree, allowing us
	 * to directly compare pointers, likely being a bit quicker. Still,
	 * unlikely that goto stuff would be a major bottleneck. */
	while (!equiv_nodes(goto_defers, label_defers)) {
		prev_defer = goto_defers;
		label_defers = label_defers->next;
		goto_defers = goto_defers->next;
	}

	/* only the defers below the common defer should be executed by the goto */
	if (goto_defers) {
		/* fuck, I actually need the one previous to this */
		assert(prev_defer->next == goto_defers);
		prev_defer->next = NULL;
	}

	/* nothing to do */
}


static int actualize_goto(struct act_state *state, struct scope *scope,
                          struct ast_node *node)
{
	assert(node->node_type == AST_GOTO);
	push_goto(state, node);

	/* clone all defers as we don't know where the label might be */
	node->_goto.defers = clone_defers(state, NULL);
	node->type = void_type();

	struct ast_node *label = find_label(state, node->_goto.label);
	/* this is a jump backwards, i.e. we can already do it */
	if (label)
		actualize_goto_defer(node, label);

	/* forward jump, handle stuff when we run into the corresponding label */
	return 0;
}

static void actualize_goto_defers(struct act_state *state,
                                  struct ast_node *label)
{
	struct act_stack *prev = state->goto_stack, *cur;
	if (prev)
		do {
			cur = prev->next;
			struct ast_node *got = prev->node;
			if (equiv_nodes(AST_GOTO(got).label, label))
				actualize_goto_defer(got, label);

		} while ((prev = cur));
}

static int actualize_label(struct act_state *state, struct scope *scope,
                           struct ast_node *node)
{
	assert(node->node_type == AST_LABEL);
	struct ast_node *prev = find_label(state, node);
	if (prev) {
		semantic_error(scope->fctx, node, "label redefined");
		semantic_info(scope->fctx, prev, "previous definition");
		return -1;
	}

	push_label(state, node);
	/* clone all defers */
	node->_label.defers = clone_defers(state, NULL);
	node->type = void_type();

	actualize_goto_defers(state, node);
	return 0;
}

static int actualize_unop(struct act_state *state,
                          struct scope *scope, struct ast_node *node)
{
	assert(node->node_type == AST_UNOP);
	struct ast_node *expr = AST_UNOP(node).expr;
	if (actualize(state, scope, expr))
		return -1;

	/* generally speaking */
	set_type(node, expr->type);

	switch (node->_unop.op) {
	case AST_DEREF: {
		struct ast_node *type = expr->type;
		if (AST_TYPE(type).kind != AST_TYPE_POINTER) {
			/* TODO: or array */
			semantic_error(scope->fctx, expr,
			               "trying to dereference something that's not a pointer");
			return -1;
		}

		set_type(node, AST_PTR_TYPE(type).base);
		assert(node->type);
		break;
	}

	case AST_REF: {
		node->type = gen_type(AST_TYPE_POINTER, NULL, NULL,
		                      NULL_LOC());
		set_type(node->AST_TYPE(type).next, expr->type);
		break;
	}

	case AST_LNOT: {
		if (AST_TYPE(expr->type).kind != AST_TYPE_PRIMITIVE) {
			semantic_error(scope->fctx, node,
			               "'!' only implemented for primitive types");
			return -1;
		}

		if (AST_PRIMITIVE_TYPE(expr->type).type == AST_VOID) {
			semantic_error(scope->fctx, node,
			               "'!' not implemented for void");
			return -1;
		}

		node->type = bool_type(scope);
		break;
	}

	default: semantic_error(scope->fctx, node,
		                "unimplemented unary operation");
		return -1;
	}

	return 0;
}

static int actualize_as(struct act_state *state,
                        struct scope *scope, struct ast_node *as)
{
	assert(as->node_type == AST_AS);
	struct ast_node *type = AST_AS(as).type;
	if (actualize(state, scope, type))
		return -1;

	set_type(as, type);
	return 0;
}

static int _replace_type_id(struct ast_node *node, void *data)
{
	if (!node)
		return 0;

	struct ast_node **pair = data;
	struct ast_node *id = pair[0];
	struct ast_node *replacement = pair[1];

	if (node->node_type != AST_TYPE)
		goto next;

	switch (AST_TYPE(node).kind) {
	case AST_TYPE_ID: *node = *clone_ast_node(replacement); break;
	case AST_TYPE_TRAIT: {
		struct ast_node *def = AST_TRAIT_TYPE(node).def;
		assert(def);
		struct ast_node *name = AST_TRAIT(def).id;
		if (same_id(id, name))
			*node = *clone_ast_node(replacement);
		break;
	}

	case AST_TYPE_STRUCT: {
		struct ast_node *def = AST_STRUCT_TYPE(node).def;
		assert(def);

		struct ast_node *name = AST_STRUCT(def).id;
		if (same_id(id, name))
			*node = *clone_ast_node(replacement);
		break;
	}
	default:
	}

next:
	return ast_call_on(_replace_type_id, node, data);
}

static int replace_type_id(struct ast_node *nodes, struct ast_node *id,
                           struct ast_node *replacement)
{
	assert(replacement->node_type == AST_TYPE);
	struct ast_node *pair[2] = {id, replacement};
	return ast_call_on(_replace_type_id, nodes, pair);
}


/* lots of overlap with actualize_struct, kind of ugly... */
static int actualize_trait(struct act_state *state, struct scope *scope,
                           struct ast_node *node)
{
	assert(node->node_type == AST_TRAIT);
	foreach_node(n, AST_TRAIT(node).body) {
		/* there's really only prodcedure body actualization left I
		 * guess, as type stuff was taken care of in the analysis phase
		 * */
		if (n->node_type != AST_PROC)
			continue;

		/* don't actualize prototypes, duh */
		if (!AST_PROC(n).body)
			continue;

		if (actualize(state, node->scope, n))
			return -1;
	}

	return 0;
}

static int actualize_struct(struct act_state *state,
                            struct scope *scope, struct ast_node *node)
{
	assert(node->node_type == AST_STRUCT);
	foreach_node(n, AST_STRUCT(node).body) {
		/* there's really only prodcedure body actualization left I
		 * guess, as type stuff was taken care of in the analysis phase
		 * */
		if (n->node_type != AST_PROC)
			continue;

		/* don't actualize prototypes, duh */
		if (!AST_PROC(n).body)
			continue;

		if (actualize(state, node->scope, n))
			return -1;
	}

	return 0;
}

/* could maybe be renamed, but essentially dot in copper works as either
 * -> or . in C, so allow structures or traits and single level pointers to
 *  structures or traits. */
static int has_members(struct ast_node *type)
{
	/* most likely */
	if (AST_TYPE(type).kind == AST_TYPE_STRUCT)
		return 1;

	if (AST_TYPE(type).kind == AST_TYPE_TRAIT)
		return 1;

	if (AST_TYPE(type).kind == AST_TYPE_CONSTRUCT)
		return 1;

	return 0;
}

static int actualize_dot(struct act_state *state,
                         struct scope *scope, struct ast_node *node)
{
	/* TODO: handle enums as well, idea is something like
	 * enum whatever {A_FLAG}
	 * ...
	 * whatever.A_FLAG
	 *
	 * possibly also if the expr is of type whatever then .A_FLAG just gets
	 * the corresponding constant?
	 **/
	assert(node->node_type == AST_DOT);
	struct ast_node *expr = AST_DOT(node).expr;
	if (actualize(state, scope, expr))
		return -1;

	struct ast_node *id = AST_DOT(node).id;
	struct ast_node *type = expr->type;

	struct ast_node *def = NULL;
	switch (AST_TYPE(type).kind) {
	case AST_TYPE_PRIMITIVE: def = AST_PRIMITIVE_TYPE(type).def; break;
	case AST_TYPE_TRAIT: def = AST_TRAIT_TYPE(type).def; break;
	case AST_TYPE_STRUCT: def = AST_STRUCT_TYPE(type).def; break;
	default: {
		char *tstr = type_str(type);
		semantic_error(scope->fctx, node,
		               "illegal type in dot expression: %s",
		               tstr);
		free(tstr);
		return -1;
	}
	}

	struct ast_node *exists = scope_find_var(def->scope, id);
	if (exists) {
		assert(exists->type);
		set_type(node, exists->type);
		return 0;
	}

	exists = scope_find_proc(def->scope, id);
	if (exists) {
		assert(exists->type);
		set_type(node, exists->type);
		return 0;
	}

	semantic_error(scope->fctx, node,
	               "does not have member");
	return -1;
}

static int actualize_init(struct act_state *state,
                          struct scope *scope, struct ast_node *node)
{
	assert(node->node_type == AST_INIT);
	/* for now just do the types, the named stuff will be checked later */
	/* TODO: how to make sure all members are initialized? */
	int ret = 0;
	enum act_flags old_flags = state->flags;
	ret = actualize(state, scope, node->_init.body);
	state->flags = old_flags;
	return ret;
}

static int actualize_assign(struct act_state *state, struct scope *scope,
                            struct ast_node *node)
{
	assert(node->node_type == AST_ASSIGN);
	struct ast_node *to = node->_assign.to;
	if (actualize(state, scope, to))
		return -1;

	struct ast_node *from = node->_assign.from;
	if (actualize(state, scope, from))
		return -1;

	if (from->node_type == AST_INIT) {
		set_type(node,  to->type);
		return actualize_init_cast(state, scope, from, to->type);
	}

	if (!types_match(to->type, from->type)) {
		char *tostr = type_str(to->type);
		char *fromstr = type_str(from->type);
		semantic_error(scope->fctx, node, "type mismatch (%s vs %s)",
		               tostr, fromstr);
		free(tostr);
		free(fromstr);
		return -1;
	}

	/** @todo rvalue vs lvalue? */
	if (!is_lvalue(to)) {
		semantic_error(scope->fctx, node,
		               "rvalue used where lvalue required");
		return -1;
	}

	set_type(node, to->type);
	return 0;
}

static int actualize_fetch(struct act_state *state, struct scope *scope,
                           struct ast_node *fetch)
{
	assert(fetch->node_type == AST_FETCH);
	struct ast_node *type = fetch->_fetch.type;
	if (actualize(state, scope, type))
		return -1;

	if (type->_type.kind != AST_TYPE_ENUM) {
		semantic_error(scope->fctx, type, "type is not an enum");
		return -1;
	}

	struct ast_node *id = fetch->_fetch.id;
	struct ast_node *def =
		file_scope_find_type(scope, AST_ID_TYPE(type).id);
	assert(def);

	struct ast_node *member = lookup_enum_member(def, id);
	if (!member) {
		char *estr = type_str(type);
		semantic_error(scope->fctx, id, "no such member in enum %s");
		free(estr);
		return -1;
	}

	set_type(fetch, def->type);
	return 0;
}

static int actualize_enum(struct act_state *state, struct scope *scope,
                          struct ast_node *node)
{
	assert(node->node_type == AST_ENUM);
	struct ast_node *type = node->_enum.type;
	struct scope *enum_scope = node->scope;

	/* TODO: here we could save space by choosing the smallest type that
	 * fits */
	if (!type) {
		type = i27_type(scope);
		node->_enum.type = type;
	} else if (actualize(state, enum_scope, type))
		return -1;

	long long counter = 0;
	node->type = type;
	struct ast_node *members = node->_enum.body;
	while (members) {
		set_type(members, type);
		if (members->_val.val) {
			struct ast_node *val = members->_val.val;
			if (actualize(state, enum_scope, val))
				return -1;

			if (val->node_type != AST_CONST) {
				semantic_error(scope->fctx, members,
				               "unable to process nonconstant expression");
				return -1;
			}

			if (val->_const.kind != AST_CONST_INTEGER) {
				semantic_error(scope->fctx, members,
				               "not expandable to an integer constant");
				return -1;
			}

			counter = val->_const.integer;
		}
		else {
			members->_val.val = gen_int(counter, NULL_LOC());
		}

		members = members->next;
		counter++;
	}

	/* TODO: check that we don't go outside the limits of the type */
	return 0;
}

static int actualize_if(struct act_state *state, struct scope *scope,
                        struct ast_node *node)
{
	assert(node->node_type == AST_IF);
	if (actualize(state, scope, AST_IF(node).cond))
		return -1;

	if (actualize(state, scope, AST_IF(node).body))
		return -1;

	if (actualize(state, scope, AST_IF(node).els))
		return -1;

	if (ast_flags(node, AST_FLAG_DOEXPR)) {
		struct ast_node *tt = ast_last_node(AST_IF(node).body)->type;
		struct ast_node *ft = ast_last_node(AST_IF(node).els)->type;
		if (!types_match(tt, ft)) {
			semantic_error(scope->fctx, node,
			               "mismatched if/else body values");
			return -1;
		}

		set_type(node, tt);
		return 0;
	}

	node->type = void_type();
	return 0;
}

static int actualize_for(struct act_state *state, struct scope *scope,
                         struct ast_node *node)
{
	assert(node->node_type == AST_FOR);
	if (actualize(state, scope, AST_FOR(node).pre))
		return -1;

	if (actualize(state, scope, AST_FOR(node).post))
		return -1;

	if (actualize(state, scope, AST_FOR(node).cond))
		return -1;

	if (actualize(state, scope, AST_FOR(node).body))
		return -1;

	node->type = void_type();
	return 0;
}

static int actualize(struct act_state *state, struct scope *scope,
                     struct ast_node *node)
{
	int ret = 0;
	if (!node)
		return ret;

	if (!node->scope)
		node->scope = scope;

	/* this node was already actualized, presumed to be fine */
	if (ast_flags(node, AST_FLAG_ACTUAL)) {
		return 0;
	}

	/* actualization started */
	ast_set_flags(node, AST_FLAG_INIT);

	/* actualization done for this node (optimistic) */
	ast_set_flags(node, AST_FLAG_ACTUAL);

	switch (node->node_type) {
	case AST_PROC:
		ret |= actualize_proc(state, scope, node);
		break;

	case AST_TRAIT: ret |= actualize_trait(state, scope, node); break;
	case AST_ALIAS: ret |= actualize_alias(state, scope, node); break;
	case AST_MACRO_CONSTRUCT: ret |= actualize_macro_construct(state, scope,
		                                                   node); break;
	case AST_MACRO_EXPAND: ret |=
		actualize_macro_expand(state, scope, node); break;
	case AST_CALL: ret |= actualize_call(state, scope, node); break;
	case AST_BINOP: ret |= actualize_binop(state, scope, node); break;
	case AST_BLOCK: ret |= actualize_block(state, scope, node); break;
	case AST_ID: ret |= actualize_id(state, scope, node); break;
	case AST_VAR: ret |= actualize_var(state, scope, node); break;
	case AST_TYPE: ret |= actualize_type(state, scope, node); break;
	case AST_EMPTY: ret |= actualize_empty(state, scope, node); break;
	case AST_CAST: ret |= actualize_cast(state, scope, node); break;
	case AST_CONST: ret |= actualize_const(state, scope, node); break;
	case AST_DEFER: ret |= actualize_defer(state, scope, node); break;
	case AST_RETURN: ret |= actualize_return(state, scope, node); break;
	case AST_GOTO: ret |= actualize_goto(state, scope, node); break;
	case AST_LABEL: ret |= actualize_label(state, scope, node); break;
	case AST_UNOP: ret |= actualize_unop(state, scope, node); break;
	case AST_AS: ret |= actualize_as(state, scope, node); break;
	case AST_STRUCT: ret |= actualize_struct(state, scope, node); break;
	case AST_DOT: ret |= actualize_dot(state, scope, node); break;
	case AST_INIT: ret |= actualize_init(state, scope, node); break;
	case AST_ASSIGN: ret |= actualize_assign(state, scope, node); break;
	case AST_FETCH: ret |= actualize_fetch(state, scope, node); break;
	case AST_ENUM: ret |= actualize_enum(state, scope, node); break;
	case AST_IF: ret |= actualize_if(state, scope, node); break;
	case AST_FOR: ret |= actualize_for(state, scope, node); break;

	default:
		/* more like internal_error, maybe? */
		semantic_error(scope->fctx, node,
		               "unimplemented actualization");
		break;
	}
	return ret;
}

int actualize_main(struct scope *root)
{
	struct ast_node *main_id = gen_id(strdup("main"), NULL_LOC());

	struct act_state state = {0};

	/* skip checking signature for now */
	struct ast_node *main = file_scope_find_proc(root, main_id);
	if (!main) {
		/* libraries are not really compilable... */
		error("no main");
		return -1;
	}
	int ret = actualize(&state, root, main);
	destroy_act_state(&state);
	return ret;
}

void replace_type(struct ast_node *type, struct ast_node *from,
                  struct ast_node *to)
{
	if (!type)
		return;

	assert(type->node_type == AST_TYPE);
	assert(from->node_type == AST_TYPE);
	assert(to->node_type == AST_TYPE);

	if (types_match(type, from)) {
		assert(type->_type.next == NULL);
		struct ast_node *clone = clone_ast_node(to);
		*type = *clone;
		return;
	}

	replace_type(type->_type.next, from, to);
}

void replace_param_types(struct ast_node *param, struct ast_node *param_type,
                         struct ast_node *arg_type)
{
	if (arg_type->_type.kind == AST_TYPE_TRAIT) {
		replace_param_types(param, param_type, arg_type);
		return;
	}

	while (param) {
		replace_type(param->type, param_type, arg_type);
		param = param->next;
	}
}
