//go:build windows

package yu_sys

import (
	yu_strconv "gogs.qqck.cn/s/tools/strconv"
	"net"
	"net/netip"
	"syscall"
	"unsafe"
)

//go:cgo_import_dynamic WSAStartup WSAStartup "ws2_32.dll"
//go:linkname WSAStartup WSAStartup
var WSAStartup Address

//go:cgo_import_dynamic socket socket "ws2_32.dll"
//go:linkname Socket socket
var Socket Address

//go:cgo_import_dynamic ioctlsocket ioctlsocket "ws2_32.dll"
//go:linkname Ioctlsocket ioctlsocket
var Ioctlsocket Address

//go:cgo_import_dynamic connect connect "ws2_32.dll"
//go:linkname Connect connect
var Connect Address

//go:cgo_import_dynamic select select "ws2_32.dll"
//go:linkname Select select
var Select Address

//go:cgo_import_dynamic send send "ws2_32.dll"
//go:linkname Send send
var Send Address

//go:cgo_import_dynamic recv recv "ws2_32.dll"
//go:linkname Recv recv
var Recv Address

//go:cgo_import_dynamic closesocket closesocket "ws2_32.dll"
//go:linkname Closesocket closesocket
var Closesocket Address

func init() {
	var j_wsadata syscall.WSAData
	WSAStartup.Call(0x02_02, unsafe.Pointer(&j_wsadata)) // 调用方可以使用的最高版本的 Windows 套接字规范。 高序字节指定次要版本号;低序字节指定主版本号。
}

const (
	AF_UNSPEC    = 0  // 地址系列未指定。
	AF_INET      = 2  // Internet 协议版本 4 (IPv4) 地址系列。
	AF_IPX       = 6  // IPX/SPX 地址系列。 仅当安装了 NWLink IPX/SPX NetBIOS 兼容传输协议时，才支持此地址系列。Windows Vista 及更高版本不支持此地址系列。
	AF_APPLETALK = 16 // AppleTalk 地址系列。 仅当安装了 AppleTalk 协议时，才支持此地址系列。Windows Vista 及更高版本不支持此地址系列。
	AF_NETBIOS   = 17 // NetBIOS 地址系列。 仅当安装了适用于 NetBIOS 的 Windows 套接字提供程序时，才支持此地址系列。32 位版本的 Windows 支持 NetBIOS 的 Windows 套接字提供程序。 默认情况下，此提供程序安装在 32 位版本的 Windows 上。64 位版本的 Windows 不支持 NetBIOS 的 Windows 套接字提供程序，包括 Windows 7、Windows Server 2008、Windows Vista、Windows Server 2003 或 Windows XP。适用于 NetBIOS 的 Windows 套接字提供程序仅支持 类型 参数设置为 SOCK_DGRAM的套接字。适用于 NetBIOS 的 Windows 套接字提供程序与 NetBIOS 编程接口不直接相关。 Windows Vista、Windows Server 2008 及更高版本不支持 NetBIOS 编程接口。
	AF_INET6     = 23 // Internet 协议版本 6 (IPv6) 地址系列。
	AF_IRDA      = 26 // IrDA (IrDA) 地址系列。仅当计算机安装了红外端口和驱动程序时，才支持此地址系列。
	AF_BTH       = 32 // 蓝牙地址系列。如果计算机安装了蓝牙适配器和驱动程序，则 SP2 或更高版本的 Windows XP 支持此地址系列。
)

const (
	SOCK_STREAM    = 1 // 一种套接字类型，它通过 OOB 数据传输机制提供排序的、可靠的双向、基于连接的字节流。 此套接字类型对 Internet 地址系列 (AF_INET 或 AF_INET6) 使用传输控制协议 (TCP) 。
	SOCK_DGRAM     = 2 // 支持数据报的套接字类型，这些数据报是固定 (通常较小) 最大长度的无连接、不可靠的缓冲区。 此套接字类型对 Internet 地址系列 (AF_INET 或 AF_INET6) 使用用户数据报协议 (UDP) 。
	SOCK_RAW       = 3 // 一种套接字类型，它提供允许应用程序操作下一层协议标头的原始套接字。 若要操作 IPv4 标头，必须在套接字上设置 IP_HDRINCL 套接字选项。 若要操作 IPv6 标头，必须在套接字上设置 IPV6_HDRINCL 套接字选项。
	SOCK_RDM       = 4 // 提供可靠消息数据报的套接字类型。 此类型的一个示例是 Windows 中的实用常规多播 (PGM) 多播协议实现，通常称为 可靠多播编程。 仅当安装了可靠多播协议时，才支持此 类型 值。
	SOCK_SEQPACKET = 5 // 提供基于数据报的伪流数据包的套接字类型。
)

const (
	IPPROTO_ICMP    = 1   // Internet 控制消息协议 (ICMP) 。 当 af 参数 AF_UNSPEC、 AF_INET或 AF_INET6 且 类型 参数 SOCK_RAW 或未指定时，此值可能为。 Windows XP 及更高版本支持此 协议 值。
	IPPROTO_IGMP    = 2   // Internet 组管理协议 (IGMP) 。 当 af 参数 AF_UNSPEC、 AF_INET或 AF_INET6 且 类型 参数 SOCK_RAW 或未指定时，此值可能为 。Windows XP 及更高版本支持此 协议 值。
	BTHPROTO_RFCOMM = 3   // 蓝牙射频通信 (蓝牙 RFCOMM) 协议。 当 af 参数 AF_BTH 且类型参数 SOCK_STREAM 时，这是一个可能的值。	具有 SP2 或更高版本的 Windows XP 支持此 协议 值。
	IPPROTO_TCP     = 6   // 传输控制协议 (TCP) 。 当 af 参数 AF_INET 或 AF_INET6 且类型参数 SOCK_STREAM 时，这是一个可能的值。
	IPPROTO_UDP     = 17  // 用户数据报协议 (UDP) 。 当 af 参数 AF_INET 或 AF_INET6 且类型参数 SOCK_DGRAM 时，这是一个可能的值。
	IPPROTO_ICMPV6  = 58  // Internet 控制消息协议版本 6 (ICMPv6) 。 当 af 参数 AF_UNSPEC、 AF_INET 或 AF_INET6 且 类型 参数 SOCK_RAW 或未指定时，此值可能为 。Windows XP 及更高版本支持此 协议 值。
	IPPROTO_RM      = 113 // 可靠多播的 PGM 协议。 当 af 参数 AF_INET 且类型参数 SOCK_RDM 时，这是一个可能的值。 在 Windows Vista 及更高版本发布的Windows SDK中，此协议也称为IPPROTO_PGM。仅当安装了可靠多播 协议 时，才支持此协议值。
)

const INVALID_SOCKET = 0

const FIONBIO = 2147772030 // 在 套接字上启用或禁用非阻止模式。 lpvInBuffer 参数指向一个无符号长 (QoS) ，如果要启用非阻止模式，则为非零;如果要禁用该模式，则为零。 创建套接字时，它将在阻止模式下运行 (即) 禁用非阻止模式。 这与 BSD 套接字一致。WSAAsyncSelect 或 WSAEventSelect 例程自动将套接字设置为非阻止模式。 如果已在套接字上发出 WSAAsyncSelect 或 WSAEventSelect ，则使用 WSAIoctl 将套接字重新设置为阻止模式的任何尝试都将失败，WSAEINVAL 会失败。 若要将套接字重新设置为阻止模式，应用程序必须首先通过调用 lEvent 参数等于零的 WSAAsyncSelect 来禁用 WSAAsyncSelect，或者通过调用 WSAEventSelect（lNetworkEvents 参数等于零）来禁用 WSAEventSelect。

const SOCKET_ERROR = -1

type Sockaddr interface {
	Sockaddr() (ptr unsafe.Pointer, len uintptr)
}

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

func (sa *Sockaddr_in) Sockaddr() (unsafe.Pointer, uintptr) {
	return unsafe.Pointer(sa), unsafe.Sizeof(*sa)
}

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

func (sa *Sockaddr_in6) Sockaddr() (unsafe.Pointer, uintptr) {
	return unsafe.Pointer(sa), unsafe.Sizeof(*sa)
}

type FDSET struct {
	Count uintptr
	Fds   uintptr // []uintptr
}

type TIMEVAL struct {
	TvSec  int32
	TvUsec int32
}

func Socket_connect(host string, port int, sec, usec int32) uintptr {
	var j_addr netip.Addr
	var j_err error
	if j_addr, j_err = netip.ParseAddr(host); j_err != nil {
		j_addrs, j_err := net.LookupHost(host)
		if j_err != nil || len(j_addrs) == 0 {
			return INVALID_SOCKET
		}
		if j_addr, j_err = netip.ParseAddr(j_addrs[0]); j_err != nil {
			return INVALID_SOCKET
		}
	}
	j_fd := uintptr(INVALID_SOCKET)
	if j_addr.Is4() {
		if j_fd = Socket.CallUintptr(AF_INET, SOCK_STREAM, IPPROTO_TCP); j_fd == INVALID_SOCKET {
			return INVALID_SOCKET
		}
	} else if j_addr.Is6() {
		if j_fd = Socket.CallUintptr(AF_INET6, SOCK_STREAM, IPPROTO_TCP); j_fd == INVALID_SOCKET {
			return INVALID_SOCKET
		}
	} else {
		return INVALID_SOCKET
	}
	var j_sockaddr Sockaddr
	if j_addr.Is4() {
		j_sockaddr = &Sockaddr_in{
			Family: AF_INET,
			Port:   uint16(port)>>8 | uint16(port)<<8,
			Addr:   j_addr.As4(),
		}
	} else if j_addr.Is6() {
		j_sockaddr = &Sockaddr_in6{
			Family:   AF_INET6,
			Port:     uint16(port)>>8 | uint16(port)<<8,
			Addr:     j_addr.As16(),
			Scope_id: yu_strconv.ParseUint32(j_addr.Zone()),
		}
	}
	j_argp := 1
	if Ioctlsocket.CallInt32(j_fd, FIONBIO, &j_argp) == SOCKET_ERROR {
		Closesocket.Call(j_fd)
		return INVALID_SOCKET
	}
	j_sec, j_usec := sec, usec
	const j_timeout = 1000 * 100
_connect:
	j_sockaddr_ptr, j_sockaddr_len := j_sockaddr.Sockaddr()
	if j_ok := Connect.CallInt32(j_fd, j_sockaddr_ptr, j_sockaddr_len); j_ok != SOCKET_ERROR && j_ok != 0 {
		Closesocket.Call(j_fd)
		return INVALID_SOCKET
	}
	if !Socket_is_write(j_fd, 0, j_timeout) {
		if j_usec > 0 {
			j_usec -= j_timeout
			goto _connect
		} else if j_sec > 0 {
			j_sec--
			j_usec = 1000 * 1000
			j_usec -= j_timeout
			goto _connect
		}
		Closesocket.Call(j_fd)
		return INVALID_SOCKET
	}
	j_argp = 0
	if Ioctlsocket.CallInt32(j_fd, FIONBIO, &j_argp) == SOCKET_ERROR {
		Closesocket.Call(j_fd)
		return INVALID_SOCKET
	}
	return j_fd
}

func Socket_is_write(fd uintptr, sec, usec int32) bool {
	switch Select.CallInt32(0, 0, unsafe.Pointer(&FDSET{1, fd}), 0, unsafe.Pointer(&TIMEVAL{sec, usec})) {
	case -1:
		return false // ErrClose
	case 0:
		return false // ErrTimeout
	case 1:
		return true
	default:
		return false // ErrNot
	}
}

func Socket_is_read(fd uintptr, sec, usec int32) bool {
	switch Select.CallInt32(0, unsafe.Pointer(&FDSET{1, fd}), 0, 0, unsafe.Pointer(&TIMEVAL{sec, usec})) {
	case -1:
		return false // ErrClose
	case 0:
		return false // ErrTimeout
	case 1:
		return true
	default:
		return false // ErrNot
	}
}

const _send_size_max = 1024 * 1024 * 32 // 32 MB

func Socket_write_tcp(fd uintptr, buf []byte, sec, usec int32) bool {
	if fd == INVALID_SOCKET {
		return false
	}
	if len(buf) == 0 {
		return true
	}
	j_buf_ptr, j_buf_len, j_move := uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf)), uintptr(0)
	for j_buf_len > 0 {
		if !Socket_is_write(fd, sec, usec) {
			return false
		}
		if j_buf_len > _send_size_max {
			j_move, _, _ = Send.Call(fd, j_buf_ptr, _send_size_max, 0)
		} else {
			j_move, _, _ = Send.Call(fd, j_buf_ptr, j_buf_len, 0)
		}
		if int32(j_move) < 0 {
			return false
		}
		j_buf_ptr += j_move
		j_buf_len -= j_move
	}
	return true
}

func Socket_read_tcp(fd uintptr, size int, sec, usec int32) []byte {
	if fd == INVALID_SOCKET {
		return nil
	}
	if size < 1 {
		return nil
	}
	if !Socket_is_read(fd, sec, usec) {
		return nil
	}
	j_buf := make([]byte, size)
	j_size, _, _ := Recv.Call(fd, j_buf, len(j_buf), 0)
	if int32(j_size) < 1 {
		return nil
	}
	j_buf = j_buf[:j_size]
	return j_buf
}
