package interp

import (
	
	
	
	
	
	
	
)

var (
	// ErrNotLive indicates that the specified ID does not refer to a (live) Go
	// routine.
	ErrNotLive = errors.New("not live")

	// ErrRunning indicates that the specified Go routine is running.
	ErrRunning = errors.New("running")

	// ErrNotRunning indicates that the specified Go routine is running.
	ErrNotRunning = errors.New("not running")
)

var rNodeType = reflect.TypeOf((*node)(nil)).Elem()

// A Debugger can be used to debug a Yaegi program.
type Debugger struct {
	interp  *Interpreter
	events  func(*DebugEvent)
	context context.Context
	cancel  context.CancelFunc

	gWait *sync.WaitGroup
	gLock *sync.Mutex
	gID   int
	gLive map[int]*debugRoutine

	result reflect.Value
	err    error
}

// go routine debug state.
type debugRoutine struct {
	id int

	mode    DebugEventReason
	running bool
	resume  chan struct{}

	fDepth int
	fStep  int
}

// node debug state.
type nodeDebugData struct {
	program     *Program
	breakOnLine bool
	breakOnCall bool
}

// frame debug state.
type frameDebugData struct {
	g     *debugRoutine
	node  *node
	name  string
	kind  frameKind
	scope *scope
}

// frame kind.
type frameKind int

const (
	// interpreter root frame.
	frameRoot frameKind = iota + 1

	// function call frame.
	frameCall

	// closure capture frame.
	frameClosure
)

// DebugOptions are the debugger options.
type DebugOptions struct {
	// If true, Go routine IDs start at 1 instead of 0.
	GoRoutineStartAt1 bool
}

// A DebugEvent is an event generated by a debugger.
type DebugEvent struct {
	debugger *Debugger
	reason   DebugEventReason
	frame    *frame
}

// DebugFrame provides access to stack frame information while debugging a
// program.
type DebugFrame struct {
	event  *DebugEvent
	frames []*frame
}

// DebugFrameScope provides access to scoped variables while debugging a
// program.
type DebugFrameScope struct {
	frame *frame
}

// DebugVariable is the name and value of a variable from a debug session.
type DebugVariable struct {
	Name  string
	Value reflect.Value
}

// DebugGoRoutine provides access to information about a Go routine while
// debugging a program.
type DebugGoRoutine struct {
	id int
}

// Breakpoint is the result of attempting to set a breakpoint.
type Breakpoint struct {
	// Valid indicates whether the breakpoint was successfully set.
	Valid bool

	// Position indicates the source position of the breakpoint.
	Position token.Position
}

// DebugEventReason is the reason a debug event occurred.
type DebugEventReason int

const (
	// continue execution normally.
	debugRun DebugEventReason = iota

	// DebugPause is emitted when a pause request is completed. Can be used with
	// Interrupt to request a pause.
	DebugPause

	// DebugBreak is emitted when a debug target hits a breakpoint.
	DebugBreak

	// DebugEntry is emitted when a debug target starts executing. Can be used
	// with Step to produce a corresponding event when execution starts.
	DebugEntry

	// DebugStepInto is emitted when a stepInto request is completed. Can be
	// used with Step or Interrupt to request a stepInto.
	DebugStepInto

	// DebugStepOver is emitted when a stepOver request is completed. Can be
	// used with Step or Interrupt to request a stepOver.
	DebugStepOver

	// DebugStepOut is emitted when a stepOut request is completed. Can be used
	// with Step or Interrupt to request a stepOut.
	DebugStepOut

	// DebugTerminate is emitted when a debug target terminates. Can be used
	// with Interrupt to attempt to terminate the program.
	DebugTerminate

	// DebugEnterGoRoutine is emitted when a Go routine is entered.
	DebugEnterGoRoutine

	// DebugExitGoRoutine is emitted when a Go routine is exited.
	DebugExitGoRoutine
)

// Debug initializes a debugger for the given program.
//
// The program will not start running until Step or Continue has been called. If
// Step is called with DebugEntry, an entry event will be generated before the
// first statement is executed. Otherwise, the debugger will behave as usual.
func ( *Interpreter) ( context.Context,  *Program,  func(*DebugEvent),  *DebugOptions) *Debugger {
	 := new(Debugger)
	.interp = 
	.events = 
	.context, .cancel = context.WithCancel()
	.gWait = new(sync.WaitGroup)
	.gLock = new(sync.Mutex)
	.gLive = make(map[int]*debugRoutine, 1)

	if  == nil {
		 = new(DebugOptions)
	}
	if .GoRoutineStartAt1 {
		.gID = 1
	}

	 := .enterGoRoutine()
	.mode = DebugEntry

	.debugger = 
	.frame.debug = &frameDebugData{kind: frameRoot, g: }

	.root.Walk(func( *node) bool {
		.setProgram()
		return true
	}, nil)

	go func() {
		defer func() { .debugger = nil }()
		defer (&DebugEvent{reason: DebugTerminate})
		defer .cancel()

		<-.resume
		.events(&DebugEvent{, DebugEnterGoRoutine, .frame})
		.result, .err = .ExecuteWithContext(, )
		.exitGoRoutine()
		.events(&DebugEvent{, DebugExitGoRoutine, .frame})
		.gWait.Wait()
	}()

	return 
}

// Wait blocks until all Go routines launched by the program have terminated.
// Wait returns the results of `(*Interpreter).Execute`.
func ( *Debugger) () (reflect.Value, error) {
	<-.context.Done()
	return .result, .err
}

// mark entry into a go routine.
func ( *Debugger) () *debugRoutine {
	 := new(debugRoutine)
	.resume = make(chan struct{})

	.gWait.Add(1)

	.gLock.Lock()
	.id = .gID
	.gID++
	.gLive[.id] = 
	.gLock.Unlock()

	return 
}

// mark exit from a go routine.
func ( *Debugger) ( *debugRoutine) {
	.gLock.Lock()
	delete(.gLive, .id)
	.gLock.Unlock()

	.gWait.Done()
}

// get the state for a given go routine, if it's live.
func ( *Debugger) ( int) (*debugRoutine, bool) {
	.gLock.Lock()
	,  := .gLive[]
	.gLock.Unlock()
	return , 
}

// mark entry into a function call.
func ( *Debugger) (,  *node,  *frame) {
	if .debug != nil {
		.debug.g.fDepth++
		return
	}

	.debug = new(frameDebugData)
	.debug.g = .anc.debug.g
	.debug.scope = .scope

	switch .kind {
	case funcLit:
		.debug.kind = frameCall

	case funcDecl:
		.debug.kind = frameCall
		.debug.name = .child[1].ident
	}

	if  != nil && .anc.kind == goStmt {
		.debug.g = .enterGoRoutine()
		.events(&DebugEvent{, DebugEnterGoRoutine, })
	}

	.debug.g.fDepth++
}

// mark exit from a function call.
func ( *Debugger) (,  *node,  *frame) {
	_ =  // ignore unused, so exitCall can have the same signature as enterCall

	.debug.g.fDepth--

	if  != nil && .anc.kind == goStmt {
		.exitGoRoutine(.debug.g)
		.events(&DebugEvent{, DebugExitGoRoutine, })
	}
}

// called by the interpreter prior to executing the node.
func ( *Debugger) ( *node,  *frame) ( bool) {
	.debug.node = 

	if  != nil && .pos == token.NoPos {
		return false
	}

	 := .debug.g
	defer func() { .running = true }()

	 := &DebugEvent{, .mode, }
	switch {
	case .mode == DebugTerminate:
		.cancel()
		return true

	case .shouldBreak():
		.reason = DebugBreak

	case .mode == debugRun:
		return false

	case .mode == DebugStepOut:
		if .fDepth >= .fStep {
			return false
		}

	case .mode == DebugStepOver:
		if .fDepth > .fStep {
			return false
		}
	}
	.events()

	.running = false
	select {
	case <-.resume:
		return false
	case <-.context.Done():
		return true
	}
}

// Continue continues execution of the specified Go routine. Continue returns
// ErrNotLive if there is no Go routine with the corresponding ID, or if it is not
// live.
func ( *Debugger) ( int) error {
	,  := .getGoRoutine()
	if ! {
		return ErrNotLive
	}

	.mode = debugRun
	.resume <- struct{}{}
	return nil
}

// update the exec mode of this routine.
func ( *debugRoutine) ( DebugEventReason) {
	if .mode == DebugTerminate {
		return
	}

	if .mode == DebugEntry &&  == DebugEntry {
		return
	}

	switch  {
	case DebugStepInto, DebugStepOver, DebugStepOut:
		.mode, .fStep = , .fDepth
	default:
		.mode = DebugPause
	}
}

// Step issues a stepInto, stepOver, or stepOut request to a stopped Go routine.
// Step returns ErrRunning if the Go routine is running. Step returns ErrNotLive
// if there is no Go routine with the corresponding ID, or if it is not live.
func ( *Debugger) ( int,  DebugEventReason) error {
	,  := .getGoRoutine()
	if ! {
		return ErrNotLive
	}

	if .running {
		return ErrRunning
	}

	.setMode()
	.resume <- struct{}{}
	return nil
}

// Interrupt issues a stepInto, stepOver, or stepOut request to a running Go
// routine. Interrupt returns ErrRunning if the Go routine is running. Interrupt
// returns ErrNotLive if there is no Go routine with the corresponding ID, or if
// it is not live.
func ( *Debugger) ( int,  DebugEventReason) bool {
	,  := .getGoRoutine()
	if ! {
		return false
	}

	.setMode()
	return true
}

// Terminate attempts to terminate the program.
func ( *Debugger) () {
	.gLock.Lock()
	 := .gLive
	.gLive = nil
	.gLock.Unlock()

	for ,  := range  {
		.mode = DebugTerminate
		close(.resume)
	}
}

// BreakpointTarget is the target of a request to set breakpoints.
type BreakpointTarget func(*Debugger, func(*node))

// PathBreakpointTarget is used to set breapoints on compiled code by path. This
// can be used to set breakpoints on code compiled with EvalPath, or source
// packages loaded by Yaegi.
func ( string) BreakpointTarget {
	return func( *Debugger,  func(*node)) {
		for ,  := range .interp.roots {
			 := .interp.fset.File(.pos)
			if  != nil && .Name() ==  {
				()
				return
			}
		}
	}
}

// ProgramBreakpointTarget is used to set breakpoints on a Program.
func ( *Program) BreakpointTarget {
	return func( *Debugger,  func(*node)) {
		(.root)
	}
}

// AllBreakpointTarget is used to set breakpoints on all compiled code. Do not
// use with LineBreakpoint.
func () BreakpointTarget {
	return func( *Debugger,  func(*node)) {
		for ,  := range .interp.roots {
			()
		}
	}
}

type breakpointSetup struct {
	roots []*node
	lines map[int]int
	funcs map[string]int
}

// BreakpointRequest is a request to set a breakpoint.
type BreakpointRequest func(*breakpointSetup, int)

// LineBreakpoint requests a breakpoint on the given line.
func ( int) BreakpointRequest {
	return func( *breakpointSetup,  int) {
		.lines[] = 
	}
}

// FunctionBreakpoint requests a breakpoint on the named function.
func ( string) BreakpointRequest {
	return func( *breakpointSetup,  int) {
		.funcs[] = 
	}
}

// SetBreakpoints sets breakpoints for the given target. The returned array has
// an entry for every request, in order. If a given breakpoint request cannot be
// satisfied, the corresponding entry will be marked invalid. If the target
// cannot be found, all entries will be marked invalid.
func ( *Debugger) ( BreakpointTarget,  ...BreakpointRequest) []Breakpoint {
	// start with all breakpoints unverified
	 := make([]Breakpoint, len())

	// prepare all the requests
	 := new(breakpointSetup)
	(, func( *node) {
		.roots = append(.roots, )
		.lines = make(map[int]int, len())
		.funcs = make(map[string]int, len())
		for ,  := range  {
			(, )
		}
	})

	// find breakpoints
	for ,  := range .roots {
		.Walk(func( *node) bool {
			// function breakpoints
			if len(.funcs) > 0 && .kind == funcDecl {
				// reset stale breakpoints
				.start.setBreakOnCall(false)

				if ,  := .funcs[.child[1].ident];  && ![].Valid {
					[].Valid = true
					[].Position = .interp.fset.Position(.start.pos)
					.start.setBreakOnCall(true)
					return true
				}
			}

			// line breakpoints
			if len(.lines) > 0 && .pos.IsValid() && .action != aNop && getExec() != nil {
				// reset stale breakpoints
				.setBreakOnLine(false)

				 := .interp.fset.Position(.pos)
				if ,  := .lines[.Line];  && ![].Valid {
					[].Valid = true
					[].Position = 
					.setBreakOnLine(true)
					return true
				}
			}

			return true
		}, nil)
	}

	return 
}

// GoRoutines returns an array of live Go routines.
func ( *Debugger) () []*DebugGoRoutine {
	.gLock.Lock()
	 := make([]*DebugGoRoutine, 0, len(.gLive))
	for  := range .gLive {
		 = append(, &DebugGoRoutine{})
	}
	.gLock.Unlock()
	sort.Slice(, func(,  int) bool { return [].id < [].id })
	return 
}

// ID returns the ID of the Go routine.
func ( *DebugGoRoutine) () int { return .id }

// Name returns "Goroutine {ID}".
func ( *DebugGoRoutine) () string { return fmt.Sprintf("Goroutine %d", .id) }

// GoRoutine returns the ID of the Go routine that generated the event.
func ( *DebugEvent) () int {
	if .frame.debug == nil {
		return 0
	}
	return .frame.debug.g.id
}

// Reason returns the reason for the event.
func ( *DebugEvent) () DebugEventReason {
	return .reason
}

// Walk the stack trace frames. The root frame is included if and only if it is
// the only frame. Closure frames are rolled up into the following call frame.
func ( *DebugEvent) ( func([]*frame) bool) {
	if .frame == .frame.root {
		([]*frame{.frame})
		return
	}

	var  *debugRoutine
	if .frame.debug != nil {
		 = .frame.debug.g
	}

	var  []*frame
	for  := .frame;  != nil &&  != .root && (.debug == nil || .debug.g == );  = .anc {
		if .debug == nil || .debug.kind != frameCall {
			 = append(, )
			continue
		}

		if len() > 0 {
			if !() {
				return
			}
		}

		 = [:0]
		 = append(, )
	}

	if len() > 0 {
		()
	}
}

// FrameDepth returns the number of call frames in the stack trace.
func ( *DebugEvent) () int {
	if .frame == .frame.root {
		return 1
	}

	var  int
	.walkFrames(func([]*frame) bool { ++; return true })
	return 
}

// Frames returns the call frames in the range [start, end).
func ( *DebugEvent) (,  int) []*DebugFrame {
	 :=  - 
	if  < 0 {
		return nil
	}

	 := []*DebugFrame{}
	.walkFrames(func( []*frame) bool {
		 := &DebugFrame{, make([]*frame, len())}
		copy(.frames, )
		 = append(, )
		return len() < 
	})
	return 
}

// Name returns the name of the stack frame. For function calls to named
// functions, this is the function name.
func ( *DebugFrame) () string {
	 := .frames[0].debug
	if  == nil {
		return "<unknown>"
	}
	switch .kind {
	case frameRoot:
		return "<init>"
	case frameClosure:
		return "<closure>"
	case frameCall:
		if .name == "" {
			return "<anonymous>"
		}
		return .name
	default:
		return "<unknown>"
	}
}

// Position returns the current position of the frame. This is effectively the
// program counter/link register. May return `Position{}`.
func ( *DebugFrame) () token.Position {
	 := .frames[0].debug
	if  == nil || .node == nil {
		return token.Position{}
	}
	return .event.debugger.interp.fset.Position(.node.pos)
}

// Program returns the program associated with the current position of the
// frame. May return nil.
func ( *DebugFrame) () *Program {
	 := .frames[0].debug
	if  == nil || .node == nil {
		return nil
	}

	return .node.debug.program
}

// Scopes returns the variable scopes of the frame.
func ( *DebugFrame) () []*DebugFrameScope {
	 := make([]*DebugFrameScope, len(.frames))
	for ,  := range .frames {
		[] = &DebugFrameScope{}
	}
	return 
}

// IsClosure returns true if this is the capture scope of a closure.
func ( *DebugFrameScope) () bool {
	return .frame.debug != nil && .frame.debug.kind == frameClosure
}

// Variables returns the names and values of the variables of the scope.
func ( *DebugFrameScope) () []*DebugVariable {
	 := .frame.debug
	if  == nil || .scope == nil {
		return nil
	}

	 := map[int]string{}
	scanScope(.scope, )

	 := make([]*DebugVariable, 0, len(.frame.data))
	for ,  := range .frame.data {
		if  := .Type(); .AssignableTo(rNodeType) || .Kind() == reflect.Ptr && .Elem().AssignableTo(rNodeType) {
			continue
		}
		,  := []
		if ! {
			continue
		}

		 = append(, &DebugVariable{, })
	}
	return 
}

func scanScope( *scope,  map[int]string) {
	for ,  := range .sym {
		if ,  := [.index];  {
			continue
		}
		[.index] = 
	}

	for ,  := range .child {
		if .def != .def {
			continue
		}
		(, )
	}
}