package autonat

import (
	"net"

	"github.com/libp2p/go-libp2p/core/host"

	ma "github.com/multiformats/go-multiaddr"
	manet "github.com/multiformats/go-multiaddr/net"
)

type dialPolicy struct {
	allowSelfDials bool
	host           host.Host
}

// skipDial indicates that a multiaddress isn't worth attempted dialing.
// The same logic is used when the autonat client is considering if
// a remote peer is worth using as a server, and when the server is
// considering if a requested client is worth dialing back.
func (d *dialPolicy) skipDial(addr ma.Multiaddr) bool {
	// skip relay addresses
	_, err := addr.ValueForProtocol(ma.P_CIRCUIT)
	if err == nil {
		return true
	}

	if d.allowSelfDials {
		return false
	}

	// skip private network (unroutable) addresses
	if !manet.IsPublicAddr(addr) {
		return true
	}
	candidateIP, err := manet.ToIP(addr)
	if err != nil {
		return true
	}

	// Skip dialing addresses we believe are the local node's
	for _, localAddr := range d.host.Addrs() {
		localIP, err := manet.ToIP(localAddr)
		if err != nil {
			continue
		}
		if localIP.Equal(candidateIP) {
			return true
		}
	}

	return false
}

// skipPeer indicates that the collection of multiaddresses representing a peer
// isn't worth attempted dialing. If one of the addresses matches an address
// we believe is ours, we exclude the peer, even if there are other valid
// public addresses in the list.
func (d *dialPolicy) skipPeer(addrs []ma.Multiaddr) bool {
	localAddrs := d.host.Addrs()
	localHosts := make([]net.IP, 0)
	for _, lAddr := range localAddrs {
		if _, err := lAddr.ValueForProtocol(ma.P_CIRCUIT); err != nil && manet.IsPublicAddr(lAddr) {
			lIP, err := manet.ToIP(lAddr)
			if err != nil {
				continue
			}
			localHosts = append(localHosts, lIP)
		}
	}

	// if a public IP of the peer is one of ours: skip the peer.
	goodPublic := false
	for _, addr := range addrs {
		if _, err := addr.ValueForProtocol(ma.P_CIRCUIT); err != nil && manet.IsPublicAddr(addr) {
			aIP, err := manet.ToIP(addr)
			if err != nil {
				continue
			}

			for _, lIP := range localHosts {
				if lIP.Equal(aIP) {
					return true
				}
			}
			goodPublic = true
		}
	}

	if d.allowSelfDials {
		return false
	}

	return !goodPublic
}