//go:build windows

package netroute

import (
	"net"
	"strconv"
	"syscall"
	"unsafe"

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

// socklen is a type for the length of a sockaddr.
type socklen uint

// ipAndZoneToSockaddr converts a net.IP (with optional IPv6 Zone) to a Sockaddr
// Returns nil if conversion fails.
func ipAndZoneToSockaddr(ip net.IP, zone string) windows.Sockaddr {
	// Unspecified?
	if ip == nil {
		if zone != "" {
			return &windows.SockaddrInet6{ZoneId: uint32(ip6ZoneToInt(zone))}
		}
		return new(windows.SockaddrInet4)
	}

	// Valid IPv4?
	if ip4 := ip.To4(); ip4 != nil && zone == "" {
		var buf [4]byte
		copy(buf[:], ip4) // last 4 bytes
		return &windows.SockaddrInet4{Addr: buf}
	}

	// Valid IPv6 address?
	if ip6 := ip.To16(); ip6 != nil {
		var buf [16]byte
		copy(buf[:], ip6)
		return &windows.SockaddrInet6{Addr: buf, ZoneId: uint32(ip6ZoneToInt(zone))}
	}

	return nil
}

// sockaddrToIPAndZone converts a Sockaddr to a net.IP (with optional IPv6 Zone)
// Returns nil if conversion fails.
func sockaddrToIPAndZone(sa windows.Sockaddr) (net.IP, string) {
	switch sa := sa.(type) {
	case *windows.SockaddrInet4:
		ip := make([]byte, 16)
		// V4InV6Prefix
		ip[10] = 0xff
		ip[11] = 0xff
		copy(ip[12:16], sa.Addr[:])
		return ip, ""
	case *windows.SockaddrInet6:
		ip := make([]byte, 16)
		copy(ip, sa.Addr[:])
		return ip, ip6ZoneToString(int(sa.ZoneId))
	}
	return nil, ""
}

func sockaddrToAny(sa windows.Sockaddr) (*windows.RawSockaddrAny, socklen, error) {
	if sa == nil {
		return nil, 0, syscall.EINVAL
	}

	switch sa := sa.(type) {
	case *windows.SockaddrInet4:
		if sa.Port < 0 || sa.Port > 0xFFFF {
			return nil, 0, syscall.EINVAL
		}
		raw := new(windows.RawSockaddrAny)
		raw.Addr.Family = windows.AF_INET
		raw4 := (*windows.RawSockaddrInet4)(unsafe.Pointer(raw))
		p := (*[2]byte)(unsafe.Pointer(&raw4.Port))
		p[0] = byte(sa.Port >> 8)
		p[1] = byte(sa.Port)
		for i := 0; i < len(sa.Addr); i++ {
			raw4.Addr[i] = sa.Addr[i]
		}
		return raw, socklen(unsafe.Sizeof(*raw4)), nil
	case *windows.SockaddrInet6:
		if sa.Port < 0 || sa.Port > 0xFFFF {
			return nil, 0, syscall.EINVAL
		}
		raw := new(windows.RawSockaddrAny)
		raw.Addr.Family = windows.AF_INET6
		raw6 := (*windows.RawSockaddrInet6)(unsafe.Pointer(raw))
		p := (*[2]byte)(unsafe.Pointer(&raw6.Port))
		p[0] = byte(sa.Port >> 8)
		p[1] = byte(sa.Port)
		raw6.Scope_id = sa.ZoneId
		for i := 0; i < len(sa.Addr); i++ {
			raw6.Addr[i] = sa.Addr[i]
		}
		return raw, socklen(unsafe.Sizeof(*raw6)), nil
	case *windows.SockaddrUnix:
		return nil, 0, syscall.EWINDOWS
	}
	return nil, 0, syscall.EAFNOSUPPORT
}

// from: go/src/pkg/net/ipsock.go

// ip6ZoneToString converts an IP6 Zone unix int to a net string
// returns "" if zone is 0
func ip6ZoneToString(zone int) string {
	if zone == 0 {
		return ""
	}
	if ifi, err := net.InterfaceByIndex(zone); err == nil {
		return ifi.Name
	}
	return strconv.Itoa(zone)
}

// ip6ZoneToInt converts an IP6 Zone net string to a unix int
// returns 0 if zone is ""
func ip6ZoneToInt(zone string) int {
	if zone == "" {
		return 0
	}
	if ifi, err := net.InterfaceByName(zone); err == nil {
		return ifi.Index
	}
	n, _ := strconv.ParseInt(zone, 10, 32)
	return int(n)
}
