package autorelay

import (
	

	ma 
	manet 
)

// This function cleans up a relay's address set to remove private addresses and curtail
// addrsplosion.
// TODO: Remove this, we don't need this. The current method tries to select the
// best address for the relay. Instead we should rely on the addresses provided by the
// relay in response to the reservation request.
func cleanupAddressSet( []ma.Multiaddr) []ma.Multiaddr {
	var ,  []ma.Multiaddr

	for ,  := range  {
		if isRelayAddr() {
			continue
		}

		if manet.IsPublicAddr() {
			 = append(, )
			continue
		}

		// discard unroutable addrs
		if manet.IsPrivateAddr() {
			 = append(, )
		}
	}

	if !hasAddrsplosion() {
		return 
	}

	return sanitizeAddrsplodedSet(, )
}

func isRelayAddr( ma.Multiaddr) bool {
	 := false

	ma.ForEach(, func( ma.Component) bool {
		switch .Protocol().Code {
		case ma.P_CIRCUIT:
			 = true
			return false
		default:
			return true
		}
	})

	return 
}

// we have addrsplosion if for some protocol we advertise multiple ports on
// the same base address.
func hasAddrsplosion( []ma.Multiaddr) bool {
	 := make(map[string]int)

	for ,  := range  {
		,  := addrKeyAndPort()
		,  := []
		if  &&  !=  {
			return true
		}
		[] = 
	}

	return false
}

func addrKeyAndPort( ma.Multiaddr) (string, int) {
	var (
		  string
		 int
	)

	ma.ForEach(, func( ma.Component) bool {
		switch .Protocol().Code {
		case ma.P_TCP, ma.P_UDP:
			 = int(binary.BigEndian.Uint16(.RawValue()))
			 += "/" + .Protocol().Name
		default:
			 := .Value()
			if  == "" {
				 = .Protocol().Name
			}
			 += "/" + 
		}
		return true
	})

	return , 
}

// clean up addrsplosion
// the following heuristic is used:
//   - for each base address/protocol combination, if there are multiple ports advertised then
//     only accept the default port if present.
//   - If the default port is not present, we check for non-standard ports by tracking
//     private port bindings if present.
//   - If there is no default or private port binding, then we can't infer the correct
//     port and give up and return all addrs (for that base address)
func sanitizeAddrsplodedSet(,  []ma.Multiaddr) []ma.Multiaddr {
	type  struct {
		 ma.Multiaddr
		 int
	}

	 := make(map[int]struct{})
	 := make(map[string][])

	for ,  := range  {
		,  := addrKeyAndPort()
		[] = struct{}{}
	}

	for ,  := range  {
		,  := addrKeyAndPort()
		[] = append([], {: , : })
	}

	var  []ma.Multiaddr
	for ,  := range  {
		if len() == 1 {
			// it's not addrsploded
			 = append(, [0].)
			continue
		}

		 := false
		for ,  := range  {
			if ,  := [.];  {
				// it matches a privately bound port, use it
				 = append(, .)
				 = true
				continue
			}

			if . == 4001 || . == 4002 {
				// it's a default port, use it
				 = append(, .)
				 = true
			}
		}

		if ! {
			// we weren't able to select a port; bite the bullet and use them all
			for ,  := range  {
				 = append(, .)
			}
		}
	}

	return 
}