//go:build windows && go1.26

package tfo

import (
	"sync"
	"syscall"
	"unsafe"

	"golang.org/x/sys/windows"
)

// operation contains superset of data necessary to perform all async IO.
//
// Copied from src/internal/poll/fd_windows.go
type operation struct {
	// Used by IOCP interface, it must be first field
	// of the struct, as our code relies on it.
	o syscall.Overlapped

	// fields used by runtime.netpoll
	runtimeCtx uintptr
	mode       int32
}

var operationPool = sync.Pool{
	New: func() any {
		return new(operation)
	},
}

// waitIO waits for the IO operation o to complete.
func (fd *pFD) waitIO(o *operation) error {
	if fd.isBlocking {
		panic("can't wait on blocking operations")
	}
	if !fd.pollable() {
		// The overlapped handle is not added to the runtime poller,
		// the only way to wait for the IO to complete is block until
		// the overlapped event is signaled.
		_, err := syscall.WaitForSingleObject(o.o.HEvent, syscall.INFINITE)
		return err
	}
	// Wait for our request to complete.
	err := fd.pd.wait(int(o.mode), fd.isFile)
	switch err {
	case nil, ErrNetClosing, ErrFileClosing, ErrDeadlineExceeded:
		// No other error is expected.
	default:
		panic("unexpected runtime.netpoll error: " + err.Error())
	}
	return err
}

// cancelIO cancels the IO operation o and waits for it to complete.
func (fd *pFD) cancelIO(o *operation) {
	if !fd.pollable() {
		return
	}
	// Cancel our request.
	err := syscall.CancelIoEx(fd.Sysfd, &o.o)
	// Assuming ERROR_NOT_FOUND is returned, if IO is completed.
	if err != nil && err != syscall.ERROR_NOT_FOUND {
		// TODO(brainman): maybe do something else, but panic.
		panic(err)
	}
	fd.pd.waitCanceled(int(o.mode))
}

// pin pins ptr for the duration of the IO operation.
// If fd is in blocking mode, pin does nothing.
func (fd *pFD) pin(mode int, ptr any) {
	if fd.isBlocking {
		return
	}
	if mode == 'r' {
		fd.readPinner.Pin(ptr)
	} else {
		fd.writePinner.Pin(ptr)
	}
}

// execIO executes a single IO operation o.
// It supports both synchronous and asynchronous IO.
func (fd *pFD) execIO(mode int, submit func(o *operation) (uint32, error)) (int, error) {
	if mode == 'r' {
		defer fd.readPinner.Unpin()
	} else {
		defer fd.writePinner.Unpin()
	}
	// Notify runtime netpoll about starting IO.
	err := fd.pd.prepare(mode, fd.isFile)
	if err != nil {
		return 0, err
	}
	o := operationPool.Get().(*operation)
	defer operationPool.Put(o)
	*o = operation{
		o: syscall.Overlapped{
			OffsetHigh: uint32(fd.offset >> 32),
			Offset:     uint32(fd.offset),
		},
		runtimeCtx: fd.pd.runtimeCtx,
		mode:       int32(mode),
	}
	// Start IO.
	if !fd.isBlocking && !fd.pollable() {
		// If the handle is opened for overlapped IO but we can't
		// use the runtime poller, then we need to use an
		// event to wait for the IO to complete.
		h, err := windows.CreateEvent(nil, 0, 0, nil)
		if err != nil {
			// This shouldn't happen when all CreateEvent arguments are zero.
			panic(err)
		}
		// Set the low bit so that the external IOCP doesn't receive the completion packet.
		o.o.HEvent = syscall.Handle(h | 1)
		defer syscall.CloseHandle(syscall.Handle(h))
	}
	fd.pin(mode, o)
	qty, err := submit(o)
	var waitErr error
	// Blocking operations shouldn't return ERROR_IO_PENDING.
	// Continue without waiting if that happens.
	if !fd.isBlocking && (err == syscall.ERROR_IO_PENDING || (err == nil && !fd.skipSyncNotif)) {
		// IO started asynchronously or completed synchronously but
		// a sync notification is required. Wait for it to complete.
		waitErr = fd.waitIO(o)
		if waitErr != nil {
			// IO interrupted by "close" or "timeout".
			fd.cancelIO(o)
			// We issued a cancellation request, but the IO operation may still succeeded
			// before the cancellation request runs.
		}
		if fd.isFile {
			err = windows.GetOverlappedResult(windows.Handle(fd.Sysfd), (*windows.Overlapped)(unsafe.Pointer(&o.o)), &qty, false)
		} else {
			var flags uint32
			err = windows.WSAGetOverlappedResult(windows.Handle(fd.Sysfd), (*windows.Overlapped)(unsafe.Pointer(&o.o)), &qty, false, &flags)
		}
	}
	switch err {
	case syscall.ERROR_OPERATION_ABORTED:
		// ERROR_OPERATION_ABORTED may have been caused by us. In that case,
		// map it to our own error. Don't do more than that, each submitted
		// function may have its own meaning for each error.
		if waitErr != nil {
			// IO canceled by the poller while waiting for completion.
			err = waitErr
		} else if fd.kind == kindPipe && fd.closing() {
			// Close uses CancelIoEx to interrupt concurrent I/O for pipes.
			// If the fd is a pipe and the Write was interrupted by CancelIoEx,
			// we assume it is interrupted by Close.
			err = errClosing(fd.isFile)
		}
	case windows.ERROR_IO_INCOMPLETE:
		// waitIO couldn't wait for the IO to complete.
		if waitErr != nil {
			// The wait error will be more informative.
			err = waitErr
		}
	}
	return int(qty), err
}

// fileKind describes the kind of file.
type fileKind byte

const (
	kindNet fileKind = iota
	kindFile
	kindConsole
	kindPipe
	kindFileNet
)

// This package uses the SetFileCompletionNotificationModes Windows
// API to skip calling GetQueuedCompletionStatus if an IO operation
// completes synchronously. There is a known bug where
// SetFileCompletionNotificationModes crashes on some systems (see
// https://support.microsoft.com/kb/2568167 for details).

var socketCanUseSetFileCompletionNotificationModes bool // determines is SetFileCompletionNotificationModes is present and sockets can safely use it

// checkSetFileCompletionNotificationModes verifies that
// SetFileCompletionNotificationModes Windows API is present
// on the system and is safe to use.
// See https://support.microsoft.com/kb/2568167 for details.
func checkSetFileCompletionNotificationModes() {
	err := syscall.LoadSetFileCompletionNotificationModes()
	if err != nil {
		return
	}
	protos := [2]int32{syscall.IPPROTO_TCP, 0}
	var buf [32]syscall.WSAProtocolInfo
	len := uint32(unsafe.Sizeof(buf))
	n, err := syscall.WSAEnumProtocols(&protos[0], &buf[0], &len)
	if err != nil {
		return
	}
	for i := int32(0); i < n; i++ {
		if buf[i].ServiceFlags1&syscall.XP1_IFS_HANDLES == 0 {
			return
		}
	}
	socketCanUseSetFileCompletionNotificationModes = true
}

func init() {
	checkSetFileCompletionNotificationModes()
}

func (fd *pFD) Init(net string, pollable bool) error {
	switch net {
	case "file":
		fd.kind = kindFile
	case "console":
		fd.kind = kindConsole
	case "pipe":
		fd.kind = kindPipe
	case "file+net":
		fd.kind = kindFileNet
	default:
		// We don't actually care about the various network types.
		fd.kind = kindNet
	}
	fd.isFile = fd.kind != kindNet
	fd.isBlocking = !pollable

	if !pollable {
		return nil
	}

	// It is safe to add overlapped handles that also perform I/O
	// outside of the runtime poller. The runtime poller will ignore
	// I/O completion notifications not initiated by us.
	err := fd.pd.init(fd)
	if err != nil {
		return err
	}
	if fd.kind != kindNet || socketCanUseSetFileCompletionNotificationModes {
		// Non-socket handles can use SetFileCompletionNotificationModes without problems.
		err := syscall.SetFileCompletionNotificationModes(fd.Sysfd,
			syscall.FILE_SKIP_SET_EVENT_ON_HANDLE|syscall.FILE_SKIP_COMPLETION_PORT_ON_SUCCESS,
		)
		fd.skipSyncNotif = err == nil
	}
	return nil
}
