package ext

import (
	"encoding"
	"strconv"
	"sync"
	"syscall"
	"unsafe"
)

// Prctl manipulates various aspects of the behavior of the calling thread or process.
func Prctl(op, arg2, arg3 uintptr) error {
	r, _, errno := syscall.Syscall(syscall.SYS_PRCTL, op, arg2, arg3)
	if r < 0 {
		return errno
	}
	return nil
}

// SetPtracer allows processes to ptrace(2) the calling process.
func SetPtracer(pid uintptr) error {
	return Prctl(syscall.PR_SET_PTRACER, pid, 0)
}

// linux/sched/coredump.h
const (
	SUID_DUMP_DISABLE = iota
	SUID_DUMP_USER
)

// SetDumpable sets the "dumpable" attribute of the calling process.
func SetDumpable(dumpable uintptr) error {
	return Prctl(syscall.PR_SET_DUMPABLE, dumpable, 0)
}

// Isatty tests whether a file descriptor refers to a terminal.
func Isatty(fd int) bool {
	var buf [8]byte
	r, _, _ := syscall.Syscall(
		syscall.SYS_IOCTL,
		uintptr(fd),
		syscall.TIOCGWINSZ,
		uintptr(unsafe.Pointer(&buf[0])),
	)
	return r == 0
}

// IgnoringEINTR makes a function call and repeats it if it returns an
// EINTR error. This appears to be required even though we install all
// signal handlers with SA_RESTART: see #22838, #38033, #38836, #40846.
// Also #20400 and #36644 are issues in which a signal handler is
// installed without setting SA_RESTART. None of these are the common case,
// but there are enough of them that it seems that we can't avoid
// an EINTR loop.
func IgnoringEINTR(fn func() error) error {
	for {
		err := fn()
		if err != syscall.EINTR {
			return err
		}
	}
}

// include/uapi/linux/close_range.h
const (
	CLOSE_RANGE_UNSHARE = 1 << (iota + 1)
	CLOSE_RANGE_CLOEXEC
)

// CloseRange close all file descriptors in a given range.
func CloseRange(first, last Uint, flags Int) error {
	_, _, errno := syscall.Syscall(
		SYS_CLOSE_RANGE,
		uintptr(first),
		uintptr(last),
		uintptr(flags),
	)
	if errno != 0 {
		return errno
	}
	return nil
}

// SchedPolicy denotes a scheduling policy defined in include/uapi/linux/sched.h.
type SchedPolicy int

// include/uapi/linux/sched.h
const (
	SCHED_NORMAL SchedPolicy = iota
	SCHED_FIFO
	SCHED_RR
	SCHED_BATCH
	_SCHED_ISO // SCHED_ISO: reserved but not implemented yet
	SCHED_IDLE
	SCHED_DEADLINE
	SCHED_EXT

	SCHED_LAST SchedPolicy = iota - 1
)

var _ encoding.TextMarshaler = SCHED_LAST
var _ encoding.TextUnmarshaler = new(SCHED_LAST)

// String returns a unique representation of policy, also used in encoding.
func (policy SchedPolicy) String() string {
	switch policy {
	case SCHED_NORMAL:
		return ""
	case SCHED_FIFO:
		return "fifo"
	case SCHED_RR:
		return "rr"
	case SCHED_BATCH:
		return "batch"
	case SCHED_IDLE:
		return "idle"
	case SCHED_DEADLINE:
		return "deadline"
	case SCHED_EXT:
		return "ext"

	default:
		return "invalid policy " + strconv.Itoa(int(policy))
	}
}

// MarshalText performs bounds checking and returns the result of String.
func (policy SchedPolicy) MarshalText() ([]byte, error) {
	if policy == _SCHED_ISO || policy < 0 || policy > SCHED_LAST {
		return nil, syscall.EINVAL
	}
	return []byte(policy.String()), nil
}

// InvalidSchedPolicyError is an invalid string representation of a [SchedPolicy].
type InvalidSchedPolicyError string

func (InvalidSchedPolicyError) Unwrap() error { return syscall.EINVAL }
func (e InvalidSchedPolicyError) Error() string {
	return "invalid scheduling policy " + strconv.Quote(string(e))
}

// UnmarshalText is the inverse of MarshalText.
func (policy *SchedPolicy) UnmarshalText(text []byte) error {
	switch string(text) {
	case "fifo":
		*policy = SCHED_FIFO
	case "rr":
		*policy = SCHED_RR
	case "batch":
		*policy = SCHED_BATCH
	case "idle":
		*policy = SCHED_IDLE
	case "deadline":
		*policy = SCHED_DEADLINE
	case "ext":
		*policy = SCHED_EXT

	case "":
		*policy = 0
		return nil
	default:
		return InvalidSchedPolicyError(text)
	}
	return nil
}

// for sched_get_priority_max and sched_get_priority_min
var (
	schedPriority     [SCHED_LAST + 1][2]Int
	schedPriorityErr  [SCHED_LAST + 1][2]error
	schedPriorityOnce [SCHED_LAST + 1][2]sync.Once
)

// GetPriorityMax returns the maximum priority value that can be used with the
// scheduling algorithm identified by policy.
func (policy SchedPolicy) GetPriorityMax() (Int, error) {
	schedPriorityOnce[policy][0].Do(func() {
		priority, _, errno := syscall.Syscall(
			syscall.SYS_SCHED_GET_PRIORITY_MAX,
			uintptr(policy),
			0, 0,
		)
		schedPriority[policy][0] = Int(priority)
		if errno != 0 {
			schedPriorityErr[policy][0] = errno
		}
	})
	return schedPriority[policy][0], schedPriorityErr[policy][0]
}

// GetPriorityMin returns the minimum priority value that can be used with the
// scheduling algorithm identified by policy.
func (policy SchedPolicy) GetPriorityMin() (Int, error) {
	schedPriorityOnce[policy][1].Do(func() {
		priority, _, errno := syscall.Syscall(
			syscall.SYS_SCHED_GET_PRIORITY_MIN,
			uintptr(policy),
			0, 0,
		)
		schedPriority[policy][1] = Int(priority)
		if errno != 0 {
			schedPriorityErr[policy][1] = errno
		}
	})
	return schedPriority[policy][1], schedPriorityErr[policy][1]
}
