// package sockaddrnet provides conversions between net.Addr and Sockaddr
package sockaddrnet

import (
	"net"
)

// NetAddrAF returns the unix AF_* type for a given net.Addr
// returns AF_UNSPEC if unknown
func NetAddrAF(addr net.Addr) int {
	switch addr := addr.(type) {
	case *net.IPAddr:
		return IPAF(addr.IP)

	case *net.TCPAddr:
		return IPAF(addr.IP)

	case *net.UDPAddr:
		return IPAF(addr.IP)

	case *net.UnixAddr:
		return AF_UNIX

	default:
		return AF_UNSPEC
	}
}

// IPAF returns the unix AF_* type for a given IP address
// returns AF_UNSPEC if unknown
func IPAF(ip net.IP) int {
	switch {
	case ip.To4() != nil:
		return AF_INET

	case ip.To16() != nil:
		return AF_INET6

	default:
		return AF_UNSPEC
	}
}

// NetAddrIPPROTO returns the unix IPPROTO_* type for a given net.Addr
// returns -1 if protocol unknown
func NetAddrIPPROTO(addr net.Addr) int {
	switch addr := addr.(type) {
	case *net.IPAddr:
		switch {
		default:
			return IPPROTO_IP

		case addr.IP.To4() != nil:
			return IPPROTO_IPV4

		case addr.IP.To16() != nil:
			return IPPROTO_IPV6
		}

	case *net.TCPAddr:
		return IPPROTO_TCP

	case *net.UDPAddr:
		return IPPROTO_UDP

	default:
		return -1
	}
}

// NetAddrSOCK returns the unix SOCK_* type for a given net.Addr
// returns 0 if type unknown
func NetAddrSOCK(addr net.Addr) int {
	switch addr := addr.(type) {
	case *net.IPAddr:
		return SOCK_DGRAM
	case *net.TCPAddr:
		return SOCK_STREAM
	case *net.UDPAddr:
		return SOCK_DGRAM
	case *net.UnixAddr:
		switch addr.Net {
		default:
			return 0
		case "unix":
			return SOCK_STREAM
		case "unixgram":
			return SOCK_DGRAM
		case "unixpacket":
			return SOCK_SEQPACKET
		}
	default:
		return 0
	}
}

// NetAddrToSockaddr converts a net.Addr to a Sockaddr.
// Returns nil if the input is invalid or conversion is not possible.
func NetAddrToSockaddr(addr net.Addr) Sockaddr {
	switch addr := addr.(type) {
	case *net.IPAddr:
		return IPAddrToSockaddr(addr)
	case *net.TCPAddr:
		return TCPAddrToSockaddr(addr)
	case *net.UDPAddr:
		return UDPAddrToSockaddr(addr)
	case *net.UnixAddr:
		sa, _ := UnixAddrToSockaddr(addr)
		return sa
	default:
		return nil
	}
}

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

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

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

	return nil
}

// IPAddrToSockaddr converts a net.IPAddr to a Sockaddr.
// Returns nil if conversion fails.
func IPAddrToSockaddr(addr *net.IPAddr) Sockaddr {
	return IPAndZoneToSockaddr(addr.IP, addr.Zone)
}

// TCPAddrToSockaddr converts a net.TCPAddr to a Sockaddr.
// Returns nil if conversion fails.
func TCPAddrToSockaddr(addr *net.TCPAddr) Sockaddr {
	sa := IPAndZoneToSockaddr(addr.IP, addr.Zone)
	switch sa := sa.(type) {
	case *SockaddrInet4:
		sa.Port = addr.Port
		return sa
	case *SockaddrInet6:
		sa.Port = addr.Port
		return sa
	default:
		return nil
	}
}

// UDPAddrToSockaddr converts a net.UDPAddr to a Sockaddr.
// Returns nil if conversion fails.
func UDPAddrToSockaddr(addr *net.UDPAddr) Sockaddr {
	sa := IPAndZoneToSockaddr(addr.IP, addr.Zone)
	switch sa := sa.(type) {
	case *SockaddrInet4:
		sa.Port = addr.Port
		return sa
	case *SockaddrInet6:
		sa.Port = addr.Port
		return sa
	default:
		return nil
	}
}

// UnixAddrToSockaddr converts a net.UnixAddr to a Sockaddr, and returns
// the type (unix.SOCK_STREAM, unix.SOCK_DGRAM, unix.SOCK_SEQPACKET)
// Returns (nil, 0) if conversion fails.
func UnixAddrToSockaddr(addr *net.UnixAddr) (Sockaddr, int) {
	t := 0
	switch addr.Net {
	case "unix":
		t = SOCK_STREAM
	case "unixgram":
		t = SOCK_DGRAM
	case "unixpacket":
		t = SOCK_SEQPACKET
	default:
		return nil, 0
	}
	return &SockaddrUnix{Name: addr.Name}, t
}

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

	case *SockaddrInet6:
		ip := make([]byte, 16)
		copy(ip, sa.Addr[:])
		return ip, IP6ZoneToString(int(sa.ZoneId))
	}
	return nil, ""
}

// SockaddrToIPAddr converts a Sockaddr to a net.IPAddr
// Returns nil if conversion fails.
func SockaddrToIPAddr(sa Sockaddr) *net.IPAddr {
	ip, zone := SockaddrToIPAndZone(sa)
	switch sa.(type) {
	case *SockaddrInet4:
		return &net.IPAddr{IP: ip}
	case *SockaddrInet6:
		return &net.IPAddr{IP: ip, Zone: zone}
	}
	return nil
}

// SockaddrToTCPAddr converts a Sockaddr to a net.TCPAddr
// Returns nil if conversion fails.
func SockaddrToTCPAddr(sa Sockaddr) *net.TCPAddr {
	ip, zone := SockaddrToIPAndZone(sa)
	switch sa := sa.(type) {
	case *SockaddrInet4:
		return &net.TCPAddr{IP: ip, Port: sa.Port}
	case *SockaddrInet6:
		return &net.TCPAddr{IP: ip, Port: sa.Port, Zone: zone}
	}
	return nil
}

// SockaddrToUDPAddr converts a Sockaddr to a net.UDPAddr
// Returns nil if conversion fails.
func SockaddrToUDPAddr(sa Sockaddr) *net.UDPAddr {
	ip, zone := SockaddrToIPAndZone(sa)
	switch sa := sa.(type) {
	case *SockaddrInet4:
		return &net.UDPAddr{IP: ip, Port: sa.Port}
	case *SockaddrInet6:
		return &net.UDPAddr{IP: ip, Port: sa.Port, Zone: zone}
	}
	return nil
}

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

// SockaddrToUnixAddr converts a Sockaddr to a net.UnixAddr
// Returns nil if conversion fails.
func SockaddrToUnixAddr(sa Sockaddr) *net.UnixAddr {
	if s, ok := sa.(*SockaddrUnix); ok {
		return &net.UnixAddr{Name: s.Name, Net: "unix"}
	}
	return nil
}

// SockaddrToUnixgramAddr converts a Sockaddr to a net.UnixAddr
// Returns nil if conversion fails.
func SockaddrToUnixgramAddr(sa Sockaddr) *net.UnixAddr {
	if s, ok := sa.(*SockaddrUnix); ok {
		return &net.UnixAddr{Name: s.Name, Net: "unixgram"}
	}
	return nil
}

// SockaddrToUnixpacketAddr converts a Sockaddr to a net.UnixAddr
// Returns nil if conversion fails.
func SockaddrToUnixpacketAddr(sa Sockaddr) *net.UnixAddr {
	if s, ok := sa.(*SockaddrUnix); ok {
		return &net.UnixAddr{Name: s.Name, Net: "unixpacket"}
	}
	return nil
}

// 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 itod(uint(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, _, _ := dtoi(zone, 0)
	return n
}

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

// Convert i to decimal string.
func itod(i uint) string {
	if i == 0 {
		return "0"
	}

	// Assemble decimal in reverse order.
	var b [32]byte
	bp := len(b)
	for ; i > 0; i /= 10 {
		bp--
		b[bp] = byte(i%10) + '0'
	}

	return string(b[bp:])
}

// Bigger than we need, not too big to worry about overflow
const big = 0xFFFFFF

// Decimal to integer starting at &s[i0].
// Returns number, new offset, success.
func dtoi(s string, i0 int) (n int, i int, ok bool) {
	n = 0
	for i = i0; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ {
		n = n*10 + int(s[i]-'0')
		if n >= big {
			return 0, i, false
		}
	}
	if i == i0 {
		return 0, i, false
	}
	return n, i, true
}
