package aghnet

import (
	"net"
)

// SubnetDetector describes IP address properties.
type SubnetDetector struct {
	// spNets is the slice of special-purpose address registries as defined
	// by RFC-6890 (https://tools.ietf.org/html/rfc6890).
	spNets []*net.IPNet

	// locServedNets is the slice of locally-served networks as defined by
	// RFC-6303 (https://tools.ietf.org/html/rfc6303).
	locServedNets []*net.IPNet
}

// NewSubnetDetector returns a new IP detector.
func NewSubnetDetector() (snd *SubnetDetector, err error) {
	spNets := []string{
		// "This" network.
		"0.0.0.0/8",
		// Private-Use Networks.
		"10.0.0.0/8",
		// Shared Address Space.
		"100.64.0.0/10",
		// Loopback.
		"127.0.0.0/8",
		// Link Local.
		"169.254.0.0/16",
		// Private-Use Networks.
		"172.16.0.0/12",
		// IETF Protocol Assignments.
		"192.0.0.0/24",
		// DS-Lite.
		"192.0.0.0/29",
		// TEST-NET-1
		"192.0.2.0/24",
		// 6to4 Relay Anycast.
		"192.88.99.0/24",
		// Private-Use Networks.
		"192.168.0.0/16",
		// Network Interconnect Device Benchmark Testing.
		"198.18.0.0/15",
		// TEST-NET-2.
		"198.51.100.0/24",
		// TEST-NET-3.
		"203.0.113.0/24",
		// Reserved for Future Use.
		"240.0.0.0/4",
		// Limited Broadcast.
		"255.255.255.255/32",

		// Loopback.
		"::1/128",
		// Unspecified.
		"::/128",
		// IPv4-IPv6 Translation Address.
		"64:ff9b::/96",

		// IPv4-Mapped Address.  Since this network is used for mapping
		// IPv4 addresses, we don't include it.
		// "::ffff:0:0/96",

		// Discard-Only Prefix.
		"100::/64",
		// IETF Protocol Assignments.
		"2001::/23",
		// TEREDO.
		"2001::/32",
		// Benchmarking.
		"2001:2::/48",
		// Documentation.
		"2001:db8::/32",
		// ORCHID.
		"2001:10::/28",
		// 6to4.
		"2002::/16",
		// Unique-Local.
		"fc00::/7",
		// Linked-Scoped Unicast.
		"fe80::/10",
	}

	// TODO(e.burkov): It's a subslice of the slice above.  Should be done
	// smarter.
	locServedNets := []string{
		// IPv4.
		"10.0.0.0/8",
		"172.16.0.0/12",
		"192.168.0.0/16",
		"127.0.0.0/8",
		"169.254.0.0/16",
		"192.0.2.0/24",
		"198.51.100.0/24",
		"203.0.113.0/24",
		"255.255.255.255/32",
		// IPv6.
		"::/128",
		"::1/128",
		"fe80::/10",
		"2001:db8::/32",
		"fd00::/8",
	}

	snd = &SubnetDetector{
		spNets:        make([]*net.IPNet, len(spNets)),
		locServedNets: make([]*net.IPNet, len(locServedNets)),
	}
	for i, ipnetStr := range spNets {
		var ipnet *net.IPNet
		_, ipnet, err = net.ParseCIDR(ipnetStr)
		if err != nil {
			return nil, err
		}

		snd.spNets[i] = ipnet
	}
	for i, ipnetStr := range locServedNets {
		var ipnet *net.IPNet
		_, ipnet, err = net.ParseCIDR(ipnetStr)
		if err != nil {
			return nil, err
		}

		snd.locServedNets[i] = ipnet
	}

	return snd, nil
}

// anyNetContains ranges through the given ipnets slice searching for the one
// which contains the ip.  For internal use only.
//
// TODO(e.burkov): Think about memoization.
func anyNetContains(ipnets *[]*net.IPNet, ip net.IP) (is bool) {
	for _, ipnet := range *ipnets {
		if ipnet.Contains(ip) {
			return true
		}
	}

	return false
}

// IsSpecialNetwork returns true if IP address is contained by any of
// special-purpose IP address registries.  It's safe for concurrent use.
func (snd *SubnetDetector) IsSpecialNetwork(ip net.IP) (is bool) {
	return anyNetContains(&snd.spNets, ip)
}

// IsLocallyServedNetwork returns true if IP address is contained by any of
// locally-served IP address registries.  It's safe for concurrent use.
func (snd *SubnetDetector) IsLocallyServedNetwork(ip net.IP) (is bool) {
	return anyNetContains(&snd.locServedNets, ip)
}
