package util

import (
	"bytes"
	"fmt"
	"net"
	"regexp"
	"strconv"
	"strings"
)

var (
	RgxPatternIpv4 = regexp.MustCompile(`(?:^|\D)((?:(?:1?[0-9]?[0-9]|2(?:[0-4]?[0-9]|5[0-5]))\.){3}(?:1?[0-9]?[0-9]|2(?:[0-4]?[0-9]|5[0-5])))(?:\D|$)`)
)

//Function to convert a ip mask to string
func MaskToString(m []byte) string {
	if len(m) == 4 {
		return fmt.Sprintf("%d.%d.%d.%d", m[0], m[1], m[2], m[3])
	}
	return ""
}

// https://groups.google.com/forum/#!topic/golang-nuts/v4eJ5HK3stI
// Convert uint to net.IP
func InetNtoa(ipnr int64) net.IP {
	var bytes [4]byte
	bytes[0] = byte(ipnr & 0xFF)
	bytes[1] = byte(ipnr >> 8 & 0xFF)
	bytes[2] = byte(ipnr >> 16 & 0xFF)
	bytes[3] = byte(ipnr >> 24 & 0xFF)
	return net.IPv4(bytes[3], bytes[2], bytes[1], bytes[0])
}

// Convert net.IP to int64
func InetAton(ipnr net.IP) int64 {
	bits := strings.Split(ipnr.String(), ".")
	b0, _ := strconv.Atoi(bits[0])
	b1, _ := strconv.Atoi(bits[1])
	b2, _ := strconv.Atoi(bits[2])
	b3, _ := strconv.Atoi(bits[3])
	var sum int64
	sum += int64(b0) << 24
	sum += int64(b1) << 16
	sum += int64(b2) << 8
	sum += int64(b3)
	return sum
}

//Function to test if the ip and mask are valid.
func ValidIPMask(ip string, mask string) error {
	ip, err := IPToCIDR(ip, mask)
	if err != nil {
		return err
	}

	if _, _, err := net.ParseCIDR(ip); err != nil {
		return err
	}

	return nil
}

func IPToCIDR(ip string, mask string) (string, error) {
	if i := net.ParseIP(ip).To4(); i == nil {
		return "", fmt.Errorf("%s is not a valid IP address.", ip)
	}
	m := net.IPMask(net.ParseIP(mask).To4())
	pre, _ := m.Size()
	if pre == 0 {
		return "", fmt.Errorf("%s is not a valid Netmask", mask)
	}

	return fmt.Sprintf("%s/%d", ip, pre), nil
}

func IPNetwork(ip string, mask string) (*net.IPNet, error) {
	cidr, err := IPToCIDR(ip, mask)
	if err != nil {
		return nil, err
	}
	_, ipn, err := net.ParseCIDR(cidr)
	if err != nil {
		return nil, err
	}
	return ipn, nil
}

var (
	a1  = net.ParseIP("10.0.0.0")
	ra1 = net.ParseIP("10.128.0.0")
	a2  = net.ParseIP("10.255.255.255")
	b1  = net.ParseIP("172.16.0.0")
	b2  = net.ParseIP("172.31.255.255")
	c1  = net.ParseIP("192.168.0.0")
	c2  = net.ParseIP("192.168.255.255")
)

func IsPrivate(IP string) bool {
	ip := strings.Split(IP, "/")
	if a := checkClassA(ip[0]); a == true {
		return true
	}

	if b := checkClassB(ip[0]); b == true {
		return true
	}

	if c := checkClassC(ip[0]); c == true {
		return true
	}
	return false
}

func IsRoutedIP(ipAddress string) bool {
	ipCidr := strings.Split(ipAddress, "/")
	ip := net.ParseIP(strings.TrimSpace(ipCidr[0]))
	if ip == nil {
		return false
	}

	if b := checkClass(ip, ra1, a2); b == true {
		return false
	}

	if b := checkClass(ip, b1, b2); b == true {
		return false
	}

	if b := checkClass(ip, c1, c2); b == true {
		return false
	}
	return true
}

// checkClass function will determine if the provided IP address falls within the
// start and end IP addresses and returns true if so.
func checkClass(ip, start, end net.IP) bool {
	if ip.To4() == nil {
		return false
	}
	if bytes.Compare(ip, start) >= 0 && bytes.Compare(ip, end) <= 0 {
		return true
	}
	return false
}

func checkClassA(ip string) bool {
	i := net.ParseIP(ip)
	if i.To4() == nil {
		return false
	}
	if bytes.Compare(i, a1) >= 0 && bytes.Compare(i, a2) <= 0 {
		return true
	}
	return false
}

func checkClassB(ip string) bool {
	i := net.ParseIP(ip)
	if i.To4() == nil {
		return false
	}
	if bytes.Compare(i, b1) >= 0 && bytes.Compare(i, b2) <= 0 {
		return true
	}
	return false
}

func checkClassC(ip string) bool {
	i := net.ParseIP(ip)
	if i.To4() == nil {
		return false
	}
	if bytes.Compare(i, c1) >= 0 && bytes.Compare(i, c2) <= 0 {
		return true
	}
	return false
}

func InetNtoString(ipnr int64) string {
	ip := InetNtoa(ipnr)
	if ip != nil {
		return ip.String()
	}
	return ""
}

var (
	prefixToMask = map[string]string{
		"32": "255.255.255.255",
		"31": "255.255.255.254",
		"30": "255.255.255.252",
		"29": "255.255.255.248",
		"28": "255.255.255.240",
		"27": "255.255.255.224",
		"26": "255.255.255.192",
		"25": "255.255.255.128",
		"24": "255.255.255.0",
		"23": "255.255.254.0",
		"22": "255.255.252.0",
		"21": "255.255.248.0",
		"20": "255.255.240.0",
		"19": "255.255.224.0",
		"18": "255.255.192.0",
		"17": "255.255.128.0",
		"16": "255.255.0.0",
		"15": "255.254.0.0",
		"14": "255.252.0.0",
		"13": "255.248.0.0",
		"12": "255.240.0.0",
		"11": "255.224.0.0",
		"10": "255.192.0.0",
		"9":  "255.128.0.0",
		"8":  "255.0.0.0",
		"7":  "254.0.0.0",
		"6":  "252.0.0.0",
		"5":  "248.0.0.0",
		"4":  "240.0.0.0",
		"3":  "224.0.0.0",
		"2":  "192.0.0.0",
		"1":  "128.0.0.0",
		"0":  "0.0.0.0",
	}
	prefixIntMap = map[int]string{
		32: "255.255.255.255",
		31: "255.255.255.254",
		30: "255.255.255.252",
		29: "255.255.255.248",
		28: "255.255.255.240",
		27: "255.255.255.224",
		26: "255.255.255.192",
		25: "255.255.255.128",
		24: "255.255.255.0",
		23: "255.255.254.0",
		22: "255.255.252.0",
		21: "255.255.248.0",
		20: "255.255.240.0",
		19: "255.255.224.0",
		18: "255.255.192.0",
		17: "255.255.128.0",
		16: "255.255.0.0",
		15: "255.254.0.0",
		14: "255.252.0.0",
		13: "255.248.0.0",
		12: "255.240.0.0",
		11: "255.224.0.0",
		10: "255.192.0.0",
		9:  "255.128.0.0",
		8:  "255.0.0.0",
		7:  "254.0.0.0",
		6:  "252.0.0.0",
		5:  "248.0.0.0",
		4:  "240.0.0.0",
		3:  "224.0.0.0",
		2:  "192.0.0.0",
		1:  "128.0.0.0",
		0:  "0.0.0.0",
	}
)

// CIDRToNetmask takes a CIDR (just the number as a string) and returns the corresponding netmask
func CIDRToNetmask(cidr string) string {
	cidr = strings.TrimLeft(cidr, "/")
	if mask, ok := prefixToMask[cidr]; ok {
		return mask
	}
	return ""
}

// PrefixToNetmask : Converts a prefix (/CIDR notation) to netmask
func PrefixToNetmask(prefix interface{}) (string, error) {
	switch v := prefix.(type) {
	case string:
		prefix = strings.TrimLeft(v, "/")
		if mask, ok := prefixToMask[v]; ok {
			return mask, nil
		}
	case int:
		if mask, ok := prefixIntMap[v]; ok {
			return mask, nil
		}
	}
	return "", fmt.Errorf("invalid cidr: %v", prefix)
}

func NetmaskToCidr(nm string) string {
	if !ValidIpV4(nm) {
		return ""
	}
	for k, v := range prefixToMask {
		if v == nm {
			return k
		}
	}
	return ""
}

func ValidIpV4(s string) bool {
	ip := net.ParseIP(s)
	if ip.String() != "<nil>" {
		return true
	}
	return false
}

func FindIpsInString(s string) (ips []string) {
	for _, m := range RgxPatternIpv4.FindAllStringSubmatch(s, -1) {
		if len(m) == 2 {
			ips = append(ips, m[1])
		}
	}
	return
}
