#include <stdlib.h>
#include <stdio.h>
#include <inttypes.h>
#include <stdarg.h>
#include <ctype.h>
#include <string.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#include "util.h"
#include "parse.h"

/* Allows us to look up types/traits by name nodes */
typedef struct Tydefn Tydefn;
typedef struct Traitdefn Traitdefn;
struct Tydefn {
	Srcloc loc;
	Node *name;
	Type *type;
};

struct Traitdefn {
	Srcloc loc;
	Node *name;
	Trait *trait;
};

#define Maxstabdepth 128
static Stab *stabstk[Maxstabdepth];
static size_t stabstkoff;

static Tyenv *envstk[Maxstabdepth];
static size_t envstkoff;


/* name hashing: we want namespaced lookups to find the
 * name even if we haven't set the namespace up, since
 * we can update it after the fact. */
ulong
nsnamehash(void *n)
{
	return strhash(namestr(n));
}

int
nsnameeq(void *a, void *b)
{
	return a == b || !strcmp(namestr(a), namestr(b));
}

static ulong
implhash(void *p)
{
	Node *n;
	ulong h;

	n = p;
	h = nsnamehash(n->impl.traitname);
	h *= tyhash(n->impl.type);
	return h;
}

static int
impleq(void *pa, void *pb)
{
	Node *a, *b;

	a = pa;
	b = pb;
	if (nsnameeq(a->impl.traitname, b->impl.traitname))
		return tyeq(a->impl.type, b->impl.type);
	return 0;
}

Stab *
mkstab(int isfunc)
{
	Stab *st;

	st = zalloc(sizeof(Stab));
	st->dcl = mkht(nsnamehash, nsnameeq);
	st->ty = mkht(nsnamehash, nsnameeq);
	st->tr = mkht(nsnamehash, nsnameeq);
	st->uc = mkht(nsnamehash, nsnameeq);
	if (isfunc) {
		st->env = mkht(nsnamehash, nsnameeq);
		st->lbl = mkht(strhash, streq);
	}
	st->impl = mkht(implhash, impleq);
	st->isfunc = isfunc;
	return st;
}

Stab *
curstab()
{
	assert(stabstkoff > 0);
	return stabstk[stabstkoff - 1];
}

void
pushstab(Stab *st)
{
	assert(stabstkoff < Maxstabdepth);
	stabstk[stabstkoff++] = st;
}

void
popstab()
{
	assert(stabstkoff > 0);
	stabstkoff--;
}

ulong
paramhash(void *p)
{
	return strhash(((Type*)p)->pname);
}

int
parameq(void *a, void *b)
{
	return streq(((Type*)a)->pname, ((Type*)b)->pname);
}

Tyenv*
mkenv()
{
	Tyenv *e;

	e = malloc(sizeof(Tyenv));
	e->super = NULL;
	e->tab = mkht(paramhash, parameq);
	return e;
}

Tyenv*
curenv()
{
	if (!envstkoff)
		return NULL;
	else
		return envstk[envstkoff - 1];
}

void
pushenv(Tyenv *env)
{
	if (!env)
		return;
	assert(envstkoff < Maxstabdepth);
	envstk[envstkoff++] = env;
}

void
popenv(Tyenv *env)
{
	if (!env)
		return;
	assert(envstkoff > 0);
	envstkoff--;
}

Stab *
findstab(Stab *st, Node *n)
{
	Stab *ns;

	if (n->name.ns) {
		ns = getns(file, n->name.ns);
		if (!ns) {
			ns = mkstab(0);
			updatens(ns, n->name.ns);
		}
		st = ns;
	}
	return st;
}

Node *
getclosed(Stab *st, Node *n)
{
	while (st && !st->env)
		st = st->super;
	if (st)
		return htget(st->env, n);
	return NULL;
}

Node **
getclosure(Stab *st, size_t *n)
{
	size_t nkeys, i;
	void **keys;
	Node **vals;

	while (st && !st->env)
		st = st->super;

	if (!st) {
		*n = 0;
		return NULL;
	}

	vals = NULL;
	*n = 0;
	keys = htkeys(st->env, &nkeys);
	for (i = 0; i < nkeys; i++)
		lappend(&vals, n, htget(st->env, keys[i]));
	free(keys);
	return vals;
}

/*
 * Searches for declarations from current
 * scope, and all enclosing scopes. Doe
 * not resolve namespaces -- that is the job
 * of the caller of this function.
 *
 * If a resoved name is not global, and i
 * not in the current scope, it is recorded
 * in the scope's closure.
 */
Node *
getdcl(Stab *st, Node *n)
{
	Node *s;
	Stab *fn;

	fn = NULL;
	do {
		s = htget(st->dcl, n);
		if (s) {
			/* record that this is in the closure of this scope */
			if (fn && !s->decl.isglobl && !s->decl.isgeneric)
				htput(fn->env, s->decl.name, s);
			return s;
		}
		if (!fn && st->env)
			fn = st;
		st = st->super;
	} while (st);
	return NULL;
}

void
putlbl(Stab *st, char *name, Node *lbl)
{
	assert(st && st->isfunc);
	if (hthas(st->lbl, name))
		fatal(lbl, "duplicate label %s, first defined on line %d\n", name, lbl->loc.line);
	htput(st->lbl, name, lbl);
}

Node *
getlbl(Stab *st, Srcloc loc, char *name)
{
	while (st && !st->isfunc)
		st = st->super;
	if (!st || !hthas(st->lbl, name))
		return NULL;
	return htget(st->lbl, name);
}

Type *
gettype_l(Stab *st, Node *n)
{
	Tydefn *t;

	if ((t = htget(st->ty, n)))
		return t->type;
	return NULL;
}

Type *
gettype(Stab *st, Node *n)
{
	Tydefn *t;

	do {
		if ((t = htget(st->ty, n)))
			return t->type;
		st = st->super;
	} while (st);
	return NULL;
}

int
hastype(Stab *st, Node *n)
{
	do {
		if (hthas(st->ty, n))
			return 1;
		st = st->super;
	} while (st);
	return 0;
}

Ucon *
getucon(Stab *st, Node *n)
{
	Ucon *uc;

	do {
		if ((uc = htget(st->uc, n)))
			return uc;
		st = st->super;
	} while (st);
	return NULL;
}

Trait *
gettrait(Stab *st, Node *n)
{
	Traitdefn *c;

	if (n->name.ns)
		st = getns(file, n->name.ns);
	do {
		if ((c = htget(st->tr, n)))
			return c->trait;
		st = st->super;
	} while (st);
	return NULL;
}

Stab *
getns(Node *file, char *name) {
	return htget(file->file.ns, name);
}

static int
mergedecl(Node *old, Node *new)
{
	Node *e, *g;

	if (old->decl.isimport && new->decl.isimport) {
		old->decl.ishidden = old->decl.ishidden && new->decl.ishidden;
		return 1;
	}
	if (old->decl.isextern || new->decl.isextern) {
		old->decl.isextern = old->decl.isextern && new->decl.isextern;
		return 1;
	}
	if (old->decl.vis == Visexport && new->decl.vis != Visexport) {
		e = old;
		g = new;
	} else if (new->decl.vis == Visexport && old->decl.vis != Visexport) {
		e = new;
		g = old;
	} else {
		return 0;
	}
	old->decl.vis = Visexport;

	if (e->decl.init && g->decl.init)
		fatal(e, "export %s double initialized on %s:%d", declname(e), fname(g->loc),
				lnum(g->loc));
	if (e->decl.isgeneric != g->decl.isgeneric)
		fatal(e, "export %s declared with different genericness on %s:%d", declname(e),
				fname(g->loc), lnum(g->loc));
	if (e->decl.isconst != g->decl.isconst)
		fatal(e, "export %s declared with different constness on %s:%d", declname(e),
				fname(g->loc), lnum(g->loc));
	if (e->decl.isextern != g->decl.isextern)
		fatal(e, "export %s declared with different externness on %s:%d", declname(e),
				fname(g->loc), lnum(g->loc));

	setns(old->decl.name, new->decl.name->name.ns);
	if (e->decl.type->type == Tyvar)
		e->decl.type = g->decl.type;
	else if (g->decl.type->type == Tyvar)
		g->decl.type = e->decl.type;
	else
		fatal(e, "FIXME: types on both prototype and decl not yet allowed\n");

	if (!e->decl.init)
		e->decl.init = g->decl.init;
	else if (!g->decl.init)
		g->decl.init = e->decl.init;
	if (old->decl.env || e->decl.env) {
		if (!old->decl.env)
			old->decl.env = e->decl.env;
		if (!e->decl.env)
			e->decl.env = old->decl.env;
		bindtype(e->decl.env, old->decl.type);
		bindtype(old->decl.env, e->decl.type);
	}

	/* FIXME: check compatible typing */
	old->decl.ishidden = e->decl.ishidden || g->decl.ishidden;
	old->decl.isimport = e->decl.isimport || g->decl.isimport;
	old->decl.isnoret = e->decl.isnoret || g->decl.isnoret;
	old->decl.isexportinit = e->decl.isexportinit || g->decl.isexportinit;
	old->decl.isglobl = e->decl.isglobl || g->decl.isglobl;
	old->decl.ispkglocal = e->decl.ispkglocal || g->decl.ispkglocal;
	old->decl.isextern = e->decl.isextern || g->decl.isextern;
	return 1;
}

void
forcedcl(Stab *st, Node *s)
{
	setns(s->decl.name, st->name);
	htput(st->dcl, s->decl.name, s);
	assert(htget(st->dcl, s->decl.name) != NULL);
}

void
putdcl(Stab *st, Node *s)
{
	Node *old;

	st = findstab(st, s->decl.name);
	old = htget(st->dcl, s->decl.name);
	if (s->decl.isauto) {
		assert(!old);
		lappend(&st->autodcl, &st->nautodcl, s);
	}
	if (!old)
		forcedcl(st, s);
	else if (!mergedecl(old, s))
		fatal(old, "%s already declared on %s:%d", namestr(s->decl.name), fname(s->loc),
				lnum(s->loc));
}

void
updatetype(Stab *st, Node *n, Type *t)
{
	Tydefn *td;

	td = htget(st->ty, n);
	if (!td)
		die("no type %s to update", namestr(n));
	td->type = t;
}

int
mergetype(Type *old, Type *new)
{
	if (!new) {
		lfatal(old->loc, "double prototyping of %s", tystr(old));
	} else if (old->vis == Visexport && new->vis != Visexport) {
		if (!old->sub && new->sub) {
			old->sub = new->sub;
			old->nsub = new->nsub;
			return 1;
		}
	} else if (new->vis == Visexport && old->vis != Visexport) {
		if (!new->sub && old->sub) {
			new->sub = old->sub;
			new->nsub = old->nsub;
			return 1;
		}
	}
	return 0;
}

void
puttype(Stab *st, Node *n, Type *t)
{
	Tydefn *td;
	Type *ty;

	st = findstab(st, n);
	setns(n, st->name);
	if (st->name && t && t->name)
		setns(t->name, st->name);

	ty = gettype(st, n);
	if (!ty) {
		if (t && hastype(st, n)) {
			t->vis = Visexport;
			updatetype(st, n, t);
		} else {
			td = xalloc(sizeof(Tydefn));
			td->loc = n->loc;
			td->name = n;
			td->type = t;
			htput(st->ty, td->name, td);
		}
	} else if (!mergetype(ty, t)) {
		fatal(n, "type %s already declared on %s:%d", tystr(ty), fname(ty->loc),
				lnum(ty->loc));
	}
}

void
putucon(Stab *st, Ucon *uc)
{
	Ucon *old;

	old = getucon(st, uc->name);
	if (old)
		lfatal(old->loc, "`%s already defined on %s:%d",
			namestr(uc->name), fname(uc->loc), lnum(uc->loc));
	setns(uc->name, st->name);
	htput(st->uc, uc->name, uc);
}

static int
mergetrait(Trait *old, Trait *new)
{
	if (old->isproto && !new->isproto)
		*old = *new;
	else if (new->isproto && !old->isproto)
		*new = *old;
	else if (!new->isimport && !old->isimport)
		return new->vis == Vishidden || old->vis == Vishidden;
	return 1;
}

void
puttrait(Stab *st, Node *n, Trait *c)
{
	Traitdefn *td;
	Trait *t;
	Type *ty;

	st = findstab(st, n);
	t = gettrait(st, n);
	if (t && !mergetrait(t, c))
		fatal(n, "trait %s already defined on %s:%d",
			namestr(n), fname(t->loc), lnum(t->loc));
	ty = gettype(st, n);
	if (ty)
		fatal(n, "trait %s defined as a type on %s:%d",
			namestr(n), fname(ty->loc), lnum(ty->loc));
	td = xalloc(sizeof(Traitdefn));
	td->loc = n->loc;
	td->name = n;
	td->trait = c;
	setns(td->name, st->name);
	htput(st->tr, td->name, td);
}

static int
mergeimpl(Node *old, Node *new)
{
	Vis vis;

	/* extern traits should be deduped in use.c. */
	if (old->impl.isextern && new->impl.isextern)
		return 1;
	if (old->impl.vis > new->impl.vis)
		vis = old->impl.vis;
	else
		vis = new->impl.vis;
	old->impl.vis = vis;
	new->impl.vis = vis;
	if (old->impl.isproto && !new->impl.isproto)
		*old = *new;
	else if (new->impl.isproto && !old->impl.isproto)
		*new = *old;
	else
		return 0;
	return 1;
}

void
putimpl(Stab *st, Node *n)
{
	Node *impl;

	st = findstab(st, n->impl.traitname);
	impl = getimpl(st, n);
	if (impl && !mergeimpl(impl, n))
		fatal(n, "trait %s already implemented over %s at %s:%d",
			namestr(n->impl.traitname), tystr(n->impl.type),
			fname(n->loc), lnum(n->loc));
	/*
	 The impl is not defined in this file, so setting the
	 trait name would be a bug here.
	 */
	setns(n->impl.traitname, st->name);
	htput(st->impl, n, n);
}

Node *
getimpl(Stab *st, Node *n)
{
	Node *imp;

	do {
		if ((imp = htget(st->impl, n)))
			return imp;
		st = st->super;
	} while (st);
	return NULL;
}

void
putns(Node *file, Stab *scope)
{
	Stab *s;

	s = getns(file, scope->name);
	if (s)
		lfatal(Zloc, "Namespace %s already defined", scope->name);
	htput(file->file.ns, scope->name, scope);
}

/*
 * Sets the namespace of a symbol table, and
 * changes the namespace of all contained symbol
 * to match it.
 */
void
updatens(Stab *st, char *name)
{
	void **k;
	size_t i, nk;
	Tydefn *td;

	if (st->name)
		die("stab %s already has namespace; Can't set to %s", st->name, name);
	st->name = strdup(name);
	htput(file->file.ns, st->name, st);

	k = htkeys(st->dcl, &nk);
	for (i = 0; i < nk; i++)
		setns(k[i], name);
	free(k);

	k = htkeys(st->ty, &nk);
	for (i = 0; i < nk; i++)
		setns(k[i], name);
	for (i = 0; i < nk; i++) {
		td = htget(st->ty, k[i]);
		if (td->type && (td->type->type == Tyname || td->type->type == Tygeneric))
			setns(td->type->name, name);
	}
	free(k);

	k = htkeys(st->uc, &nk);
	for (i = 0; i < nk; i++)
		setns(k[i], name);
	free(k);
}

static void
bindtype_rec(Tyenv *e, Type *t, Bitset *visited)
{
	size_t i;
	Type *tt;

	t = tysearch(t);
	if (bshas(visited, t->tid))
		return;
	bsput(visited, t->tid);
	switch (t->type) {
	case Typaram:
		tt = htget(e->tab, t);
		if (tt && tt != t)
			tytab[t->tid] = tt;
		else if (!boundtype(t))
			htput(e->tab, t, t);
		break;
	case Tygeneric:
		for (i = 0; i < t->ngparam; i++)
			bindtype_rec(e, t->gparam[i], visited);
		break;
	case Tyname:
		for (i = 0; i < t->narg; i++)
			bindtype_rec(e, t->arg[i], visited);
		break;
	case Tyunres:
		for (i = 0; i < t->narg; i++)
			bindtype_rec(e, t->arg[i], visited);
		break;
	case Tystruct:
		for (i = 0; i < t->nmemb; i++)
			bindtype_rec(e, t->sdecls[i]->decl.type, visited);
		break;
	case Tyunion:
		for (i = 0; i < t->nmemb; i++)
			if (t->udecls[i]->etype)
				bindtype_rec(e, t->udecls[i]->etype, visited);
		break;
	default:
		for (i = 0; i < t->nsub; i++)
			bindtype_rec(e, t->sub[i], visited);
		break;
	}
}

/* Binds the type parameters present in the
 * current type into the type environment */
void
bindtype(Tyenv *env, Type *t)
{
	Bitset *visited;

	if (!t)
		return;
	visited = mkbs();
	bindtype_rec(env, t, visited);
	bsfree(visited);
}

/* If the current environment binds a type,
 * we return true */
Type*
boundtype(Type *t)
{
	Tyenv *e;
	Type *r;

	for (e = curenv(); e; e = e->super) {
		r = htget(e->tab, t);
		if (r)
			return r;
	}
	return NULL;
}
