// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

#include	<u.h>
#include	<libc.h>
#include	"go.h"
#include	"y.tab.h"

static void	dumpexporttype(Type *t);

// Mark n's symbol as exported
void
exportsym(Node *n)
{
	if(n == N || n->sym == S)
		return;
	if(n->sym->flags & (SymExport|SymPackage)) {
		if(n->sym->flags & SymPackage)
			yyerror("export/package mismatch: %S", n->sym);
		return;
	}
	n->sym->flags |= SymExport;

	exportlist = list(exportlist, n);
}

// Mark n's symbol as package-local
static void
packagesym(Node *n)
{
	if(n == N || n->sym == S)
		return;
	if(n->sym->flags & (SymExport|SymPackage)) {
		if(n->sym->flags & SymExport)
			yyerror("export/package mismatch: %S", n->sym);
		return;
	}
	n->sym->flags |= SymPackage;

	exportlist = list(exportlist, n);
}

int
exportname(char *s)
{
	Rune r;

	if((uchar)s[0] < Runeself)
		return 'A' <= s[0] && s[0] <= 'Z';
	chartorune(&r, s);
	return isupperrune(r);
}

static int
initname(char *s)
{
	return strcmp(s, "init") == 0;
}

void
autoexport(Node *n, int ctxt)
{
	if(n == N || n->sym == S)
		return;
	if((ctxt != PEXTERN && ctxt != PFUNC) || dclcontext != PEXTERN)
		return;
	if(n->ntype && n->ntype->op == OTFUNC && n->ntype->left)	// method
		return;
	if(exportname(n->sym->name) || initname(n->sym->name))
		exportsym(n);
	else
		packagesym(n);
}

static void
dumppkg(Pkg *p)
{
	char *suffix;

	if(p == nil || p == localpkg || p->exported || p == builtinpkg)
		return;
	p->exported = 1;
	suffix = "";
	if(!p->direct)
		suffix = " // indirect";
	Bprint(bout, "\timport %s \"%Z\"%s\n", p->name, p->path, suffix);
}

// Look for anything we need for the inline body
static void reexportdep(Node *n);
static void
reexportdeplist(NodeList *ll)
{
	for(; ll ;ll=ll->next)
		reexportdep(ll->n);
}

static void
reexportdep(Node *n)
{
	Type *t;

	if(!n)
		return;

//	print("reexportdep %+hN\n", n);
	switch(n->op) {
	case ONAME:
		switch(n->class&~PHEAP) {
		case PFUNC:
			// methods will be printed along with their type
			if(!n->type || n->type->thistuple > 0)
				break;
			// fallthrough
		case PEXTERN:
			if (n->sym && n->sym->pkg != localpkg && n->sym->pkg != builtinpkg)
				exportlist = list(exportlist, n);
		}
		break;


	case OLITERAL:
		t = n->type;
		if(t != types[n->type->etype] && t != idealbool && t != idealstring) {
			if(isptr[t->etype])
				t = t->type;
			if (t && t->sym && t->sym->def && t->sym->pkg != localpkg  && t->sym->pkg != builtinpkg) {
//				print("reexport literal type %+hN\n", t->sym->def);
				exportlist = list(exportlist, t->sym->def);
			}
		}
		// fallthrough
	case OTYPE:
		if (n->sym && n->sym->pkg != localpkg && n->sym->pkg != builtinpkg)
			exportlist = list(exportlist, n);
		break;

	// for operations that need a type when rendered, put the type on the export list.
	case OCONV:
	case OCONVIFACE:
	case OCONVNOP:
	case ODOTTYPE:
	case OSTRUCTLIT:
	case OPTRLIT:
		t = n->type;
		if(!t->sym && t->type)
			t = t->type;
		if (t && t->sym && t->sym->def && t->sym->pkg != localpkg  && t->sym->pkg != builtinpkg) {
//			print("reexport convnop %+hN\n", t->sym->def);
			exportlist = list(exportlist, t->sym->def);
		}
		break;
	}

	reexportdep(n->left);
	reexportdep(n->right);
	reexportdeplist(n->list);
	reexportdeplist(n->rlist);
	reexportdeplist(n->ninit);
	reexportdep(n->ntest);
	reexportdep(n->nincr);
	reexportdeplist(n->nbody);
	reexportdeplist(n->nelse);
}


static void
dumpexportconst(Sym *s)
{
	Node *n;
	Type *t;

	n = s->def;
	typecheck(&n, Erv);
	if(n == N || n->op != OLITERAL)
		fatal("dumpexportconst: oconst nil: %S", s);

	t = n->type;	// may or may not be specified
	dumpexporttype(t);

	if(t != T && !isideal(t))
		Bprint(bout, "\tconst %#S %#T = %#V\n", s, t, &n->val);
	else
		Bprint(bout, "\tconst %#S = %#V\n", s, &n->val);
}

static void
dumpexportvar(Sym *s)
{
	Node *n;
	Type *t;

	n = s->def;
	typecheck(&n, Erv|Ecall);
	if(n == N || n->type == T) {
		yyerror("variable exported but not defined: %S", s);
		return;
	}

	t = n->type;
	dumpexporttype(t);

	if(t->etype == TFUNC && n->class == PFUNC) {
		if (n->inl) {
			// when lazily typechecking inlined bodies, some re-exported ones may not have been typechecked yet.
			// currently that can leave unresolved ONONAMEs in import-dot-ed packages in the wrong package
			if(debug['l'] < 2)
				typecheckinl(n);
			Bprint(bout, "\tfunc %#S%#hT { %#H }\n", s, t, n->inl);
			reexportdeplist(n->inl);
		} else
			Bprint(bout, "\tfunc %#S%#hT\n", s, t);
	} else
		Bprint(bout, "\tvar %#S %#T\n", s, t);
}

static int
methcmp(const void *va, const void *vb)
{
	Type *a, *b;
	
	a = *(Type**)va;
	b = *(Type**)vb;
	return strcmp(a->sym->name, b->sym->name);
}

static void
dumpexporttype(Type *t)
{
	Type *f;
	Type **m;
	int i, n;

	if(t == T)
		return;
	if(t->printed || t == types[t->etype] || t == bytetype || t == runetype || t == errortype)
		return;
	t->printed = 1;

	if(t->sym != S && t->etype != TFIELD)
		dumppkg(t->sym->pkg);

	dumpexporttype(t->type);
	dumpexporttype(t->down);

	if (t->sym == S || t->etype == TFIELD)
		return;

	n = 0;
	for(f=t->method; f!=T; f=f->down) {	
		dumpexporttype(f);
		n++;
	}

	m = mal(n*sizeof m[0]);
	i = 0;
	for(f=t->method; f!=T; f=f->down)
		m[i++] = f;
	qsort(m, n, sizeof m[0], methcmp);

	Bprint(bout, "\ttype %#S %#lT\n", t->sym, t);
	for(i=0; i<n; i++) {
		f = m[i];
		if (f->type->nname && f->type->nname->inl) { // nname was set by caninl
			// when lazily typechecking inlined bodies, some re-exported ones may not have been typechecked yet.
			// currently that can leave unresolved ONONAMEs in import-dot-ed packages in the wrong package
			if(debug['l'] < 2)
				typecheckinl(f->type->nname);
			Bprint(bout, "\tfunc (%#T) %#hhS%#hT { %#H }\n", getthisx(f->type)->type, f->sym, f->type, f->type->nname->inl);
			reexportdeplist(f->type->nname->inl);
		} else
			Bprint(bout, "\tfunc (%#T) %#hhS%#hT\n", getthisx(f->type)->type, f->sym, f->type);
	}
}

static void
dumpsym(Sym *s)
{
	if(s->flags & SymExported)
		return;
	s->flags |= SymExported;

	if(s->def == N) {
		yyerror("unknown export symbol: %S", s);
		return;
	}
//	print("dumpsym %O %+S\n", s->def->op, s);
	dumppkg(s->pkg);

	switch(s->def->op) {
	default:
		yyerror("unexpected export symbol: %O %S", s->def->op, s);
		break;

	case OLITERAL:
		dumpexportconst(s);
		break;

	case OTYPE:
		if(s->def->type->etype == TFORW)
			yyerror("export of incomplete type %S", s);
		else
			dumpexporttype(s->def->type);
		break;

	case ONAME:
		dumpexportvar(s);
		break;
	}
}

void
dumpexport(void)
{
	NodeList *l;
	int32 i, lno;
	Pkg *p;

	lno = lineno;

	Bprint(bout, "\n$$  // exports\n    package %s", localpkg->name);
	if(safemode)
		Bprint(bout, " safe");
	Bprint(bout, "\n");

	for(i=0; i<nelem(phash); i++)
		for(p=phash[i]; p; p=p->link)
			if(p->direct)
				dumppkg(p);

	for(l=exportlist; l; l=l->next) {
		lineno = l->n->lineno;
		dumpsym(l->n->sym);
	}

	Bprint(bout, "\n$$  // local types\n\n$$\n");   // 6l expects this. (see ld/go.c)

	lineno = lno;
}

/*
 * import
 */

/*
 * return the sym for ss, which should match lexical
 */
Sym*
importsym(Sym *s, int op)
{
	if(s->def != N && s->def->op != op)
		redeclare(s, "during import");

	// mark the symbol so it is not reexported
	if(s->def == N) {
		if(exportname(s->name) || initname(s->name))
			s->flags |= SymExport;
		else
			s->flags |= SymPackage;	// package scope
	}
	return s;
}

/*
 * return the type pkg.name, forward declaring if needed
 */
Type*
pkgtype(Sym *s)
{
	Type *t;

	importsym(s, OTYPE);
	if(s->def == N || s->def->op != OTYPE) {
		t = typ(TFORW);
		t->sym = s;
		s->def = typenod(t);
	}
	if(s->def->type == T)
		yyerror("pkgtype %S", s);
	return s->def->type;
}

void
importimport(Sym *s, Strlit *z)
{
	// Informational: record package name
	// associated with import path, for use in
	// human-readable messages.
	Pkg *p;

	p = mkpkg(z);
	if(p->name == nil) {
		p->name = s->name;
		pkglookup(s->name, nil)->npkg++;
	} else if(strcmp(p->name, s->name) != 0)
		yyerror("conflicting names %s and %s for package \"%Z\"", p->name, s->name, p->path);
	
	if(!incannedimport && myimportpath != nil && strcmp(z->s, myimportpath) == 0) {
		yyerror("import \"%Z\": package depends on \"%Z\" (import cycle)", importpkg->path, z);
		errorexit();
	}
}

void
importconst(Sym *s, Type *t, Node *n)
{
	Node *n1;

	importsym(s, OLITERAL);
	convlit(&n, t);

	if(s->def != N)	 // TODO: check if already the same.
		return;

	if(n->op != OLITERAL) {
		yyerror("expression must be a constant");
		return;
	}

	if(n->sym != S) {
		n1 = nod(OXXX, N, N);
		*n1 = *n;
		n = n1;
	}
	n->orig = newname(s);
	n->sym = s;
	declare(n, PEXTERN);

	if(debug['E'])
		print("import const %S\n", s);
}

void
importvar(Sym *s, Type *t)
{
	Node *n;

	importsym(s, ONAME);
	if(s->def != N && s->def->op == ONAME) {
		if(eqtype(t, s->def->type))
			return;
		yyerror("inconsistent definition for var %S during import\n\t%T\n\t%T", s, s->def->type, t);
	}
	n = newname(s);
	n->type = t;
	declare(n, PEXTERN);

	if(debug['E'])
		print("import var %S %lT\n", s, t);
}

void
importtype(Type *pt, Type *t)
{
	Node *n;

	// override declaration in unsafe.go for Pointer.
	// there is no way in Go code to define unsafe.Pointer
	// so we have to supply it.
	if(incannedimport &&
	   strcmp(importpkg->name, "unsafe") == 0 &&
	   strcmp(pt->nod->sym->name, "Pointer") == 0) {
		t = types[TUNSAFEPTR];
	}

	if(pt->etype == TFORW) {
		n = pt->nod;
		copytype(pt->nod, t);
		pt->nod = n;		// unzero nod
		pt->sym->lastlineno = parserline();
		declare(n, PEXTERN);
		checkwidth(pt);
	} else if(!eqtype(pt->orig, t))
		yyerror("inconsistent definition for type %S during import\n\t%lT\n\t%lT", pt->sym, pt, t);

	if(debug['E'])
		print("import type %T %lT\n", pt, t);
}
