package proc

import (
	"sync"

	"github.com/go-delve/delve/pkg/elfwriter"
	"github.com/go-delve/delve/pkg/proc/internal/ebpf"
)

// Process represents the target of the debugger. This
// target could be a system process, core file, etc.
//
// Implementations of Process are not required to be thread safe and users
// of Process should not assume they are.
// There is one exception to this rule: it is safe to call RequestManualStop
// concurrently with ContinueOnce.
type Process interface {
	BinInfo() *BinaryInfo
	EntryPoint() (uint64, error)

	FindThread(threadID int) (Thread, bool)
	ThreadList() []Thread

	Breakpoints() *BreakpointMap

	// Memory returns a memory read/writer for this process's memory.
	Memory() MemoryReadWriter
}

// ProcessInternal holds a set of methods that need to be implemented by a
// Delve backend. Methods in the Process interface are safe to be called by
// clients of the 'proc' library, while all other methods are only called
// directly within 'proc'.
type ProcessInternal interface {
	Process
	// Valid returns true if this Process can be used. When it returns false it
	// also returns an error describing why the Process is invalid (either
	// ErrProcessExited or ErrProcessDetached).
	Valid() (bool, error)
	Detach(bool) error

	// RequestManualStop attempts to stop all the process' threads.
	RequestManualStop(cctx *ContinueOnceContext) error

	WriteBreakpoint(*Breakpoint) error
	EraseBreakpoint(*Breakpoint) error

	SupportsBPF() bool
	SetUProbe(string, int64, []ebpf.UProbeArgMap) error
	GetBufferedTracepoints() []ebpf.RawUProbeParams

	// DumpProcessNotes returns ELF core notes describing the process and its threads.
	// Implementing this method is optional.
	DumpProcessNotes(notes []elfwriter.Note, threadDone func()) (bool, []elfwriter.Note, error)
	// MemoryMap returns the memory map of the target process. This method must be implemented if CanDump is true.
	MemoryMap() ([]MemoryMapEntry, error)

	// StartCallInjection notifies the backend that we are about to inject a function call.
	StartCallInjection() (func(), error)
}

// RecordingManipulation is an interface for manipulating process recordings.
type RecordingManipulation interface {
	// Recorded returns true if the current process is a recording and the path
	// to the trace directory.
	Recorded() (recorded bool, tracedir string)
	// Direction changes execution direction.
	ChangeDirection(Direction) error
	// GetDirection returns the current direction of execution.
	GetDirection() Direction
	// When returns current recording position.
	When() (string, error)
	// Checkpoint sets a checkpoint at the current position.
	Checkpoint(where string) (id int, err error)
	// Checkpoints returns the list of currently set checkpoint.
	Checkpoints() ([]Checkpoint, error)
	// ClearCheckpoint removes a checkpoint.
	ClearCheckpoint(id int) error
}

// RecordingManipulationInternal is an interface that a Delve backend can
// implement if it is a recording.
type RecordingManipulationInternal interface {
	RecordingManipulation

	// Restart restarts the recording from the specified position, or from the
	// last checkpoint if pos == "".
	// If pos starts with 'c' it's a checkpoint ID, otherwise it's an event
	// number.
	// Returns the new current thread after the restart has completed.
	Restart(cctx *ContinueOnceContext, pos string) (Thread, error)
}

// Direction is the direction of execution for the target process.
type Direction int8

const (
	// Forward direction executes the target normally.
	Forward Direction = 0
	// Backward direction executes the target in reverse.
	Backward Direction = 1
)

// Checkpoint is a checkpoint
type Checkpoint struct {
	ID    int
	When  string
	Where string
}

// ContinueOnceContext is an object passed to ContinueOnce that the backend
// can use to communicate with the target layer.
type ContinueOnceContext struct {
	ResumeChan chan<- struct{}
	StopMu     sync.Mutex
	// manualStopRequested is set if all the threads in the process were
	// signalled to stop as a result of a Halt API call. Used to disambiguate
	// why a thread is found to have stopped.
	manualStopRequested bool
}

// CheckAndClearManualStopRequest will check for a manual
// stop and then clear that state.
func (cctx *ContinueOnceContext) CheckAndClearManualStopRequest() bool {
	cctx.StopMu.Lock()
	defer cctx.StopMu.Unlock()
	msr := cctx.manualStopRequested
	cctx.manualStopRequested = false
	return msr
}

func (cctx *ContinueOnceContext) GetManualStopRequested() bool {
	cctx.StopMu.Lock()
	defer cctx.StopMu.Unlock()
	return cctx.manualStopRequested
}
