// 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.

package ogle

import (
	"debug/proc";
	"exp/eval";
	"fmt";
	"log";
)

const debugParseRemoteType = false

// A remoteType is the local representation of a type in a remote process.
type remoteType struct {
	eval.Type;
	// The size of values of this type in bytes.
	size	int;
	// The field alignment of this type.  Only used for
	// manually-constructed types.
	fieldAlign	int;
	// The maker function to turn a remote address of a value of
	// this type into an interpreter Value.
	mk	maker;
}

var manualTypes = make(map[Arch]map[eval.Type]*remoteType)

// newManualType constructs a remote type from an interpreter Type
// using the size and alignment properties of the given architecture.
// Most types are parsed directly out of the remote process, but to do
// so we need to layout the structures that describe those types ourselves.
func newManualType(t eval.Type, arch Arch) *remoteType {
	if nt, ok := t.(*eval.NamedType); ok {
		t = nt.Def;
	}

	// Get the type map for this architecture
	typeMap, _ := manualTypes[arch];
	if typeMap == nil {
		typeMap = make(map[eval.Type]*remoteType);
		manualTypes[arch] = typeMap;

		// Construct basic types for this architecture
		basicType := func(t eval.Type, mk maker, size int, fieldAlign int) {
			t = t.(*eval.NamedType).Def;
			if fieldAlign == 0 {
				fieldAlign = size;
			}
			typeMap[t] = &remoteType{t, size, fieldAlign, mk};
		};
		basicType(eval.Uint8Type, mkUint8, 1, 0);
		basicType(eval.Uint32Type, mkUint32, 4, 0);
		basicType(eval.UintptrType, mkUintptr, arch.PtrSize(), 0);
		basicType(eval.Int16Type, mkInt16, 2, 0);
		basicType(eval.Int32Type, mkInt32, 4, 0);
		basicType(eval.IntType, mkInt, arch.IntSize(), 0);
		basicType(eval.StringType, mkString, arch.PtrSize() + arch.IntSize(), arch.PtrSize());
	}

	if rt, ok := typeMap[t]; ok {
		return rt;
	}

	var rt *remoteType;
	switch t := t.(type) {
	case *eval.PtrType:
		var elem *remoteType;
		mk := func(r remote) eval.Value { return remotePtr{r, elem} };
		rt = &remoteType{t, arch.PtrSize(), arch.PtrSize(), mk};
		// Construct the element type after registering the
		// type to break cycles.
		typeMap[eval.Type(t)] = rt;
		elem = newManualType(t.Elem, arch);

	case *eval.ArrayType:
		elem := newManualType(t.Elem, arch);
		mk := func(r remote) eval.Value { return remoteArray{r, t.Len, elem} };
		rt = &remoteType{t, elem.size * int(t.Len), elem.fieldAlign, mk};

	case *eval.SliceType:
		elem := newManualType(t.Elem, arch);
		mk := func(r remote) eval.Value { return remoteSlice{r, elem} };
		rt = &remoteType{t, arch.PtrSize() + 2 * arch.IntSize(), arch.PtrSize(), mk};

	case *eval.StructType:
		layout := make([]remoteStructField, len(t.Elems));
		offset := 0;
		fieldAlign := 0;
		for i, f := range t.Elems {
			elem := newManualType(f.Type, arch);
			if fieldAlign == 0 {
				fieldAlign = elem.fieldAlign;
			}
			offset = arch.Align(offset, elem.fieldAlign);
			layout[i].offset = offset;
			layout[i].fieldType = elem;
			offset += elem.size;
		}
		mk := func(r remote) eval.Value { return remoteStruct{r, layout} };
		rt = &remoteType{t, offset, fieldAlign, mk};

	default:
		log.Crashf("cannot manually construct type %T", t);
	}

	typeMap[t] = rt;
	return rt;
}

var prtIndent = ""

// parseRemoteType parses a Type structure in a remote process to
// construct the corresponding interpreter type and remote type.
func parseRemoteType(a aborter, rs remoteStruct) *remoteType {
	addr := rs.addr().base;
	p := rs.addr().p;

	// We deal with circular types by discovering cycles at
	// NamedTypes.  If a type cycles back to something other than
	// a named type, we're guaranteed that there will be a named
	// type somewhere in that cycle.  Thus, we continue down,
	// re-parsing types until we reach the named type in the
	// cycle.  In order to still create one remoteType per remote
	// type, we insert an empty remoteType in the type map the
	// first time we encounter the type and re-use that structure
	// the second time we encounter it.

	rt, ok := p.types[addr];
	if ok && rt.Type != nil {
		return rt;
	} else if !ok {
		rt = &remoteType{};
		p.types[addr] = rt;
	}

	if debugParseRemoteType {
		sym := p.syms.SymByAddr(uint64(addr));
		name := "<unknown>";
		if sym != nil {
			name = sym.Name;
		}
		log.Stderrf("%sParsing type at %#x (%s)", prtIndent, addr, name);
		prtIndent += " ";
		defer func() {
			prtIndent = prtIndent[0 : len(prtIndent)-1];
		}();
	}

	// Get Type header
	itype := proc.Word(rs.field(p.f.Type.Typ).(remoteUint).aGet(a));
	typ := rs.field(p.f.Type.Ptr).(remotePtr).aGet(a).(remoteStruct);

	// Is this a named type?
	var nt *eval.NamedType;
	uncommon := typ.field(p.f.CommonType.UncommonType).(remotePtr).aGet(a);
	if uncommon != nil {
		name := uncommon.(remoteStruct).field(p.f.UncommonType.Name).(remotePtr).aGet(a);
		if name != nil {
			// TODO(austin) Declare type in appropriate remote package
			nt = eval.NewNamedType(name.(remoteString).aGet(a));
			rt.Type = nt;
		}
	}

	// Create type
	var t eval.Type;
	var mk maker;
	switch itype {
	case p.runtime.PBoolType:
		t = eval.BoolType;
		mk = mkBool;
	case p.runtime.PUint8Type:
		t = eval.Uint8Type;
		mk = mkUint8;
	case p.runtime.PUint16Type:
		t = eval.Uint16Type;
		mk = mkUint16;
	case p.runtime.PUint32Type:
		t = eval.Uint32Type;
		mk = mkUint32;
	case p.runtime.PUint64Type:
		t = eval.Uint64Type;
		mk = mkUint64;
	case p.runtime.PUintType:
		t = eval.UintType;
		mk = mkUint;
	case p.runtime.PUintptrType:
		t = eval.UintptrType;
		mk = mkUintptr;
	case p.runtime.PInt8Type:
		t = eval.Int8Type;
		mk = mkInt8;
	case p.runtime.PInt16Type:
		t = eval.Int16Type;
		mk = mkInt16;
	case p.runtime.PInt32Type:
		t = eval.Int32Type;
		mk = mkInt32;
	case p.runtime.PInt64Type:
		t = eval.Int64Type;
		mk = mkInt64;
	case p.runtime.PIntType:
		t = eval.IntType;
		mk = mkInt;
	case p.runtime.PFloat32Type:
		t = eval.Float32Type;
		mk = mkFloat32;
	case p.runtime.PFloat64Type:
		t = eval.Float64Type;
		mk = mkFloat64;
	case p.runtime.PFloatType:
		t = eval.FloatType;
		mk = mkFloat;
	case p.runtime.PStringType:
		t = eval.StringType;
		mk = mkString;

	case p.runtime.PArrayType:
		// Cast to an ArrayType
		typ := p.runtime.ArrayType.mk(typ.addr()).(remoteStruct);
		len := int64(typ.field(p.f.ArrayType.Len).(remoteUint).aGet(a));
		elem := parseRemoteType(a, typ.field(p.f.ArrayType.Elem).(remotePtr).aGet(a).(remoteStruct));
		t = eval.NewArrayType(len, elem.Type);
		mk = func(r remote) eval.Value { return remoteArray{r, len, elem} };

	case p.runtime.PStructType:
		// Cast to a StructType
		typ := p.runtime.StructType.mk(typ.addr()).(remoteStruct);
		fs := typ.field(p.f.StructType.Fields).(remoteSlice).aGet(a);

		fields := make([]eval.StructField, fs.Len);
		layout := make([]remoteStructField, fs.Len);
		for i := range fields {
			f := fs.Base.(remoteArray).elem(int64(i)).(remoteStruct);
			elemrs := f.field(p.f.StructField.Typ).(remotePtr).aGet(a).(remoteStruct);
			elem := parseRemoteType(a, elemrs);
			fields[i].Type = elem.Type;
			name := f.field(p.f.StructField.Name).(remotePtr).aGet(a);
			if name == nil {
				fields[i].Anonymous = true;
			} else {
				fields[i].Name = name.(remoteString).aGet(a);
			}
			layout[i].offset = int(f.field(p.f.StructField.Offset).(remoteUint).aGet(a));
			layout[i].fieldType = elem;
		}

		t = eval.NewStructType(fields);
		mk = func(r remote) eval.Value { return remoteStruct{r, layout} };

	case p.runtime.PPtrType:
		// Cast to a PtrType
		typ := p.runtime.PtrType.mk(typ.addr()).(remoteStruct);
		elem := parseRemoteType(a, typ.field(p.f.PtrType.Elem).(remotePtr).aGet(a).(remoteStruct));
		t = eval.NewPtrType(elem.Type);
		mk = func(r remote) eval.Value { return remotePtr{r, elem} };

	case p.runtime.PSliceType:
		// Cast to a SliceType
		typ := p.runtime.SliceType.mk(typ.addr()).(remoteStruct);
		elem := parseRemoteType(a, typ.field(p.f.SliceType.Elem).(remotePtr).aGet(a).(remoteStruct));
		t = eval.NewSliceType(elem.Type);
		mk = func(r remote) eval.Value { return remoteSlice{r, elem} };

	case p.runtime.PMapType, p.runtime.PChanType, p.runtime.PFuncType, p.runtime.PInterfaceType, p.runtime.PUnsafePointerType, p.runtime.PDotDotDotType:
		// TODO(austin)
		t = eval.UintptrType;
		mk = mkUintptr;

	default:
		sym := p.syms.SymByAddr(uint64(itype));
		name := "<unknown symbol>";
		if sym != nil {
			name = sym.Name;
		}
		err := fmt.Sprintf("runtime type at %#x has unexpected type %#x (%s)", addr, itype, name);
		a.Abort(FormatError(err));
	}

	// Fill in the remote type
	if nt != nil {
		nt.Complete(t);
	} else {
		rt.Type = t;
	}
	rt.size = int(typ.field(p.f.CommonType.Size).(remoteUint).aGet(a));
	rt.mk = mk;

	return rt;
}
