package proc

import (
	"errors"

	"github.com/go-delve/delve/pkg/dwarf/op"
)

// Thread represents a thread.
type Thread interface {
	// Breakpoint will return the breakpoint that this thread is stopped at or
	// nil if the thread is not stopped at any breakpoint.
	Breakpoint() *BreakpointState
	ThreadID() int

	// Registers returns the CPU registers of this thread. The contents of the
	// variable returned may or may not change to reflect the new CPU status
	// when the thread is resumed or the registers are changed by calling
	// SetPC/SetSP/etc.
	// To insure that the returned variable won't change call the Copy
	// method of Registers.
	Registers() (Registers, error)

	// RestoreRegisters restores saved registers
	RestoreRegisters(Registers) error
	BinInfo() *BinaryInfo
	// ProcessMemory returns the process memory.
	ProcessMemory() MemoryReadWriter
	// SetCurrentBreakpoint updates the current breakpoint of this thread, if adjustPC is true also checks for breakpoints that were just hit (this should only be passed true after a thread resume)
	SetCurrentBreakpoint(adjustPC bool) error
	// SoftExc returns true if this thread received a software exception during the last resume.
	SoftExc() bool
	// Common returns the CommonThread structure for this thread
	Common() *CommonThread

	// SetReg changes the value of the specified register. A minimal
	// implementation of this interface can support just setting the PC
	// register.
	SetReg(uint64, *op.DwarfRegister) error
}

// Location represents the location of a thread.
// Holds information on the current instruction
// address, the source file:line, and the function.
type Location struct {
	PC   uint64
	File string
	Line int
	Fn   *Function
}

// CommonThread contains fields used by this package, common to all
// implementations of the Thread interface.
type CommonThread struct {
	CallReturn   bool // returnValues are the return values of a call injection
	returnValues []*Variable
	g            *G // cached g for this thread
}

// ReturnValues reads the return values from the function executing on
// this thread using the provided LoadConfig.
func (t *CommonThread) ReturnValues(cfg LoadConfig) []*Variable {
	loadValues(t.returnValues, cfg)
	return t.returnValues
}

// topframe returns the two topmost frames of g, or thread if g is nil.
func topframe(tgt *Target, g *G, thread Thread) (Stackframe, Stackframe, error) {
	var frames []Stackframe
	var err error

	if g == nil {
		frames, err = ThreadStacktrace(tgt, thread, 1)
	} else {
		frames, err = GoroutineStacktrace(tgt, g, 1, StacktraceReadDefers)
	}
	if err != nil {
		return Stackframe{}, Stackframe{}, err
	}
	switch len(frames) {
	case 0:
		return Stackframe{}, Stackframe{}, errors.New("empty stack trace")
	case 1:
		return frames[0], Stackframe{}, nil
	default:
		return frames[0], frames[1], nil
	}
}

func setPC(thread Thread, newPC uint64) error {
	return thread.SetReg(thread.BinInfo().Arch.PCRegNum, op.DwarfRegisterFromUint64(newPC))
}

func setSP(thread Thread, newSP uint64) error {
	return thread.SetReg(thread.BinInfo().Arch.SPRegNum, op.DwarfRegisterFromUint64(newSP))
}

func setClosureReg(thread Thread, newClosureReg uint64) error {
	return thread.SetReg(thread.BinInfo().Arch.ContextRegNum, op.DwarfRegisterFromUint64(newClosureReg))
}

func setLR(thread Thread, newLR uint64) error {
	return thread.SetReg(thread.BinInfo().Arch.LRRegNum, op.DwarfRegisterFromUint64(newLR))
}

// ThreadLocation returns the threads location, including the file:line
// of the corresponding source code, the function we're in
// and the current instruction address.
func ThreadLocation(thread Thread) (*Location, error) {
	regs, err := thread.Registers()
	if err != nil {
		return nil, err
	}
	pc := regs.PC()
	f, l, fn := thread.BinInfo().PCToLine(pc)
	return &Location{PC: pc, File: f, Line: l, Fn: fn}, nil
}
