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

// A FormatError indicates a failure to process information in or
// about a remote process, such as unexpected or missing information
// in the object file or runtime structures.
type FormatError string

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

// An UnknownArchitecture occurs when trying to load an object file
// that indicates an architecture not supported by the debugger.
type UnknownArchitecture elf.Machine

func (e UnknownArchitecture) String() string {
	return "unknown architecture: " + elf.Machine(e).String();
}

// A ProcessNotStopped error occurs when attempting to read or write
// memory or registers of a process that is not stopped.
type ProcessNotStopped struct{}

func (e ProcessNotStopped) String() string {
	return "process not stopped";
}

// An UnknownGoroutine error is an internal error representing an
// unrecognized G structure pointer.
type UnknownGoroutine struct {
	OSThread	proc.Thread;
	Goroutine	proc.Word;
}

func (e UnknownGoroutine) String() string {
	return fmt.Sprintf("internal error: unknown goroutine (G %#x)", e.Goroutine);
}

// A NoCurrentGoroutine error occurs when no goroutine is currently
// selected in a process (or when there are no goroutines in a
// process).
type NoCurrentGoroutine struct{}

func (e NoCurrentGoroutine) String() string {
	return "no current goroutine";
}

// A Process represents a remote attached process.
type Process struct {
	Arch;
	proc	proc.Process;

	// The symbol table of this process
	syms	*gosym.Table;

	// A possibly-stopped OS thread, or nil
	threadCache	proc.Thread;

	// Types parsed from the remote process
	types	map[proc.Word]*remoteType;

	// Types and values from the remote runtime package
	runtime	runtimeValues;

	// Runtime field indexes
	f	runtimeIndexes;

	// Globals from the sys package (or from no package)
	sys	struct {
		lessstack, goexit, newproc, deferproc, newprocreadylocked	*gosym.Func;
		allg								remotePtr;
		g0								remoteStruct;
	};

	// Event queue
	posted	[]Event;
	pending	[]Event;
	event	Event;

	// Event hooks
	breakpointHooks		map[proc.Word]*breakpointHook;
	goroutineCreateHook	*goroutineCreateHook;
	goroutineExitHook	*goroutineExitHook;

	// Current goroutine, or nil if there are no goroutines
	curGoroutine	*Goroutine;

	// Goroutines by the address of their G structure
	goroutines	map[proc.Word]*Goroutine;
}

/*
 * Process creation
 */

// NewProcess constructs a new remote process around a traced
// process, an architecture, and a symbol table.
func NewProcess(tproc proc.Process, arch Arch, syms *gosym.Table) (*Process, os.Error) {
	p := &Process{
		Arch: arch,
		proc: tproc,
		syms: syms,
		types: make(map[proc.Word]*remoteType),
		breakpointHooks: make(map[proc.Word]*breakpointHook),
		goroutineCreateHook: new(goroutineCreateHook),
		goroutineExitHook: new(goroutineExitHook),
		goroutines: make(map[proc.Word]*Goroutine),
	};

	// Fill in remote runtime
	p.bootstrap();

	switch {
	case p.sys.allg.addr().base == 0:
		return nil, FormatError("failed to find runtime symbol 'allg'");
	case p.sys.g0.addr().base == 0:
		return nil, FormatError("failed to find runtime symbol 'g0'");
	case p.sys.newprocreadylocked == nil:
		return nil, FormatError("failed to find runtime symbol 'newprocreadylocked'");
	case p.sys.goexit == nil:
		return nil, FormatError("failed to find runtime symbol 'sys.goexit'");
	}

	// Get current goroutines
	p.goroutines[p.sys.g0.addr().base] = &Goroutine{p.sys.g0, nil, false};
	err := try(func(a aborter) {
		g := p.sys.allg.aGet(a);
		for g != nil {
			gs := g.(remoteStruct);
			fmt.Printf("*** Found goroutine at %#x\n", gs.addr().base);
			p.goroutines[gs.addr().base] = &Goroutine{gs, nil, false};
			g = gs.field(p.f.G.Alllink).(remotePtr).aGet(a);
		}
	});
	if err != nil {
		return nil, err;
	}

	// Create internal breakpoints to catch new and exited goroutines
	p.OnBreakpoint(proc.Word(p.sys.newprocreadylocked.Entry)).(*breakpointHook).addHandler(readylockedBP, true);
	p.OnBreakpoint(proc.Word(p.sys.goexit.Entry)).(*breakpointHook).addHandler(goexitBP, true);

	// Select current frames
	for _, g := range p.goroutines {
		g.resetFrame();
	}

	p.selectSomeGoroutine();

	return p, nil;
}

func elfGoSyms(f *elf.File) (*gosym.Table, os.Error) {
	text := f.Section(".text");
	symtab := f.Section(".gosymtab");
	pclntab := f.Section(".gopclntab");
	if text == nil || symtab == nil || pclntab == nil {
		return nil, nil;
	}

	symdat, err := symtab.Data();
	if err != nil {
		return nil, err;
	}
	pclndat, err := pclntab.Data();
	if err != nil {
		return nil, err;
	}

	pcln := gosym.NewLineTable(pclndat, text.Addr);
	tab, err := gosym.NewTable(symdat, pcln);
	if err != nil {
		return nil, err;
	}

	return tab, nil;
}

// NewProcessElf constructs a new remote process around a traced
// process and the process' ELF object.
func NewProcessElf(tproc proc.Process, f *elf.File) (*Process, os.Error) {
	syms, err := elfGoSyms(f);
	if err != nil {
		return nil, err;
	}
	if syms == nil {
		return nil, FormatError("Failed to find symbol table");
	}
	var arch Arch;
	switch f.Machine {
	case elf.EM_X86_64:
		arch = Amd64;
	default:
		return nil, UnknownArchitecture(f.Machine);
	}
	return NewProcess(tproc, arch, syms);
}

// bootstrap constructs the runtime structure of a remote process.
func (p *Process) bootstrap() {
	// Manually construct runtime types
	p.runtime.String = newManualType(eval.TypeOfNative(rt1String{}), p.Arch);
	p.runtime.Slice = newManualType(eval.TypeOfNative(rt1Slice{}), p.Arch);
	p.runtime.Eface = newManualType(eval.TypeOfNative(rt1Eface{}), p.Arch);

	p.runtime.Type = newManualType(eval.TypeOfNative(rt1Type{}), p.Arch);
	p.runtime.CommonType = newManualType(eval.TypeOfNative(rt1CommonType{}), p.Arch);
	p.runtime.UncommonType = newManualType(eval.TypeOfNative(rt1UncommonType{}), p.Arch);
	p.runtime.StructField = newManualType(eval.TypeOfNative(rt1StructField{}), p.Arch);
	p.runtime.StructType = newManualType(eval.TypeOfNative(rt1StructType{}), p.Arch);
	p.runtime.PtrType = newManualType(eval.TypeOfNative(rt1PtrType{}), p.Arch);
	p.runtime.ArrayType = newManualType(eval.TypeOfNative(rt1ArrayType{}), p.Arch);
	p.runtime.SliceType = newManualType(eval.TypeOfNative(rt1SliceType{}), p.Arch);

	p.runtime.Stktop = newManualType(eval.TypeOfNative(rt1Stktop{}), p.Arch);
	p.runtime.Gobuf = newManualType(eval.TypeOfNative(rt1Gobuf{}), p.Arch);
	p.runtime.G = newManualType(eval.TypeOfNative(rt1G{}), p.Arch);

	// Get addresses of type.*runtime.XType for discrimination.
	rtv := reflect.Indirect(reflect.NewValue(&p.runtime)).(*reflect.StructValue);
	rtvt := rtv.Type().(*reflect.StructType);
	for i := 0; i < rtv.NumField(); i++ {
		n := rtvt.Field(i).Name;
		if n[0] != 'P' || n[1] < 'A' || n[1] > 'Z' {
			continue;
		}
		sym := p.syms.LookupSym("type.*runtime." + n[1:len(n)]);
		if sym == nil {
			continue;
		}
		rtv.Field(i).(*reflect.Uint64Value).Set(sym.Value);
	}

	// Get runtime field indexes
	fillRuntimeIndexes(&p.runtime, &p.f);

	// Fill G status
	p.runtime.runtimeGStatus = rt1GStatus;

	// Get globals
	p.sys.lessstack = p.syms.LookupFunc("sys.lessstack");
	p.sys.goexit = p.syms.LookupFunc("goexit");
	p.sys.newproc = p.syms.LookupFunc("sys.newproc");
	p.sys.deferproc = p.syms.LookupFunc("sys.deferproc");
	p.sys.newprocreadylocked = p.syms.LookupFunc("newprocreadylocked");
	if allg := p.syms.LookupSym("allg"); allg != nil {
		p.sys.allg = remotePtr{remote{proc.Word(allg.Value), p}, p.runtime.G};
	}
	if g0 := p.syms.LookupSym("g0"); g0 != nil {
		p.sys.g0 = p.runtime.G.mk(remote{proc.Word(g0.Value), p}).(remoteStruct);
	}
}

func (p *Process) selectSomeGoroutine() {
	// Once we have friendly goroutine ID's, there might be a more
	// reasonable behavior for this.
	p.curGoroutine = nil;
	for _, g := range p.goroutines {
		if !g.isG0() && g.frame != nil {
			p.curGoroutine = g;
			return;
		}
	}
}

/*
 * Process memory
 */

func (p *Process) someStoppedOSThread() proc.Thread {
	if p.threadCache != nil {
		if _, err := p.threadCache.Stopped(); err == nil {
			return p.threadCache;
		}
	}

	for _, t := range p.proc.Threads() {
		if _, err := t.Stopped(); err == nil {
			p.threadCache = t;
			return t;
		}
	}
	return nil;
}

func (p *Process) Peek(addr proc.Word, out []byte) (int, os.Error) {
	thr := p.someStoppedOSThread();
	if thr == nil {
		return 0, ProcessNotStopped{};
	}
	return thr.Peek(addr, out);
}

func (p *Process) Poke(addr proc.Word, b []byte) (int, os.Error) {
	thr := p.someStoppedOSThread();
	if thr == nil {
		return 0, ProcessNotStopped{};
	}
	return thr.Poke(addr, b);
}

func (p *Process) peekUintptr(a aborter, addr proc.Word) proc.Word {
	return proc.Word(mkUintptr(remote{addr, p}).(remoteUint).aGet(a));
}

/*
 * Events
 */

// OnBreakpoint returns the hook that is run when the program reaches
// the given program counter.
func (p *Process) OnBreakpoint(pc proc.Word) EventHook {
	if bp, ok := p.breakpointHooks[pc]; ok {
		return bp;
	}
	// The breakpoint will register itself when a handler is added
	return &breakpointHook{commonHook{nil, 0}, p, pc};
}

// OnGoroutineCreate returns the hook that is run when a goroutine is created.
func (p *Process) OnGoroutineCreate() EventHook {
	return p.goroutineCreateHook;
}

// OnGoroutineExit returns the hook that is run when a goroutine exits.
func (p *Process) OnGoroutineExit() EventHook {
	return p.goroutineExitHook;
}

// osThreadToGoroutine looks up the goroutine running on an OS thread.
func (p *Process) osThreadToGoroutine(t proc.Thread) (*Goroutine, os.Error) {
	regs, err := t.Regs();
	if err != nil {
		return nil, err;
	}
	g := p.G(regs);
	gt, ok := p.goroutines[g];
	if !ok {
		return nil, UnknownGoroutine{t, g};
	}
	return gt, nil;
}

// causesToEvents translates the stop causes of the underlying process
// into an event queue.
func (p *Process) causesToEvents() ([]Event, os.Error) {
	// Count causes we're interested in
	nev := 0;
	for _, t := range p.proc.Threads() {
		if c, err := t.Stopped(); err == nil {
			switch c := c.(type) {
			case proc.Breakpoint:
				nev++;
			case proc.Signal:
				// TODO(austin)
				//nev++;
			}
		}
	}

	// Translate causes to events
	events := make([]Event, nev);
	i := 0;
	for _, t := range p.proc.Threads() {
		if c, err := t.Stopped(); err == nil {
			switch c := c.(type) {
			case proc.Breakpoint:
				gt, err := p.osThreadToGoroutine(t);
				if err != nil {
					return nil, err;
				}
				events[i] = &Breakpoint{commonEvent{p, gt}, t, proc.Word(c)};
				i++;
			case proc.Signal:
				// TODO(austin)
			}
		}
	}

	return events, nil;
}

// postEvent appends an event to the posted queue.  These events will
// be processed before any currently pending events.
func (p *Process) postEvent(ev Event) {
	n := len(p.posted);
	m := n*2;
	if m == 0 {
		m = 4;
	}
	posted := make([]Event, n+1, m);
	for i, p := range p.posted {
		posted[i] = p;
	}
	posted[n] = ev;
	p.posted = posted;
}

// processEvents processes events in the event queue until no events
// remain, a handler returns EAStop, or a handler returns an error.
// It returns either EAStop or EAContinue and possibly an error.
func (p *Process) processEvents() (EventAction, os.Error) {
	var ev Event;
	for len(p.posted) > 0 {
		ev, p.posted = p.posted[0], p.posted[1:len(p.posted)];
		action, err := p.processEvent(ev);
		if action == EAStop {
			return action, err;
		}
	}

	for len(p.pending) > 0 {
		ev, p.pending = p.pending[0], p.pending[1:len(p.pending)];
		action, err := p.processEvent(ev);
		if action == EAStop {
			return action, err;
		}
	}

	return EAContinue, nil;
}

// processEvent processes a single event, without manipulating the
// event queues.  It returns either EAStop or EAContinue and possibly
// an error.
func (p *Process) processEvent(ev Event) (EventAction, os.Error) {
	p.event = ev;

	var action EventAction;
	var err os.Error;
	switch ev := p.event.(type) {
	case *Breakpoint:
		hook, ok := p.breakpointHooks[ev.pc];
		if !ok {
			break;
		}
		p.curGoroutine = ev.Goroutine();
		action, err = hook.handle(ev);

	case *GoroutineCreate:
		p.curGoroutine = ev.Goroutine();
		action, err = p.goroutineCreateHook.handle(ev);

	case *GoroutineExit:
		action, err = p.goroutineExitHook.handle(ev);

	default:
		log.Crashf("Unknown event type %T in queue", p.event);
	}

	if err != nil {
		return EAStop, err;
	} else if action == EAStop {
		return EAStop, nil;
	}
	return EAContinue, nil;
}

// Event returns the last event that caused the process to stop.  This
// may return nil if the process has never been stopped by an event.
//
// TODO(austin) Return nil if the user calls p.Stop()?
func (p *Process) Event() Event {
	return p.event;
}

/*
 * Process control
 */

// TODO(austin) Cont, WaitStop, and Stop.  Need to figure out how
// event handling works with these.  Originally I did it only in
// WaitStop, but if you Cont and there are pending events, then you
// have to not actually continue and wait until a WaitStop to process
// them, even if the event handlers will tell you to continue.  We
// could handle them in both Cont and WaitStop to avoid this problem,
// but it's still weird if an event happens after the Cont and before
// the WaitStop that the handlers say to continue from.  Or we could
// handle them on a separate thread.  Then obviously you get weird
// asynchrony things, like prints while the user it typing a command,
// but that's not necessarily a bad thing.

// ContWait resumes process execution and waits for an event to occur
// that stops the process.
func (p *Process) ContWait() os.Error {
	for {
		a, err := p.processEvents();
		if err != nil {
			return err;
		} else if a == EAStop {
			break;
		}
		err = p.proc.Continue();
		if err != nil {
			return err;
		}
		err = p.proc.WaitStop();
		if err != nil {
			return err;
		}
		for _, g := range p.goroutines {
			g.resetFrame();
		}
		p.pending, err = p.causesToEvents();
		if err != nil {
			return err;
		}
	}
	return nil;
}

// Out selects the caller frame of the current frame.
func (p *Process) Out() os.Error {
	if p.curGoroutine == nil {
		return NoCurrentGoroutine{};
	}
	return p.curGoroutine.Out();
}

// In selects the frame called by the current frame.
func (p *Process) In() os.Error {
	if p.curGoroutine == nil {
		return NoCurrentGoroutine{};
	}
	return p.curGoroutine.In();
}
