package native

import (
	"debug/elf"
	"errors"
	"fmt"
	"syscall"
	"unsafe"

	sys "golang.org/x/sys/unix"

	"github.com/go-delve/delve/pkg/proc"
	"github.com/go-delve/delve/pkg/proc/linutil"
)

func (thread *nativeThread) fpRegisters() ([]proc.Register, []byte, error) {
	var err error
	var arm_fpregs linutil.ARM64PtraceFpRegs
	thread.dbp.execPtraceFunc(func() { arm_fpregs.Vregs, err = ptraceGetFpRegset(thread.ID) })
	fpregs := arm_fpregs.Decode()
	if err != nil {
		err = fmt.Errorf("could not get floating point registers: %v", err.Error())
	}
	return fpregs, arm_fpregs.Vregs, err
}

func (t *nativeThread) restoreRegisters(savedRegs proc.Registers) error {
	sr := savedRegs.(*linutil.ARM64Registers)

	var restoreRegistersErr error
	t.dbp.execPtraceFunc(func() {
		restoreRegistersErr = ptraceSetGRegs(t.ID, sr.Regs)
		if restoreRegistersErr != syscall.Errno(0) {
			return
		}
		if sr.Fpregset != nil {
			iov := sys.Iovec{Base: &sr.Fpregset[0], Len: uint64(len(sr.Fpregset))}
			_, _, restoreRegistersErr = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_SETREGSET, uintptr(t.ID), uintptr(elf.NT_FPREGSET), uintptr(unsafe.Pointer(&iov)), 0, 0)
		}
	})
	if restoreRegistersErr == syscall.Errno(0) {
		restoreRegistersErr = nil
	}
	return restoreRegistersErr
}

const (
	_MAX_ARM64_WATCH = 16
	_NT_ARM_HW_WATCH = 0x403
	_TRAP_HWBKPT     = 0x4
)

type watchpointState struct {
	num      uint8
	debugVer uint8
	words    []uint64
}

func (wpstate *watchpointState) set(idx uint8, addr, ctrl uint64) {
	wpstate.words[1+idx*2] = addr
	wpstate.words[1+idx*2+1] = ctrl
}

// getWatchpoints reads the NT_ARM_HW_WATCH ptrace register set.
// The format of this register set is described by user_hwdebug_state in
// arch/arm64/include/uapi/asm/ptrace.h.
// It consists of one 64bit word containing:
//  * 1byte number of watchpoints
//  * 1byte debug architecture version (the 4 least significant bits of ID_AA64DFR0_EL1)
//  * 6bytes padding
// Followed by 2 64bit words for each watchpoint, up to a maximum of 16
// watchpoints. The first word contains the address at which the watchpoint
// is set (DBGWVRn_EL1), the second word is the control register for the
// watchpoint (DBGWCRn_EL1), as described by
// ARM - Architecture Reference Manual Armv8, for A-profile architectures
// section D13.3.11
// where only the BAS, LSC, PAC and E fields are accessible.
func (t *nativeThread) getWatchpoints() (*watchpointState, error) {
	words := make([]uint64, _MAX_ARM64_WATCH*2+1)
	iov := sys.Iovec{Base: (*byte)(unsafe.Pointer(&words[0])), Len: uint64(len(words)) * uint64(unsafe.Sizeof(words[0]))}
	var err error
	t.dbp.execPtraceFunc(func() {
		_, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_GETREGSET, uintptr(t.ID), _NT_ARM_HW_WATCH, uintptr(unsafe.Pointer(&iov)), 0, 0)
	})
	if err != syscall.Errno(0) {
		return nil, err
	}
	wpstate := &watchpointState{num: uint8(words[0] & 0xff), debugVer: uint8((words[0] >> 8) & 0xff), words: words}
	if wpstate.num > _MAX_ARM64_WATCH {
		// According to the specification this should never be more than 16 but
		// the code here will not work if this limit ever gets relaxed.
		wpstate.num = _MAX_ARM64_WATCH
	}
	// remove the watchpoint registers that don't exist from the words slice so
	// that we won't try later to write them (which would cause an ENOSPC
	// error).
	wpstate.words = wpstate.words[:wpstate.num*2+1]
	return wpstate, nil
}

// setWatchpoints saves the watchpoint state of the given thread.
// See (*nativeThread).getWatchpoints for a description of how this works.
func (t *nativeThread) setWatchpoints(wpstate *watchpointState) error {
	iov := sys.Iovec{Base: (*byte)(unsafe.Pointer(&(wpstate.words[0]))), Len: uint64(len(wpstate.words)) * uint64(unsafe.Sizeof(wpstate.words[0]))}
	var err error
	t.dbp.execPtraceFunc(func() {
		_, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_SETREGSET, uintptr(t.ID), _NT_ARM_HW_WATCH, uintptr(unsafe.Pointer(&iov)), 0, 0)
	})
	if err != syscall.Errno(0) {
		return err
	}
	return nil
}

type ptraceSiginfoArm64 struct {
	signo uint32
	errno uint32
	code  uint32
	addr  uint64    // only valid if Signo is SIGTRAP, SIGFPE, SIGILL, SIGBUS or SIGEMT
	pad   [128]byte // the total size of siginfo_t on ARM64 is 128 bytes so this is more than enough padding for all the fields we don't care about
}

func (t *nativeThread) findHardwareBreakpoint() (*proc.Breakpoint, error) {
	var siginfo ptraceSiginfoArm64
	var err error
	t.dbp.execPtraceFunc(func() {
		_, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_GETSIGINFO, uintptr(t.ID), 0, uintptr(unsafe.Pointer(&siginfo)), 0, 0)
	})
	if err != syscall.Errno(0) {
		return nil, err
	}
	if siginfo.signo != uint32(sys.SIGTRAP) || (siginfo.code&0xffff) != _TRAP_HWBKPT {
		return nil, nil
	}

	for _, bp := range t.dbp.Breakpoints().M {
		if bp.WatchType != 0 && siginfo.addr >= bp.Addr && siginfo.addr < bp.Addr+uint64(bp.WatchType.Size()) {
			return bp, nil
		}
	}

	return nil, fmt.Errorf("could not find hardware breakpoint for address %#x", siginfo.addr)
}

func (t *nativeThread) writeHardwareBreakpoint(addr uint64, wtype proc.WatchType, idx uint8) error {
	wpstate, err := t.getWatchpoints()
	if err != nil {
		return err
	}
	if idx >= wpstate.num {
		return errors.New("hardware breakpoints exhausted")
	}

	const (
		readBreakpoint  = 0x1
		writeBreakpoint = 0x2
		lenBitOffset    = 5
		typeBitOffset   = 3
		privBitOffset   = 1
	)

	var typ uint64
	if wtype.Read() {
		typ |= readBreakpoint
	}
	if wtype.Write() {
		typ |= writeBreakpoint
	}

	len := uint64((1 << wtype.Size()) - 1) // arm wants the length expressed as address bitmask

	priv := uint64(3)

	ctrl := (len << lenBitOffset) | (typ << typeBitOffset) | (priv << privBitOffset) | 1
	wpstate.set(idx, addr, ctrl)

	return t.setWatchpoints(wpstate)
}

func (t *nativeThread) clearHardwareBreakpoint(addr uint64, wtype proc.WatchType, idx uint8) error {
	wpstate, err := t.getWatchpoints()
	if err != nil {
		return err
	}
	if idx >= wpstate.num {
		return errors.New("hardware breakpoints exhausted")
	}
	wpstate.set(idx, 0, 0)
	return t.setWatchpoints(wpstate)
}
