// 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/gosym";
	"debug/proc";
	"exp/eval";
	"log";
	"os";
)

/*
 * Remote frame pointers
 */

// A NotOnStack error occurs when attempting to access a variable in a
// remote frame where that remote frame is not on the current stack.
type NotOnStack struct {
	Fn		*gosym.Func;
	Goroutine	*Goroutine;
}

func (e NotOnStack) String() string {
	return "function " + e.Fn.Name + " not on " + e.Goroutine.String() + "'s stack";
}

// A remoteFramePtr is an implementation of eval.PtrValue that
// represents a pointer to a function frame in a remote process.  When
// accessed, this locates the function on the current goroutine's
// stack and returns a structure containing the local variables of
// that function.
type remoteFramePtr struct {
	p	*Process;
	fn	*gosym.Func;
	rt	*remoteType;
}

func (v remoteFramePtr) String() string {
	// TODO(austin): This could be a really awesome string method
	return "<remote frame>";
}

func (v remoteFramePtr) Assign(t *eval.Thread, o eval.Value) {
	v.Set(t, o.(eval.PtrValue).Get(t));
}

func (v remoteFramePtr) Get(t *eval.Thread) eval.Value {
	g := v.p.curGoroutine;
	if g == nil || g.frame == nil {
		t.Abort(NoCurrentGoroutine{});
	}

	for f := g.frame; f != nil; f = f.aOuter(t) {
		if f.fn != v.fn {
			continue;
		}

		// TODO(austin): Register for shootdown with f
		return v.rt.mk(remote{f.fp, v.p});
	}

	t.Abort(NotOnStack{v.fn, g});
	panic();
}

func (v remoteFramePtr) Set(t *eval.Thread, x eval.Value) {
	// Theoretically this could be a static error.  If remote
	// packages were packages, remote frames could just be defined
	// as constants.
	t.Abort(ReadOnlyError("remote frames cannot be assigned to"));
}

/*
 * Remote packages
 */

// TODO(austin): Remote packages are implemented as structs right now,
// which has some weird consequences.  You can attempt to assign to a
// remote package.  It also produces terrible error messages.
// Ideally, these would actually be packages, but somehow first-class
// so they could be assigned to other names.

// A remotePackage is an implementation of eval.StructValue that
// represents a package in a remote process.  It's essentially a
// regular struct, except it cannot be assigned to.
type remotePackage struct {
	defs []eval.Value;
}

func (v remotePackage) String() string {
	return "<remote package>";
}

func (v remotePackage) Assign(t *eval.Thread, o eval.Value) {
	t.Abort(ReadOnlyError("remote packages cannot be assigned to"));
}

func (v remotePackage) Get(t *eval.Thread) eval.StructValue {
	return v;
}

func (v remotePackage) Field(t *eval.Thread, i int) eval.Value {
	return v.defs[i];
}

/*
 * Remote variables
 */

// populateWorld defines constants in the given world for each package
// in this process.  These packages are structs that, in turn, contain
// fields for each global and function in that package.
func (p *Process) populateWorld(w *eval.World) os.Error {
	type def struct {
		t	eval.Type;
		v	eval.Value;
	}
	packages := make(map[string]map[string]def);

	for _, s := range p.syms.Syms {
		if s.ReceiverName() != "" {
			// TODO(austin)
			continue;
		}

		// Package
		pkgName := s.PackageName();
		switch pkgName {
		case "", "type", "extratype", "string", "go":
			// "go" is really "go.string"
			continue;
		}
		pkg, ok := packages[pkgName];
		if !ok {
			pkg = make(map[string]def);
			packages[pkgName] = pkg;
		}

		// Symbol name
		name := s.BaseName();
		if _, ok := pkg[name]; ok {
			log.Stderrf("Multiple definitions of symbol %s", s.Name);
			continue;
		}

		// Symbol type
		rt, err := p.typeOfSym(&s);
		if err != nil {
			return err;
		}

		// Definition
		switch s.Type {
		case 'D', 'd', 'B', 'b':
			// Global variable
			if rt == nil {
				continue;
			}
			pkg[name] = def{rt.Type, rt.mk(remote{proc.Word(s.Value), p})};

		case 'T', 't', 'L', 'l':
			// Function
			s := s.Func;
			// TODO(austin): Ideally, this would *also* be
			// callable.  How does that interact with type
			// conversion syntax?
			rt, err := p.makeFrameType(s);
			if err != nil {
				return err;
			}
			pkg[name] = def{eval.NewPtrType(rt.Type), remoteFramePtr{p, s, rt}};
		}
	}

	// TODO(austin): Define remote types

	// Define packages
	for pkgName, defs := range packages {
		fields := make([]eval.StructField, len(defs));
		vals := make([]eval.Value, len(defs));
		i := 0;
		for name, def := range defs {
			fields[i].Name = name;
			fields[i].Type = def.t;
			vals[i] = def.v;
			i++;
		}
		pkgType := eval.NewStructType(fields);
		pkgVal := remotePackage{vals};

		err := w.DefineConst(pkgName, pkgType, pkgVal);
		if err != nil {
			log.Stderrf("while defining package %s: %v", pkgName, err);
		}
	}

	return nil;
}

// typeOfSym returns the type associated with a symbol.  If the symbol
// has no type, returns nil.
func (p *Process) typeOfSym(s *gosym.Sym) (*remoteType, os.Error) {
	if s.GoType == 0 {
		return nil, nil;
	}
	addr := proc.Word(s.GoType);
	var rt *remoteType;
	err := try(func(a aborter) {
		rt = parseRemoteType(a, p.runtime.Type.mk(remote{addr, p}).(remoteStruct));
	});
	if err != nil {
		return nil, err;
	}
	return rt, nil;
}

// makeFrameType constructs a struct type for the frame of a function.
// The offsets in this struct type are such that the struct can be
// instantiated at this function's frame pointer.
func (p *Process) makeFrameType(s *gosym.Func) (*remoteType, os.Error) {
	n := len(s.Params)+len(s.Locals);
	fields := make([]eval.StructField, n);
	layout := make([]remoteStructField, n);
	i := 0;

	// TODO(austin): There can be multiple locals/parameters with
	// the same name.  We probably need liveness information to do
	// anything about this.  Once we have that, perhaps we give
	// such fields interface{} type?  Or perhaps we disambiguate
	// the names with numbers.  Disambiguation is annoying for
	// things like "i", where there's an obvious right answer.

	for _, param := range s.Params {
		rt, err := p.typeOfSym(param);
		if err != nil {
			return nil, err;
		}
		if rt == nil {
			//fmt.Printf(" (no type)\n");
			continue;
		}
		// TODO(austin): Why do local variables carry their
		// package name?
		fields[i].Name = param.BaseName();
		fields[i].Type = rt.Type;
		// Parameters have positive offsets from FP
		layout[i].offset = int(param.Value);
		layout[i].fieldType = rt;
		i++;
	}

	for _, local := range s.Locals {
		rt, err := p.typeOfSym(local);
		if err != nil {
			return nil, err;
		}
		if rt == nil {
			continue;
		}
		fields[i].Name = local.BaseName();
		fields[i].Type = rt.Type;
		// Locals have negative offsets from FP - PtrSize
		layout[i].offset = -int(local.Value) - p.PtrSize();
		layout[i].fieldType = rt;
		i++;
	}

	fields = fields[0:i];
	layout = layout[0:i];
	t := eval.NewStructType(fields);
	mk := func(r remote) eval.Value { return remoteStruct{r, layout} };
	return &remoteType{t, 0, 0, mk}, nil;
}
