//go:build windows && go1.27

package tfo

import (
	"runtime"
	"sync/atomic"
	"syscall"
	"unsafe"
)

// pFD is a file descriptor. The net and os packages embed this type in
// a larger type representing a network connection or OS file.
//
// Stay in sync with FD in src/internal/poll/fd_windows.go
type pFD struct {
	// Lock sysfd and serialize access to Read and Write methods.
	fdmu fdMutex

	// System file descriptor. Immutable until Close.
	Sysfd syscall.Handle

	// I/O poller.
	pd pollDesc

	// The file offset for the next read or write.
	// Overlapped IO operations don't use the real file pointer,
	// so we need to keep track of the offset ourselves.
	offset int64

	// For console I/O.
	lastbits       []byte   // first few bytes of the last incomplete rune in last write
	readuint16     []uint16 // buffer to hold uint16s obtained with ReadConsole
	readbyte       []byte   // buffer to hold decoding of readuint16 from utf16 to utf8
	readbyteOffset int      // readbyte[readOffset:] is yet to be consumed with file.Read

	// Semaphore signaled when file is closed.
	csema uint32

	// Don't wait from completion port notifications for successful
	// operations that complete synchronously.
	waitOnSuccess bool

	// Whether this is a streaming descriptor, as opposed to a
	// packet-based descriptor like a UDP socket.
	IsStream bool

	// Whether a zero byte read indicates EOF. This is false for a
	// message based socket connection.
	ZeroReadIsEOF bool

	// Whether the handle is owned by os.File.
	isFile bool

	// The kind of this file.
	kind fileKind

	// Whether FILE_FLAG_OVERLAPPED was not set when opening the file.
	isBlocking bool

	// Whether the handle is currently associated with the IOCP.
	associated bool

	// readPinner and writePinner are automatically unpinned
	// before execIO returns.
	readPinner  runtime.Pinner
	writePinner runtime.Pinner
}

func (fd *netFD) init() error {
	if err := fd.pfd.Init(fd.net, true); err != nil {
		return err
	}
	switch fd.net {
	case "udp", "udp4", "udp6":
		// Disable reporting of PORT_UNREACHABLE errors.
		// See https://go.dev/issue/5834.
		ret := uint32(0)
		flag := uint32(0)
		size := uint32(unsafe.Sizeof(flag))
		err := syscall.WSAIoctl(fd.pfd.Sysfd, syscall.SIO_UDP_CONNRESET, (*byte)(unsafe.Pointer(&flag)), size, nil, 0, &ret, nil, 0)
		if err != nil {
			return wrapSyscallError("wsaioctl", err)
		}
		// Disable reporting of NET_UNREACHABLE errors.
		// See https://go.dev/issue/68614.
		ret = 0
		flag = 0
		size = uint32(unsafe.Sizeof(flag))
		const SIO_UDP_NETRESET = syscall.IOC_IN | syscall.IOC_VENDOR | 15
		err = syscall.WSAIoctl(fd.pfd.Sysfd, SIO_UDP_NETRESET, (*byte)(unsafe.Pointer(&flag)), size, nil, 0, &ret, nil, 0)
		if err != nil {
			return wrapSyscallError("wsaioctl", err)
		}
	}
	return nil
}

// closing returns true if fd is closing.
func (fd *pFD) closing() bool {
	return atomic.LoadUint64(&fd.fdmu.state)&mutexClosed != 0
}

func pinPtrsFromBuf(buf []byte) []any {
	if len(buf) == 0 {
		return nil
	}
	return []any{unsafe.SliceData(buf)}
}

func (fd *pFD) ConnectEx(ra syscall.Sockaddr, b []byte) (n int, err error) {
	return fd.execIO('w', func(o *operation) (qty uint32, err error) {
		err = syscall.ConnectEx(fd.Sysfd, ra, unsafe.SliceData(b), uint32(len(b)), &qty, &o.o)
		return
	}, pinPtrsFromBuf(b)...)
}
