package transform

import (
	"fmt"
	"net"
	"strings"
)

// ReverseDomainName turns a CIDR block into a reversed (in-addr) name.
func ReverseDomainName(cidr string) (string, error) {

	// If it is an IP address, add the /32 or /128
	ip := net.ParseIP(cidr)
	if ip != nil {
		if ip.To4() != nil {
			cidr = cidr + "/32"
		} else {
			cidr = cidr + "/128"
		}
	}

	a, c, err := net.ParseCIDR(cidr)
	if err != nil {
		return "", err
	}
	base, err := reverseaddr(a.String())
	if err != nil {
		return "", err
	}
	base = strings.TrimRight(base, ".")
	if !a.Equal(c.IP) {
		return "", fmt.Errorf("CIDR %v has 1 bits beyond the mask", cidr)
	}

	bits, total := c.Mask.Size()
	var toTrim int
	if bits == 0 {
		return "", fmt.Errorf("cannot use /0 in reverse CIDR")
	}

	// Handle IPv4 "Classless in-addr.arpa delegation" RFC2317:
	if total == 32 && bits >= 25 && bits < 32 {
		// first address / netmask . Class-b-arpa.
		fparts := strings.Split(c.IP.String(), ".")
		first := fparts[3]
		bparts := strings.SplitN(base, ".", 2)
		return fmt.Sprintf("%s/%d.%s", first, bits, bparts[1]), nil
	}

	// Handle IPv4 Class-full and IPv6:
	if total == 32 {
		if bits%8 != 0 {
			return "", fmt.Errorf("IPv4 mask must be multiple of 8 bits")
		}
		toTrim = (total - bits) / 8
	} else if total == 128 {
		if bits%4 != 0 {
			return "", fmt.Errorf("IPv6 mask must be multiple of 4 bits")
		}
		toTrim = (total - bits) / 4
	} else {
		return "", fmt.Errorf("invalid address (not IPv4 or IPv6): %v", cidr)
	}

	parts := strings.SplitN(base, ".", toTrim+1)
	return parts[len(parts)-1], nil
}

// copied from go source.
// https://github.com/golang/go/blob/bfc164c64d33edfaf774b5c29b9bf5648a6447fb/src/net/dnsclient.go#L15

// reverseaddr returns the in-addr.arpa. or ip6.arpa. hostname of the IP
// address addr suitable for rDNS (PTR) record lookup or an error if it fails
// to parse the IP address.
func reverseaddr(addr string) (arpa string, err error) {
	ip := net.ParseIP(addr)
	if ip == nil {
		return "", &net.DNSError{Err: "unrecognized address", Name: addr}
	}
	if ip.To4() != nil {
		return uitoa(uint(ip[15])) + "." + uitoa(uint(ip[14])) + "." + uitoa(uint(ip[13])) + "." + uitoa(uint(ip[12])) + ".in-addr.arpa.", nil
	}
	// Must be IPv6
	buf := make([]byte, 0, len(ip)*4+len("ip6.arpa."))
	// Add it, in reverse, to the buffer
	for i := len(ip) - 1; i >= 0; i-- {
		v := ip[i]
		buf = append(buf, hexDigit[v&0xF])
		buf = append(buf, '.')
		buf = append(buf, hexDigit[v>>4])
		buf = append(buf, '.')
	}
	// Append "ip6.arpa." and return (buf already has the final .)
	buf = append(buf, "ip6.arpa."...)
	return string(buf), nil
}

// Convert unsigned integer to decimal string.
func uitoa(val uint) string {
	if val == 0 { // avoid string allocation
		return "0"
	}
	var buf [20]byte // big enough for 64bit value base 10
	i := len(buf) - 1
	for val >= 10 {
		q := val / 10
		buf[i] = byte('0' + val - q*10)
		i--
		val = q
	}
	// val < 10
	buf[i] = byte('0' + val)
	return string(buf[i:])
}

const hexDigit = "0123456789abcdef"
