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

/*
 * Hooks and events
 */

// An EventHandler is a function that takes an event and returns a
// response to that event and possibly an error.  If an event handler
// returns an error, the process stops and no other handlers for that
// event are executed.
type EventHandler func(e Event) (EventAction, os.Error)

// An EventAction is an event handler's response to an event.  If all
// of an event's handlers execute without returning errors, their
// results are combined as follows: If any handler returned
// EAContinue, then the process resumes (without returning from
// WaitStop); otherwise, if any handler returned EAStop, the process
// remains stopped; otherwise, if all handlers returned EADefault, the
// process resumes.  A handler may return EARemoveSelf bit-wise or'd
// with any other action to indicate that the handler should be
// removed from the hook.
type EventAction int

const (
	EARemoveSelf	EventAction	= 0x100;
	EADefault	EventAction	= iota;
	EAStop;
	EAContinue;
)

// A EventHook allows event handlers to be added and removed.
type EventHook interface {
	AddHandler(EventHandler);
	RemoveHandler(EventHandler);
	NumHandler() int;
	handle(e Event) (EventAction, os.Error);
	String() string;
}

// EventHook is almost, but not quite, suitable for user-defined
// events.  If we want user-defined events, make EventHook a struct,
// special-case adding and removing handlers in breakpoint hooks, and
// provide a public interface for posting events to hooks.

type Event interface {
	Process() *Process;
	Goroutine() *Goroutine;
	String() string;
}

type commonHook struct {
	// Head of handler chain
	head	*handler;
	// Number of non-internal handlers
	len	int;
}

type handler struct {
	eh	EventHandler;
	// True if this handler must be run before user-defined
	// handlers in order to ensure correctness.
	internal	bool;
	// True if this handler has been removed from the chain.
	removed	bool;
	next	*handler;
}

func (h *commonHook) AddHandler(eh EventHandler) {
	h.addHandler(eh, false);
}

func (h *commonHook) addHandler(eh EventHandler, internal bool) {
	// Ensure uniqueness of handlers
	h.RemoveHandler(eh);

	if !internal {
		h.len++;
	}
	// Add internal handlers to the beginning
	if internal || h.head == nil {
		h.head = &handler{eh, internal, false, h.head};
		return;
	}
	// Add handler after internal handlers
	// TODO(austin) This should probably go on the end instead
	prev := h.head;
	for prev.next != nil && prev.internal {
		prev = prev.next;
	}
	prev.next = &handler{eh, internal, false, prev.next};
}

func (h *commonHook) RemoveHandler(eh EventHandler) {
	plink := &h.head;
	for l := *plink; l != nil; plink, l = &l.next, l.next {
		if l.eh == eh {
			if !l.internal {
				h.len--;
			}
			l.removed = true;
			*plink = l.next;
			break;
		}
	}
}

func (h *commonHook) NumHandler() int {
	return h.len;
}

func (h *commonHook) handle(e Event) (EventAction, os.Error) {
	action := EADefault;
	plink := &h.head;
	for l := *plink; l != nil; plink, l = &l.next, l.next {
		if l.removed {
			continue;
		}
		a, err := l.eh(e);
		if a & EARemoveSelf == EARemoveSelf {
			if !l.internal {
				h.len--;
			}
			l.removed = true;
			*plink = l.next;
			a &^= EARemoveSelf;
		}
		if err != nil {
			return EAStop, err;
		}
		if a > action {
			action = a;
		}
	}
	return action, nil;
}

type commonEvent struct {
	// The process of this event
	p	*Process;
	// The goroutine of this event.
	t	*Goroutine;
}

func (e *commonEvent) Process() *Process {
	return e.p;
}

func (e *commonEvent) Goroutine() *Goroutine {
	return e.t;
}

/*
 * Standard event handlers
 */

// EventPrint is a standard event handler that prints events as they
// occur.  It will not cause the process to stop.
func EventPrint(ev Event) (EventAction, os.Error) {
	// TODO(austin) Include process name here?
	fmt.Fprintf(os.Stderr, "*** %v\n", ev.String());
	return EADefault, nil;
}

// EventStop is a standard event handler that causes the process to stop.
func EventStop(ev Event) (EventAction, os.Error) {
	return EAStop, nil;
}

/*
 * Breakpoints
 */

type breakpointHook struct {
	commonHook;
	p	*Process;
	pc	proc.Word;
}

// A Breakpoint event occurs when a process reaches a particular
// program counter.  When this event is handled, the current goroutine
// will be the goroutine that reached the program counter.
type Breakpoint struct {
	commonEvent;
	osThread	proc.Thread;
	pc		proc.Word;
}

func (h *breakpointHook) AddHandler(eh EventHandler) {
	h.addHandler(eh, false);
}

func (h *breakpointHook) addHandler(eh EventHandler, internal bool) {
	// We register breakpoint events lazily to avoid holding
	// references to breakpoints without handlers.  Be sure to use
	// the "canonical" breakpoint if there is one.
	if cur, ok := h.p.breakpointHooks[h.pc]; ok {
		h = cur;
	}
	oldhead := h.head;
	h.commonHook.addHandler(eh, internal);
	if oldhead == nil && h.head != nil {
		h.p.proc.AddBreakpoint(h.pc);
		h.p.breakpointHooks[h.pc] = h;
	}
}

func (h *breakpointHook) RemoveHandler(eh EventHandler) {
	oldhead := h.head;
	h.commonHook.RemoveHandler(eh);
	if oldhead != nil && h.head == nil {
		h.p.proc.RemoveBreakpoint(h.pc);
		h.p.breakpointHooks[h.pc] = nil, false;
	}
}

func (h *breakpointHook) String() string {
	// TODO(austin) Include process name?
	// TODO(austin) Use line:pc or at least sym+%#x
	return fmt.Sprintf("breakpoint at %#x", h.pc);
}

func (b *Breakpoint) PC() proc.Word {
	return b.pc;
}

func (b *Breakpoint) String() string {
	// TODO(austin) Include process name and goroutine
	// TODO(austin) Use line:pc or at least sym+%#x
	return fmt.Sprintf("breakpoint at %#x", b.pc);
}

/*
 * Goroutine create/exit
 */

type goroutineCreateHook struct {
	commonHook;
}

func (h *goroutineCreateHook) String() string {
	return "goroutine create";
}

// A GoroutineCreate event occurs when a process creates a new
// goroutine.  When this event is handled, the current goroutine will
// be the newly created goroutine.
type GoroutineCreate struct {
	commonEvent;
	parent	*Goroutine;
}

// Parent returns the goroutine that created this goroutine.  May be
// nil if this event is the creation of the first goroutine.
func (e *GoroutineCreate) Parent() *Goroutine {
	return e.parent;
}

func (e *GoroutineCreate) String() string {
	// TODO(austin) Include process name
	if e.parent == nil {
		return fmt.Sprintf("%v created", e.t);
	}
	return fmt.Sprintf("%v created by %v", e.t, e.parent);
}

type goroutineExitHook struct {
	commonHook;
}

func (h *goroutineExitHook) String() string {
	return "goroutine exit";
}

// A GoroutineExit event occurs when a Go goroutine exits.
type GoroutineExit struct {
	commonEvent;
}

func (e *GoroutineExit) String() string {
	// TODO(austin) Include process name
	//return fmt.Sprintf("%v exited", e.t);
	// For debugging purposes
	return fmt.Sprintf("goroutine %#x exited", e.t.g.addr().base);
}
