// 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 "runtime.h"

int32	iface_debug	= 0;

typedef	struct	Sigt	Sigt;
typedef	struct	Sigi	Sigi;
typedef	struct	Itype	Itype;

/*
 * the layout of Iface, Sigt and Sigi are known to the compiler
 */
struct	Sigt
{
	byte*	name;
	uint32	hash;		// hash of type		// first is alg
	uint32	offset;		// offset of substruct	// first is width
	void	(*fun)(void);
};

struct	Sigi
{
	byte*	name;
	uint32	hash;
	uint32	perm;		// location of fun in Sigt // first is size
};

struct	Itype
{
	Sigi*	sigi;
	Sigt*	sigt;
	Itype*	link;
	int32	bad;
	int32	unused;
	void	(*fun[])(void);
};

static	Iface	niliface;
static	Itype*	hash[1009];
static	Lock	ifacelock;

Sigi	sigi·empty[2] =	{ (byte*)"interface { }" };

static void
printsigi(Sigi *si)
{
	int32 i;
	byte *name;

	sys·printpointer(si);
	prints("{");
	prints((int8*)si[0].name);
	prints(":");
	for(i=1;; i++) {
		name = si[i].name;
		if(name == nil)
			break;
		prints("[");
		sys·printint(i);
		prints("]\"");
		prints((int8*)name);
		prints("\"");
		sys·printint(si[i].hash%999);
		prints("/");
		sys·printint(si[i].perm);
	}
	prints("}");
}

static void
printsigt(Sigt *st)
{
	int32 i;
	byte *name;

	sys·printpointer(st);
	prints("{");
	prints((int8*)st[0].name);
	prints(":");
	sys·printint(st[0].hash);	// first element has alg
	prints(",");
	sys·printint(st[0].offset);	// first element has width
	for(i=1;; i++) {
		name = st[i].name;
		if(name == nil)
			break;
		prints("[");
		sys·printint(i);
		prints("]\"");
		prints((int8*)name);
		prints("\"");
		sys·printint(st[i].hash%999);
		prints("/");
		sys·printint(st[i].offset);
		prints("/");
		sys·printpointer(st[i].fun);
	}
	prints("}");
}

static void
printiface(Iface i)
{
	prints("(");
	sys·printpointer(i.type);
	prints(",");
	sys·printpointer(i.data);
	prints(")");
}

static Itype*
itype(Sigi *si, Sigt *st, int32 canfail)
{
	int32 locked;
	int32 nt, ni;
	uint32 ihash, h;
	byte *sname, *iname;
	Itype *m;

	// compiler has provided some good hash codes for us.
	h = 0;
	if(si)
		h += si->hash;
	if(st)
		h += st->hash >> 8;
	h %= nelem(hash);

	// look twice - once without lock, once with.
	// common case will be no lock contention.
	for(locked=0; locked<2; locked++) {
		if(locked)
			lock(&ifacelock);
		for(m=hash[h]; m!=nil; m=m->link) {
			if(m->sigi == si && m->sigt == st) {
				if(m->bad) {
					m = nil;
					if(!canfail) {
						// this can only happen if the conversion
						// was already done once using the , ok form
						// and we have a cached negative result.
						// the cached result doesn't record which
						// interface function was missing, so jump
						// down to the interface check, which will
						// give a better error.
						goto throw;
					}
				}
				// prints("old itype\n");
				if(locked)
					unlock(&ifacelock);
				return m;
			}
		}
	}
	
	ni = si[0].perm;	// first entry has size
	m = mal(sizeof(*m) + ni*sizeof(m->fun[0]));
	m->sigi = si;
	m->sigt = st;

throw:
	nt = 1;
	for(ni=1;; ni++) {	// ni=1: skip first word
		iname = si[ni].name;
		if(iname == nil)
			break;

		// pick up next name from
		// interface signature
		ihash = si[ni].hash;

		for(;; nt++) {
			// pick up and compare next name
			// from structure signature
			sname = st[nt].name;
			if(sname == nil) {
				if(!canfail) {
					prints("cannot convert type ");
					prints((int8*)st[0].name);
					prints(" to interface ");
					prints((int8*)si[0].name);
					prints(": missing method ");
					prints((int8*)iname);
					prints("\n");
					if(iface_debug) {
						prints("interface");
						printsigi(si);
						prints("\ntype");
						printsigt(st);
						prints("\n");
					}
					throw("interface conversion");
				}
				m->bad = 1;
				m->link = hash[h];
				hash[h] = m;
				if(locked)
					unlock(&ifacelock);
				return nil;
			}
			if(ihash == st[nt].hash && strcmp(sname, iname) == 0)
				break;
		}
		m->fun[si[ni].perm] = st[nt].fun;
	}
	m->link = hash[h];
	hash[h] = m;
	// printf("new itype %p\n", m);
	if(locked)
		unlock(&ifacelock);
	return m;
}

// ifaceT2I(sigi *byte, sigt *byte, elem any) (ret any);
void
sys·ifaceT2I(Sigi *si, Sigt *st, ...)
{
	byte *elem;
	Iface *ret;
	int32 alg, wid;

	elem = (byte*)(&st+1);
	wid = st->offset;
	ret = (Iface*)(elem + rnd(wid, 8));
	ret->type = itype(si, st, 0);

	if(iface_debug) {
		prints("T2I sigi=");
		printsigi(si);
		prints(" sigt=");
		printsigt(st);
		prints(" elem=");
		sys·printpointer(*(void**)elem);
		prints("\n");
	}

	alg = st->hash & 0xFF;
	wid = st->offset;
	if(wid <= sizeof ret->data)
		algarray[alg].copy(wid, &ret->data, elem);
	else{
		ret->data = mal(wid);
		if(iface_debug)
			printf("T2I mal %d %p\n", wid, ret->data);
		algarray[alg].copy(wid, ret->data, elem);
	}

	if(iface_debug) {
		prints("T2I ret=");
		printiface(*ret);
		prints("\n");
	}

	FLUSH(&ret);
}

// ifaceI2T(sigt *byte, iface any) (ret any);
void
sys·ifaceI2T(Sigt *st, Iface i, ...)
{
	Itype *im;
	byte *ret;
	int32 wid, alg;

	ret = (byte*)(&i+1);

	if(iface_debug) {
		prints("I2T sigt=");
		printsigt(st);
		prints(" iface=");
		printiface(i);
		prints("\n");
	}

	im = i.type;
	if(im == nil) {
		prints("interface is nil, not ");
		prints((int8*)st[0].name);
		prints("\n");
		throw("interface conversion");
	}

	if(im->sigt != st) {
		prints((int8*)im->sigi[0].name);
		prints(" is ");
		prints((int8*)im->sigt[0].name);
		prints(", not ");
		prints((int8*)st[0].name);
		prints("\n");
		throw("interface conversion");
	}

	alg = st->hash & 0xFF;
	wid = st->offset;
	if(wid <= sizeof i.data)
		algarray[alg].copy(wid, ret, &i.data);
	else
		algarray[alg].copy(wid, ret, i.data);

	if(iface_debug) {
		prints("I2T ret=");
		sys·printpointer(*(void**)ret);
		prints("\n");
	}
	FLUSH(&ret);
}

// ifaceI2T2(sigt *byte, iface any) (ret any, ok bool);
void
sys·ifaceI2T2(Sigt *st, Iface i, ...)
{
	byte *ret;
	bool *ok;
	Itype *im;
	int32 alg, wid;

	ret = (byte*)(&i+1);
	alg = st->hash & 0xFF;
	wid = st->offset;
	ok = (bool*)(ret+rnd(wid, 8));

	if(iface_debug) {
		prints("I2T2 sigt=");
		printsigt(st);
		prints(" iface=");
		printiface(i);
		prints("\n");
	}

	im = i.type;
	if(im == nil || im->sigt != st) {
		*ok = false;
		sys·memclr(ret, wid);
	} else {
		*ok = true;
		if(wid <= sizeof i.data)
			algarray[alg].copy(wid, ret, &i.data);
		else
			algarray[alg].copy(wid, ret, i.data);
	}
	if(iface_debug) {
		prints("I2T2 ret=");
		sys·printpointer(*(void**)ret);
		sys·printbool(*ok);
		prints("\n");
	}
}

// ifaceI2I(sigi *byte, iface any) (ret any);
void
sys·ifaceI2I(Sigi *si, Iface i, Iface ret)
{
	Itype *im;

	if(iface_debug) {
		prints("I2I sigi=");
		printsigi(si);
		prints(" iface=");
		printiface(i);
		prints("\n");
	}

	im = i.type;
	if(im == nil) {
		// If incoming interface is uninitialized (zeroed)
		// make the outgoing interface zeroed as well.
		ret = niliface;
	} else {
		ret = i;
		if(im->sigi != si)
			ret.type = itype(si, im->sigt, 0);
	}

	if(iface_debug) {
		prints("I2I ret=");
		printiface(ret);
		prints("\n");
	}

	FLUSH(&ret);
}

// ifaceI2I2(sigi *byte, iface any) (ret any, ok bool);
void
sys·ifaceI2I2(Sigi *si, Iface i, Iface ret, bool ok)
{
	Itype *im;

	if(iface_debug) {
		prints("I2I2 sigi=");
		printsigi(si);
		prints(" iface=");
		printiface(i);
		prints("\n");
	}

	im = i.type;
	if(im == nil) {
		// If incoming interface is uninitialized (zeroed)
		// make the outgoing interface zeroed as well.
		ret = niliface;
		ok = 1;
	} else {
		ret = i;
		ok = 1;
		if(im->sigi != si) {
			ret.type = itype(si, im->sigt, 1);
			if(ret.type == nil) {
				ret = niliface;
				ok = 0;
			}
		}
	}

	if(iface_debug) {
		prints("I2I ret=");
		printiface(ret);
		prints("\n");
	}

	FLUSH(&ret);
	FLUSH(&ok);
}

uint64
ifacehash(Iface a)
{
	int32 alg, wid;
	
	if(a.type == nil)
		return 0;
	alg = a.type->sigt->hash & 0xFF;
	wid = a.type->sigt->offset;
	if(algarray[alg].hash == nohash) {
		// calling nohash will throw too,
		// but we can print a better error.
		printf("hash of unhashable type %s\n", a.type->sigt->name);
		throw("interface hash");
	}
	if(wid <= sizeof a.data)
		return algarray[alg].hash(wid, &a.data);
	else
		return algarray[alg].hash(wid, a.data);
}

bool
ifaceeq(Iface i1, Iface i2)
{
	int32 alg, wid;
	bool ret;

	if(iface_debug) {
		prints("Ieq i1=");
		printiface(i1);
		prints(" i2=");
		printiface(i2);
		prints("\n");
	}

	ret = false;

	// are they both nil
	if(i1.type == nil) {
		if(i2.type == nil)
			goto yes;
		goto no;
	}
	if(i2.type == nil)
		goto no;

	// are they the same type?
	if(i1.type->sigt != i2.type->sigt)
		goto no;

	alg = i1.type->sigt->hash & 0xFF;
	wid = i1.type->sigt->offset;

	if(algarray[alg].equal == noequal) {
		// calling noequal will throw too,
		// but we can print a better error.
		printf("comparing uncomparable type %s\n", i1.type->sigt->name);
		throw("interface compare");
	}

	if(wid <= sizeof i1.data) {
		if(!algarray[alg].equal(wid, &i1.data, &i2.data))
			goto no;
	} else {
		if(!algarray[alg].equal(wid, i1.data, i2.data))
			goto no;
	}

yes:
	ret = true;
no:
	if(iface_debug) {
		prints("Ieq ret=");
		sys·printbool(ret);
		prints("\n");
	}
	return ret;
}

// ifaceeq(i1 any, i2 any) (ret bool);
void
sys·ifaceeq(Iface i1, Iface i2, bool ret)
{
	ret = ifaceeq(i1, i2);
	FLUSH(&ret);
}

void
sys·printinter(Iface i)
{
	printiface(i);
}

void
sys·Reflect(Iface i, uint64 retit, string rettype, bool retindir)
{
	int32 wid;

	if(i.type == nil) {
		retit = 0;
		rettype = nil;
		retindir = false;
	} else {
		retit = (uint64)i.data;
		rettype = gostring(i.type->sigt->name);
		wid = i.type->sigt->offset;
		retindir = wid > sizeof i.data;
	}
	FLUSH(&retit);
	FLUSH(&rettype);
	FLUSH(&retindir);
}

extern Sigt *gotypesigs[];
extern int32 ngotypesigs;


// The reflection library can ask to unreflect on a type
// that has never been used, so we don't have a signature for it.
// For concreteness, suppose a program does
//
// 	type T struct{ x []int }
// 	var t T;
// 	v := reflect.NewValue(v);
// 	vv := v.Field(0);
// 	if s, ok := vv.Interface().(string) {
// 		print("first field is string");
// 	}
//
// vv.Interface() returns the result of sys.Unreflect with
// a typestring of "[]int".  If []int is not used with interfaces
// in the rest of the program, there will be no signature in gotypesigs
// for "[]int", so we have to invent one.  The only requirements
// on the fake signature are:
//
//	(1) any interface conversion using the signature will fail
//	(2) calling sys.Reflect() returns the args to unreflect
//
// (1) is ensured by the fact that we allocate a new Sigt,
// so it will necessarily be != any Sigt in gotypesigs.
// (2) is ensured by storing the type string in the signature
// and setting the width to force the correct value of the bool indir.
//
// Note that (1) is correct behavior: if the program had tested
// for .([]int) instead of .(string) above, then there would be a
// signature with type string "[]int" in gotypesigs, and unreflect
// wouldn't call fakesigt.

static	Sigt	*fake[1009];
static	int32	nfake;

static Sigt*
fakesigt(string type, bool indir)
{
	Sigt *sigt;
	uint32 h;
	int32 i, locked;

	if(type == nil)
		type = emptystring;

	h = 0;
	for(i=0; i<type->len; i++)
		h = h*37 + type->str[i];
	h += indir;
	h %= nelem(fake);
	
	for(locked=0; locked<2; locked++) {
		if(locked)
			lock(&ifacelock);
		for(sigt = fake[h]; sigt != nil; sigt = (Sigt*)sigt->fun) {
			// don't need to compare indir.
			// same type string but different indir will have
			// different hashes.
			if(mcmp(sigt->name, type->str, type->len) == 0)
			if(sigt->name[type->len] == '\0') {
				if(locked)
					unlock(&ifacelock);
				return sigt;
			}
		}
	}

	sigt = mal(2*sizeof sigt[0]);
	sigt[0].name = mal(type->len + 1);
	mcpy(sigt[0].name, type->str, type->len);
	sigt[0].hash = AFAKE;	// alg
	if(indir)
		sigt[0].offset = 2*sizeof(niliface.data);  // big width
	else
		sigt[0].offset = 1;  // small width
	sigt->fun = (void*)fake[h];
	fake[h] = sigt;
	unlock(&ifacelock);
	return sigt;
}

static int32
cmpstringchars(string a, uint8 *b)
{
	int32 i;
	byte c1, c2;

	for(i=0;; i++) {
		if(i == a->len)
			c1 = 0;
		else
			c1 = a->str[i];
		c2 = b[i];
		if(c1 < c2)
			return -1;
		if(c1 > c2)
			return +1;
		if(c1 == 0)
			return 0;
	}
}

static Sigt*
findtype(string type, bool indir)
{
	int32 i, lo, hi, m;
	
	lo = 0;
	hi = ngotypesigs;
	while(lo < hi) {
		m = lo + (hi - lo)/2;
		i = cmpstringchars(type, gotypesigs[m]->name);
		if(i == 0)
			return gotypesigs[m];
		if(i < 0)
			hi = m;
		else
			lo = m+1;
	}
	return fakesigt(type, indir);
}


void
sys·Unreflect(uint64 it, string type, bool indir, Iface ret)
{
	Sigt *sigt;

	ret = niliface;

	if(cmpstring(type, emptystring) == 0)
		goto out;

	// if we think the type should be indirect
	// and caller does not, play it safe, return nil.
	sigt = findtype(type, indir);
	if(indir != (sigt[0].offset > sizeof ret.data))
		goto out;

	ret.type = itype(sigi·empty, sigt, 0);
	ret.data = (void*)it;

out:
	FLUSH(&ret);
}