//go:build darwin

package syscall

import (
	"internal/itoa"
	"unsafe"
)

// This file defines errno and constants to match the darwin libsystem ABI.
// Values have been copied from src/syscall/zerrors_darwin_amd64.go.

// This function returns the error location in the darwin ABI.
// Discovered by compiling the following code using Clang:
//
//	#include <errno.h>
//	int getErrno() {
//	    return errno;
//	}
//
//export __error
func libc___error() *int32

// getErrno returns the current C errno. It may not have been caused by the last
// call, so it should only be relied upon when the last call indicates an error
// (for example, by returning -1).
func getErrno() Errno {
	errptr := libc___error()
	return Errno(uintptr(*errptr))
}

func (e Errno) Is(target error) bool {
	switch target.Error() {
	case "permission denied":
		return e == EACCES || e == EPERM
	case "file already exists":
		return e == EEXIST
	case "file does not exist":
		return e == ENOENT
	}
	return false
}

// Source: upstream zerrors_darwin_amd64.go
const (
	DT_BLK     = 0x6
	DT_CHR     = 0x2
	DT_DIR     = 0x4
	DT_FIFO    = 0x1
	DT_LNK     = 0xa
	DT_REG     = 0x8
	DT_SOCK    = 0xc
	DT_UNKNOWN = 0x0
	DT_WHT     = 0xe
	F_GETFL    = 0x3
	F_SETFL    = 0x4
	O_NONBLOCK = 0x4
)

// Source: https://opensource.apple.com/source/xnu/xnu-7195.81.3/bsd/sys/errno.h.auto.html
const (
	EPERM       Errno = 1
	ENOENT      Errno = 2
	EACCES      Errno = 13
	EEXIST      Errno = 17
	EINTR       Errno = 4
	ENOTDIR     Errno = 20
	EISDIR      Errno = 21
	EINVAL      Errno = 22
	EMFILE      Errno = 24
	EROFS       Errno = 30
	EPIPE       Errno = 32
	EAGAIN      Errno = 35
	ENOTCONN    Errno = 57
	ETIMEDOUT   Errno = 60
	ENOSYS      Errno = 78
	EWOULDBLOCK Errno = EAGAIN
)

type Signal int

// Source: https://opensource.apple.com/source/xnu/xnu-7195.81.3/bsd/sys/signal.h
const (
	SIGINT  Signal = 2  /* interrupt */
	SIGQUIT Signal = 3  /* quit */
	SIGILL  Signal = 4  /* illegal instruction (not reset when caught) */
	SIGTRAP Signal = 5  /* trace trap (not reset when caught) */
	SIGABRT Signal = 6  /* abort() */
	SIGFPE  Signal = 8  /* floating point exception */
	SIGKILL Signal = 9  /* kill (cannot be caught or ignored) */
	SIGBUS  Signal = 10 /* bus error */
	SIGSEGV Signal = 11 /* segmentation violation */
	SIGPIPE Signal = 13 /* write on a pipe with no one to read it */
	SIGTERM Signal = 15 /* software termination signal from kill */
	SIGCHLD Signal = 20 /* to parent on child stop or exit */
)

func (s Signal) Signal() {}

func (s Signal) String() string {
	if 0 <= s && int(s) < len(signals) {
		str := signals[s]
		if str != "" {
			return str
		}
	}
	return "signal " + itoa.Itoa(int(s))
}

var signals = [...]string{}

const (
	Stdin  = 0
	Stdout = 1
	Stderr = 2
)

const (
	O_RDONLY = 0x0
	O_WRONLY = 0x1
	O_RDWR   = 0x2
	O_APPEND = 0x8
	O_SYNC   = 0x80
	O_CREAT  = 0x200
	O_TRUNC  = 0x400
	O_EXCL   = 0x800

	O_CLOEXEC = 0x01000000
)

// Source: https://opensource.apple.com/source/xnu/xnu-7195.81.3/bsd/sys/mman.h.auto.html
const (
	PROT_NONE  = 0x00 // no permissions
	PROT_READ  = 0x01 // pages can be read
	PROT_WRITE = 0x02 // pages can be written
	PROT_EXEC  = 0x04 // pages can be executed

	MAP_SHARED  = 0x0001 // share changes
	MAP_PRIVATE = 0x0002 // changes are private

	MAP_FILE      = 0x0000 // map from file (default)
	MAP_ANON      = 0x1000 // allocated from memory, swap space
	MAP_ANONYMOUS = MAP_ANON
)

type Timespec struct {
	Sec  int64
	Nsec int64
}

// Unix returns the time stored in ts as seconds plus nanoseconds.
func (ts *Timespec) Unix() (sec int64, nsec int64) {
	return int64(ts.Sec), int64(ts.Nsec)
}

// Source: upstream ztypes_darwin_amd64.go
type Dirent struct {
	Ino       uint64
	Seekoff   uint64
	Reclen    uint16
	Namlen    uint16
	Type      uint8
	Name      [1024]int8
	Pad_cgo_0 [3]byte
}

type Stat_t struct {
	Dev       int32
	Mode      uint16
	Nlink     uint16
	Ino       uint64
	Uid       uint32
	Gid       uint32
	Rdev      int32
	Pad_cgo_0 [4]byte
	Atimespec Timespec
	Mtimespec Timespec
	Ctimespec Timespec
	Btimespec Timespec
	Size      int64
	Blocks    int64
	Blksize   int32
	Flags     uint32
	Gen       uint32
	Lspare    int32
	Qspare    [2]int64
}

// Source: https://github.com/apple/darwin-xnu/blob/main/bsd/sys/_types/_s_ifmt.h
const (
	S_IEXEC  = 0x40
	S_IFBLK  = 0x6000
	S_IFCHR  = 0x2000
	S_IFDIR  = 0x4000
	S_IFIFO  = 0x1000
	S_IFLNK  = 0xa000
	S_IFMT   = 0xf000
	S_IFREG  = 0x8000
	S_IFSOCK = 0xc000
	S_IFWHT  = 0xe000
	S_IREAD  = 0x100
	S_IRGRP  = 0x20
	S_IROTH  = 0x4
	S_IRUSR  = 0x100
	S_IRWXG  = 0x38
	S_IRWXO  = 0x7
	S_IRWXU  = 0x1c0
	S_ISGID  = 0x400
	S_ISTXT  = 0x200
	S_ISUID  = 0x800
	S_ISVTX  = 0x200
	S_IWGRP  = 0x10
	S_IWOTH  = 0x2
	S_IWRITE = 0x80
	S_IWUSR  = 0x80
	S_IXGRP  = 0x8
	S_IXOTH  = 0x1
	S_IXUSR  = 0x40
)

func Stat(path string, p *Stat_t) (err error) {
	data := cstring(path)
	n := libc_stat(&data[0], unsafe.Pointer(p))

	if n < 0 {
		err = getErrno()
	}
	return
}

func Fstat(fd int, p *Stat_t) (err error) {
	n := libc_fstat(int32(fd), unsafe.Pointer(p))

	if n < 0 {
		err = getErrno()
	}
	return
}

func Lstat(path string, p *Stat_t) (err error) {
	data := cstring(path)
	n := libc_lstat(&data[0], unsafe.Pointer(p))
	if n < 0 {
		err = getErrno()
	}
	return
}

func Fdopendir(fd int) (dir uintptr, err error) {
	r0 := libc_fdopendir(int32(fd))
	dir = uintptr(r0)
	if dir == 0 {
		err = getErrno()
	}
	return
}

func Pipe2(fds []int, flags int) (err error) {
	// Mac only has Pipe, which ignores the flags argument
	buf := make([]int32, 2)
	fail := int(libc_pipe(&buf[0]))
	if fail < 0 {
		err = getErrno()
	} else {
		fds[0] = int(buf[0])
		fds[1] = int(buf[1])
	}
	return
}

func Chmod(path string, mode uint32) (err error) {
	data := cstring(path)
	fail := int(libc_chmod(&data[0], mode))
	if fail < 0 {
		err = getErrno()
	}
	return
}

func closedir(dir uintptr) (err error) {
	e := libc_closedir(unsafe.Pointer(dir))
	if e != 0 {
		err = getErrno()
	}
	return
}

func readdir_r(dir uintptr, entry *Dirent, result **Dirent) (err error) {
	e1 := libc_readdir_r(unsafe.Pointer(dir), unsafe.Pointer(entry), unsafe.Pointer(result))
	if e1 != 0 {
		err = getErrno()
	}
	return
}

func Getpagesize() int {
	return int(libc_getpagesize())
}

// The following RawSockAddr* types have been copied from the Go source tree and
// are here purely to fix build errors.

type RawSockaddr struct {
	Len    uint8
	Family uint8
	Data   [14]int8
}

type RawSockaddrInet4 struct {
	Len    uint8
	Family uint8
	Port   uint16
	Addr   [4]byte /* in_addr */
	Zero   [8]int8
}

type RawSockaddrInet6 struct {
	Len      uint8
	Family   uint8
	Port     uint16
	Flowinfo uint32
	Addr     [16]byte /* in6_addr */
	Scope_id uint32
}

// int pipe(int32 *fds);
//
//export pipe
func libc_pipe(fds *int32) int32

// int getpagesize();
//
//export getpagesize
func libc_getpagesize() int32

// int open(const char *pathname, int flags, mode_t mode);
//
//export syscall_libc_open
func libc_open(pathname *byte, flags int32, mode uint32) int32
