import socket
import re
import struct
from parallels.utils import unique_list, is_run_on_windows


def is_ipv4(ip_address):
	"""Check if specified string is an IPv4 address"""

	# We cannot use socket.inet_pton because it is not available on Windows
	# and may be some Unix OSes
	# https://docs.python.org/2/library/socket.html#socket.inet_pton
	# Availability: Unix (maybe not all platforms).

	# Simple solution taken from:
	# http://stackoverflow.com/questions/319279/how-to-validate-ip-address-in-python
	# This solution is:
	# - quite simple and 
	# - does not require any external libraries
	# - works on any platform

	pattern = re.compile(r"""
		^
		(?:
		  # Dotted variants:
		  (?:
			# Decimal 1-255 (no leading 0's)
			[3-9]\d?|2(?:5[0-5]|[0-4]?\d)?|1\d{0,2}
		  |
			0x0*[0-9a-f]{1,2}  # Hexadecimal 0x0 - 0xFF (possible leading 0's)
		  |
			0+[1-3]?[0-7]{0,2} # Octal 0 - 0377 (possible leading 0's)
		  )
		  (?:                  # Repeat 0-3 times, separated by a dot
			\.
			(?:
			  [3-9]\d?|2(?:5[0-5]|[0-4]?\d)?|1\d{0,2}
			|
			  0x0*[0-9a-f]{1,2}
			|
			  0+[1-3]?[0-7]{0,2}
			)
		  ){0,3}
		|
		  0x0*[0-9a-f]{1,8}    # Hexadecimal notation, 0x0 - 0xffffffff
		|
		  0+[0-3]?[0-7]{0,10}  # Octal notation, 0 - 037777777777
		|
		  # Decimal notation, 1-4294967295:
		  429496729[0-5]|42949672[0-8]\d|4294967[01]\d\d|429496[0-6]\d{3}|
		  42949[0-5]\d{4}|4294[0-8]\d{5}|429[0-3]\d{6}|42[0-8]\d{7}|
		  4[01]\d{8}|[1-3]\d{0,9}|[4-9]\d{0,8}
		)
		$
	""", re.VERBOSE | re.IGNORECASE)

	return pattern.match(ip_address) is not None


def is_ipv6(ip_address):
	"""Check if specified string is an IPv6 address 
	"""

	# See comment inside is_ipv4 function - the same is applicable here.

	pattern = re.compile(r"""
		^
		\s*                      # Leading whitespace
		(?!.*::.*::)             # Only a single whildcard allowed
		(?:(?!:)|:(?=:))         # Colon iff it would be part of a wildcard
		(?:                      # Repeat 6 times:
			[0-9a-f]{0,4}        #   A group of at most four hexadecimal digits
			(?:(?<=::)|(?<!::):) #   Colon unless preceeded by wildcard
		){6}                     #
		(?:                      # Either
			[0-9a-f]{0,4}        #   Another group
			(?:(?<=::)|(?<!::):) #   Colon unless preceeded by wildcard
			[0-9a-f]{0,4}        #   Last group
			(?: (?<=::)          #   Colon iff preceeded by exacly one colon
			 |  (?<!:)           #
			 |  (?<=:) (?<!::) : #
			 )                   # OR
		 |                       #   A v4 address with NO leading zeros 
			(?:25[0-4]|2[0-4]\d|1\d\d|[1-9]?\d)
			(?: \.
				(?:25[0-4]|2[0-4]\d|1\d\d|[1-9]?\d)
			){3}
		)
		\s*                         # Trailing whitespace
		$
	""", re.VERBOSE | re.IGNORECASE | re.DOTALL)

	return pattern.match(ip_address) is not None


def resolve(address):
	"""Resolve hostname into IP address.
	
	Return the first IP address, or None if we were not able to resolve.
	If address is already a valid IP address - return it as is.
	"""
	addresses = resolve_all(address)
	if len(addresses) == 0:
		return None
	else:
		return addresses[0]


def resolve_all(address):
	"""Resolve hostname into list of IP addresses.
	
	If address is already a valid IP address - return
	list that contains only that address.
	Otherwise resolve and return list of IP addresses 
	(IPv6 and IPv4 all in one list). 
	If we were not able to resolve, empty list is returned."""
	if is_ipv4(address) or is_ipv6(address):
		return [address]
	else:
		try:
			ip_addresses = unique_list([
				ainfo[4][0] for ainfo in socket.getaddrinfo(address, None)]
			)
			return ip_addresses
		except socket.gaierror:
			return []


def collapse_ipv4_mask(mask):
	"""Convert string network mask to integer.

	Note: this function is not implemented when running on Windows, and returns None.
	"""
	if is_run_on_windows():
		return None

	buf = socket.inet_pton(socket.AF_INET, mask)
	n = struct.unpack('!I', buf)[0]
	all_bits = 0xffffffff
	for i in xrange(33):
		if ((all_bits << (32-i)) & all_bits) == n:
			return i
	raise ValueError("Invalid IPv4 mask: %s" % mask)

