// 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";
)

// A RemoteMismatchError occurs when an operation that requires two
// identical remote processes is given different process.  For
// example, this occurs when trying to set a pointer in one process to
// point to something in another process.
type RemoteMismatchError string

func (e RemoteMismatchError) String() string {
	return string(e);
}

// A ReadOnlyError occurs when attempting to set or assign to a
// read-only value.
type ReadOnlyError string

func (e ReadOnlyError) String() string {
	return string(e);
}

// A maker is a function that converts a remote address into an
// interpreter Value.
type maker func(remote) eval.Value

type remoteValue interface {
	addr() remote;
}

// remote represents an address in a remote process.
type remote struct {
	base	proc.Word;
	p	*Process;
}

func (v remote) Get(a aborter, size int) uint64 {
	// TODO(austin) This variable might temporarily be in a
	// register.  We could trace the assembly back from the
	// current PC, looking for the beginning of the function or a
	// call (both of which guarantee that the variable is in
	// memory), or an instruction that loads the variable into a
	// register.
	//
	// TODO(austin) If this is a local variable, it might not be
	// live at this PC.  In fact, because the compiler reuses
	// slots, there might even be a different local variable at
	// this location right now.  A simple solution to both
	// problems is to include the range of PC's over which a local
	// variable is live in the symbol table.
	//
	// TODO(austin) We need to prevent the remote garbage
	// collector from collecting objects out from under us.
	var arr [8]byte;
	buf := arr[0:size];
	_, err := v.p.Peek(v.base, buf);
	if err != nil {
		a.Abort(err);
	}
	return uint64(v.p.ToWord(buf));
}

func (v remote) Set(a aborter, size int, x uint64) {
	var arr [8]byte;
	buf := arr[0:size];
	v.p.FromWord(proc.Word(x), buf);
	_, err := v.p.Poke(v.base, buf);
	if err != nil {
		a.Abort(err);
	}
}

func (v remote) plus(x proc.Word) remote {
	return remote{v.base + x, v.p};
}

func tryRVString(f func(a aborter) string) string {
	var s string;
	err := try(func(a aborter) { s = f(a) });
	if err != nil {
		return fmt.Sprintf("<error: %v>", err);
	}
	return s;
}

/*
 * Bool
 */

type remoteBool struct {
	r remote;
}

func (v remoteBool) String() string {
	return tryRVString(func(a aborter) string { return fmt.Sprintf("%v", v.aGet(a)) });
}

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

func (v remoteBool) Get(t *eval.Thread) bool {
	return v.aGet(t);
}

func (v remoteBool) aGet(a aborter) bool {
	return v.r.Get(a, 1) != 0;
}

func (v remoteBool) Set(t *eval.Thread, x bool) {
	v.aSet(t, x);
}

func (v remoteBool) aSet(a aborter, x bool) {
	if x {
		v.r.Set(a, 1, 1);
	} else {
		v.r.Set(a, 1, 0);
	}
}

func (v remoteBool) addr() remote {
	return v.r;
}

func mkBool(r remote) eval.Value {
	return remoteBool{r};
}

/*
 * Uint
 */

type remoteUint struct {
	r	remote;
	size	int;
}

func (v remoteUint) String() string {
	return tryRVString(func(a aborter) string { return fmt.Sprintf("%v", v.aGet(a)) });
}

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

func (v remoteUint) Get(t *eval.Thread) uint64 {
	return v.aGet(t);
}

func (v remoteUint) aGet(a aborter) uint64 {
	return v.r.Get(a, v.size);
}

func (v remoteUint) Set(t *eval.Thread, x uint64) {
	v.aSet(t, x);
}

func (v remoteUint) aSet(a aborter, x uint64) {
	v.r.Set(a, v.size, x);
}

func (v remoteUint) addr() remote {
	return v.r;
}

func mkUint8(r remote) eval.Value {
	return remoteUint{r, 1};
}

func mkUint16(r remote) eval.Value {
	return remoteUint{r, 2};
}

func mkUint32(r remote) eval.Value {
	return remoteUint{r, 4};
}

func mkUint64(r remote) eval.Value {
	return remoteUint{r, 8};
}

func mkUint(r remote) eval.Value {
	return remoteUint{r, r.p.IntSize()};
}

func mkUintptr(r remote) eval.Value {
	return remoteUint{r, r.p.PtrSize()};
}

/*
 * Int
 */

type remoteInt struct {
	r	remote;
	size	int;
}

func (v remoteInt) String() string {
	return tryRVString(func(a aborter) string { return fmt.Sprintf("%v", v.aGet(a)) });
}

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

func (v remoteInt) Get(t *eval.Thread) int64 {
	return v.aGet(t);
}

func (v remoteInt) aGet(a aborter) int64 {
	return int64(v.r.Get(a, v.size));
}

func (v remoteInt) Set(t *eval.Thread, x int64) {
	v.aSet(t, x);
}

func (v remoteInt) aSet(a aborter, x int64) {
	v.r.Set(a, v.size, uint64(x));
}

func (v remoteInt) addr() remote {
	return v.r;
}

func mkInt8(r remote) eval.Value {
	return remoteInt{r, 1};
}

func mkInt16(r remote) eval.Value {
	return remoteInt{r, 2};
}

func mkInt32(r remote) eval.Value {
	return remoteInt{r, 4};
}

func mkInt64(r remote) eval.Value {
	return remoteInt{r, 8};
}

func mkInt(r remote) eval.Value {
	return remoteInt{r, r.p.IntSize()};
}

/*
 * Float
 */

type remoteFloat struct {
	r	remote;
	size	int;
}

func (v remoteFloat) String() string {
	return tryRVString(func(a aborter) string { return fmt.Sprintf("%v", v.aGet(a)) });
}

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

func (v remoteFloat) Get(t *eval.Thread) float64 {
	return v.aGet(t);
}

func (v remoteFloat) aGet(a aborter) float64 {
	bits := v.r.Get(a, v.size);
	switch v.size {
	case 4:
		return float64(v.r.p.ToFloat32(uint32(bits)));
	case 8:
		return v.r.p.ToFloat64(bits);
	}
	panic("Unexpected float size ", v.size);
}

func (v remoteFloat) Set(t *eval.Thread, x float64) {
	v.aSet(t, x);
}

func (v remoteFloat) aSet(a aborter, x float64) {
	var bits uint64;
	switch v.size {
	case 4:
		bits = uint64(v.r.p.FromFloat32(float32(x)));
	case 8:
		bits = v.r.p.FromFloat64(x);
	default:
		panic("Unexpected float size ", v.size);
	}
	v.r.Set(a, v.size, bits);
}

func (v remoteFloat) addr() remote {
	return v.r;
}

func mkFloat32(r remote) eval.Value {
	return remoteFloat{r, 4};
}

func mkFloat64(r remote) eval.Value {
	return remoteFloat{r, 8};
}

func mkFloat(r remote) eval.Value {
	return remoteFloat{r, r.p.FloatSize()};
}

/*
 * String
 */

type remoteString struct {
	r remote;
}

func (v remoteString) String() string {
	return tryRVString(func(a aborter) string { return v.aGet(a) });
}

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

func (v remoteString) Get(t *eval.Thread) string {
	return v.aGet(t);
}

func (v remoteString) aGet(a aborter) string {
	rs := v.r.p.runtime.String.mk(v.r).(remoteStruct);
	str := proc.Word(rs.field(v.r.p.f.String.Str).(remoteUint).aGet(a));
	len := rs.field(v.r.p.f.String.Len).(remoteInt).aGet(a);

	bytes := make([]uint8, len);
	_, err := v.r.p.Peek(str, bytes);
	if err != nil {
		a.Abort(err);
	}
	return string(bytes);
}

func (v remoteString) Set(t *eval.Thread, x string) {
	v.aSet(t, x);
}

func (v remoteString) aSet(a aborter, x string) {
	// TODO(austin) This isn't generally possible without the
	// ability to allocate remote memory.
	a.Abort(ReadOnlyError("remote strings cannot be assigned to"));
}

func mkString(r remote) eval.Value {
	return remoteString{r};
}

/*
 * Array
 */

type remoteArray struct {
	r		remote;
	len		int64;
	elemType	*remoteType;
}

func (v remoteArray) String() string {
	res := "{";
	for i := int64(0); i < v.len; i++ {
		if i > 0 {
			res += ", ";
		}
		res += v.elem(i).String();
	}
	return res+"}";
}

func (v remoteArray) Assign(t *eval.Thread, o eval.Value) {
	// TODO(austin) Could do a bigger memcpy if o is a
	// remoteArray in the same Process.
	oa := o.(eval.ArrayValue);
	for i := int64(0); i < v.len; i++ {
		v.Elem(t, i).Assign(t, oa.Elem(t, i));
	}
}

func (v remoteArray) Get(t *eval.Thread) eval.ArrayValue {
	return v;
}

func (v remoteArray) Elem(t *eval.Thread, i int64) eval.Value {
	return v.elem(i);
}

func (v remoteArray) elem(i int64) eval.Value {
	return v.elemType.mk(v.r.plus(proc.Word(int64(v.elemType.size)*i)));
}

func (v remoteArray) Sub(i int64, len int64) eval.ArrayValue {
	return remoteArray{v.r.plus(proc.Word(int64(v.elemType.size)*i)), len, v.elemType};
}

/*
 * Struct
 */

type remoteStruct struct {
	r	remote;
	layout	[]remoteStructField;
}

type remoteStructField struct {
	offset		int;
	fieldType	*remoteType;
}

func (v remoteStruct) String() string {
	res := "{";
	for i := range v.layout {
		if i > 0 {
			res += ", ";
		}
		res += v.field(i).String();
	}
	return res+"}";
}

func (v remoteStruct) Assign(t *eval.Thread, o eval.Value) {
	// TODO(austin) Could do a bigger memcpy.
	oa := o.(eval.StructValue);
	l := len(v.layout);
	for i := 0; i < l; i++ {
		v.Field(t, i).Assign(t, oa.Field(t, i));
	}
}

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

func (v remoteStruct) Field(t *eval.Thread, i int) eval.Value {
	return v.field(i);
}

func (v remoteStruct) field(i int) eval.Value {
	f := &v.layout[i];
	return f.fieldType.mk(v.r.plus(proc.Word(f.offset)));
}

func (v remoteStruct) addr() remote {
	return v.r;
}

/*
 * Pointer
 */

// TODO(austin) Comparing two remote pointers for equality in the
// interpreter will crash it because the Value's returned from
// remotePtr.Get() will be structs.

type remotePtr struct {
	r		remote;
	elemType	*remoteType;
}

func (v remotePtr) String() string {
	return tryRVString(func(a aborter) string {
		e := v.aGet(a);
		if e == nil {
			return "<nil>";
		}
		return "&" + e.String();
	});
}

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

func (v remotePtr) Get(t *eval.Thread) eval.Value {
	return v.aGet(t);
}

func (v remotePtr) aGet(a aborter) eval.Value {
	addr := proc.Word(v.r.Get(a, v.r.p.PtrSize()));
	if addr == 0 {
		return nil;
	}
	return v.elemType.mk(remote{addr, v.r.p});
}

func (v remotePtr) Set(t *eval.Thread, x eval.Value) {
	v.aSet(t, x);
}

func (v remotePtr) aSet(a aborter, x eval.Value) {
	if x == nil {
		v.r.Set(a, v.r.p.PtrSize(), 0);
		return;
	}
	xr, ok := x.(remoteValue);
	if !ok || v.r.p != xr.addr().p {
		a.Abort(RemoteMismatchError("remote pointer must point within the same process"));
	}
	v.r.Set(a, v.r.p.PtrSize(), uint64(xr.addr().base));
}

func (v remotePtr) addr() remote {
	return v.r;
}

/*
 * Slice
 */

type remoteSlice struct {
	r		remote;
	elemType	*remoteType;
}

func (v remoteSlice) String() string {
	return tryRVString(func(a aborter) string {
		b := v.aGet(a).Base;
		if b == nil {
			return "<nil>";
		}
		return b.String();
	});
}

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

func (v remoteSlice) Get(t *eval.Thread) eval.Slice {
	return v.aGet(t);
}

func (v remoteSlice) aGet(a aborter) eval.Slice {
	rs := v.r.p.runtime.Slice.mk(v.r).(remoteStruct);
	base := proc.Word(rs.field(v.r.p.f.Slice.Array).(remoteUint).aGet(a));
	nel := rs.field(v.r.p.f.Slice.Len).(remoteInt).aGet(a);
	cap := rs.field(v.r.p.f.Slice.Cap).(remoteInt).aGet(a);
	if base == 0 {
		return eval.Slice{nil, nel, cap};
	}
	return eval.Slice{remoteArray{remote{base, v.r.p}, nel, v.elemType}, nel, cap};
}

func (v remoteSlice) Set(t *eval.Thread, x eval.Slice) {
	v.aSet(t, x);
}

func (v remoteSlice) aSet(a aborter, x eval.Slice) {
	rs := v.r.p.runtime.Slice.mk(v.r).(remoteStruct);
	if x.Base == nil {
		rs.field(v.r.p.f.Slice.Array).(remoteUint).aSet(a, 0);
	} else {
		ar, ok := x.Base.(remoteArray);
		if !ok || v.r.p != ar.r.p {
			a.Abort(RemoteMismatchError("remote slice must point within the same process"));
		}
		rs.field(v.r.p.f.Slice.Array).(remoteUint).aSet(a, uint64(ar.r.base));
	}
	rs.field(v.r.p.f.Slice.Len).(remoteInt).aSet(a, x.Len);
	rs.field(v.r.p.f.Slice.Cap).(remoteInt).aSet(a, x.Cap);
}
