package control

import (
	"encoding/binary"
	"os"
	"syscall"
	"unsafe"

	M "github.com/metacubex/sing/common/metadata"
)

func bindToInterface(conn syscall.RawConn, network string, address string, finder InterfaceFinder, interfaceName string, interfaceIndex int, preferInterfaceName bool) error {
	return Raw(conn, func(fd uintptr) error {
		if interfaceIndex == -1 {
			if finder == nil {
				return os.ErrInvalid
			}
			iif, err := finder.ByName(interfaceName)
			if err != nil {
				return err
			}
			interfaceIndex = iif.Index
			if err != nil {
				return err
			}
		}
		handle := syscall.Handle(fd)
		if M.ParseSocksaddr(address).AddrString() == "" {
			err := bind4(handle, interfaceIndex)
			if err != nil {
				return err
			}
			// try bind ipv6, if failed, ignore. it's a workaround for windows disable interface ipv6
			bind6(handle, interfaceIndex)
			return nil
		}
		switch network {
		case "tcp4", "udp4", "ip4":
			return bind4(handle, interfaceIndex)
		default:
			return bind6(handle, interfaceIndex)
		}
	})
}

const (
	IP_UNICAST_IF   = 31
	IPV6_UNICAST_IF = 31
)

func bind4(handle syscall.Handle, ifaceIdx int) error {
	var bytes [4]byte
	binary.BigEndian.PutUint32(bytes[:], uint32(ifaceIdx))
	idx := *(*uint32)(unsafe.Pointer(&bytes[0]))
	return syscall.SetsockoptInt(handle, syscall.IPPROTO_IP, IP_UNICAST_IF, int(idx))
}

func bind6(handle syscall.Handle, ifaceIdx int) error {
	return syscall.SetsockoptInt(handle, syscall.IPPROTO_IPV6, IPV6_UNICAST_IF, ifaceIdx)
}
