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

// Ogle is the beginning of a debugger for Go.
package ogle

import (
	"bufio";
	"debug/elf";
	"debug/proc";
	"exp/eval";
	"fmt";
	"go/scanner";
	"go/token";
	"os";
	"strconv";
	"strings";
)

var world *eval.World
var curProc *Process

func Main() {
	world = eval.NewWorld();
	defineFuncs();
	r := bufio.NewReader(os.Stdin);
	for {
		print("; ");
		line, err := r.ReadSlice('\n');
		if err != nil {
			break;
		}

		// Try line as a command
		cmd, rest := getCmd(line);
		if cmd != nil {
			err := cmd.handler(rest);
			if err != nil {
				scanner.PrintError(os.Stderr, err);
			}
			continue;
		}

		// Try line as code
		code, err := world.Compile(string(line));
		if err != nil {
			scanner.PrintError(os.Stderr, err);
			continue;
		}
		v, err := code.Run();
		if err != nil {
			fmt.Fprintf(os.Stderr, err.String());
			continue;
		}
		if v != nil {
			println(v.String());
		}
	}
}

// newScanner creates a new scanner that scans that given input bytes.
func newScanner(input []byte) (*scanner.Scanner, *scanner.ErrorVector) {
	sc := new(scanner.Scanner);
	ev := new(scanner.ErrorVector);
	ev.Init();
	sc.Init("input", input, ev, 0);

	return sc, ev;
}

/*
 * Commands
 */

// A UsageError occurs when a command is called with illegal arguments.
type UsageError string

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

// A cmd represents a single command with a handler.
type cmd struct {
	cmd	string;
	handler	func([]byte) os.Error;
}

var cmds = []cmd{
	cmd{"load", cmdLoad},
	cmd{"bt", cmdBt},
}

// getCmd attempts to parse an input line as a registered command.  If
// successful, it returns the command and the bytes remaining after
// the command, which should be passed to the command.
func getCmd(line []byte) (*cmd, []byte) {
	sc, _ := newScanner(line);
	pos, tok, lit := sc.Scan();
	if sc.ErrorCount != 0 || tok != token.IDENT {
		return nil, nil;
	}

	slit := string(lit);
	for i := range cmds {
		if cmds[i].cmd == slit {
			return &cmds[i], line[pos.Offset + len(lit) : len(line)];
		}
	}
	return nil, nil;
}

// cmdLoad starts or attaches to a process.  Its form is similar to
// import:
//
//  load [sym] "path" [;]
//
// sym specifies the name to give to the process.  If not given, the
// name is derived from the path of the process.  If ".", then the
// packages from the remote process are defined into the current
// namespace.  If given, this symbol is defined as a package
// containing the process' packages.
//
// path gives the path of the process to start or attach to.  If it is
// "pid:<num>", then attach to the given PID.  Otherwise, treat it as
// a file path and space-separated arguments and start a new process.
//
// load always sets the current process to the loaded process.
func cmdLoad(args []byte) os.Error {
	ident, path, err := parseLoad(args);
	if err != nil {
		return err;
	}
	if curProc != nil {
		return UsageError("multiple processes not implemented");
	}
	if ident != "." {
		return UsageError("process identifiers not implemented");
	}

	// Parse argument and start or attach to process
	var fname string;
	var tproc proc.Process;
	if len(path) >= 4 && path[0:4] == "pid:" {
		pid, err := strconv.Atoi(path[4:len(path)]);
		if err != nil {
			return err;
		}
		fname, err = os.Readlink(fmt.Sprintf("/proc/%d/exe", pid));
		if err != nil {
			return err;
		}
		tproc, err = proc.Attach(pid);
		if err != nil {
			return err;
		}
		println("Attached to", pid);
	} else {
		parts := strings.Split(path, " ", 0);
		if len(parts) == 0 {
			fname = "";
		} else {
			fname = parts[0];
		}
		tproc, err = proc.ForkExec(fname, parts, os.Environ(), "", []*os.File{os.Stdin, os.Stdout, os.Stderr});
		if err != nil {
			return err;
		}
		println("Started", path);
		// TODO(austin) If we fail after this point, kill tproc
		// before detaching.
	}

	// Get symbols
	f, err := os.Open(fname, os.O_RDONLY, 0);
	if err != nil {
		tproc.Detach();
		return err;
	}
	defer f.Close();
	elf, err := elf.NewFile(f);
	if err != nil {
		tproc.Detach();
		return err;
	}
	curProc, err = NewProcessElf(tproc, elf);
	if err != nil {
		tproc.Detach();
		return err;
	}

	// Prepare new process
	curProc.OnGoroutineCreate().AddHandler(EventPrint);
	curProc.OnGoroutineExit().AddHandler(EventPrint);

	err = curProc.populateWorld(world);
	if err != nil {
		tproc.Detach();
		return err;
	}

	return nil;
}

func parseLoad(args []byte) (ident string, path string, err os.Error) {
	err = UsageError("Usage: load [sym] \"path\"");
	sc, ev := newScanner(args);

	var toks [4]token.Token;
	var lits [4][]byte;
	for i := range toks {
		_, toks[i], lits[i] = sc.Scan();
	}
	if sc.ErrorCount != 0 {
		err = ev.GetError(scanner.NoMultiples);
		return;
	}

	i := 0;
	switch toks[i] {
	case token.PERIOD, token.IDENT:
		ident = string(lits[i]);
		i++;
	}

	if toks[i] != token.STRING {
		return;
	}
	path, uerr := strconv.Unquote(string(lits[i]));
	if uerr != nil {
		err = uerr;
		return;
	}
	i++;

	if toks[i] == token.SEMICOLON {
		i++;
	}
	if toks[i] != token.EOF {
		return;
	}

	return ident, path, nil;
}

// cmdBt prints a backtrace for the current goroutine.  It takes no
// arguments.
func cmdBt(args []byte) os.Error {
	err := parseNoArgs(args, "Usage: bt");
	if err != nil {
		return err;
	}

	if curProc == nil || curProc.curGoroutine == nil {
		return NoCurrentGoroutine{};
	}

	f := curProc.curGoroutine.frame;
	if f == nil {
		fmt.Println("No frames on stack");
		return nil;
	}

	for f.Inner() != nil {
		f = f.Inner();
	}

	for i := 0; i < 100; i++ {
		if f == curProc.curGoroutine.frame {
			fmt.Printf("=> ");
		} else {
			fmt.Printf("   ");
		}
		fmt.Printf("%8x %v\n", f.pc, f);
		f, err = f.Outer();
		if err != nil {
			return err;
		}
		if f == nil {
			return nil;
		}
	}

	fmt.Println("...");
	return nil;
}

func parseNoArgs(args []byte, usage string) os.Error {
	sc, ev := newScanner(args);
	_, tok, _ := sc.Scan();
	if sc.ErrorCount != 0 {
		return ev.GetError(scanner.NoMultiples);
	}
	if tok != token.EOF {
		return UsageError(usage);
	}
	return nil;
}

/*
 * Functions
 */

// defineFuncs populates world with the built-in functions.
func defineFuncs() {
	t, v := eval.FuncFromNativeTyped(fnOut, fnOutSig);
	world.DefineConst("Out", t, v);
	t, v = eval.FuncFromNativeTyped(fnContWait, fnContWaitSig);
	world.DefineConst("ContWait", t, v);
	t, v = eval.FuncFromNativeTyped(fnBpSet, fnBpSetSig);
	world.DefineConst("BpSet", t, v);
}

// printCurFrame prints the current stack frame, as it would appear in
// a backtrace.
func printCurFrame() {
	if curProc == nil || curProc.curGoroutine == nil {
		return;
	}
	f := curProc.curGoroutine.frame;
	if f == nil {
		return;
	}
	fmt.Printf("=> %8x %v\n", f.pc, f);
}

// fnOut moves the current frame to the caller of the current frame.
func fnOutSig() {
}
func fnOut(t *eval.Thread, args []eval.Value, res []eval.Value) {
	if curProc == nil {
		t.Abort(NoCurrentGoroutine{});
	}
	err := curProc.Out();
	if err != nil {
		t.Abort(err);
	}
	// TODO(austin) Only in the command form
	printCurFrame();
}

// fnContWait continues the current process and waits for a stopping event.
func fnContWaitSig() {
}
func fnContWait(t *eval.Thread, args []eval.Value, res []eval.Value) {
	if curProc == nil {
		t.Abort(NoCurrentGoroutine{});
	}
	err := curProc.ContWait();
	if err != nil {
		t.Abort(err);
	}
	// TODO(austin) Only in the command form
	ev := curProc.Event();
	if ev != nil {
		fmt.Printf("%v\n", ev);
	}
	printCurFrame();
}

// fnBpSet sets a breakpoint at the entry to the named function.
func fnBpSetSig(string) {
}
func fnBpSet(t *eval.Thread, args []eval.Value, res []eval.Value) {
	// TODO(austin) This probably shouldn't take a symbol name.
	// Perhaps it should take an interface that provides PC's.
	// Functions and instructions can implement that interface and
	// we can have something to translate file:line pairs.
	if curProc == nil {
		t.Abort(NoCurrentGoroutine{});
	}
	name := args[0].(eval.StringValue).Get(t);
	fn := curProc.syms.LookupFunc(name);
	if fn == nil {
		t.Abort(UsageError("no such function " + name));
	}
	curProc.OnBreakpoint(proc.Word(fn.Entry)).AddHandler(EventStop);
}
