package socket

import (
	
	
	
	
)

// maxDatagramSize bounds a single read from the UDP socket. QUIC packets never
// exceed this; larger reads would be truncated by quic-go anyway.
const maxDatagramSize = 1452 + 512 // generous: max QUIC packet plus headroom

var ipRecvPool = make(chan []byte, 1024)

// IpTransport is the direct-UDP transport: it reads datagrams from a
// net.PacketConn and forwards them to the [MagicConn]'s recv channel, and sends
// datagrams the magic socket routes to it. It is the Go analog of the Rust
// IpTransport (iroh/src/socket/transports/ip.rs).
//
// Create one with [NewIpTransport] and start its recv loop with [IpTransport.Serve].
type IpTransport struct {
	conn   *net.UDPConn
	recvCh chan<- recvBatch
}

// NewIpTransport returns an IpTransport over conn that delivers received
// datagrams to recvCh. The transport does not take ownership of conn; the caller
// closes it.
func ( *net.UDPConn,  chan<- recvBatch) *IpTransport {
	return &IpTransport{conn: , recvCh: }
}

// LocalAddr returns the bound local address of the underlying socket.
func ( *IpTransport) () net.Addr { return .conn.LocalAddr() }

// Serve runs the receive loop until ctx is cancelled or the socket is closed.
// Each datagram is delivered to the recv channel tagged with its real remote IP
// address (canonicalized: an IPv4-mapped IPv6 source becomes plain IPv4, to
// match iroh/src/socket/transports/ip.rs:221 to_canonical). Empty datagrams and
// transient errors are skipped; a closed socket ends the loop cleanly.
func ( *IpTransport) ( context.Context) {
	for {
		if .Err() != nil {
			return
		}
		 := getIPRecvBuffer()
		, ,  := .conn.ReadFromUDPAddrPort()
		if  != nil {
			putIPRecvBuffer()
			if errors.Is(, net.ErrClosed) || .Err() != nil {
				return
			}
			// Transient read error (e.g. ICMP-driven recv error on some
			// platforms): keep serving.
			continue
		}
		if  == 0 {
			putIPRecvBuffer()
			// Timeout or platform quirk; nothing to deliver.
			continue
		}
		recordUDPReceive(1, false)
		// The transport address is internal to iroh and is always the canonical
		// (unmapped) form. iroh/src/socket/transports/ip.rs:219.
		 := canonicalAddrPort()
		 := recvBatch{data: [:], ip: , releaseIP: true}
		if !.enqueue(, ) {
			return
		}
	}
}

func ( *IpTransport) ( context.Context,  recvBatch) bool {
	select {
	case .recvCh <- :
		return true
	default:
	}
	select {
	case .recvCh <- :
		return true
	case <-.Done():
		.release()
		return false
	}
}

func getIPRecvBuffer() []byte {
	select {
	case  := <-ipRecvPool:
		return 
	default:
		return make([]byte, maxDatagramSize)
	}
}

func putIPRecvBuffer( []byte) {
	if cap() != maxDatagramSize {
		return
	}
	select {
	case ipRecvPool <- [:maxDatagramSize]:
	default:
	}
}

// send writes p to the IP destination dst. The destination is canonicalized so
// an IPv4-mapped IPv6 address is sent as plain IPv4, matching
// iroh/src/socket/transports/ip.rs:310 canonical_addr. It reports the number of
// bytes written.
func ( *IpTransport) ( []byte,  netip.AddrPort) (int, error) {
	 = canonicalAddrPort()
	,  := .conn.WriteToUDPAddrPort(, )
	return , 
}

func canonicalAddrPort( netip.AddrPort) netip.AddrPort {
	 := .Addr()
	if !.Is4In6() {
		return 
	}
	return netip.AddrPortFrom(.Unmap(), .Port())
}

func udpAddrFromAddrPort( netip.AddrPort) *net.UDPAddr {
	return net.UDPAddrFromAddrPort(canonicalAddrPort())
}

func addrPortFromUDPAddr( *net.UDPAddr) netip.AddrPort {
	return canonicalAddrPort(.AddrPort())
}

// addrPort extracts a netip.AddrPort from a net.Addr, handling the *net.UDPAddr
// that net.PacketConn.ReadFrom returns as well as anything already carrying an
// AddrPort.
func addrPort( net.Addr) (netip.AddrPort, bool) {
	switch v := .(type) {
	case *net.UDPAddr:
		return addrPortFromUDPAddr(), true
	case interface{ () netip.AddrPort }:
		return canonicalAddrPort(.()), true
	default:
		,  := netip.ParseAddrPort(.String())
		if  != nil {
			return netip.AddrPort{}, false
		}
		return canonicalAddrPort(), true
	}
}