package proc

import "runtime"

// Arch defines an interface for representing a
// CPU architecture.
type Arch interface {
	SetGStructOffset(ver GoVersion, iscgo bool)
	PtrSize() int
	BreakpointInstruction() []byte
	BreakpointSize() int
	GStructOffset() uint64
}

// AMD64 represents the AMD64 CPU architecture.
type AMD64 struct {
	ptrSize                 int
	breakInstruction        []byte
	breakInstructionLen     int
	gStructOffset           uint64
	hardwareBreakpointUsage []bool
}

// AMD64Arch returns an initialized AMD64
// struct.
func AMD64Arch() *AMD64 {
	var breakInstr = []byte{0xCC}

	return &AMD64{
		ptrSize:                 8,
		breakInstruction:        breakInstr,
		breakInstructionLen:     len(breakInstr),
		hardwareBreakpointUsage: make([]bool, 4),
	}
}

// SetGStructOffset sets the offset of the G struct on the AMD64
// arch struct. The offset is dependent on the Go compiler Version
// and whether or not the target program was externally linked.
func (a *AMD64) SetGStructOffset(ver GoVersion, isextld bool) {
	switch runtime.GOOS {
	case "darwin":
		a.gStructOffset = 0x8a0
	case "linux":
		a.gStructOffset = 0xfffffffffffffff0
		if isextld || ver.AfterOrEqual(GoVersion{1, 5, -1, 2, 0}) || ver.IsDevel() {
			a.gStructOffset += 8
		}
	case "windows":
		// Use ArbitraryUserPointer (0x28) as pointer to pointer
		// to G struct per:
		// https://golang.org/src/runtime/cgo/gcc_windows_amd64.c
		a.gStructOffset = 0x28
	}
}

// PtrSize returns the size of a pointer
// on this architecture.
func (a *AMD64) PtrSize() int {
	return a.ptrSize
}

// BreakpointInstruction returns the Breakpoint
// instruction for this architecture.
func (a *AMD64) BreakpointInstruction() []byte {
	return a.breakInstruction
}

// BreakpointSize returns the size of the
// breakpoint instruction on this architecture.
func (a *AMD64) BreakpointSize() int {
	return a.breakInstructionLen
}

// GStructOffset returns the offset of the G
// struct in thread local storage.
func (a *AMD64) GStructOffset() uint64 {
	return a.gStructOffset
}
